PIC32MX220F032B (5) -- AC97 コーデックを接続する (2)
PIC32MX220F032B には DMA コントローラが 4 ch 内蔵されています。
ひとつの DMA コントローラを入出力に兼用できないので、AC97 コーデックの ADC 入力と DAC 出力とを同時に利用する場合には DMA コントローラを 2 ch 使用することになります。
DMA 用のバッファとしては、1 オーディオ・フレームを格納する 32 バイトのバッファを「2 面」用意する、つまり「ダブル・バッファ」あるいは「ピンポン・バッファ」と呼ばれる形式で行います。
DMA ハードウェアが、たとえば「A 面」をアクセスしている最中には、アプリケーション側は「B 面」をアクセスし、両者の競合を避けます。
DMA 側の「A 面」アクセスが終了したら、「A 面」と「B 面」の役割を入れ替えて、DMA 側は「B 面」、アプリケーション側は「A 面」をアクセスします。
そのあたりを模式的に書いた図を下に示します。
上の図には、DMA コントローラ自体は書いてありません。
PIC32MX の DMA コントローラでは、半分の量まで転送が完了した時点で割り込みを発生させることができ、これを DMA バッファの切り替えのタイミングとして活かすことができます。
上の図では、「バッファ[0]」と「バッファ[1]」を連続した位置に並べており、DMA としては、このふたつのバッファの内容をすべて転送し終えた時点が 1 回分の転送完了となります。
半分だけ転送完了した時点、つまりバッファ[0]側の転送が完了した時点で、「HALF_EMPTY」あるいは「HALF_FULL」割り込みが発生し、割り込みハンドラに制御が移ります。
割り込みハンドラでは、この割り込みをトリガとしてアプリケーション側のアクセスをバッファ[0]側に切り替えます。
DMA がバッファふたつ分完了すると、完了割り込み「SOURCE_DONE」あるいは「DEST_DONE」が発生し、割り込みハンドラではアプリケーション側のアクセスをバッファ[1]側に切り替えます。
DMA 転送 1 回分としては、これで終了ですが、DMA の自動リピート・モードを有効にしておくと、CPU の介在なして、自動的に再び転送アドレスをバッファの先頭に戻して DMA 転送を (無限に) 繰り返します。
下に AC97 出力側 (送信側) の DMA 設定および割り込み設定プログラムの断片を示します。
#define AC97_BUFWORDS 16 #define AC97_BUFBYTES (sizeof(uint16_t)*AC97_BUFWORDS) #define AC97_TxDMA 0 #define AC97_RxDMA 1 #define AC97_SPI 2 #define AC97_FIFO_DEPTH 8 uint16_t AC97_Txbuf[2][AC97_BUFWORDS]; // DMA setup for AC97 output DmaChnOpen(AC97_TxDMA, // DMA channel for AC97 output 3, // low DMA priority DMA_OPEN_AUTO); // auto repeat DmaChnSetTxfer( AC97_TxDMA, // DMA ch for AC97 output (void *)AC97_Txbuf, // source pntr (void *)SpiChnBuffer(AC97_SPI), // dest pntr 2*AC97_BUFBYTES, // source size (dbl buffer) sizeof(uint16_t), // dest size (16 bit data) AC97_FIFO_DEPTH*sizeof(uint16_t) // cell size ); DmaChnSetEventControl(AC97_TxDMA, DMA_EV_START_IRQ(_SPI2_TX_IRQ) ); DmaChnSetEvEnableFlags(AC97_TxDMA, (DMA_EV_SRC_FULL | DMA_EV_SRC_HALF)); DmaChnEnable(AC97_TxDMA); // interrupt settings INTSetVectorPriority(INT_DMA_0_VECTOR, INT_PRIORITY_LEVEL_5); INTSetVectorSubPriority(INT_DMA_0_VECTOR, INT_SUB_PRIORITY_LEVEL_3); INTEnable(INT_SOURCE_DMA(AC97_TxDMA), INT_ENABLED);
DmaChnOpen() 関数では、DMA のプライオリティや各種動作モードを設定します。
DMA_OPEN_AUTO フラグを指定すると、オート・リピート動作となります。
DmaChnSetTxfer() 関数では、転送元 (source) および転送先 (destination) の先頭アドレスやバイト・サイズを指定します。
DmaChnSetEventControl() 関数では、(1 セル分の) DMA 転送を開始するトリガとなる (DMA 外部のモジュールからの) 割り込みイベントを指定します。
DmaChnSetEvEnableFlags() 関数は、DMA コントローラ自身が生成する割り込みイベントの指定をします。
DmaChnEnable() 関数は DMA コントローラの動作を開始します。 ただし、実際のデータ転送は指定した割り込みイベントが発生するまでは開始しません。
PIC32 用の周辺ライブラリでは DMA 転送トリガとなる割り込みイベントを指定する関数には、引数の数と型が同じで、名前の似た次の 3 つの関数があります。
- DmaChnSetEventControl()
- DmaChnSetEventControlFlags()
- DmaChnWriteEventControlFlags()
「Set」と「Write」の違い、「Flags」が付くかどうかの違いだけです。
このうち、1 番目の DmaChnSetEventControl() 関数は #define で定義されるシンボルで、実体は 3 番目の DmaChnWriteEventControlFlags() 関数になります。
2 番目の DmaChnSetEventControlFlags() 関数の名前に含まれる「Set」は bit set / bit clr の「set」の意味で、引数の「1」になっているビット位置に対応する DCHxECON レジスタのビットを「1」にする動作しかしません。
DCHxECON レジスタ内の割り込み番号のデフォルト値は全部のビットが「1」の 255 なので、この関数では割り込み番号をデフォルトの 255 から変更することはできません。
前回、「当初 DMA が起動せず悩んだ」と書きましたが、それは 2 番目の DmaChnSetEventControlFlags() 関数を使っていたためでした。
DMA 開始のための割り込み番号が正しく設定されず、いつまでたっても DMA が開始されない状態になっていました。
XC32 コンパイラ付属のサンプル・プログラムと比較しても、特におかしい部分は見当たらず、半日くらい悩んでいましたが、上記の関数の違いに気が付き、書き換えたらあっさり動きました。