やること
前回、1からHPSのプロジェクトを作って見ました。LEDをつけるだけだと普通のCPUでも簡単にできるのでハードっぽいのを見たいところです。
今回は秋月のサーボモータを動かしてみます。
仕様確認
秋月のHPからデータシートを確認できます。
全体をperiod,ONの時間をdutyと呼ぶみたいです。
FPGAのコード変更
PWMの処理ですが、まずはLEDをPWMでチカチカさせてみます。8つあるけど一緒にチカチカ。C言語で書くとこんな感じでしょうかね。
while(1)
{
for(count=0; count<period; count++)
led = (count<duty);
}
前回のプロジェクトをそのまま使います。ファイル名を変えると面倒くさいのでファイル名はそのまま処理だけ変えることにします。
led.vを以下のように修正しました。
// led.v
// 簡単なPWM回路
`timescale 1 ps / 1 ps
module new_component (
input wire [7:0] avs_s0_address, // avs_s0.address
input wire avs_s0_read, // .read
output reg [31:0] avs_s0_readdata, // .readdata
input wire avs_s0_write, // .write
input wire [31:0] avs_s0_writedata, // .writedata
input wire clock_clk, // clock.clk
input wire reset_reset, // reset.reset
output wire [7:0] led // led output
);
reg [31:0] count;
reg [31:0] duty;
reg [31:0] period;
// レジスタ読み込み
always @(posedge clock_clk)
begin
case(avs_s0_address)
8'h00 : avs_s0_readdata <= period;
8'h01 : avs_s0_readdata <= duty;
8'h02 : avs_s0_readdata <= count;
default : avs_s0_readdata <= 0;
endcase
end
// レジスタ書き込み
always @(posedge clock_clk)
begin
if(reset_reset)
begin
duty <= 0;
period <= 0;
end
else if(avs_s0_write)
begin
case(avs_s0_address)
8'h00 : period <= avs_s0_writedata;
8'h01 : duty <= avs_s0_writedata;
endcase
end
end
// PWMカウンタ
always @(posedge clock_clk)
begin
if(reset_reset)
count <= 0;
else if(count>=period)
count <= 0;
else
count <= count+1;
end
// とりあえずLED全点灯してみる
assign led = (count<=duty) ? 8'hff : 8'h00;
endmodule
CPUから見えるアドレスは32bit換算で
0000 : period
0001 : duty
としました。
このファイルだけ変えてQuartusでコンパイルし直します。
動作確認
出来上がったATLAS_SOC_GHRD.rbfをここで作った環境の
fat/ATLAS_SOC_GHRD/output_files/ATLAS_SOC_GHRD.rbf
にコピーします。
再起動します。
reboot
再起動すると新しいFPGAになっています。
最初に1秒周期、0.5秒ONというのを試してみます。
led.vに入ってくるクロックはHPSの設定で100MHzになっています。
1秒は100,000,000clkなので、電卓アプリで100,000,000を16進数に変換すると0x5f5e100。0.5秒はその半分で0x2faf080と計算できました。
cd app
./lw 0 5f5e100
./lw 1 2faf080
と入力するとLEDが1秒間隔で点滅します。
サーボモーターをつなげる
Atlas-SoC基板の上のピンヘッダGPIO0にサーボモータをつなげることにします。Atlas-SoCの回路図からGPIO0のピンアサインは
サーボモータは5Vなので電源は
pin11:Vcc 5V
pin12:GND
を使うことにします。
制御出力はその近くのGPIOを使うことにします。
pin10:GPIO_0_D9
サーボモータのピンアサインは以下になっていたので
オレンジ色コンタクトをハウジングから抜いて、茶色と赤をそのまま11,12に挿します。オレンジ色はそのまま10ピンにつなげます。
TOPの変更
ピンが決まったのでverilogのtopを変えてLEDをそのままGPIOから出します。
Quartusでde0_nano_soc_baseline.vを開いて、
GPIO0がコメントアウトで無効になっているのでコメントアウトを削除。
assign GPIO_0[9] = LED[0];
を追加します。
全部書くとこうなります。
長いけど変えたのは2行だけです。
//--------------------------------------------------------------------------//
// Title: de0_nano_soc_baseline.v //
// Rev: Rev 0.1 //
// Last Revised: 09/14/2015 //
//--------------------------------------------------------------------------//
// Description: Baseline design file contains DE0 Nano SoC //
// Board pins and I/O Standards. //
//--------------------------------------------------------------------------//
//Copyright 2015 Altera Corporation. All rights reserved. Altera products
//are protected under numerous U.S. and foreign patents, maskwork rights,
//copyrights and other intellectual property laws.
//
//This reference design file, and your use thereof, is subject to and
//governed by the terms and conditions of the applicable Altera Reference
//Design License Agreement. By using this reference design file, you
//indicate your acceptance of such terms and conditions between you and
//Altera Corporation. In the event that you do not agree with such terms and
//conditions, you may not use the reference design file. Please promptly
//destroy any copies you have made.
//
//This reference design file being provided on an "as-is" basis and as an
//accommodation and therefore all warranties, representations or guarantees
//of any kind (whether express, implied or statutory) including, without
//limitation, warranties of merchantability, non-infringement, or fitness for
//a particular purpose, are specifically disclaimed. By making this
//reference design file available, Altera expressly does not recommend,
//suggest or require that this reference design file be used in combination
//with any other product not provided by Altera
//----------------------------------------------------------------------------
//Group Enable Definitions
//This lists every pinout group
//Users can enable any group by uncommenting the corresponding line below:
//`define enable_ADC
//`define enable_ARDUINO
`define enable_GPIO0
//`define enable_GPIO1
`define enable_HPS
module de0_nano_soc_baseline(
//////////// CLOCK //////////
input FPGA_CLK_50,
input FPGA_CLK2_50,
input FPGA_CLK3_50,
`ifdef enable_ADC
//////////// ADC //////////
/* 3.3-V LVTTL */
output ADC_CONVST,
output ADC_SCLK,
output ADC_SDI,
input ADC_SDO,
`endif
`ifdef enable_ARDUINO
//////////// ARDUINO ////////////
/* 3.3-V LVTTL */
inout [15:0] ARDUINO_IO,
inout ARDUINO_RESET_N,
`endif
`ifdef enable_GPIO0
//////////// GPIO 0 ////////////
/* 3.3-V LVTTL */
inout [35:0] GPIO_0,
`endif
`ifdef enable_GPIO1
//////////// GPIO 1 ////////////
/* 3.3-V LVTTL */
inout [35:0] GPIO_1,
`endif
`ifdef enable_HPS
//////////// HPS //////////
/* 3.3-V LVTTL */
inout HPS_CONV_USB_N,
/* SSTL-15 Class I */
output [14:0] HPS_DDR3_ADDR,
output [2:0] HPS_DDR3_BA,
output HPS_DDR3_CAS_N,
output HPS_DDR3_CKE,
output HPS_DDR3_CS_N,
output [3:0] HPS_DDR3_DM,
inout [31:0] HPS_DDR3_DQ,
output HPS_DDR3_ODT,
output HPS_DDR3_RAS_N,
output HPS_DDR3_RESET_N,
input HPS_DDR3_RZQ,
output HPS_DDR3_WE_N,
/* DIFFERENTIAL 1.5-V SSTL CLASS I */
output HPS_DDR3_CK_N,
output HPS_DDR3_CK_P,
inout [3:0] HPS_DDR3_DQS_N,
inout [3:0] HPS_DDR3_DQS_P,
/* 3.3-V LVTTL */
output HPS_ENET_GTX_CLK,
inout HPS_ENET_INT_N,
output HPS_ENET_MDC,
inout HPS_ENET_MDIO,
input HPS_ENET_RX_CLK,
input [3:0] HPS_ENET_RX_DATA,
input HPS_ENET_RX_DV,
output [3:0] HPS_ENET_TX_DATA,
output HPS_ENET_TX_EN,
inout HPS_GSENSOR_INT,
inout HPS_I2C0_SCLK,
inout HPS_I2C0_SDAT,
inout HPS_I2C1_SCLK,
inout HPS_I2C1_SDAT,
inout HPS_KEY,
inout HPS_LED,
inout HPS_LTC_GPIO,
output HPS_SD_CLK,
inout HPS_SD_CMD,
inout [3:0] HPS_SD_DATA,
output HPS_SPIM_CLK,
input HPS_SPIM_MISO,
output HPS_SPIM_MOSI,
inout HPS_SPIM_SS,
input HPS_UART_RX,
output HPS_UART_TX,
input HPS_USB_CLKOUT,
inout [7:0] HPS_USB_DATA,
input HPS_USB_DIR,
input HPS_USB_NXT,
output HPS_USB_STP,
`endif
//////////// KEY ////////////
/* 3.3-V LVTTL */
input [1:0] KEY,
//////////// LED ////////////
/* 3.3-V LVTTL */
output [7:0] LED,
//////////// SW ////////////
/* 3.3-V LVTTL */
input [3:0] SW
);
hps u0 (
.led_0_led_output (LED), // led_0_led.output
.hps_io_hps_io_emac1_inst_TX_CLK (HPS_ENET_GTX_CLK), // hps_io.hps_io_emac1_inst_TX_CLK
.hps_io_hps_io_emac1_inst_TXD0 (HPS_ENET_TX_DATA[0]), // .hps_io_emac1_inst_TXD0
.hps_io_hps_io_emac1_inst_TXD1 (HPS_ENET_TX_DATA[1]), // .hps_io_emac1_inst_TXD1
.hps_io_hps_io_emac1_inst_TXD2 (HPS_ENET_TX_DATA[2]), // .hps_io_emac1_inst_TXD2
.hps_io_hps_io_emac1_inst_TXD3 (HPS_ENET_TX_DATA[3]), // .hps_io_emac1_inst_TXD3
.hps_io_hps_io_emac1_inst_RXD0 (HPS_ENET_RX_DATA[0]), // .hps_io_emac1_inst_RXD0
.hps_io_hps_io_emac1_inst_MDIO (HPS_ENET_MDIO), // .hps_io_emac1_inst_MDIO
.hps_io_hps_io_emac1_inst_MDC (HPS_ENET_MDC), // .hps_io_emac1_inst_MDC
.hps_io_hps_io_emac1_inst_RX_CTL (HPS_ENET_RX_DV), // .hps_io_emac1_inst_RX_CTL
.hps_io_hps_io_emac1_inst_TX_CTL (HPS_ENET_TX_EN), // .hps_io_emac1_inst_TX_CTL
.hps_io_hps_io_emac1_inst_RX_CLK (HPS_ENET_RX_CLK), // .hps_io_emac1_inst_RX_CLK
.hps_io_hps_io_emac1_inst_RXD1 (HPS_ENET_RX_DATA[1]), // .hps_io_emac1_inst_RXD1
.hps_io_hps_io_emac1_inst_RXD2 (HPS_ENET_RX_DATA[2]), // .hps_io_emac1_inst_RXD2
.hps_io_hps_io_emac1_inst_RXD3 (HPS_ENET_RX_DATA[3]), // .hps_io_emac1_inst_RXD3
.hps_io_hps_io_sdio_inst_CMD (HPS_SD_CMD), // .hps_io_sdio_inst_CMD
.hps_io_hps_io_sdio_inst_D0 (HPS_SD_DATA[0]), // .hps_io_sdio_inst_D0
.hps_io_hps_io_sdio_inst_D1 (HPS_SD_DATA[1]), // .hps_io_sdio_inst_D1
.hps_io_hps_io_sdio_inst_CLK (HPS_SD_CLK), // .hps_io_sdio_inst_CLK
.hps_io_hps_io_sdio_inst_D2 (HPS_SD_DATA[2]), // .hps_io_sdio_inst_D2
.hps_io_hps_io_sdio_inst_D3 (HPS_SD_DATA[3]), // .hps_io_sdio_inst_D3
.hps_io_hps_io_usb1_inst_D0 (HPS_USB_DATA[0]), // .hps_io_usb1_inst_D0
.hps_io_hps_io_usb1_inst_D1 (HPS_USB_DATA[1]), // .hps_io_usb1_inst_D1
.hps_io_hps_io_usb1_inst_D2 (HPS_USB_DATA[2]), // .hps_io_usb1_inst_D2
.hps_io_hps_io_usb1_inst_D3 (HPS_USB_DATA[3]), // .hps_io_usb1_inst_D3
.hps_io_hps_io_usb1_inst_D4 (HPS_USB_DATA[4]), // .hps_io_usb1_inst_D4
.hps_io_hps_io_usb1_inst_D5 (HPS_USB_DATA[5]), // .hps_io_usb1_inst_D5
.hps_io_hps_io_usb1_inst_D6 (HPS_USB_DATA[6]), // .hps_io_usb1_inst_D6
.hps_io_hps_io_usb1_inst_D7 (HPS_USB_DATA[7]), // .hps_io_usb1_inst_D7
.hps_io_hps_io_usb1_inst_CLK (HPS_USB_CLKOUT), // .hps_io_usb1_inst_CLK
.hps_io_hps_io_usb1_inst_STP (HPS_USB_STP), // .hps_io_usb1_inst_STP
.hps_io_hps_io_usb1_inst_DIR (HPS_USB_DIR), // .hps_io_usb1_inst_DIR
.hps_io_hps_io_usb1_inst_NXT (HPS_USB_NXT), // .hps_io_usb1_inst_NXT
.hps_io_hps_io_spim1_inst_CLK (HPS_SPIM_CLK), // .hps_io_spim1_inst_CLK
.hps_io_hps_io_spim1_inst_MOSI (HPS_SPIM_MOSI), // .hps_io_spim1_inst_MOSI
.hps_io_hps_io_spim1_inst_MISO (HPS_SPIM_MISO), // .hps_io_spim1_inst_MISO
.hps_io_hps_io_spim1_inst_SS0 (HPS_SPIM_SS), // .hps_io_spim1_inst_SS0
.hps_io_hps_io_uart0_inst_RX (HPS_UART_RX), // .hps_io_uart0_inst_RX
.hps_io_hps_io_uart0_inst_TX (HPS_UART_TX), // .hps_io_uart0_inst_TX
.hps_io_hps_io_i2c0_inst_SDA (HPS_I2C0_SDAT), // .hps_io_i2c0_inst_SDA
.hps_io_hps_io_i2c0_inst_SCL (HPS_I2C0_SCLK), // .hps_io_i2c0_inst_SCL
.hps_io_hps_io_i2c1_inst_SDA (HPS_I2C1_SDAT), // .hps_io_i2c1_inst_SDA
.hps_io_hps_io_i2c1_inst_SCL (HPS_I2C1_SCLK), // .hps_io_i2c1_inst_SCL
.memory_mem_a (HPS_DDR3_ADDR), // memory.mem_a
.memory_mem_ba (HPS_DDR3_BA), // .mem_ba
.memory_mem_ck (HPS_DDR3_CK_P), // .mem_ck
.memory_mem_ck_n (HPS_DDR3_CK_N), // .mem_ck_n
.memory_mem_cke (HPS_DDR3_CKE), // .mem_cke
.memory_mem_cs_n (HPS_DDR3_CS_N), // .mem_cs_n
.memory_mem_ras_n (HPS_DDR3_RAS_N), // .mem_ras_n
.memory_mem_cas_n (HPS_DDR3_CAS_N), // .mem_cas_n
.memory_mem_we_n (HPS_DDR3_WE_N), // .mem_we_n
.memory_mem_reset_n (HPS_DDR3_RESET_N), // .mem_reset_n
.memory_mem_dq (HPS_DDR3_DQ), // .mem_dq
.memory_mem_dqs (HPS_DDR3_DQS_P), // .mem_dqs
.memory_mem_dqs_n (HPS_DDR3_DQS_N), // .mem_dqs_n
.memory_mem_odt (HPS_DDR3_ODT), // .mem_odt
.memory_mem_dm (HPS_DDR3_DM), // .mem_dm
.memory_oct_rzqin (HPS_DDR3_RZQ), // .oct_rzqin
);
assign GPIO_0[9] = LED[0];
endmodule
Quartusでコンパイルし直します。
sambaでfatにコピーして、rebootしておきます。
servoアプリを作る
毎回lwアプリで16進数で入力するのが面倒くさいのでアプリを作ります。
periodは常に20msなのでdutyだけms単位で指定するアプリにしましょう。
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include "lw_driver.h"
int main(int argc, char ** argv)
{
double duty, clk_1sec;
uint32_t data;
// 引数チェック
if(argc!=2 )
{
printf("servo duty(msec)\n");
exit(EXIT_FAILURE);
}
if(open_lw())
exit(EXIT_FAILURE);
clk_1sec = 100e6; // 100MHz
sscanf(argv[1], "%lf", &duty);
// period
data = (uint32_t)(20e-3*clk_1sec); // 20ms
write_lw(0, data);
//duty
data = (uint32_t)(duty*1e-3*clk_1sec); // 引数ms
write_lw(1, data);
close_lw();
exit(EXIT_SUCCESS);
}
浮動小数点が使えるので計算も楽ですね。アプリの引数も浮動小数点にしています。
sambaのappにこのファイルを置いて、
Atlas-SoCのLinux上から
gcc servo.c lw_driver.c -o servo
と入力するだけでコンパイルができます。
一瞬です。もちろん再起動も不要。ソフトの開発は楽でいいよね。
FPGAのコンパイルは時間がかかって。。。。
動作確認
./servo duty[ms]
で実行できます。0.5~2.4を入れるとサーボがキュッキュと動きます。
ピタッと止まらないことがあるけど、400円にしては上出来ですかね。
終わりに
これで自作FPGAモジュールをCから簡単に制御できることが確認できました。
32bitのレジスタは広くていいですね。100,000,000なんてでかい数値も何も考えずに渡せます。
計算はC言語が簡単でいいし、FPGAの処理は100MHzで動きます。最強じゃないですか。
開発のしやすさはC言語の方が楽ですね。Linuxなのでセルフコンパイルも問題なくできます。
最後の最後でFPGA処理にしたいところです。