2012年06月21日

DACを設計してみよう

QFN32 パッケージの MachXO2-256 という FPGA でなにが出来るのか、検討しているのだが。... DAC が作れそうな気がする。FPGA は大概、コア用の電源と IO 用の電源に分かれている。この QFN32 も 例外ではないどころか、VCCIO が 4 つもある。ちゃんと電源を設計すれば、ノイズ的に有利かも知れない。ただ、オーディオDAC に匹敵するようなものには、できないだろう。どこまでのものが作れるかも追求しない。256 に入れられて 検証できるものを目指す。

AVR など PWM を持った MCU は 多いわけだが、ΣΔ変調の DAC を持っているものは、殆ど無い。同じ bit幅でも、ΣΔ変調だと ノイズを減らせるそうだ。ちょっと試してみたい。

ΣΔ変調の原理

    1 bit の ON/OFF だけで DAC を作ると 256 クロック使って ようやく 8bit 幅になる。このことは、PWM と変わらない。ノイズを減らせるのは、どうしてかというと PWM では 0 が続いたあと 1 にするだけのところを、1 を分散させて ノイズの周波数を 上げるためらしい。

    どうやって そういう 0/1 を作るのだろう?

    なかなか理解できなかったのだが、3bit の DAC を使って 8bit 出力することを考えてみたら、分かったような気がする。

    例えば、8'b01011010 を 3bit の DAC で出力するには ... 上位 3bit の 3'b010 をまず出力して 時々 3'b011 も出力することで 中間値を表現する。

    3'b010 を DACで出力すれば 5'b11010 が出力されなかった分として残る。次も 3'b010 なら 5'b11010 + 5'b11010 が残るのだが ... 桁上がりして、6'b110100 になる。だから 3'b011 を DAC で出力して、5'b10100を出力されなかった分として 覚え直す。

    要するに、ずっと 5'b11010 を加算していって、桁上がりしたときに 3'b011 を DACに出力すれば良いのだ。

    では、0/1 だけで表現するには ... 8'b01011010 を ずっと 加算していって、桁上がりしたときに 1 を出力すれば良い。256 回加算すれば、16'b 01011010 00000000 になるから、確かに 8'b01011010 個の 1 を出力したことになる。

    フィルタがどうとか に囚われてよく分からなかったのだが、どうも原理は簡単な話のようだ。

