H8 + uPD6376 (16-bit DAC) で WAV を聴いてみる

概要

H8 に秋月電子で手に入る安価な 16-bit DAC uPD6376 を接続して WAV を聴いてみました。オペアンプは単一電源用途では定番の LM358N を使用しました。

YM2151 FM OPM を聴いてみるで、YM2151 をこの DAC で聴いてみたところかなりいい感じに聴けたので、ならば WAV もやるかというわけで作ってみました。作った結果、やはりいい感じに聴けました。 この程度の回路でここまでの音質が得られるのかというのが率直な感想です。 私が持っているどの再生機器よりも音質が良いので、これでしか音楽を聴かなくなってしまいました。

↓製作した H8 + uPD6376 試聴環境 (クリックで拡大)

見栄えに反して音質はかなり良い

戻る


パーツの入手性

パーツはほとんど秋月電子で手に入ります。 主な部品は H8/3052 (64F3052F25), uPD6376, LM358N, 2SC1815, FT245RL, Mini USB コネクタ, ユニバーサル基板, ヘッダピン, LED で、これらは実際に秋月電子で購入したものを使っています。

H8/3052 は単体で買ってピッチ変換基板に実装して手配線して使うよりも、 「AKI-H8/3052F マイコンボード」を購入した方が手軽で確実でしかも安いです(自分は単体で買ってしまったので手配線しましたが)。 20[MHz] 版と 25[MHz] 版が売られていますが、 特に理由がなければ 25[MHz] 版を買えばよいと思います(今回の用途では 20[MHz] 動作だと処理時間的に厳しいと思います)。


uPD6376 をマイコンと接続する

あまり参考にならないと思いますが、私は以下のように接続しました。 Vdd / Vss ピン等は各 IC の資料を参照してください。 ちなみに FT245RL のデータを P7 に割り当てたのは私のミスで、P7 は入力専用ポートのため FT245RL からはデータの受信しかできません(今回は受信しかしないので OK ですが)。

[H8/3052]                  [uPD6376]
SCK0 (16)       -->        CLK  (16)
TxD0 (12)       -->        LSI  (15)
P61  (59)       -->        LRCK (13)

[H8/3052]                  [FT245RL]
PA2 (95)        -->        RD#    (13)
PA3 (96)        -->        WR     (14)
PA4 (97)        <--        TXE#   (22)
PA5 (98)        <--        RXF#   (23)
PA6 (99)        <--        PWREN# (12)
P70 (78)        <--        D0 (1)
P71 (79)        <--        D1 (5)
P72 (80)        <--        D2 (3)
P73 (81)        <--        D3 (11)
P74 (82)        <--        D4 (2)
P75 (83)        <--        D5 (9)
P76 (84)        <--        D6 (10)
P77 (85)        <--        D7 (6)

冒頭の写真では H8 と DAC の間に IC が仲介していますが、これは後で不要と判明したため、現在は取り外しています。


FT245RL からデータを受信する

RXF# が L になったのを確認してから RD# を L にし、P7DR からデータを読み出し、RD# を H に戻します。 今回は受信のみなので、FT245RL の操作は基本的にこれだけです。


uPD6376 にデータを送信する

今回もっとも苦労したのがここです。

まず H8 のクロック同期モードは LSB → MSB の順にデータを送信しますが、uPD6276 は MSB → LSB の順に受け取るので、ビットの並びをいちいち逆順にしてから送信する必要があります。これは変換テーブルを作成して対応しています。

H8 のクロック同期モードでは、ボーレート(B)は以下の式から求められます(N は BRR の設定値(0 ≦ N ≦ 255)、Phi は動作周波数25[MHz])。

N = Phi / (8 * 2^(2n-1) * B) * 1000000 - 1

この式は N についての式なので、B の式にすると、次のようになります。

B = 1000000 * Phi / (8 * 2^(2n-1)) / (1+N)

今回はできるだけ高速に転送できれば良いので、n=0, Phi=25 として、

B = 6250000 / (1+N)

