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

次は Atmel ATmega シリーズの SPI モジュールを使った場合です。
オーバーサンプリング DAC を使用できるように 384 fs も生成しています。 ただし、実際に接続して確認はしていません。
ATmega168 と BU9480 との接続は下の図のように行います。

SPI は ISP (In-System Programming) 端子と共用していますから、プログラムの書き込み時に DAC の BCLK、SDAT 端子も変化します。
このとき、LRCK がフローティングだと、オーディオ出力に異音を生じますから、LRCK がフローティングにならないようにプルアップしています。
タイマ 2 で 24 MHz のクロック (スペック外) を 1/4 にして、6 MHz の 384 fs クロックを作っています。 タイマ 2 出力からタイマ 0 の外部クロック入力へ、外部で接続します。
タイマ 0 では 1/384 して LRCK (15.625 kHz) を生成しています。
SPI は次のように設定しています。

  • マスター・モード
  • SPI 転送モード 0
  • MSB ファースト
  • SPI クロック = fosc / 32

タイマおよび SPI モジュールの初期化コードを次に示します。 (WinAVR の gcc 使用)

// Timer2/Timer0 setup for digital audio DAC
// 384fs generation (24 MHz / 4 = 6 MHz) using Timer2
  TCCR2A = (_BV(COM2B0) | _BV(WGM21)); // OC2B flip at compare, CTC mode
  TCCR2B = _BV(CS20);  // clk_2TS with no prescaler (24 MHz)
  OCR2A  = 2-1;        // divide by 2
  OCR2B  = OCR2A;      // compare value
  DDRD  |= _BV(PD3);   // enable OC2B output
// LRCK generation (6 MHz / 384 = 15.625 kHz) using Timer0
  TCCR0A  = (_BV(COM0B0) | _BV(WGM01)); // OC0B flip at compare, CTC mode
  TCCR0B  = (_BV(CS02) | _BV(CS01));  ; // clock source = falling edge of T0
  OCR0A   = 192-1;        // divide by 192
  OCR0B   = OCR0A;        // compare value
  TIMSK0  = _BV(OCIE0A);  // Timer0 Output Compare Match A interrupt
  DDRD   |= _BV(PD5);     // enable OC0B output
// SPI setup for digital audio DAC
  SPSR   = _BV(SPI2X);
  SPCR   = (_BV(SPE) | _BV(MSTR) | _BV(SPR1)); // enable SPI, fosc/32, Master, CPHA=0, DORD=0, CPOL=0
  DDRB  |= (_BV(PB3) | _BV(PB5));      // enable output for MOSI(PB3), SCK(PB5)
  DDRC  |= _BV(PC5);

SPI モジュールでは、受信側にはバッファレジスタがありますが、送信側にはバッファがなく、シフトレジスタに直接書き込む形になるので、2バイト目を出力するには、1バイト目の出力完了を待たなければなりません。
フラグをチェックしながらループで待つのは非効率ですから、1バイト目はタイマ割り込みルーチンで出力し、2バイト目の準備として SPI 送信完了割り込みを許可しておきます。
2バイト目は SPI 送信完了割り込みルーチンで出力したあと、 SPI 送信完了割り込みを禁止しておきます。
割り込みルーチンのリストを下に示します。 リストでは省略してありますが、出力データの上位バイトが GPIOR2 に、下位バイトが GPIOR1 に格納されています。

// Timer0 compare A match interrupt
ISR(TIMER0_COMPA_vect)
{
    ...
    SPSR;            // read SPSR to clear SPIF
    SPDR  = GPIOR2;  // output high byte
    SPCR |= (_BV(SPIE)); // enable SPI interrupt to output low byte
} // ISR()

// SPI Serial Transfer Complete interrupt
ISR(SPI_STC_vect) {
  SPDR  = GPIOR1;       // low byte
  SPCR &= (~_BV(SPIE)); // disable SPI interrupt
} // ISR()