PSoCカスタムコンポーネントにDMAを実装する

Implement DMA on PSoC custom components
 
PSoCの標準DMAコンポーネントとやりとりする手段を用意さえすれば、カスタムコンポーネントでDMAによるデータ転送を実装することができます。

標準DMAコンポーネントに自身の仕様を伝えるためには、「DMA Capability」というXMLで構成された宣言ファイルを作成して、コンポーネントに追加する必要があります。このファイルを作成するには、「Add Component Item」より「DMA Capability」を選択します。
pa_in.cydmacap
<?xml version="1.0" encoding="us-ascii"?>
<DMACapability>
  <Category name="pa_in_fifo" 
            enabled="true" 
            bytes_in_burst="1"
            bytes_in_burst_is_strict="true" 
            spoke_width="1" 
            inc_addr="false" 
            each_burst_req_request="true">
    <Location name="`$INSTANCE_NAME`_DMA_PTR" enabled="true" direction="source"/>
  </Category>
</DMACapability>
内容
Categoryタグパラメーターを格納する。複数記述すれば、DMAコンポーネントで個別の値を適用させることができるようになる。
nameカテゴリーを識別するために使う名称
enabledこのカテゴリーを利用可能にする
bytes_in_burst一度(バースト)に転送するメモリーサイズ(byte単位)
bytes_in_burst_is_strictbytes_in_burstの値を強制し、ユーザーにオプション指定をできないようにさせる。
spoke_widthレジスタのサイズ(byte単位)。例えば標準FFタイマーコンポーネントは16bitサイズなので"2"が適用されている
inc_addr転送ごとにアドレスの値を増やしていくか
each_burst_req_requestバースト転送ごとにDMAの要求を行うか。falseなら初回だけ要求を出し、あとは自動で転送が行われる。
Locationタグ転送元/転送先の定義を記述
name対象のアドレス名。APIヘッダファイルで定義したものを使う。
enabledこのアドレスを使用可能にする
directionアドレスが転送元(Source)か転送先(Destination)か、あるいは両方(Both)か

並列入力のサンプルではレジストが8bitなので、上記コードの値を使用しています。

APIヘッダーではFIFOレジスタのアドレスポインタを取得するマクロを定義します。
pa_in.h
#ifndef __`$INSTANCE_NAME`_H__
#define __`$INSTANCE_NAME`_H__

#define `$INSTANCE_NAME`_DMA_PTR ((reg8*)`$INSTANCE_NAME`_dp__F0_REG)
    
#endif
トップデザインの編集ではカスタムコンポーネントとInterruptコンポーネントの間にDMAコンポーネントを挟みます。これにより、DMAコンポーネントによる転送が完了するとNRQ信号が発生し、割り込みが実行されます。
DRQ(DMAリクエスト)端子を表示するにはDMAコンポーネントの「Hardware Request」を「Disable」以外にします。「Hardware Termination」を有効にすると、DMA転送の中止信号を受け取るための端子が表示されます。

「Hardware Request」のオプションには「Derived」「Rising Edge」「Level」の3つがあります。イベントなど不定期に転送が実行される場合は「Rising Edge」を、I2Sのように継続的に転送が実行される場合は「Level」を選択するとよいでしょう。「Derived」はPSoCが最適な接続を選びますが、カスタムコンポーネントでは原則として「Rising Edge」が適用されます。

デザインが完了したら次はC言語の記述といくわけですが、PSoC CreatorにはDMAに特化したコード生成ツールが用意されているので、こちらを積極的に使っていきましょう。
まずは、コードを生成する元になるプロジェクトとDMAコンポーネントを選びます。
続いて転送元と転送先を指定します。自作のコンポーネントがリストに無かったり、「Set Manually」で希望する値を指定できないようであれば、対象の「cydmacap」ファイルが正しい状態にあるかを確認しましょう。
「Number of TDs」の「TD」は「トランザクションディスクリプタ」を意味し、ここに転送情報が格納されます。これを複数個作成し、それぞれを連結することでDMAチェーンを作成し、ダブルバッファのように扱うことができます。

「Loop」を選択すると、転送アドレスが末端に到達すると先頭アドレスへと自動で戻り、転送が続行されます。「Single Chain」であれば、末端に到達すると転送が終了します。

プログラム実行中にどのTDにデータが転送されているかを知るには「CyDmaChStatus」を使用します(第2引数に現在のTD番号が代入される)。

最後に個々のTDの設定を行います。
TD#TDの番号
Endianエンディアンの変換(バイトスワップ)
Enable trqtrq信号の受信による転送中止の許可
Enable nrq転送完了時にnrq信号を送信するかどうか
LengthDMAが転送するバイトサイズ
Source転送元アドレス
Inc転送元アドレスの値を増やしていくかどうか
Destination転送先アドレス
Inc転送先アドレスの値を増やしていくかどうか
Auto Nextプログラムの指示なしに自動で次のTDを実行するか
Next TD次に連結するTDの番号。最後であれば「End」を選択

これでDMAの初期化コードが生成されるので、メインコードにこの文章を貼り付けます。ただし、define文や永続的に使用する変数がごっちゃになっているので、適切に振り分ける必要があります。
dma_init.c
/* Defines for pa_in_dma */
#define pa_in_dma_BYTES_PER_BURST 1
#define pa_in_dma_REQUEST_PER_BURST 1
#define pa_in_dma_SRC_BASE (CYDEV_PERIPH_BASE)
#define pa_in_dma_DST_BASE (CYDEV_SRAM_BASE)

/* Variable declarations for pa_in_dma */
/* Move these variable declarations to the top of the function */
uint8 pa_in_dma_Chan;
uint8 pa_in_dma_TD[1];

uint8 res_buffer[32];

void InitDma()
{
    /* DMA Configuration for pa_in_dma */
    pa_in_dma_Chan = pa_in_dma_DmaInitialize(pa_in_dma_BYTES_PER_BURST, pa_in_dma_REQUEST_PER_BURST, 
        HI16(pa_in_dma_SRC_BASE), HI16(pa_in_dma_DST_BASE));
    pa_in_dma_TD[0] = CyDmaTdAllocate();
    CyDmaTdSetConfiguration(pa_in_dma_TD[0], 32, pa_in_dma_TD[0], pa_in_dma__TD_TERMOUT_EN | CY_DMA_TD_INC_DST_ADR);
    CyDmaTdSetAddress(pa_in_dma_TD[0], LO16((uint32)pa_in_DMA_PTR), LO16((uint32)res_buffer));
    CyDmaChSetInitialTd(pa_in_dma_Chan, pa_in_dma_TD[0]);
    CyDmaChEnable(pa_in_dma_Chan, 1);
}
あとはInterruptコンポーネントの初期化関数を追加すれば完成です。表示される内容は以前のサンプルと同じですが、32バイト単位で一度に表示されていきます。
main.c
CY_ISR(OnIntrupptPaInDma)
{
    char str[10];
    for(int n = 0; n < 32; n++){
        sprintf(str, "%x ", res_buffer[n]);
        UART_PutString(str);
    }
}

int main(void)
{
    CyGlobalIntEnable;

    isr_dma_StartEx(OnIntrupptPaInDma);
    InitDma();
    
    UART_Start();

    for(;;)
    {
    }
}
2019/06/18