作ってみよう

    module dac # (
    parameter WIDTH = 8
    , parameter WITH_LATCH = 0
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output A_OUT
    );

    reg r_stb = 1'b0;
    reg [WIDTH-1:0] r_data = 0;
    reg [WIDTH-1:0] fraction = 0;
    reg [WIDTH-1:0] count = 0;
    reg r_out = 1'b0;

    always @(posedge CLK)
    begin
    r_stb <= STB;
    if (&count | (STB & ~r_stb))
    begin
    count <= 0;
    fraction <= I_DATA;
    r_out <= 0;
    if (WITH_LATCH)
    r_data <= I_DATA;
    end
    else
    begin
    { r_out, fraction } <= { 1'b0 ,fraction }
    + { 1'b0, (WITH_LATCH ? r_data : I_DATA) };
    count <= count + 1;
    end
    end

    assign A_OUT = r_out;

    endmodule

    取り敢えず書いてみたのがこれ。ラッチあり/なし を選べたり ビット幅を指定できるようにしてみた。そうそう、新しい呪文を覚えた。&count は、count の各ビットを AND する意味で all 1 のとき 1 になる。

    書いてみて思ったのだが、毎回 Nbit の加算をしている。これはクロックを上げられないのではないか?

    例えば 16 bit の出力 を 50kHz でするとする。65536 倍のクロック が必要で 計算すると 3.2768 GHz で駆動することに。とりあえず 3bit の DAC を使うとすれば ... 409.6 MHz 。これでも無理な話だが、周波数が減る上に 加算する bit 数も減る。

    さて、この 3bit の DAC もまた、ΣΔで作ってやれば、どうなるのだろう? 同じ 1bit のΣΔでも最大周波数が上がるのではないだろうか?

N bit ΣΔ

    module dac5 # (
    parameter AWIDTH = 5
    , parameter WIDTH = 16
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output [AWIDTH-1:0] A_OUT
    );

    reg r_stb = 1'b0;
    reg [WIDTH-1:0] r_data = 0;
    reg [WIDTH-AWIDTH-1:0] fraction = 0;
    reg [WIDTH-AWIDTH-1:0] count = 0;
    reg [AWIDTH-1:0] r_out = 0;

    wire [WIDTH-AWIDTH-1:0] WK = 0;
    wire [AWIDTH-1:0] A_ZERO = 0;
    wire [AWIDTH-1:0] A_WK = ~A_ZERO -1;
    wire [WIDTH-1:0] I_MAX = { A_WK , ~WK };
    wire [WIDTH-1:0] I_DATA2 = (&I_DATA[WIDTH-1:WIDTH-AWIDTH])
    ? I_DATA : I_MAX;

    always @(posedge CLK)
    begin
    r_stb <= STB;
    if (&count | (STB & ~r_stb))
    begin
    count <= 0;
    r_data <= I_DATA2;
    fraction <= I_DATA2[WIDTH-AWIDTH-1:0];
    r_out <= I_DATA2[WIDTH-1:WIDTH-AWIDTH];
    end
    else
    begin
    { r_out, fraction } <= { A_ZERO , fraction } + r_data;
    count <= count + 1;
    end
    end

    assign A_OUT = r_out;

    endmodule

    例えば 3bit DAC では、3'b111 より大きな値は、出力できないので 上限値を設けてやる。後は最初に説明したとおり。

    すなおに、N bit の R-2R ラダー DAC を使っても良いのだが、これと 1bit ΣΔを組み合わせてみたい。

    module dacx # (
    parameter WIDTH = 8
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output A_OUT
    );

    reg [2:0] hi_clk;
    wire [2:0] hi_out;
    wire lo_stb = CLK;

    always @(negedge CLK)
    begin
    hi_clk <= hi_clk + 1;
    end

    dac #( .WIDTH(3) , .WITH_LATCH(0) ) dac_lo ( .CLK(CLK), .STB(lo_stb)
    , .I_DATA(hi_out), .A_OUT(A_OUT) );
    dac5 #( .AWIDTH(3) , .WIDTH(WIDTH-3) ) dac_hi ( .CLK(hi_clk[2])
    , .STB(STB), .I_DATA(I_DATA) , .A_OUT(hi_out) );

    endmodule

    こうかな?

    ところで、ラッチなしにして、2 ** N 回クロックを待たないで、適時値を変更しても、それなりに追従する。dac5 の方もラッチなしに対応しておいた方が応用が効きそうだ。

    あとは、フィルタがどうとか ... だが、クロックはたっぷりある。簡単なものなら加算だけで作れそうな気がする。これはまた別途考えてみたい。

    それとは別に、データをどうやって受け取るかという問題もある。パラレルは無理だしなにより面倒。やっぱり SPI ? 実は、JTAG 経由の通信というのも検討中。

R-2R ラダー DAC

    簡単に説明すると

    A_OUT_2 --- R ---+----- Analog
    |
    2R
    |
    A_OUT_1 --- R ---+
    |
    2R
    |
    A_OUT_0 --- R ---+
    |
    2R
    |
    GND

    こんな回路。抵抗が 2 種類しかいらないが、値が厳密。あと電流は取れないから、オペアンプを使ってボルテージ・フォロワを組む。

      FPGA 出力には、(数十Ωの)抵抗分がある。これにより R と 2R の比が崩れるので注意。また、出力電流を 4段階ぐらいで調整できる(たぶん 抵抗分が変わる)ので、チューニングに使えるかも。

    秋月で 1608 をリール売りしているから、1K/2K , 1.2K/2.4K , 1.5K/3K とかの組みを選ぶと良さそう。2500 個もあるから、選別しほうだい。

    選別には、SMDテスターが便利そう。3000 カウントだから 3K は避けたほうが良いか。

