SSブログ

PSoC 3 で、 DMA 対応倍増器を作った [PSoC]このエントリーを含むはてなブックマーク#

DMA 対応倍増器

前回まで、 CPU から FIFO に対して読み書きをするコンポーネントを作成してきました。 今回は、 DMA を接続して、自動的に出力を受け取らせる仕組みを取り入れてみました。

題材とするコンポーネントは、10倍増器です。 コンポーネント自身には、 ST_PUT 状態の時に演算の終了を示すためにアサートされる "drq" (DMA Request) 出力を追加してあります。 この信号の立ち上がりを DMA が検出したら、 FIFO から計算結果を取り出して、 RAM に転送します。

DMA Wizard への対応

DMA Wizard (1)

PSoC Creator で、 DMA を使うためには、かなり長文のソースコードを書かなくてはなりません。 この DMA の設定を簡単に生成するためのツールが、 DMA Wizard です。

DMA Wizard は、コンポーネントの情報、例えばレジスタのアドレスをリストに表示します。 ユーザは、提示された選択肢の中から転送元と転送先を選んで、簡単に DMA 設定コードを得る事ができます。 ただし、そのためには、コンポーネントが DMA Wizard に対応している必要があります。


DMA Wizard 対応コンポーネント

DMA に対応させるために必要なのが、拡張子 "cydmacap" の付いた "DMA Capability File" です。 このファイルは、 XML 形式で記述されており、 DMA の転送元および転送先のレジスタアドレスや、そのビット長などの情報が記述されています。

今回は、拡張子 "h" の付いた API ヘッダファイルも作成しています。 DMA Wizard では、レジスタアドレスを指定しますが、何か名前を付けておいた方が理解しやすくなります。 ここでは、ヘッダファイルにマクロ定義 #define を記述することによって、名前を付けています。

DMA Capability File の記述

DMA Capability File の作成

コンポーネントのコンテキストメニューから "Add Component Item" でダイアログを呼び出して、 "DMA Capability" を選択すると、 DMA Capability File のテンプレートが作成されます。 このテンプレートのコメント以外の部分を以下のように変更しています。

<DMACapability>

  <Category name="Primary" 
            enabled="`=$EnableDMA`" 
            bytes_in_burst="2"
            bytes_in_burst_is_strict="true" 
            spoke_width="2" 
            inc_addr="false" 
            each_burst_req_request="true">
    <Location name="`$INSTANCE_NAME`_INPUT_16BIT_PTR" enabled="true" direction="destination"/>
    <Location name="`$INSTANCE_NAME`_OUTPUT_16BIT_PTR" enabled="true" direction="source"/>
  </Category>
  
</DMACapability>

DMA Capability File の書き方については、現在のところ、マニュアルもドキュメントもありません。 唯一、手掛かりとなるのは、ファイルとして作成されるテンプレートに記述されたコメントだけです。 以下、記入すべき項目について、列挙します。


項目名属性名内容
Category Category には、レジスタへのアクセス方法に関する情報が記述されます。 レジスタのビット幅が異なるなどの複数の Category を併記することも出来ます。
name name には、複数の Category を区別するための名前が記述されます。 DMA Wizard では、複数の Category が存在する時に限り Category を選択するリストが表示されます。 今回のように Category が一つだけの場合には、設定しても使用されません。
enabled enabled は、 true または false の値をとります。 この値が false の時には、 DMA Wizard から認識されないので、リストに表示されなくなります。 主にコンポーネントの設定ダイアログから指定する目的で使われます。 今回の例でも、設定プロパティ EnableDMA の値を使っています。
bytes_in_burst この値は、 DMA の一度の転送で送受信されるデータのバイト数を指定します。 今回の例では、 FIFO から2バイトずつ演算結果が出力されますので、 "2" を指定します。
bytes_in_burst_is_strict この値は、 true または false の値をとります。 先の属性 "bytes_in_burst" で指定された値は、 DMA Wizard で変更することも出来ます。 これを変更不可能にするのが、この属性です。 この値が true の時には、属性 "bytes_in_burst" で決められた値は、 DMA Wizard では変更できません。 今回の例では、必ず2バイトの演算結果を出力しますので、 "true" を指定します。
spoke_width この値には、レジスタの接続されるスポークのビット幅をバイト単位で指定します。 スポークのビット幅は、ハードウェアにより、接続されるブロックごとに決定されます。 今回の場合は、 UDB の FIFO に読み書きを行うため、16ビットのスポークに接続されます。 そのため、設定する値は、 "2" となります。
inc_addr この値は、 true または false の値をとり、一回の転送の後、アドレスを増加させるかどうかを指定します。 今回の場合は、特定の FIFO レジスタに対して読み書きを行うため、アドレスは固定されます。 そのため、設定する値は、 "false" となります。
each_busrt_req_request この値は、 true または false の値をとり、 DMA 要求が到着した時に複数回の転送を行うかどうかを指定します。 今回の場合は、2バイトの演算結果ごとに DMA 要求が到着する仕組みになっていますので、 "true" を指定します。
Location Location には、転送元または転送先に関する情報が記述されます。 レジスタアドレスごとに一つの Location が記述されます。
name name には、レジスタのアドレスを示す名前が記述されます。 DMA Wizard では、この名前がリストに表示されます。 この例では、 API ヘッダファイルに定義したレジスタアドレスを指定しています。
enabled enabled は、 true または false の値をとります。 この値が false の時には、 DMA Wizard から認識されないので、リストに表示されなくなります。 この属性も、コンポーネントの設定ダイアログなどから指定する目的で使われます。 今回の例では、 "true" を指定して、常に DMA Wizard から認識されるようになっています。
direction この値は、レジスタのアクセス方向を示します。 "source" が指定された時、このアドレスは転送元として扱われます。 また、 "destination" が指定されたら、転送先として扱われます。 さらに、 "both" が指定されたら、転送元または転送先の両方に使えるものとして扱われます。 今回の例では、レジスタに応じて、 "source" または "destination" を指定しています。

