どうもkohaniiです。
このブログでMAX V 5M160 CPLDの細い評価ボードを作っています。
ナウなUSB Type-Cを採用しています。

上のページに回路図が載っています。
このボードには16MHzの水晶発振器が載っていて(写真ではJTAGでほとんど隠れています)、
それがI/Oに繋がっているので、外付け部品が不要で正確な周波数の発振器などが作れます。
ただ好きな周波数を出すためには、16MHzからの単純な分周機では周波数分解能が低すぎて正確な周波数が出しにくいです。
DDS
そこで役に立つのがDDS(ダイレクト・デジタル・シンセサイザ)です。
この仕組みを使うと、ジッタが出る代わりに(平均すると)中途半端な周波数を生成できます。
詳細は英語版のWikipediaが参考になります。
ちなみに音声編集ソフトのAudacityなどのシンプルなトーンジェネレーターもこの仕組みを採用しているみたいです。
Audacityで矩形波のチャープ信号を、サンプリングレート16KHzで生成した例
CPLDやFPGAなどの並行処理で実行できる部品を使うと、高サンプリングレートで出力できます。
今回はボード内臓の水晶発振器の16MHzをサンプリングレートにします。
この場合ナイキスト周波数の8MHzまでの波形を生成できます。
Verilogソースコード
Quartus Prime Lite Editionで開発しました。

`default_nettype none
module dds24 (
input wire clock,
input wire [23:0] frequency,
output wire [7:0] out
);
reg [23:0] accumulator = 0;
always @(posedge clock) begin
accumulator <= accumulator + frequency;
end
assign out = accumulator[23:16];
endmodule
module max5_nano_simple_dds (
input wire clock_16m,
//SPI MODE0 MSBFIRST 32bit
input wire spi_chip_select, //Low Enable
input wire spi_clock,
input wire spi_data,
output wire pulse_out
);
reg [31:0] spi_shift_register = 0;
reg [23:0] frequency = 461;
reg [7:0] duty = 128;//0% ~ 99.609375%
always @(posedge spi_clock) begin
if(spi_chip_select == 1'd_0) begin
spi_shift_register <= {spi_shift_register[30:0], spi_data};
end
end
always @(posedge spi_chip_select) begin
frequency <= spi_shift_register[31:8];
duty <= spi_shift_register[7:0];
end
wire [7:0] dds_out;
dds24 dds(
.clock(clock_16m),
.frequency(frequency),
.out(dds_out)
);
assign pulse_out = (dds_out < duty);
endmodule
トップレベルモジュールがmax5_nano_simple_ddsです。
ピンアサインはこんな感じです。

モジュールのdds24がDDS部分です。
行っていることはシンプルで、毎クロックに
accumulator <= accumulator + frequency;
を実行するだけです。
そうするとaccumulatorは(frequencyが1以上なら)ノコギリ波状に変化します。
accumulatorは24bitなのですが、下位16bitは捨てて上位8bitだけ出力(dds_out)しています。
その8bitの最上位ビットからデューティ比50%の矩形波(パルス波)が出力されます。
ただそれだと矩形波(デューティ比50%)専用になってしまうので、
pulse_out = (dds_out < duty);
とdutyと比較することで、デューティ比256段階(0% ~ 約99.61%)で調整できるパルス波にしています。
最低周波数は、16MHz ÷ 2の24乗 なので、約0.9537Hzです。
そしてこの周波数が周波数分解能で、
frequencyの値 = 周波数 ÷ 最低周波数になります。
なので、大体1Hz精度の周波数が生成できます。
そして、最高周波数は 最低周波数 × 2の23乗 で、8MHzです。
frequencyの値をそれ以上上げても折り返すだけです。
次に制御ですが、通信にSPIを採用しました。理由は楽だからです。
制御部分はこの部分です
reg [31:0] spi_shift_register = 0;
reg [23:0] frequency = 461;
reg [7:0] duty = 128;//0% ~ 99.609375%
always @(posedge spi_clock) begin
if(spi_chip_select == 1'd_0) begin
spi_shift_register <= {spi_shift_register[30:0], spi_data};
end
end
always @(posedge spi_chip_select) begin
frequency <= spi_shift_register[31:8];
duty <= spi_shift_register[7:0];
end
spi_shift_registerがシリアルからパラレルに変換するシフトレジスタの内容です。
spi_shift_registerは32bitで、4byteの内容を保持できます。
spi_clockの立ち上がりエッジで、spi_chip_selectが0の時にラッチ、シフトします。
そして、spi_chip_selectの立ち上がりエッジで、レジスタに内容をラッチします。
あとはラッチしたfrequencyをdds24モジュールに渡すだけです。
ただし、このままだと常に同じ波形が永遠と発されるだけなので、外部で制御する必要があります
Arduino Nano
そこで、制御にArduino Nanoを使います。
Arduino Nanoのプログラムはこちらです。
#include#define CS_PIN 9 void setup() { SPI.begin(); digitalWrite(CS_PIN, HIGH); pinMode(CS_PIN, OUTPUT); } uint32_t frequency_conversion(double in) { return (uint32_t)((double)in * 1.048576); } int counter = 0; void loop() { uint32_t frequency = frequency_conversion(fmod(pow((double)counter / 5, 2), 10000)); uint8_t duty = counter; digitalWrite(CS_PIN, LOW); SPI.transfer(frequency >> 16); SPI.transfer(frequency >> 8); SPI.transfer(frequency >> 0); SPI.transfer(duty); digitalWrite(CS_PIN, HIGH); delay(10); counter++; }
Arduino NanoのCS_PIN、SCK、MOSIとCPLDと繋ぎます。
と、行きたいところですが、Arduino Nanoの電源電圧は5Vで、CPLDの電源電圧は3.3Vです。
このままではCPLDが壊れる危険性があります。
そこで、ロジックレベル変換モジュールを通して、電圧を変換します。
完成

ブレッドボードで繋げてみました。
左上がCPLDボードで、下がArduino Nano(の互換機)、右の青い基板がロジックレベル変換モジュールです。



CPLDボードにはUSB Blasterが繋がっています。
Arduino Nanoのプログラムでは、
0Hz ~ 10KHzのチャープ信号をデューティー比を変えながら、出力させています。
これならPCのライン入力で録音できるので、
実際に録音してみました。こちらです。(音量注意)
サンプリングレート16KHzと比べて、かなり正確な高音が出力されています。
このCPLDで、パルス波専用ファンクションジェネレーターを作る予定です。
おわり





コメント