ディジタルオーディオ用 DAC をマイコンにつなぐ(10) -- STARM (Cortex-M3)

しばらくサボっていたのですが、やっとディジタルオーディオ用 DAC (ROHM BU9480F) を CQ-STARM 基板につなぎました。
CQ-STARM 基板に搭載されている STmicro STM32F103VB プロセッサには SPI が2回路内蔵されていますが、SPI1 は SD/MMC カードインターフェースに使われているので、SPI2 のほうを使うことにします。
この SPI 回路は 16 ビット転送モードを持っているので、ソフトウェアの負担は軽くなります。
DAC との接続は、このような回路になります。(→)
SPI2 は remap 不可能なので、必然的に SPI2_SCK (52 番ピン、J3-10)、SPI2_MOSI (54 番ピン、J3-8) は確定します。
サンプリング周期の割り込み源としてタイマ 3 を使っているので、DAC の LRCK としては、タイマ 3 の チャネル 4 の PWM 出力 (TIM3_CH4、36 番ピン、J3-23) を使うことにします。
タイマ 3 の初期化部分のコードは次のようになります。

// Timer 3 setup ( fs generation )
  RCC_APB1ENR_bit.TIM3EN = 1; // enable clock 
                              // for TIM3
  GPIOB_CRL = 0x000000A0; // PB1 = TIM3 CH4 out
  TIM3_CR1   = 0x94;  // down count, interrupt
                      // on OVF/UDF only
  TIM3_CR2   = 0x00;  // 
  TIM3_SMCR  = 0x00;  // slave mode disable
  TIM3_CCMR1 = 0x00;  // no ch1, ch2 compare
  TIM3_CCMR2 = 0x6800;// ch3 = disable, 
                      // ch4 = PWM1, OC4 = out
  TIM3_PSC   = 0;     // set prescaler to 1/1
  TIM3_ARR   = 4500-1;// set fs to 16 kHz
  TIM3_CCR4  = (4500 >> 1);  // duty register

  TIM3_CCER = 0x3000; // OC4 enable and invert
  TIM3_DIER = 0x0001; // enable UIE only
  SETENA0_bit.SETENA29 = 1; // enable TIM3 int   
  TIM3_CR1_bit.CEN     = 1; // enable timer 3

実際には、タイマ周期レジスタ (TIM3_ARR) および PWM のデューティーレジスタ (TIM3_CCR4) は、サンプリング周波数を変えるたびに設定し直されます。
SPI2 の初期化部分のコードは次のようになります。

// SPI setup for 16-bit Serial DAC
  
  RCC_APB1ENR_bit.SPI2EN = 1; // enable clock 
                              // for SPI2
  SPI2_CR1 = 0x0B44 + (0x03 << 3); //  16bit,
//          master, MSB first, 72/16 = 4.5MHz clk

ここで、この SPI 回路は、マスターモードの場合でも NSS (Slave Select) 信号に影響されることを注意する必要があります。
NSS 信号の動作モードには次の 3 種類があります。

  • ハードウェア入力モード
  • ハードウェア出力モード
  • ソフトウェアモード

ハードウェア入力モードでは、NSS ピンは入力となり、「H」レベルに保持しておかないと送信動作をしません。
ハードウェア出力モードでは、NSS ピンはスレーブセレクト出力として機能します。
ソフトウェアモードでは NSS ピンは無関係となりますが、代わりに SPI_CR1 レジスタ内に設けられた SSI ビットが送信動作のイネーブルに使われます。
最初、このことに気づかなくて、SPI が動作せず悩みました。
DAC 出力はスレーブセレクトで制御する必要はないので、ソフトウェアモードとし、初期設定でイネーブルしたままにしておきます。
サンプリング周期での割り込みルーチンでの処理は次のようになります。

// Timer3 IRQ handler
void TIM3_IRQHandler(void)
{
  int32_t lw;
  if (TIM3_SR_bit.UIF & 0x01) {  // UIF set?
    t1_tick = 1;    // set timer1 tick flag
    lw = wave_que[wq_rd_ix]; // get wave data
    SPI2_DR = lw; // output 16-bit data to DAC
    wq_rd_ix = (wq_rd_ix + 1) & (WAVEQUESIZE-1);
    TIM3_SR_bit.UIF = 0;  // clear UIF flag
  } // if (TIM3->SR ...
} // void IRQ_Handler()

16 ビット転送モードを使っているので、DAC 出力のために追加すべき処理は

    SPI2_DR = lw; // output 16-bit data to DAC

の1行だけです。