API ヘッダファイルの記述

API ヘッダファイルは、以下のように記述しています。

#define `$INSTANCE_NAME`_INPUT_16BIT_PTR ((reg16 *)`$INSTANCE_NAME`_dp_u0__16BIT_F0_REG)
#define `$INSTANCE_NAME`_INPUT_PTR ((reg8 *)`$INSTANCE_NAME`_dp_u0__F0_REG)

#define `$INSTANCE_NAME`_OUTPUT_16BIT_PTR ((reg16 *)`$INSTANCE_NAME`_dp_u0__16BIT_F1_REG)
#define `$INSTANCE_NAME`_OUTPUT_PTR ((reg8 *)`$INSTANCE_NAME`_dp_u0__F1_REG)

今回作成したプロジェクトでは、入り口の FIFO のアドレスと出口の FIFO のアドレスを、 DMA からのアクセスおよび CPU からのアクセスについて別々に登録しています。 これは、 DMA からアクセスする場合には16ビットバスを、 CPU からアクセスする場合には8ビットバスを使用するという制約によります。 "16BIT" が付いた名前を DMA で使い、もう一つの方を CPU で使います。

DMA Wizard の設定

DMA Wizard (2)

DMA Wizard 対応のコンポーネントが出来たら、 DMA Wizard で DMA 設定コードを生成させます。 DMA Wizard を起動したら、まず、プロジェクトと転送に使用する DMA コンポーネントを指定します。


DMA Wizard (3)

次は、転送元と転送先のコンポーネントを指定します。 今回の場合には、カスタムコンポーネントの Times10 から出力された演算結果を SRAM に転送するのが目的です。 そこで、 "Source" には Times10 を、 "Destination" には SRAM を指定します。

一転送あたりのバイト数などの設定は、 DMA Capability File からコピーされてきます。 転送を繰り返して行いたいので、 Loop にチェックを入れておきます。


DMA Wizard (4)

次は、 Transaction Descriptor の詳細を設定します。 今回のプロジェクトは、 PSoC 3 で16ビットレジスタを操作するため、 FIFO レジスタのエンディアンとコンパイラが想定するエンディアンが異なります。 そのため、 Endian の設定を "2" に変更して、16ビットアクセスができるようにしています。

"result" は、 main.c ソースコードで使用する予定の SRAM 上の配列の名前です。 この例のように、配列名を使って表現すると、わざわざ、配列の長さを数える手間が省けます。


DMA Wizard (5)

設定項目を入力したら、コードが生成されます。 これを "main.c" ソースコードの適切な場所にコピーすると、 DMA の初期設定を行う事ができます。

テストプログラム

テストプログラムは、16ビットの範囲内で計算が実現できるすべての組み合わせをテストして、その結果を LCD モジュールに表示します。 コンセプトは、前回のプログラムと同じなのですが、今回は、 FIFO からデータを取り出す操作を CPU ではなく DMA が行っています。 以下では、 "main.c" のコードを抜粋しながら解説していきます。

uint16      result[64];

計算結果は、配列 "result" に DMA で転送されます。 DMA Wizard の設定で "Loop" にチェックを入れたので、配列の最後まで達したら、元のアドレスに戻って転送を行います。

/* Variable declarations for DMA */
uint8 DMA_Chan;
uint8 DMA_TD[1];