となります。よって N に 0 を設定すれば 6.25[MHz] のボーレートが得られることになりますが、H8/3052 のハードウェアマニュアルの表には 4[MHz] までしか記載がないため、N=1 に設定して 3.125[MHz] を使います。

今回は SCI0 をクロック同期モードで使用します。よって SMR0 の bit7 を 1 にセットします。また、上述により N=1 に設定するので、BRR0 には 0x01 を設定します。BRR0 を設定後 1-bit 期間以上経過してから SCR0 の TE ビットをセットします。

	// init SCI0 (clock sync mode)
	SMR0 = 0x80;
	BRR0 = 0x01; /* ~3MHz clock */
	asm ("nop");
	asm ("nop");
	asm ("nop");
	asm ("nop");
	asm ("nop");
	asm ("nop");
	asm ("nop");
	asm ("nop");
	asm ("nop");
	asm ("nop");
	SCR0 = 0x20;

H8 の SCI0 から uPD6376 に送信する場合、データ線は1本しかないので、L と R はシリアルに(交互)送信する必要があります。よって uPD6376 の1ピンは未接続とし、 RSI は GND に接続します。あとは下図のようにデータを送信します。

データの送信は、上位バイト、下位バイトの順で送信します。それぞれのバイトは、 ビットの並びを逆順にしてから送信します。下のコードの Lh, Ll, Rh, Rl はすでにビットの並びを逆にしてあります。

	SET_LRCK_HIGH;

	// wait for TDRE 
	while(!(SSR0 & 0x80));
	// set upper byte to TDR 
	TDR0 = Lh;
	SSR0 = 0x00;	// start transmit

	// wait for TDRE 
	while(!(SSR0 & 0x80));

	// write lower byte 
	TDR0 = Ll;
	SSR0 = 0x00;

	// wait for TDE 
	while(!(SSR0 & 0x04));

	SET_LRCK_LOW;

	// write upper byte 
	TDR0 = Rh;
	SSR0 = 0x00;	// start transmit

	// wait for TDRE 
	while(!(SSR0 & 0x80));
	// write lower byte 
	TDR0 = Rl;
	SSR0 = 0x00;

なんかいろいろごちゃごちゃとやっていますが、下のように書いた方がわかりやすいかもしれません。

  1. LRCK を H にする
  2. SSR.TDRE (送信バッファが空) が 1 であることを確認する
  3. L の上位バイト Lh を TDR に書き込む
  4. SSR0.TDRE をクリアし、送信を開始する
  5. SSR.TDRE が 1 になるのを待つ
  6. L の下位バイト Ll を TDR に書き込む
  7. SSR0.TDRE をクリアする
  8. SSR.TDE (送信完了) が 1 になるのを待つ
  9. LRCK を L にする
  10. R の上位バイト Rh を TDR に書き込む
  11. SSR0.TDRE をクリアし、送信を開始する
  12. SSR.TDRE が 1 になるのを待つ
  13. R の下位バイト Rl を TDR に書き込む

ポイントは 8 と 9 の、TDE を検出してから LRCK を変えるところです。私はここを TDRE 待ちにしていてハマりました。


ソースコード

あとはソースコードくらいしかないですね。

25[MHz] 動作の H8 で 44100[Hz] 周期の処理をする場合は約 567 クロック、48000[Hz] の場合は約 521 クロックで L と R 両方の処理を完了していなければなりません。H8 の平均命令処理クロック数を 5 とすると、 100 命令程度しか実行できません。実際にやってみるとかなり厳しかったです。全力でデータ転送を実行してやっと間に合う感じです。 当初割り込みを使用しようと考えていましたが、私が試した限りでは割り込みを使うやり方ではアセンブラでどう記述しても処理が間に合いませんでした。 当初 CompactFlash から WAV を読み出して。。。などと考えていましたが、結局無理でした。

