PIC32MX220F032B (6) -- AC97 コーデックを接続する (3)

AC97 コーデックとインターフェースする信号の波形写真を示します。
まだコーデック自体は実装しておらず、ソフトウェアの方もアプリケーションの入出力部分は実装していません。
ダブル・バッファ構成の DMA バッファおよび割り込みハンドラで 1/2 転送完了割り込みと全転送完了割り込みを受け取っているだけです。
SPI モジュールは SPI2 を使用しています。
本来は AC97 コーデックから供給される 12.288 MHz のビット・クロックの代わりに、外部クリスタル・オシレータの 12 MHz を使っています。
まず、オーディオ・フレーム全体の波形を下に示します。

上のトレース (Ch1) が「SYNC」(SPI2 の SS2 出力) 信号で、オーディオ・フレームの開始を示す 16 ビット・クロック幅だけ「H」となる信号です。
SYNC パルスが 2 個見えているので、次のフレームの頭の部分も見えていることになります。
下のトレース (Ch2) が「SDATA_OUT」(SDO2 出力) 信号で、ほとんどの部分でゼロで、フレーム頭の「スロット0」の部分だけにゼロでない値を入れてあります。
DMA バッファはダブル・バッファ構成なので、アプリケーションがデータを更新しなくても、ふたつのバッファに納められた 2 種のデータ・パターンを交互に繰り返し出力します。
上の写真では、最初のフレームのスロット 0 データは 0x8001、2 番目のフレームのスロット 0 データは 0x8889 となっています。
最初の 0x8001 は b15 と b0 のみ「1」となっているので、SYNC パルスの前縁および後縁付近に 1 本ずつパルスが立つ形になっています。
2 番目の 0x8889 は b15、b11、b7、b3、b0 のみ「1」となりますが、確かに 5 本のパルスがあるのが分かります。
下の写真はスロット 0 付近を拡大したもので、0x8001 データ出力の場合です。

AC97 規格では、I2S インターフェースと同様に、SYNC 信号の立ち上がりエッジから 1 クロック分遅れて SDATA_OUT 信号の MSB が送出されます。
データの b15 の「1」の立ち上がりと、 SYNC 信号の立ち上がりとの間にすきまがあるのが分かります。
また、データの b0 の「1」の立ち上がりエッジは、SYNC 信号の立ち下りエッジと同じタイミングで出力されています。
SYNC 信号の立ち上がり部分をさらに拡大したものが次の写真です。

一番上のトレース (Ref1) が SYNC 信号で、これは波形メモリに格納しておいたものを見ており、リアルタイムの波形ではありません。
真ん中のトレース (Ch1) が SDATA_OUT 信号で、一番下のトレース (Ch2) が BIT_CLK (SCK2 入力) です。
Ch1 と Ch2 はリアルタイムで観測している波形です。
SPI はスレーブ・モードで動作させており、BIT_CLK (SCK2) は入力になります。
SYNC 信号 (SS2) や SDATA_OUT 信号 (SDO2) は SCK2 の立ち上がりで変化します。
ただし、クロックに対して遅延をともなっており、写真の波形からは約 20 ns のディレイと読み取れます。
下の写真は、割り込みハンドラでのバッファ切り替えのようすを RA4 ポートに出力して観察したものです。

上のトレース (Ch1) が SYNC 信号、下のトレース (Ch2) が RA4 ポート出力です。
現在のところ、アプリケーション側のプログラムは作っていないので、割り込みハンドラは下のリストのように割り込み要因をポーリングしてクリアし、ポート (RA4) に出力しているだけです。