/* DMA Configuration for DMA */
#define DMA_BYTES_PER_BURST 2
#define DMA_REQUEST_PER_BURST 1
#define DMA_SRC_BASE (CYDEV_PERIPH_BASE)
#define DMA_DST_BASE (CYDEV_SRAM_BASE)

DMA Wizard で生成されるコードは、変数宣言、マクロ宣言、初期設定の三つの部分に分かれています。 この例では、まず変数宣言とマクロ宣言を行っています。 このため、変数は、グローバルな変数として宣言されることになります。

/* Initialize the DMA channel */
void DMA_Init(void) {
    DMA_Chan = DMA_DmaInitialize(DMA_BYTES_PER_BURST, DMA_REQUEST_PER_BURST, 
        HI16(DMA_SRC_BASE), HI16(DMA_DST_BASE));
    DMA_TD[0] = CyDmaTdAllocate();
    CyDmaTdSetConfiguration(DMA_TD[0], sizeof result, DMA_TD[0], TD_SWAP_EN | TD_INC_DST_ADR);
    CyDmaTdSetAddress(DMA_TD[0], LO16((uint32)Times10_OUTPUT_16BIT_PTR), LO16((uint32)result));
    CyDmaChSetInitialTd(DMA_Chan, DMA_TD[0]);
    CyDmaChEnable(DMA_Chan, 1);
}

残りの初期設定部分は、関数 DMA_Init として定義しました。 詳細については、ここでは解説しません。

void main()
{
    uint8           i;
    uint8           j;
    uint16          a;
    uint16          a10;
    volatile uint16 w = 0;
    CYBIT           good;
        
    LCD_Start();
    DMA_Init();

初期設定部分で、 LCD とDMA の初期設定を行っています。 これで、倍増器の演算が終わり次第、自動的に配列 result に結果が転送されます。

    CR1_Write(1);
    good = 1;
    a = 0;
    for (i = 0; i < 102; i++) {
        for (j = 0; j < 64; j++) {
            CY_SET_REG16(Times10_INPUT_PTR, a + j);
        }
        for (j = 0; j < 64; j++) {
            a10 = (a + j) * 10;
            w = result[j];
            if (a10 != w) {
                good = 0;
                goto escape;
            }
        }
        a += 64;
    }
    escape:

プログラムは、これまでと同様に倍増器の FIFO0 に16ビットの値を書込み、その演算結果を CPU で検証するという流れになっています。

これまでと異なっているのは、 FIFO0 に64個の値をまとめて書き込み、 DMA によって配列 result に出力された演算結果を64個ずつ検証している点です。 このため、プログラムは、64個の値の書き込みを102セット、つまり "0" から "6527" までの 64×102=6528 個の値を書込むようになっています。

    LCD_Position(0, 0);
    if (good) {
        LCD_PrintString("GOOD");
    } else {
        LCD_PrintString("BAD");
        LCD_Position(1, 0);
        LCD_PrintNumber(a + j);
        LCD_Position(1, 8);
        LCD_PrintNumber(w);
    }

演算の検証の結果、すべて正しければ、 LCD に "GOOD" が表示されます。 もし、誤りが見つかったら、"BAD" の表示と共に FIFO0 に書き込んだ値と演算結果が表示されます。

    CyDmaChDisable(DMA_Chan);
    a = 0;
    CY_SET_REG16(Times10_INPUT_PTR, a++);
    CY_SET_REG16(Times10_INPUT_PTR, a++);

    /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */
    for(;;)
    {
        CY_SET_REG16(Times10_INPUT_PTR, a++);
        w = CY_GET_REG16(Times10_OUTPUT_PTR);
    }
}

一通り、検証が終わったら、 DMA の動きを止めて、前回同様の CPU で書き込んで CPU で読み出すループに入ります。 DMA に対応はしましたが、 DMA 専用ではないので、これまで通りの動作をさせることができます。

プロジェクトアーカイブ

この記事を書くために作成したプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。

関連文献

トランジスタ技術 2013年 07月号 [雑誌]

トランジスタ技術 2013年 07月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2013/06/10
  • メディア: 雑誌

nice!(0)  コメント(2)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 0

コメント 2

iwamito

psocのbootloader, bootloadableの使い方教えて下さい。USBでフィールドアップデート出来たら嬉しいです。
by iwamito (2013-07-17 21:56) 

じゅんぱ

素晴らしい!
まだ、理解できていませんが、ほぼ探していたことを書いて頂いて感謝します。
つい、最近psoc5LPで組み込みをはじめて、cpuを介さずに、ADCからVerilog コンポーネントにデータを渡す方法を探していたのですが、どうにもみつかりませんでした。逆パターンではありますが、糸口になればと思います。ありがとうございました。
by じゅんぱ (2019-06-08 16:46) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

トラックバックの受付は締め切りました

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。