DE0で正弦波を出してみた。
前回のDE0で音を出してみた。のつづき。前回は0と1を440Hzで繰り返すことで矩形波を鳴らしたけど、今度は正弦波を出してみる。となると、スピーカーに単純に0Vか5Vかを送るんじゃなく、0〜5Vのアナログ信号で波形を作る必要がある。つまりDAコンバーター(DAC)が要る。
DAコンバーターとたたかう
こないだ買ったDE0拡張キットにはMCP4922っていうDACのICが入ってたので、まずはこれをブレッドボードに載せて、DE0の外部インターフェースやスピーカーとつなぐ配線をする。ブレッドボードって使うのは実は初めて(俺の子供の頃はこんなのなかった気がする。電子工作と言えばラグ板かエッチングだった)。
このMCP4922は12ビットのDACなので、12ビットのデータを入れると0〜5Vのアナログ信号に変換してくれる。がしかし、12ビットのデータをSPIっていうシリアルインタフェースを通じて渡さねばならない。SPIってなんぞ。。ググったら出てきたこのページの真ん中あたりの解説を読んだり、DE0拡張キットのマニュアルを読んだりして理解を試みる。12ビットのデータの頭に4ビットのヘッダ情報をつけて、合計16ビットのデータを1ビットずつ順番に入れてあげると、それに応じたアナログ信号を出力してくれる…って仕様らしい。見よう見まねでHDLのコードを書いて動かしてみるけど、最初は当然動かない。なのでロジアナとにらめっこしながらデバッグ作業。。結局はこんなコードになった。
module SPI ( input ICLK, input [7:0] DAT, output nCS, output SDI ); // base 18 counter reg [4:0] cnt; always @(posedge ICLK) begin if (cnt == 5'd17) cnt = 0; else cnt = cnt + 1; end // CS reg cs; always @(negedge ICLK) begin if (cnt == 5'd17) cs = 1'b1; if (cnt == 5'd15) cs = 1'b0; end assign nCS = ~cs;
まずはクロックを18回数えるカウンタをつくる。カウンタが0〜15のときはSPIでデータを1ビットずつ送り、16・17のときはCS#(データを送ってることをDACに伝える信号)を介して「データ送ったよ」と教える。そのタイミングでDACがデータの数値に応じた電圧でアナログ信号を出力してくれる。データの中身としては、とりあえず簡単な矩形波を出してみるために、440Hzで0xFFと0x00を出力する簡単なカウンタをつなげておいた。
このデータを1ビットずつ送るために、16ビットのシフトレジスタを用意する。
// 16 bit shift register reg [15:0] sreg; always @(negedge ICLK) begin if (cnt == 5'd17) begin // load command and data into sreg sreg = {4'b1111, DAT, 4'b0000}; end else begin // shift sreg = {sreg[14:0], 1'b0}; end end // SDI assign SDI = sreg[15]; endmodule
カウンタが0に戻るタイミングで、コマンド4bit+データ8bit+パディング4bitの合計16ビットをレジスタsregにロードする。あとは、sreg = {sreg[14:0], 1'b0};って書けば1回ごとにレジスタ内容が左にシフトするので、sreg[15](一番左のビット)の値をDACに送る。
このやり方は理解できるのだけど、デバッグが難しかった。。DE0に内蔵のロジアナとにらめっこすることしばし。しかしこのロジアナはFPGAの中のレジスタの値しか見られない。DACまわりの信号がどんなことになってるか調べるために本物のロジアナがほしいなぁ〜とつくづく思った。しょうがないので年季の入った俺のテスターを引っ張りだしてきて、DACの足の電圧をひとつひとつ確かめる。例えばクロックのところが2.5Vくらいなら、まぁそんなもんだろう。。とか。信号の確認とデバッグを地道に進めていって、やっと音が出た。嬉しい。。しかしハード開発は難しいなぁ。。仕事でなくてよかった。
サインテーブルを作る
あとは正弦波のデータをDACの送るだけ。Google Spreadsheet(Excelなぞ使わない)でこんなテーブルを作る:
そして、440Hz / 128の間隔で0〜127の値を出力するカウンタを使い、このテーブルから値を読み出すコードを書く。
// clock for each step // 50Mhz / 440Hz / 128 = 887.78 reg [11:0] cnt; wire stepclk; always @(posedge CLK) begin if (cnt == 12'd888) cnt = 0; else cnt = cnt + 1; end assign stepclk = cnt == 0; // counter for each step reg [6:0] stepcnt; always @(posedge stepclk) begin if (stepcnt == 7'd127) stepcnt = 0; else stepcnt = stepcnt + 1; end assign DAT = sin(stepcnt); // sin table function [7:0] sin; input [7:0] step; begin case (step) 8'd0: sin=8'd128; 8'd1: sin=8'd134; 8'd2: sin=8'd140; 8'd3: sin=8'd146;
この8ビットデータをさきほどのSPIのコードに入れてあげると、おぉ、たしかに正弦波っぽい音がでた!
..ちょっとザラザラした正弦波な気もするが、まぁスピーカーもビットレートもアレだしこんなもんだろう。
次の野望:次はなにやろうかなぁ〜。。Web MusicのAdvent Calendarにエントリするまでにもうちょっとヒネりたい。
コード全体はここに置いといた。