PSoC5LP Prototyping Kit (8) --- SPDIF_Tx と DMA (7)
ISR (Interrupt Service Routine) の作成方法について前回述べたのとは方針を変えることにしました。
インタラプト・コンポーネントの配置により自動生成される C ソース・コードには変更を加えず、ISR は main.c ファイル中で定義して、API の Start() 関数の代わりに StartEx() 関数を呼び出して ISR のアドレスを登録します。
ISR の内容を下に示します。
CY_ISR(ISR_spdif_tx0_InterruptEx) { uint8_t curr_td; // get current TD number CyDmaChStatus(DMA_spdif_tx0_Chan, &curr_td, NULL); if (curr_td == DMA_spdif_tx0_TD[0]) { // the first TD // reset FIFO read index dac_fifo.rd_ix = 0; } else { // advance FIFO read index to next DMA buffer chunk dac_fifo.rd_ix += DMA_BUF_SIZE; } // if (curr_td == ... } // CY_ISR(ISR_spdif_tx0_InterruptEx)
FIFO バッファから SPDIF モジュールへのデータ転送は DMA ハードウェアで自動的に行なわれるため、DMA 転送完了割り込みでは DMA 関連については何もすることがありません。
FIFO バッファに書き込むソフトウェア側で FIFO が一杯かどうかを知るための dac_fifo.rd_ix 変数の値を更新する必要があるだけです。
DMA 転送完了割り込みごとに DMA_BUF_SIZE だけ増加させていき、FIFO_BUF_SIZE でラップ・アラウンドさせます。
通常の使用状態では、この方法で特に問題はありませんが、たとえば、デバッガを起動してプログラムの実行を一時停止している状態では、DMA は継続しているのに割り込みの応答は停止して、両者が協調しなくなっています。 プログラムの停止により内容が更新されなくなった FIFO の中身を、DMA が延々と繰り返して出力することになります。
その後、実行を再開しても、DMA と割り込みとの関係が崩れていて、FIFO に関する同期も自然には回復しなくなります。
そこで、上記の実際の ISR では DMA 関係の API 関数 CyDmaChStatus() を呼び出して現在の TD 番号を curr_td 変数に読み出しています。
そして、控えておいた TD[0] と curr_td を比較し、両者が一致して、これが最初の TD であることが分かると dac_fifo.rd_ix を 0 にリセットします。 この時点で、ソフトウェア側と DMA ハード側の同期が取れることになります。
TD[0] と curr_td とが一致しない場合は単に dac_fifo.rd_ix を DMA_BUF_SIZE 増やすだけです。
main() 関数を下に示します。
int main() { /* Place your initialization/startup code here (e.g. MyInst_Start()) */ const uint32_t fract_bits = 4; // bit width of fraction part // sin_cos_freq = 48.000 [kHz] * 3775 / (2 * pi * 2^16) = 440.05 [Hz] // sin_cos_freq = 46.875 [kHz] * 3865 / (2 * pi * 2^16) = 440.00 [Hz] const uint16_t acc_mul = 3775; const uint32_t acc_shift = 16; volatile int32_t sin_acc = 0; volatile int32_t cos_acc = (0x7c00L << fract_bits); // initialize FIFO write/read index dac_fifo.wr_ix = 0; dac_fifo.rd_ix = 0; // start components DMAconfig(); SPDIF_Tx0_Start(); SPDIF_Tx0_EnableTx(); ISR_spdif_tx0_StartEx(ISR_spdif_tx0_InterruptEx); CyGlobalIntEnable; /* Uncomment this line to enable global interrupts. */ for(;;) { /* Place your application code here. */ if (dac_fifo.rd_ix != dac_fifo.wr_ix) { // FIFO has room // update stereo audio sample dac_fifo.buf[dac_fifo.wr_ix++] = (sin_acc >> fract_bits); // L ch dac_fifo.buf[dac_fifo.wr_ix++] = (cos_acc >> fract_bits); // R ch // wrap around FIFO write index if (DAC_FIFO_SIZE <= dac_fifo.wr_ix) { dac_fifo.wr_ix = 0; } // if (DAC_FIFO_SIZE <= ... // DDA calculation sin_acc += ((acc_mul * cos_acc) >> acc_shift); cos_acc -= ((acc_mul * sin_acc) >> acc_shift); } // if (dac_fifo.rd_ix != ... } // for (;;) { ... } // int main()
FIFO の読み/書きインデクスの初期化を行い、各コンポーネントの Start を行い、無限ループ中で DDA による約 440 Hz のサイン波/コサイン波の発生を行ない FIFO バッファに書き込んでいます。
ISR_spdif_tx0_StartEx() 関数が割り込みサービス・ルーチンのアドレスを指定した上でインタラプト・コンポーネントをスタートさせる API 関数です。