H8 は1命令よりも細かい処理をすることができないので、1 サンプルの処理には最大 1 命令の実行クロック数に相当するジッタが存在します。これは人間が聴いて分かるレベルではないとして無視しています。 これを考慮する場合は専用の再生回路を作る必要があると思います。簡単なものであれば CPLD 1 個で作れそうです。H8 だとデータ転送だけでいっぱいいっぱいなので、パラレルで FIFO に書き込んでいくだけで再生してくれるような補助回路があった方がソフトの処理にも余裕ができて良さそうです。

WAV のヘッダは 0x2c (44) バイトにのみ対応しています。期待通りの WAV ヘッダを検知しないと DAC へデータ送信を開始しません。WAV のヘッダ解析は改良の余地があります。


DAC まわりの回路

DAC uPD6376 の回路は FC 互換機のプロジェクトで製作したものを流用しています。 今回は L と R を交互に送信するため、uPD6376 は Serial Data Input モードに設定します。このために uPD6376 の 1 ピンはオープンにしています。あと RSI は GND に接続し、LRCK が H のときに L (左)データ入力となるようにしています。

DAC の前の 74LS244 は、FPGA などで使用する場合の 3.3V の信号を 5V に変換するためのもので、今回の H8 では不要です。

uPD6376 からの出力を LM358N (バッファアンプ)に通し、 2SC1815 で電流増幅して 220[uF] のコンデンサで DC 成分を取り除いてイヤフォンに接続します (1 回路しか示していませんが、ステレオのため L と R の 2 回路分製作します)。

データシートには詳しく書かれていませんが、RREF と LREF ピンで DAC 出力の中間電圧を 2.5[V] に調整することができれば、2SC1815 の電流増幅部を 2SC1815 と 2SA1015 の SEPP にすることができると思います。 SEPP の方が良いかどうかは分かりませんが、試してみたい回路ではあります。 上の回路では、RREF と LREF ピンは 2.18[V] となっていました。


所感

そもそも uPD6376 を使いはじめた契機は、FC 互換機 のプロジェクトで試した PWM 方式のオーディオ出力の音質に満足できなかったことでした。PWM 方式は L と R それぞれ 1 ビットしか使わない利点がありますが、これに満足できないとなると、R-2R 方式の DAC を自作するか、素直に専用の DAC IC を使用するしかありません。実際に抵抗を 100 本くらい使って R-2R 方式の DAC も試作しましたが、こちらは PWM 方式よりも良い音質が得られるものの、 そのままでは使用する出力ピン数が多くなるのと、回路規模が不自然なくらい大きくなるので没にしました。(^^;

そんなわけで、ある程度普通に聴けそうな DAC を探したところ、秋月電子で 2 個 500 円という価格に惹かれて購入し使いはじめました。音質はそれほど重視しておらず、オペアンプも LM358N と、音質重視するのであればまず使わない IC を使っています。また、単一 5[V] 電源という時点でかなり使えるオペアンプが限定されてしまうため、この時点で音質はある程度妥協しなければなりません。

とりあえず PWM 方式よりも高音質であれば OK な回路でしたが、意外といい感じなので、今回の製作に至りました。 これの前に、VS1011E (mp3 デコーダ) を試しましたが、アナログ部の S/N 比が悪いらしく、無音状態で「スー」音がかなり気になったため、これも没になりました。

この DAC 回路は、LM358N を使っている割には無音状態でも回路自体の雑音がほとんど聞こえません。 回路自体の雑音が小さいため、録音自体に含まれている雑音に気が付くほどです。

回路的には、出力の 2SC1815 の部分をもう少しなんとかしたいところですが、期待以上の音質が得られているので OK とします。こんな回路でもかなり聴けることが判明したので、今度は秋月電子で手に入る FN1242A を使ってもう少し音質を重視した回路を作ってみようと思います。

今回のプロジェクトで H8 の遅さが浮き彫りになったので、メインで使用するマイコンを ARM や SH2 などに移行したいと思います。 FN1242A は 3.3V 動作なのと、ARM は OpenOCD でソースレベルデバッグができそうなので ARM マイコンを使う予定です。

戻る


(C) 2011 Ki inserted by FC2 system