AlteraのFPGA開発環境には、Nios IIというFPGA組み込み用のCPUライブラリ(FPGAの世界ではIPというらしい)がついていて、このプロセッサをベースとしてCのプログラムが動かせるようになってる。
そこにSDRAMコントローラのIPやLCDコントローラのIPなどを組み合わせていくと、一台のコンピュータになる。
そうすると、ハードウェア制御はHDLで書いて、手続きはCで書いてという使い分けができるようになって、実際に動くモノを作るには非常に生産性がよい。
まあ、そうなると、Cの部分は単なる組み込み開発になってそんなに目新しいものでもなくなるのだけど、HDLのコンパイル(FPGAの世界では合成というらしい)に比べてCのコンパイルは格段に早いし、なにより手続きが書きやすいので、自分で作った回路の動作確認などもやりやすくなる。
ただ、そのためにはNios IIプロセッサとやりとりするためのAvalonバスに接続できる回路を書いてあげる必要があって、その作法を知っておかないといけない。
まあ、バスに接続するといっても、モジュールの入出力をあわせてあげるだけなのだけど。それが単独で動かすよりは結構大変。
そこで、ちょうど手元に2ch A/Dコンバータがあるので、練習に、これを制御しつつCプログラムから読み出せるようにコンポーネントを作ってみた。(A/DコンバータはSPIというプロトコルで制御するのだけど、SPIコアもすでに用意されてるので実際はそっちが使える)
そしたら、こんな感じでコンポーネントとして接続する。
なんか、一度登録した自作コンポーネントを認識してくれてないのだけど・・・
Cプログラムの開発はEclipseベースのツールで行う。
#include <stdio.h> #include "io.h" #include "system.h" int main() { printf("Hello from Nios II!Yes!!\n"); int airtmp; int varreg; airtmp = IORD(ADC2CH_0_BASE, 1); varreg = IORD(ADC2CH_0_BASE, 0); double c = (airtmp - 0x14) / 4.; printf("var:%x tmp:%x=%.2f\n", varreg, airtmp, c); FILE* lcd = fopen("/dev/lcd_0", "w"); fprintf(lcd, "hello from nios!\nvar:%x tmp:%.2f", varreg, c); fclose(lcd); FILE* uart = fopen("/dev/uart_0", "w"); fprintf(uart, "hello!\n\rit is %.2fC\n\r", c); fclose(uart); }
IORDが、Avalonバスから値を読み込むマクロ。
普通にprintfすると、Eclipseの出力ウィンドウにメッセージが表示される。LCDディスプレイやUARTシリアル通信のコントローラも組み込んでいるので、printfで出力できてすばらしい!
今回はch0に可変抵抗、ch1に温度センサをつけていて、その値が表示される。温度センサの値は、14hを引いて4で割ると摂氏温度になるらしい。
見にくいけど、LCDとEclipseの出力とターミナルにメッセージが表示されている。19℃らしい。
最初リセット信号が、onでリセット、offでリセット解除となっていると気付かなくて、ハマりまくった。
A/Dコンバータのクロックが1KHzで、50MHzのネイティブ側からすれば5万クロックあるので、単純にwaitrequestを解除すると同じデータを出力するときに次の読み込み要求が来てしまって、アドレス指定を無視した結果が返ってしまっていた。ネイティブの1クロックだけwaitrequestを解除させて対処。
readyフラグみたいなのを用意して、Cプログラムでウェイトさせる必要もあるかも。
参考にしたのはこの本で、DE0でFPGAを始めるなら必ず目をとおしたほうがいいと思う。
FPGA ボードで学ぶ組込みシステム開発入門 ?Altera編?
- 作者: 小林優
- 出版社/メーカー: 技術評論社
- 発売日: 2011/09/22
- メディア: 大型本
- 購入: 3人 クリック: 31回
- この商品を含むブログ (19件) を見る
Linuxを動かす解説もあるんだけど、ビルドが通らなかったのであきらめた。
Avalonバスの資料はこういうのがあった。日本語版はないのかな?
「Avalon Interface Specifications」
http://www.altera.com/literature/manual/mnl_avalon_spec.pdf
あと、すでに用意されてるコンポーネントIPの説明はこんなのがあった。
「Nios II プロセッサ・リファレンス・ハンドブック セクションII. ペリフェラル・サポート」
http://www.altera.co.jp/literature/hb/nios2/n2cpu_nii5v1_02_j.pdf
とりあえずソースはこんな感じになった。
※2013/2/13 追記 MCP3002を想定していますが、最上位ビットがとれなかったり、タイミングの問題があるのでこのままでは使えません。データ取得をposedgeで行う必要があります。クロックも1MHzくらいで動くのでここまで遅くする必要はありません。
module adc2ch( input clk, input reset, input address, input read, output [15:0] readdata, output reg waitrequest, output sclk, output scs, output reg sdi, input sdo ); //1KHzをつくる reg [14:0] cnt; reg clk1k; always @(posedge clk or posedge reset) begin if(reset) begin cnt <= 15'd0; end else if(cnt == 15'd24999) begin if(!clk1k && scnt == 5'd16) begin waitrequest <= 1'b0; end clk1k <= clk1k + 1'b1; cnt <= 15'd0; end else begin cnt <= cnt + 15'd1; waitrequest <= 1'b1; end end //状態カウンタ reg [4:0] scnt; always @(posedge clk1k or posedge reset) begin if(reset) begin scnt <= 5'd0; end else if(scnt == 5'd16) begin scnt <= 5'd0; end else if(scnt == 5'd0) begin if(read) scnt <= 5'd1; end else begin scnt <= scnt + 5'd1; end end //SPI処理 reg [9:0] tmpdata; reg [9:0] result; reg cs; assign scs = ~cs; assign sclk = clk1k & cs; assign readdata = {6'd0, result}; always @(negedge clk1k) begin case(scnt) 5'd0: begin cs <= 1'b0; end 5'd1: begin //start cs <= 1'b1; sdi <= 1'b1; end 5'd2: begin //single sdi <= 1'b1; end 5'd3: begin //ch sdi <= address; end 5'd4: begin //msbf sdi <= 1'b1; end 5'd5: begin tmpdata <= 10'd0; end 5'd16: begin cs <= 1'b0; result <= tmpdata; end default: begin tmpdata <= {tmpdata[8:0], sdo}; end endcase end endmodule