PSoC5LP Prototyping Kit (7) --- SPDIF_Tx と DMA (6)

DMA コンポーネントに接続したインタラプト・コンポーネントでも DMA 関係の定義を使うので、DMA Wizard で生成された C ソース・コードのうち、#define 文の定義などを集めてヘッダ・ファイル「main.h」として独立させ、ISR (Interrupt Service Routine) 側でもインクルードして利用します。
下にその内容を示します。

#ifndef _MAIN_H_
#define _MAIN_H_
    
#include <project.h>

/* DMA Configuration for DMA_spdif_tx0 */
#define DMA_spdif_tx0_BYTES_PER_BURST 1
#define DMA_spdif_tx0_REQUEST_PER_BURST 1
#define DMA_spdif_tx0_SRC_BASE (CYDEV_SRAM_BASE)
#define DMA_spdif_tx0_DST_BASE (CYDEV_PERIPH_BASE)

#define N_DMA_BUFS     (16)    

// 16 stereo samples = 32 audio samples = 64 byte
#define DMA_BUF_SIZE   (32)
#define DMA_BUF_BYTES  (sizeof(int16_t) * DMA_BUF_SIZE)

#define DAC_FIFO_SIZE  (DMA_BUF_SIZE * N_DMA_BUFS)
#define DAC_FIFO_BYTES (sizeof(int16_t) * DAC_FIFO_SIZE)
    
typedef struct tag_dac_fifo {
  volatile int wr_ix;
  volatile int rd_ix;
  int16_t      buf[DAC_FIFO_SIZE];
} dac_fifo_t ; 

extern dac_fifo_t dac_fifo;

extern uint8 DMA_spdif_tx0_Chan; 
extern uint8 DMA_spdif_tx0_TD[N_DMA_BUFS];
#endif // _MAIN_H_

DAC 出力 FIFO バッファの定義なども含めてあります。
main.c 内の DMA 初期設定部分は下のような DMAconfig() 関数としてまとめました。

#include "main.h"

. . . . . <中略> . . . . .

uint8 DMA_spdif_tx0_Chan; 
uint8 DMA_spdif_tx0_TD[N_DMA_BUFS];

dac_fifo_t dac_fifo;

. . . . . <中略> . . . . .

void DMAconfig( void )
{
  int i;
  void *p = dac_fifo.buf;

// initialize DMA channel config
  DMA_spdif_tx0_Chan = DMA_spdif_tx0_DmaInitialize(DMA_spdif_tx0_BYTES_PER_BURST, 
                                                   DMA_spdif_tx0_REQUEST_PER_BURST, 
                                                   HI16(DMA_spdif_tx0_SRC_BASE), 
                                                   HI16(DMA_spdif_tx0_DST_BASE));
  for (i = 0; i < N_DMA_BUFS; i++) {
// allocate TD from pool and remember it    
    DMA_spdif_tx0_TD[i] = CyDmaTdAllocate();
  } // for (i = 0; ...
  for (i = 0; i < N_DMA_BUFS; i++) { // for all TD
// set config part of this TD    
    CyDmaTdSetConfiguration(DMA_spdif_tx0_TD[i], 
                            DMA_BUF_BYTES, 
                            DMA_spdif_tx0_TD[(i + 1) % N_DMA_BUFS], 
                            ( DMA_spdif_tx0__TD_TERMOUT_EN 
                            | TD_INC_SRC_ADR ));
    
// set source / dest address part of this TD    
    CyDmaTdSetAddress(DMA_spdif_tx0_TD[i], 
                      LO16((uint32)p), 
                      LO16((uint32)SPDIF_Tx0_TX_FIFO_0_PTR));

// advance DMA buffer pointer
    p += DMA_BUF_BYTES;
  } // for (i = 0; ...
// initial TD number of this DMA channel
  CyDmaChSetInitialTd(DMA_spdif_tx0_Chan, DMA_spdif_tx0_TD[0]);

// start DMA hardware
  CyDmaChEnable(DMA_spdif_tx0_Chan, 1);
} // void DMAconfig()

DMA_spdif_tx0_Chan は DMA 初期化 API 関数を呼び出すと割り当てられる DMA チャネル番号を控えておくためのもので、以降、DMA チャネル番号を指定して API 関数を呼び出す必要がある限りは保存しておく必要があります。
もし、ユーザ作成の DMA 初期化関数の中でのみ必要なら、関数の実行が終了すると廃棄される auto 変数として定義することができます。
ここでは、グローバル変数として、永続的に値が保持されるようにしてあります。
同様に DMA_spdif_tx0_TD[] は TD (Transaction Descriptor) の番号割り当てを保持しておくための配列で、同様にグローバル変数としています。 この配列は DMA の ISR でも参照しています。
DMA Wizard で作成されるプログラムの断片では、TD ひとつひとつに対応してコードが生成されます。
したがって、TD 数を 16 と指定すれば、TD 数 = 1 と指定した場合の 16 倍の行数の TD 設定コードになります。
今回のプロジェクトの設定では、各 TD は Next TD 部分とソース・アドレス以外は 16 個全て同じ内容なので、for ループで設定するようにして行数を減らしました。
DMA_spdif_tx0_DmaInitialize() 関数はチャネル・コンフィグ部分の設定です。 HI16() は上位 16 ビットを抜き出すためのマクロです。
CyDmaTdAllocate() は未使用の TD のリストから TD をひとつ割り当てて、その番号を返す関数です。
ループしない直鎖の TD チェインによる DMA を行なって、また新たに違う設定での DMA を行なう必要がある場合、DMA 転送が終了して不要になった TD を「返却」して「再利用」する必要があります。 使いっぱなしにしておくと、いくら 128 個の TD があるといっても、いつかは尽きてしまいます。
TD を返却する必要がある場合には、返却する時点まで割り当てられた TD 番号を記憶しておきます。
一方、連続的に動作するループ状の TD の場合には、常に使用中で返却する場面は生じませんから、永続的に記憶しておく必要はありません。
今回のプロジェクトでは ISR 中で DMA_spdif_tx0_TD[0] のみ使用しているので、覚えておくのはこれだけでよいのですが、16 個の TD 番号をグローバル変数で記録するようにしています。
最初の for ループで必要な TD 16 個すべての割り当てを受けています。
2 番目の for ループで各 TD に下の 2 つの関数で設定を行なっています。

  • CyDmaTdSetConfiguration()
  • CyDmaTdSetAddress()

TD は 32 ビット・ワードのメモリ 2 ワードで構成されていて、それぞれのワードに

  • 転送カウント / Next TD / 動作モード・ビット
  • ソース / デスティネーション・アドレス

が振り分けられて納められているので、それぞれに対応した上記の関数で設定を行います。
CyDmaChSetInitialTd() 関数でチャネル・コンフィグ部分に「最初の TD 番号」を設定します。
CyDmaChEnable() 関数で DMA ハードウェアをイネーブルします。
ここで作成した DMAconfig() 関数が、他のコンポーネントでの 〜_Start() 関数に相当することになります。
次回はインタラプト・コンポーネント内の記述と、main() 関数の記述について説明します。