2015年12月1日公開
はじめに
このところ「デジタル・コンサート・ホール」というもので楽しみ始めました(音だけで楽しんでいます)。チケット購入は週間視聴コースからで、PayPalでも決済できます。
http://www.digitalconcerthall.com/
さて、AD5611という10bit DACがあります。小型の6ピンのパッケージで「nanoDAC」と呼ばれていますが、「プチDAC」という感じです。
AD5611: 10ビット D/Aコンバータ、nanoDAC(TM)、2.7~5.5Vで100μA未満
謎の一石二鳥をめざし(笑)、これをFPGAから駆動させてみます。この技術ノートの後半にも、そのRTLも公開します。図1, 2はSC70パッケージのAD5611AKSZです。パッケージがSC70というのも「プチDAC」のとおり、小さくて素敵ですね。
(入手した状態。静電バッグに入っている)
図2. 10ビットnanoDAC AD5611AKSZ
(静電バッグから取り出したリール)
やはり遊ぶには(ホントは)オーディオ信号か?
知り合いの方からもこんなコメントもいただきました。「44.1kHz/16bitのオーディオ信号を25倍くらいにオーバ・サンプリングして、簡単に2次くらいでΣΔ変調して、このDACに突っ込んであげるのも楽しそうですね」。
私も「デジタル・コンサート・ホール」を聞いたりする都合(?)上、HD (High Definition)なディジタル・オーディオの信号再生にもこだわりたいところです。
その点からすれば、この方のアイディアはなかなか面白いです!私は絶対に気がつかないところでした。たしかにAD5611AKSZでもレートも十分間に合います。AD5611AKSZ ではSCLK = 30MHzまで行けますので、たとえば18 SCLKで1 SEQ(1DACコード)を組むとすると、1.666Mspsですから、サンプリング44kHzとすれば37倍程度までのオーバ・サンプリングまでが実現できますね。
とか何とか言っても、現実には時間的余裕もないことから、このAD5611AKSZを用いてHDな再生システムなど作り込むなんて夢の夢ではありますが…。
なお、このDACファミリは分解能が複数あって、
AD5601 8ビットAD5611 10ビット(今回使うもの)
AD5621 12ビット
AD5641 14ビット
となっています。AD5641以外はデータシートが共用です。
ΣΔ変調をかけるとSN比が向上する
ここでこの方から頂いた「簡単に2次くらいでΣΔ変調して」という話題をアナログ・デバイセズの技術資料をネタに少しご紹介してみます。図3、図4は参考文献[1]「The Data Conversion Handbook」のChapter 3「Data Converter Architectures」から抜粋したものです(CQ出版社からも「A-D/D-A変換ICの実用技術」として翻訳本が発売されています)。図3はナイキスト・サンプリングと、オーバ・サンプリングと、ΣΔ変調を用いた場合のそれぞれのAD変換(これはAD変換です)結果として得られるノイズのようすを示しています。ΣΔ変調により、ノイズ成分が高周波帯にシェーピングされる(押し出される)ことにより、不要な部分「REMOVED NOISE」とあるところをフィルタリングすれば高いSN比が実現できるというものです。
また図4はΣΔ変調の次数と、それぞれのオーバ・サンプリング比により実現できるSN比の改善率(dB)を表しています。この方のおっしゃる「2次で」そして「AD5611AKSZ でSCLK = 30MHz、44kHzレートなら37倍程度までのオーバ・サンプリング」というところを見てみると、60dBのSN比の改善ができそうです。1ビットは約6dBなので10ビット相当になります。AD5611AKSZは10ビットのDACなので、20ビット相当のDACの性能が実現できることになりそうです。
14ビットDACの AD5641を使えば、24ビット相当が実現できそうなわけですね!
図3. ADCアーキテクチャごとによるSN比改善のようす。
一番下がΣΔ変調を用いたもの。
ADI技術資料「The Data Conversion Handbook」Fig. 3.125から抜粋
図4. ΣΔ変調の次数と、それぞれのオーバ・サンプリング比により実現できるSN比の向上率。
ADI技術資料ADI技術資料「The Data Conversion Handbook」Fig. 3.131から抜粋
はんだ付けしてみたようす
ということで実装のようすをお見せします。SC70ということで、ピンピッチが0.65mmなのですが、図5に示す秋月電子のSOT23の変換基板に図6のように実装してみました。見ていただいて分かるようにピンピッチ(SC70 = 0.65mm)と基板のパッド(SOT23 = 0.95mm)のサイズが合わないのではんだ付けは慎重に行いました。
ピッチが合わないとしても、片側3ピンずつだから出来る荒業かもしれません(笑)。
はんだ付けといえば、0.5mmの400ピンクラスのFPGAを手はんだではんだ付けしたことがあります。最初は失敗しましたが、コツ(いろいろあるのですが)をつかめばキレイに手はんだできるようになりました。最初は高価なICを複数個「お釈迦」にしてしまった覚えがあります。0.5mmピッチの長いリードが、複数、曲がって、グチャグチャに、ショートしていく、そのようすは、涙なしには語れません…。
図5. nanoDAC AD5611AKSZ(SC70パッケージ0.65mmピッチ)と
秋月電子のSOT23変換基板(0.95mmピッチ)
図6. nanoDAC AD5611AKSZを
SOT23変換基板に実装したようす
FPGAは書籍の付録で
AD5611AKSZにデータを送り込むFPGAについてご紹介します。図7はCQ出版の書籍の付録としてついていた、XC3S100Eを使ったボードです。これは図8に示す本で、Verilogの本なのですが、私はVerilogが書けないのでVHDLで使っています(汗)。
動作させたいGCLKピンにCLKを接続するために、33MHzのXTALからワイヤリングしてあります。
SPIのSCLK(16.5MHz)の信号は、ピンヘッダ端子から1kΩを経由させてAD5611AKSZにデータを与えてみました。大丈夫かと思ったところ、波形が「なまりすぎ」でしたので、330Ωに変更して、図9のオシロの波形となりました。
図7. AD5611AKSZにデータを送り込むFPGAには
書籍の付録を利用
図8. FPGAボードがついていた書籍。
この書籍自体はVerilogでの記述だがVerilogが書けないのでVHDLでコーディング
図9. FPGAボードからの16.5MHzのSCLK信号
330Ωで信号なまりを取りつつ波形を安定化
図10. SPIの1シーケンス(SYNCがLになっている)
図11. SPIのシーケンス間(SYNCの立下りが2箇所みえる)
図12. 1シーケンスのスタート(SYNCの立下りがみえる)
シリアル波形がでた!でた!
実際にFPGAにプログラミングしてみて、SPIのシリアル波形をロジアナで観測してみました。図10はSPIの1シーケンスのようすです。1フレームを表すSYNC信号がLになっているのが見えます。図11はシーケンス間のようすを観測してみたものです。SYNCの立下りが2箇所みえます。1フレーム全長と次のフレームのスタートというところです。図12は1シーケンスのスタートのようすです。SYNCの立下りがみえています。
動きそうな動作を一応しています。
プチDACを実装したようす
AD5611AKSZを実装したようすをお見せします。図13に示すような、手もちのDIP用実験プラットフォーム(?)のアキのところにグラウンドがきちんと取れるようにはんだ付けしてあります。手前はSCLKなどのデジタル入力の接続です。さきの説明のようにFPGAとは330Ωで分離しています。
AD5611AKSZは電源電圧を1/1024に分割するDACで(つまりリファレンス電圧が無い)、精度を出そうとすれば「電源電圧の精度」を高めることが基本になります。ここでは高精度LDOであるADP3301を使ってみました(図14)。このLDOは電圧精度が室温で±0.8%というもので、結構な特性が得られる優れモノです。
ADP3301: リニア・レギュレータ、100mAの低ドロップアウト、高精度、anyCAP(R)
三角波信号波形がでた!でた!
最終的にできた三角波信号の波形を図15に示します。1変換サイクルで1LSBインクリメント(デクリメント)しているので、周期は2048 / fs(fs = 515.6kHz)となり結構低めです。
図13. DIP用実験プラットフォームと言えば聞こえがいいが、実験基板のアキのところ実装したようす
図14. 高精度LDO ADP3301をDAC電源(リファレンス電圧)として利用し精度を高める
RTLソースのご紹介(三角波を発生)
以下は「おまけソースつき」のソースです(駄菓子のおまけみたいですが)。VHDLで書いてあります。図16のソースコードは図10~図12で示したAD5611AKSZへのSPI通信を制御するモジュールのソースコードです。
図17はその上位モジュールとして、三角波波形生成とCONVSTART信号を生成するモジュールのソースコードです。ここのコード部分は上位との接続用としてのentity記述やarchtecture記述は省略してあります。謎の「一石二鳥」を目指しているために、2つのAD5611を設定できるようになっています。それから制御ビット2ビットは"00"固定にしてあります。
ステート・マシン設計が大好きなので、どうでもいいようなところをステート・マシンで作っています(汗)。
図15. AD5611AKSZ出力で得られた三角波信号
おまけのおまけ
これまでのところで、この技術ノートも終わりにしようと思っていましたが、このシリーズの特設ページがありましたのでご紹介しておきます。
-------------------------
-- AD5611 serial control
-------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
-- This module dedicates for two DACs
entity CtrlAD5611 is
port(RESET : in std_logic;
CLK : in std_logic;
CONVST : in std_logic;
DAData1 : in std_logic_vector (9 downto 0);
DAData2 : in std_logic_vector (9 downto 0);
SYNC_low : out std_logic;
SCLK : out std_logic;
SDIN1 : out std_logic;
SDIN2 : out std_logic
);
end CtrlAD5611;
architecture Behavioral of CtrlAD5611 is
type LOAD_STATE is (Idle, Loading);
signal ShiftReg1, ShiftReg2 : std_logic_vector (15 downto 0);
signal SclkTimer : std_logic_vector (3 downto 0);
signal SclkToggle : std_logic;
signal STATEREG, NEXTSTATE : LOAD_STATE;
begin
SCLK <= SclkToggle;
SDIN1 <= ShiftReg1(15);
SDIN2 <= ShiftReg2(15);
process(CLK, RESET) begin
if (RESET = '1') then
STATEREG <= Idle;
elsif (CLK'event and CLK='1') then
STATEREG <= NEXTSTATE;
end if;
end process;
process(STATEREG, CONVST, SclkTimer, SclkToggle) begin
case STATEREG is
when Idle =>
if (CONVST = '1') then
NEXTSTATE <= Loading;
else
NEXTSTATE <= Idle;
end if;
when Loading =>
if (SclkTimer = "1111" and SclkToggle = '0') then
NEXTSTATE <= Idle;
else
NEXTSTATE <= Loading;
end if;
when others =>
NEXTSTATE <= Idle;
end case;
end process;
-- Set chip select pin
process(CLK, RESET) begin
if (RESET = '1') then
SYNC_low <= '1';
elsif (CLK'event and CLK='1') then
if (NEXTSTATE = Loading) then
SYNC_low <= '0';
else
SYNC_low <= '1';
end if;
end if;
end process;
-- Set SCLK internal signal w/16 times toggle
process(CLK, RESET) begin
if (RESET = '1') then
SclkToggle <= '1';
elsif (CLK'event and CLK='1') then
if (STATEREG = Loading) then
SclkToggle <= not SclkToggle;
else
SclkToggle <= '1';
end if;
end if;
end process;
-- SCLK toggle timer
process(CLK, RESET) begin
if (RESET = '1') then
SclkTimer <= "0000";
elsif (CLK'event and CLK='1') then
if (STATEREG = Loading and SclkToggle = '0') then
SclkTimer <= SclkTimer + 1;
end if;
end if;
end process;
-- Data load registers
process(CLK, RESET) begin
if (RESET = '1') then
ShiftReg1 <= x"0000";
ShiftReg2 <= x"0000";
elsif (CLK'event and CLK='1') then
if (CONVST = '1') then
ShiftReg1 <= "00" & DAData1 & "0000";
ShiftReg2 <= "00" & DAData2 & "0000";
elsif (STATEREG = Loading and SclkToggle = '0') then
ShiftReg1 <= ShiftReg1(14 downto 0) & "0";
ShiftReg2 <= ShiftReg2(14 downto 0) & "0";
end if;
end if;
end process;
end Behavioral;
-------------------------
-- triangle waveform generation
-------------------------
process(CLK, RESET) begin
if (RESET = '1') then
DACtimer <= "000000";
CONVST <= '0';
elsif (CLK'event and CLK='1') then
DACtimer <= DACtimer + 1;
if (DACtimer = "111111") then
CONVST <= '1';
else
CONVST <= '0';
end if;
end if;
end process;
-- For triangle waveform
process(CLK, RESET) begin
if (RESET = '1') then
Result1 <= "0000000000";
elsif (CLK'event and CLK='1') then
if (CONVST = '1') then
if (UpDirection = '1') then
Result1 <= Result1 + 1;
else
Result1 <= Result1 - 1;
end if;
end if;
end if;
end process;
-- For triangle waveform
process(CLK, RESET) begin
if (RESET = '1') then
UpDirection <= '1';
elsif (CLK'event and CLK='1') then
if (UpDirection = '1' and Result1 = "1111111110") then
UpDirection <= '0';
elsif (UpDirection = '0' and Result1 = "0000000001") then
UpDirection <= '1';
end if;
end if;
end process;