ディジタルオーディオ用 DAC をマイコンにつなぐ(3)

ここからは、各マイコンごとに、FM音源プログラムでの実例を示します。
まず最初は CQ 出版「インターフェース」2007年5月号付録基板 (CQ_V850) の V850 の例です。

BU9480F (ROHM)

BU9480 は2倍オーバーサンプリングということになっていますが、使われているフィルタは、単純な一次ホールド (相加平均) 特性です。
現在 n 番目のフレームで、L_n, R_n のデータが入力されていると仮定すると、両方のデータがそろった、サイクル最後の LRCK の立上がりエッジで、L_{n-1}, R_{n-1} に対応するアナログ値が出力され、LRCK の半サイクル後の立下りエッジで (L_{n-1}+L_n)/2, (R_{n-1}+R_n)/2 に対応するアナログ値が出力されるようになっています。
L ch だけデータを入力して、R ch については全くデータを入力しない (もちろんクロックも) 場合、L/R ともに同じデータが入力されたかのように振る舞います。
他の DAC の場合、L/R どちらかのデータ入力時のクロックが途切れると、出力を強制的にミュートするものもあります。
BU9480 の場合、1チャンネルの出力だけ使う場合には L ch だけデータを入力すればよく、手間が省けます。

V850 の場合

V850 では3線式可変長シリアルI/O(CSIB)モジュールの CSIB1 を使っています。
下の回路図のように、シリアル・クロック出力 SCKB1 を DAC の BCLK につなぎ、シリアル・データ出力 SOB1 を DAC の SDAT につなぎます。

LRCK は 16 ビット・タイマ/イベント・カウンタP(TMP)モジュールの TMP2 を使って生成しています。 TOP21 出力を DAC の LRCK につなぎます。
TMP2 タイマおよび CSIB1 モジュールの初期化のコード部分を抜き出したリストを示します。

void v850_port_setup(void)
{
  ...
// Timer2 setup
  TP2CCR0  = 1250 - 1; // 20 MHz / 1250 = 16 kHz
  TP2CCR1  = TP2CCR0 / 2;
  TP2OPT0  = 0x00; // CCR1 = compare, CCR0 = compare
  TP2IOC0  = 0x05; // TOP21 = square wave, TOP20 = square wave
  TP2CTL1  = 0x04; // interval timer mode
  TP2CTL0  = 0x80; // timer enable, clock = fxx
  TP2CCIC0 = 0x07; // enable Timer2 Compare 0 interrupt, level = 7
  PFCE9L.6  = 0x01; // P96 = TOP21
  PFC9L.6   = 0x01; // P96 = TOP21
  PMC9L.6   = 0x01; // P96 = TOP21

// CSIB setup for digital audio DAC
  PFC9H.0   = 0x01; // P98 = SOB1
  PMC9H.0   = 0x01; // P98 = SOB1
  PFC9H.1   = 0x01; // P99 = SCKB1
  PMC9H.1   = 0x01; // P99 = SCKB1
  CB1CTL1   = 0x19; // CKP=1, DAP=1, clk=fxx/4 master
  CB1CTL2   = 0x08; // 16 bit transfer
  CB1CTL0   = 0xC0; // enable, TX enable, single, MSB first
  ...
} // void v850_port_setup()

実際には、同時発音数を切り替える時に、タイマの分周数を変えて、サンプリング周波数を変化させています。
CSIB1 の動作モードは、

  • MSB ファースト
  • シングル転送モード
  • 通信タイプ4
  • クロック = fxx/4
  • マスタ・モード
  • 16 ビット転送

に設定しています。
ハードウェアで 16 ビット転送がサポートされているので、タイマ割り込みで SPI 出力する部分は、下のリストのように、きわめて簡単です。

// Timer2 IRQ handler
#pragma interrupt INTTP2CC0 user_TP2CC0 // Timer2 Compare 0 interrupt

__interrupt void user_TP2CC0(void)
{
  int w;
  ...
  CB1TX = w; // spi output
  ...
} // __interrupt void user_TP2CC0()

リストでは省略してありますが、w に出力すべきデータが用意されているとして、その値を送信データ・レジスタ CB1TX に書き込むだけです。
前回の出力が終了して、アイドル状態にあることは保証されているので、送信レジスタの空きステータスをチェックする必要もなく、また、L ch のデータだけを書いていますから、非常に簡単です。