// handler for the DMA channel 0 interrupt
void __ISR(_DMA0_VECTOR, IPL5SOFT) DmaHandler0(void)
{
  int  evFlags; // event flags when getting the interrupt

  INTClearFlag(INT_SOURCE_DMA(DMA_CHANNEL0)); // ACK to INT contl
  evFlags = DmaChnGetEvFlags(DMA_CHANNEL0);   // get the event flags
  if(DMA_EV_SRC_FULL & evFlags) { // DMA source buffer fully xfered
    DmaChnClrEvFlags(DMA_CHANNEL0, DMA_EV_SRC_FULL);
    LATASET = (1 << 4); // RA4 = H
  } else if (DMA_EV_SRC_HALF & evFlags) { // DMA source buffer half xfered
    DmaChnClrEvFlags(DMA_CHANNEL0, DMA_EV_SRC_HALF);
    LATACLR = (1 << 4); // RA4 = L
  } // if (DMA_EV_SRC_FULL & evFlags) ...
} // void _ISR(_DMA0_VECTOR, ...

DMA バッファ[0] の転送完了を示す DMA ソース・ハーフ・イベントで RA4 を「0」にしており、DMA バッファ[0] がアプリケーション側からアクセス可能になったことを示しています。
一方、DMA バッファ[1] の転送完了を示す DMA ソース・フル・イベントで RA4 を「1」にしており、DMA バッファ[1] がアプリケーション側からアクセス可能になったことを示しています。
上の波形写真で、オーディオ・フレームの真ん中あたりで RA4 がトグルしているのは、そこでそのフレームに対する DMA 転送が完了するからです。
DMA の「セル・サイズ」(バースト転送長)を SPI の FIFO 容量いっぱいの 8 ワードとしているため、1 オーディオ・フレームの 16 ワードに対して、セル転送 2 回で処理できることになります。
オーディオ・フレームの先頭付近でセル転送が 1 回、フレームの中央付近でセル転送が 1 回実行され、その時点で 1 フレーム分の転送が完了となります。
そのあたりの事情を詳しく見たのが下の波形です。

上のトレース (Ch1) が SYNC 信号、下のトレース (Ch2) が送信 FIFO 中のデータ・ワード数をアナログ電圧として観測したものです。
メイン・ルーチン内の無限ループで、送信 FIFO 中のデータ・ワード数を示す数値を読み取り、それを RB ポートに接続した重み付き抵抗ネットワークによる DA コンバータに出力して、アナログ電圧を得ています。
抵抗ネットワークによる DA コンバータは「ケンケン」さんの PIC32MX 用 NTSC カラーコンポジットビデオ信号出力プログラム (→ こちら) 用に配線したものをそのまま使いました。
FIFO 内のデータ・ワード数のアナログ波形は、「のこぎり波」のようになっていますが、鋭く立ち上がっている方は DMA によって FIFO 内にデータが急速に積み上げられている状態を示しています。
なだらかに下降している部分は SPI で送信されて送信 FIFO からデータが順次取り除かれていく状態を示しています。
12 MHz クロックでは約 1.33 μs ごとに 1 ワードずつ「消費」されていきます。
波形写真では、「階段状」に下降していくのが見えています。
フレームの中央部で波形の頭が平らになり、「のこぎり波」というより「台形波」という感じになっている部分は、そこで DMA 転送が完了して、割り込みハンドラに制御が移るため、メイン・ルーチンに制御が戻るまでの間は FIFO 内ワード数の出力更新が途絶えるのが原因です。
ただし、割り込みハンドラについては送信側しか実装していません
(01/19 追記: 割り込みハンドラの影響についての記述を修正しました)
下の写真は、同様のアナログ出力を受信 FIFO 内のデータ・ワード数に対して行ったものです。

これは受信なので、送信とは転送方向が反対になり、SPI 受信完了で「発生」したデータが「ゆっくり」受信 FIFO に積まれ、DMA によって「急速」に受信 FIFO から「引き抜かれる」形となり、送信とは逆向きの「のこぎり波」となります。
フレーム中央部の波形の頭が平らになるのは、送信用の割り込みハンドラの影響です。
まだ実装していない、本来の受信側の DMA 完了割り込みの発生位置はフレーム先頭付近になります。