FPGA 版 FM 音源 (79) -- TX7 (OPS) 測定 (6)
今回はソフトウェアについて触れます。
まず、DMA チャネルの初期化部分のソースを下に示します。 16 ビット・パラレル・データ転送を 8 ビット DMA ふたつ (DMA_H, DMA_L) で実現しており、両者の内容はほとんど同じなので、代表として DMA_L の方だけを示します。
#define N_POLY (16) volatile int flag = 0; volatile int val_ofs = 0; uint8_t da_val_l[2*N_POLY]; /* Defines for DMA_L */ #define DMA_L_BYTES_PER_BURST 1 #define DMA_L_REQUEST_PER_BURST 1 #define DMA_L_SRC_BASE (CYDEV_PERIPH_BASE) #define DMA_L_DST_BASE (CYDEV_SRAM_BASE) static uint8 DMA_L_Chan; static uint8 DMA_L_TD[2]; void setup_DMA_L( void ) { /* DMA Configuration for DMA_L */ DMA_L_Chan = DMA_L_DmaInitialize(DMA_L_BYTES_PER_BURST, DMA_L_REQUEST_PER_BURST, HI16(DMA_L_SRC_BASE), HI16(DMA_L_DST_BASE)); DMA_L_TD[0] = CyDmaTdAllocate(); DMA_L_TD[1] = CyDmaTdAllocate(); CyDmaTdSetConfiguration(DMA_L_TD[0], N_POLY, DMA_L_TD[1], (CY_DMA_TD_INC_DST_ADR | DMA_L__TD_TERMOUT_EN)); CyDmaTdSetConfiguration(DMA_L_TD[1], N_POLY, DMA_L_TD[0], (CY_DMA_TD_INC_DST_ADR | DMA_L__TD_TERMOUT_EN)); CyDmaTdSetAddress(DMA_L_TD[0], LO16((uint32)DA_REG_L_Status_PTR), LO16((uint32)&da_val_l[0])); CyDmaTdSetAddress(DMA_L_TD[1], LO16((uint32)DA_REG_L_Status_PTR), LO16((uint32)&da_val_l[N_POLY])); CyDmaChSetInitialTd(DMA_L_Chan, DMA_L_TD[0]); CyDmaChEnable(DMA_L_Chan, 1); } // void setup_DMA_L() CY_ISR(DMA_L_isr_InterruptEx) { uint8_t curr_td; CyDmaChStatus(DMA_L_Chan, &curr_td, NULL); val_ofs = (N_POLY * (DMA_L_TD[0] == curr_td)); flag |= 0x01; } // CY_ISR()
メモリ内に DMA バッファとして、16 エントリ × 2 面の 32 エントリの 8 ビット・データ配列 (da_val_h、da_val_l) を確保しています。
DMA の TD (Transaction Descriptor) として、下の図のように、転送カウント数 16 の DMA_L_TD[0], DMA_L_TD[1] を定義し、お互いを連結して環状リストとしています。
DMA_L_TD[0] の DST_ADR として &(da_val_l[0])、DMA_L_TD[1] の DST_ADR として &(da_val_l[N_POLY]) を定義して、ダブル・バッファを実現しています。
それぞれの TD の属性として、終了割り込み発生が定義してあり、呼び出される DMA_L_isr_InterruptEX 関数でメインループ側で読み出す DMA バッファの「面」を切り替えます。
ハード的に DMA_H と DMA_L との両方に同じデータ・リクエスト信号が供給されているので、DMA 要求は DMA_H 側と DMA_L 側とで同時に発生しますが、実際には DMA チャネルの優先度の高い方が先にサービスされます。
初期化コードの中では特に優先度の設定はありませんが、回路図作成時の DMA コンポーネントの配置順からシステム側が回路合成/API 生成時に勝手に設定するようです。
優先度の設定に依存せず、DMA_H 側と DMA_L 側のどちらの終了割り込み関数が先にサービスされても問題ないように、DMA の「面」切り替えは両方の関数で行ない、転送完了を示すフラグは H 側、 L 側それぞれ別のビットを操作するようにしています。
メイン・ループ側では両方のビットのフラグが立っているのを検出すると、書き込みが終了した「面」のバッファから 16 音分のデータを読み出し、累算して 48 kHz サンプルひとつを作りだします。
次にメイン・ループ部を示します。
int i, j; int acc; int mant; int scale; uint8_t dac_h; uint8_t dac_l; . . . . . <中略> . . . . . while (1) { // main loop if (0x03 == flag) { // buffer filled flag = 0; // clear flag acc = 0; // clear accumulator j = val_ofs; for (i = 0; i < N_POLY; i++) { dac_h = da_val_h[j]; // DAC hi byte dac_l = da_val_l[j]; // DAC lo byte mant = (((0x0f & (uint16_t)dac_h) << 8) | dac_l); mant -= 2048; // convert to 2's complement scale = ((0xf0 & dac_h) >> 4); // scale factor mant *= scale; // apply scaling acc += mant; // accumulate j++; // advance array index } // for (i = 0; ... if ( 32767L < acc) { acc = 32767L; } else if (-32768L > acc) { acc = -32768L; } dac_h = ((uint16_t)acc) >> 8; dac_l = (uint8_t )acc; fifo[wr_ix++] = dac_l; fifo[wr_ix++] = dac_h; // L-ch fifo[wr_ix++] = dac_l; fifo[wr_ix++] = dac_h; // R-ch if (FIFO_BYTES <= wr_ix) { wr_ix = 0; // wrap around } // if (FIFO_BYTES <= ... } // if (0x03 == sem) { ... if (SPDIF0_AUDIO_0_FIFO_NOT_FULL & SPDIF0_ReadStatus() ) { // FIFO has room SPDIF0_WriteTxByte(fifo[rd_ix], 0x01 & (rd_ix >> 1)); if (FIFO_BYTES <= (++rd_ix)) { rd_ix = 0; // wrap around } // if (FIFO_BYTES <= ... } // if () } // while (1) { ...
変数「flag」の値が 0x03 であれば H 側 L 側ともに 16 音分の DMA 転送が完了したということですから、DMA バッファから値を読み出し、変換して、16 音分を加算して 48 kHz サンプルひとつ分を作りだします。 そうでなければ、DMA バッファについては何もしません。
次に、SPDIF モジュールのデータ FIFO に「空き」があるかどうかを調べて、空きがあればメモリ中の fifo[] 配列から 1 バイト読み出してデータ FIFO に書き込みます。
OPS からの信号 SF0 〜 SF3 は、12 ビット DAC 信号 DA[12:1] のシフト/スケーリング量を示すものです。
SF0 / SF1 / SF2 / SF3 の値は互いに排他的で、常にどれかひとつだけが「1」となり、他の 3 つは「0」となります。
アナログ的には、
という操作をします。
アナログ的に SFn の「n」ビット右シフトを実行していることになります。
ディジタル的には、右シフトで下位ビットが「桁落ち」するのは好ましくなく、逆に、
- SF0 == 1 なら、DA[12:1] を 8 倍に (15 ビットに拡大)
- SF1 == 1 なら、DA[12:1] を 4 倍に (14 ビットに拡大)
- SF2 == 1 なら、DA[12:1] を 2 倍に (13 ビットに拡大)
- SF3 == 1 なら、DA[12:1] そのまま (12 ビットそのまま)
と、(3 - n) ビット左シフトして、下位ビットが失われないようにします。
SF0, SF1, SF2, SF3 をこの順番に並べ、「2 進数」として読んだものは上に示す「倍率」の値となっています。
ます、下のように「仮数部」(mantissa) として、上位 8 ビット、下位 8 ビットから抽出したストレート・バイナリの 12 ビット・データを 2 の補数に変換します。
mant = (((0x0f & (uint16_t)dac_h) << 8) | dac_l); mant -= 2048; // convert to 2's complement
上位 8 ビットに含まれる SF0 〜 SF3 を「倍率」として、
scale = ((0xf0 & dac_h) >> 4); // scale factor mant *= scale; // apply scaling acc += mant; // accumulate j++; // advance array index
のようにスケーリングを施し、累算して、次のデータに進みます。