動作周波数について

    2 つの モジュールにすることで、周波数を上げられないか? やってみたのだが、4bit でも 16bit でも 150MHz ぐらいで、あまり変わらない。2 つの モジュールにしても、やっぱり あまり変わらない。要するに CLK 自体が ボトルネック。150MHz ということは、12bit で 36.6 kHz が上限。3bit の R-2R DAC + 10 bit ΣΔあたりが妥当?

    ところで、100 MHz で 駆動したとして 例えば 010101 というパターンが出てきたとき素直に駆動して良いものなのだろうか? 1bit ならともかく、多ビットだと 周波数を落としたくなる。

    PWM だと 周期が低くなりすぎるし ... PWM + ΣΔ が良いのだろうか? PWM でも Phase Correct MODE というのが AVR にある。UP していって TOP まで行ったら 逆に DOWN していく。こうすると スイッチング周波数は、普通の PWM の 1/2 。例えば 4bit Phase Correct MODE PWM なら CLK が 100 MHz でも スイッチング周波数は、3 MHz ぐらいまで落とせる。ちょっとこれを検討してみたい。2 つの モジュール は、既に出来ているから、片方を PWM にするだけ -- 難しいことはない。

    3bit の R-2R DAC で PWM を使い 、さらに上位を ΣΔにするのは、ちょっと難しい。これも 挑戦してみよう。

    PWM を使うのとは別に 010101 というパターンを 例えば 000111 というのに変換してしまうのはどうだろう?

    考えたのは、次のやりかた。

      今出力しているのが 1 だとする。次に出力するのが、01 だと分かっているなら 10 にしてしまうのだ。(0 についても同様)

      1 01 → 1 10
      0 10 → 0 01

      要するに 単独の 1/0 を出ないようにしてしまうわけだから 絶対に 2bit は続くわけだ。3 bit に拡張するなら、上記に加えて

      1 001 → 1 100
      0 110 → 0 011

      とする。これで 3 つ以上連続することが保証される。

      ただ、この処理には問題がある。例えば 2 を出力する場合 、わざわざ離れた位置に 1 を配置するわけだが、これを くっつけてしまう。連続した数を数えておいて 、くっつける処理をキャンセルする必要がある。

      なにやら面倒な話になってきた。やはり PWM の方が良さそうな。

SPI 通信の例

    秋月で売っている DAC MCP4922 や ADC の MCP3002 は、SPI で通信する。プロトコルを調べてみた。

    DAC :
     ・ 1 データ 送受信の度に CS を 上げ下げする必要がある。
     ・ 1 データは、MSB first , 16 bit 単位

    bit15 ~ A/B チャネル選択
    bit14:12 config
    bit11:0 データ[11:0]

    ADC:
     ・ 1 データ 送受信の度に CS を 上げ下げする必要がある。
     ・ 1 データは、MSB first , 24 bit 単位

    bit23 START (1)
    bit22:20 config (チャネル含む)
    (ADC → Master)
    bit19 NULL (0)
    bit18:9 データ[9:0]
    bit8:0 データ[1:9]

     ・ START を 遅らせて 16 bit 単位 にすることも可能

    bit15 (0)
    bit14 START (1)
    bit13:11 config
    (ADC → Master)
    bit10 NULL (0)
    bit9:0 データ[9:0]

    CS を 上げ下げする必要があるのは、FPGA 側としては、都合が良いかも。SPI というからには、MSB first にした方が自然。バイト単位になるような配慮も必要。変換タイミングは、正確にしたいだろうから 別途ということになりそう。(AVR だと PWM を使う)

    もっと大きな問題があった。クロックをどうしよう。変換タイミングを完全に正確にするには、送信側から CLK をもらうか、DAC 側のタイミングに 合わせてもらうか ...

    送信側から CLK をもらうにしても 50 MHz 〜 100 MHz というのは厳しい。規模が大きい FPGA だと PLL があるが、MachXO2-256 にはない。

    今回のは、DAC 側のタイミングに 合わせてもらうことにしよう。それにしても 問題が。AVR を使うならなんとかなるのだろうが、PC と通信する場合は、どうしよう。

    普通にシリアルで CTS/RTS 制御で良いのかな? 115Kbps だと 11.5 Kbytes/sec になってしまって 性能が足りない。1M bps とか できたら 3M bps とか欲しいところだが ... 途切れなく送り込めるのかどうかが問題。

    DAC を設計するなら MachXO2-1200 ぐらいは 欲しいところで、PC とのインターフェイスに FT2232H を使いたいということになりそうだ。 そういうものとして、『MachXO2 Breakout ボード』があるのだが ... 今回は 256 を使いこなすのがテーマなのでパス。とにかく AVR で使えるものだけを考えよう。

    ここまでの検討で
     ・ SPI を使う。
     ・ データリクエストを DAC 側が出す。
     ・ 16bit 単位
     ・ DAC データは、12bit-14bit の範囲
    ということに決めた。

    プロトコルは、別途。

ソースコード

関連記事
posted by すz at 22:55| Comment(0) | TrackBack(0) | CPLD
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/56627955
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック