SSブログ

PSoC Creator で FM0+ の Lチカ (4) [FMx]このエントリーを含むはてなブックマーク#

FM0+ 評価ボード

前回まで、ソフトウェアループ、 PWM、タイマ割り込みを使って「Lチカ」を行ってきました。 今回は、 Direct Memory Access (DMA) を使って、「Lチカ」してみます。

使用するボードは、 FM0+ S6E1C-Series Starter Kit (FM0-64L-S6E1C3) です。

回路図に GPIO と RT と DSTC を配置する

GPIO と RT と DSTC を配置する

プロジェクトを作成するところまでは、前回までと同じです。 今回は、GPIO コンポーネントと Reload Timer (RT) コンポーネントに加えて Descriptor System data Transfer Controller (DSTC) コンポーネントを配置します。 この記事では、 DMA を使うと言いましたが、この MCU には DMA というブロックがありません。 「CPU を煩わせずにデータの転送を行う機能」として DSTC というブロックが DMA の代わりに搭載されています。 このブロックは、ディスクリプタベースのデータ転送機能を持っており、 DMA よりはるかに高機能で複雑な動作をさせることが出来ます。

このシステムでは、 RT でタイミング信号を発生し、このタイミング信号で DSTC にトリガをかけ、 GPIO の出力値を更新します。 出力するデータにトグルパターンを使用する事で、タイミング信号の周期で「Lチカ」が出来ます。

DSTC は "Component Catalog" の "System" から回路図に Drag&Drop します。 インスタンスの名前は、 "DSTC" としました。

コンポーネントの設定

RT の Interrupt タブ

まず、 RT コンポーネントを設定します。 この MCU では、 RT が発生した割り込み信号をタイミング信号として使用します。 そのため、割り込みを発生させるための設定を行わなくてはなりません。

「Interrupt タブ」で、 "bRtUnderflowIrq" を "true" に設定して、周期的な割り込み信号を発生させます。 ただし、コールバック関数を呼び出す必要はありません。 このような場合、 "pfnRtUnderflowIrqCb" を削除して空欄にしておくと関数の作成を省略できます。

"bTouchNvic" は "false" に設定します。 発生した信号を Nested Vector Interrupt Controller (NVIC) に渡す必要はありません。 以上で、タイミング信号が発生できます。


RT の RT タブ

「RT タブ」での変更点は、周期割り込みを使う場合と同じです。 "enRtPres" で RT のクロックに使用されるプリスケーラの分周比を "1/2048" に設定します。


DSTC の Basic タブ

DSTC コンポーネントはデフォルトのまま使用します。

これで回路図の設定は終わりです。

端子を割り当てる

端子の設定

次は、端子の割り当てを行います。 "Pin_Red" に "P3D" を設定します。

以上でハードウェアの設定は終わりです。 プログラムを記述する前に "Build" して、あらかじめ API を作成しておきます。

RT の初期設定

RTの初期設定

ソースコードのうち、 RT コンポーネントの初期設定は、以下のようになっています。

#include "project.h"

#define     CYCLE           (8000000/2048/2)    // 0.5秒を作る分周比

// RTコンポーネントの初期化処理
void RtInit(void) {
    Bt_Rt_Init(&RT_HW, &RT_Config);         // 初期化
    Bt_Rt_WriteCycleVal(&RT_HW, CYCLE);     // 割り込み周期
    Bt_Rt_EnableCount(&RT_HW);              // カウンタを起動
}

周期割り込みを使った時と同じように、0.5秒ごとに割り込み信号を発生させます。 この初期化関数では、カウンタを起動する所まで実行しますが、トリガはかけていません。 RT と DSTC 両方のコンポーネントの準備が出来てから、トリガをかけます。

DSTC の初期設定

DSTCの初期設定

DSTC の初期設定は、かなり複雑です。

// GPIOに出力するパターンデータ
uint32_t gpio_data[2] = {0xFFFFFFFF, 0x00000000};

gpio_data[] には、 GPIO の出力レジスタに代入する値が格納されています。 RT からタイミング信号を受け取るたびに、これらの値を一つずつ出力レジスタに入れる事により、 LED を点滅させます。 この配列には、ふたつの要素のみ定義されていますが、繰り返し機能を使う事によって、二番目の出力の後、一番目の出力に戻り、無限に LED を点滅させることが出来ます。

// DSTCのディスクリプタは、永続した記憶域に配置する
stc_dstc_des01234_t descriptor;     // DSTC descriptor

DSTC の制御に使われるデータは、ディスクリプタと呼ばれるメモリ領域に配置されます。 ここでは、 descriptor という変数をディスクリプタ領域として使用します。

DSTC のディスクリプタは、その内容によって可変長です。 ここで使用されているディスクリプタは、4ワードのディスクリプタです。

// DSTCコンポーネントの初期化処理
void DstcInit(void) {
    // 初期設定構造体の作成
    stc_dstc_config_t dstc_config = {
        0,                          // ディスクリプタの先頭は、後で設定する
        DSTC_Config                 // コンポーネントで設定されたパラメータ
    };

DstcInit() 関数の冒頭で、 dstc_config 構造体を宣言しています。 DSTC コンポーネントは、この構造体を初期化関数に渡すことで初期化します。

構造体の最初の要素には、ディスクリプタの先頭ポインタを入れます。 これは、後で設定します。 それ以降の要素は、コンポーネントの設定ダイアログで設定した内容を DSTC_Config というマクロで渡します。 このマクロは、複数の要素を並べた内容になっており、事実上、設定構造体の初期設定でしか使用する事ができません。 不自由なのですが、完結したライブラリの仕様ですので従いましょう。

    
    // ディスクリプタの設定
    PDL_ZERO_STRUCT(descriptor);
    descriptor.DES0.DV    = 3u;     // 転送終了後もDSTCが継続して権限を持つ
    descriptor.DES0.MODE  = 1u;     // リクエストごとに1ワード転送する
    descriptor.DES0.ORL   = 1u;     // 転送終了後カウンタパラメータを再設定する
    descriptor.DES0.TW    = 2u;     // 32-bitワード
    descriptor.DES0.SAC   = 1u;     // 転送元アドレスを1ワードずつインクリメントしてINNERで再設定する
    descriptor.DES0.DAC   = 5u;     // 転送先アドレスは固定する
    descriptor.DES0.CHRS  = 0u;     // ディスクリプタチェーンは使わない
    descriptor.DES0.DMSET = 0u;     // 転送終了後も転送信号を受け付ける
    descriptor.DES0.CHLK  = 0u;     // ディスクリプタチェーンは使わない
    descriptor.DES0.ACK   = 1u;     // データ転送後にACKを返す
    // DES0フィールドのチェックサムを計算する
    descriptor.DES0.PCHK  = DSTC_PCHK_CALC(descriptor.u32DES0);

    // 転送カウンタの設定
    descriptor.DES1_mode1.IIN = 2u; // 2ワード転送する
    descriptor.DES1_mode1.IRM = 2u; // 2ワード残っている
    descriptor.DES1_mode1.ORM = 1u; // INNER転送を1回実行する

    // 転送元・転送先アドレス
    descriptor.DES2 = (uint32_t)gpio_data;          // 出力パターンデータ
    descriptor.DES3 = (uint32_t)&FM_GPIO_PDOR3;     // GPIOの出力データレジスタ

    // 転送終了後に再設定されるカウンタ値
    descriptor.DES4_mode1 = descriptor.DES1_mode1;  // DES1のカウンタ値をコピーする

DSTC の転送単位は、 8bit, 16bit, 32bit から選びます。 転送単位を示すのが、 DES0.TW フィールドです。 今回は、 GPIO の出力レジスタに 32bit で書き込みを行います。

DSTC では、一つのディスクリプタで繰り返し転送を行う事が出来ます。 この繰り返しを行うためのループ二重になっており、それぞれのループを INNER (内側)と OUTER (外側)と呼んでいます。 INNER の繰り返し回数と OUTER の繰り返し回数を掛け合わせたものが、ディスクリプタの転送回数になります。 INNER カウンタの初期値と繰り返し回数を表すのが、 DES1_mode1.IIN と DES1_mode1.IRM です。 また、 OUTER カウンタの初期値は DES1_mode1.ORM です。

DSTC のディスクリプタには、 MODE0 と MODE1 の二種類があります。 モードを指定するのが、 DES0.MODE フィールドです。 MODE0 は、 INNER の繰り返しをまとめて実行します。 一方、 MODE1 は、 INNER の転送を1転送単位ずつ実行します。 今回は、 MODE1 を使い、1転送ずつ GPIO 出力を変更して LED の点滅を表現します。

転送元のデータは、 gpio_data[] 配列に入っており、 DES2 でアドレスを指定します。 また、転送先は P3 ポートの出力レジスタである FM_GPIO_PDOR3 を DES3 で指定します。 これらの転送元アドレスは、1転送ごとにアドレスをインクリメントして配列中の次の値を使用します。 また、アドレスは、 INNER の繰り返し回数のあと、元のアドレスに再設定されます。 このアドレスの振る舞いを指定しているのが、 DES0.SAC です。 一方、転送先アドレスは、出力レジスタに固定されたままです。 この振る舞いを指定しているのが、 DES0.DAC です。

ここで、転送先に FM_GPIO_PDOR3 を指定していますが、そもそも Build しないと GPIO コンポーネントが本当に P3 に割り当てられたかはわかりません。 本来であれば、 GPIO コンポーネントに配置された出力レジスタのアドレスが定義されれば良いのですが、そうなってはいません。 ぜひ、マクロを加えてほしい所です。

OUTER ループが既定の繰り返し回数に達したら、転送が終了します。 このとき、 DES1_mode1, DES2, DES3 のそれぞれの値を再設定する機能があります。 この機能を働かせるか否かを決めるのが DES0.ORL フィールドです。 今回の設定では、 DES1_mode1 の繰り返しカウンタだけを再設定しています。 再設定値は DES4_mode1 に設定しますが、 DES1_mode1 と同じ内容ですので、そのままコピーしています。

今回の使い方では、転送が終了しても続けて転送を行いたいので、終了後も転送を継続するための設定が必要です。 この設定を行っているのが、 DES0.DV です。 デフォルトの 0 にすると、転送終了後のディスクリプタのアクセス権限を CPU が持つのですが、 3 にすると継続して DSTC がアクセス権限を保持し、転送リクエストを待ち続けます。

DES0.PCHK には、 DES0 のチェックサムが入ります。 このチェックサムが合わないと、ディスクリプタは無効になります。 チェックサムは DSTC_PCHK_CALC マクロで計算させます。

    
    // ディスクリプタの先頭アドレス
    dstc_config.u32Destp = (uint32_t)&descriptor;
    
    // 設定構造体でDSTCを設定する
    Dstc_Init(&dstc_config);
}

最後に設定構造体にディスクリプタのアドレスを設定し、 DSTC を初期化します。 これで、 DSTC の設定ができました。

main() 関数の記述

main()関数

初期化ルーチンが揃ったら、 main() 関数を記述します。

int main(void) {
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    // コンポーネントの初期設定
    Pin_Red_GpioInitOut(1u);
    RtInit();
    DstcInit();
    
    // DSTCの制御
    Dstc_SetCommand(CmdErclr);                      // DSTCのエラーをクリア
    Dstc_SetCommand(CmdRbclr);                      // 何かをクリア
    Dstc_SetHwdesp(DSTC_IRQ_NUMBER_BT0_IRQ0, 0u);   // RTのIRQ0で0番目のDESを起動する
    Dstc_SetDreqenbBit(DSTC_IRQ_NUMBER_BT0_IRQ0);   // RTのIRQ0信号をDSTCに引き込む

    // RTの制御
    Bt_Rt_EnableSwTrig(&RT_HW);                     // RTの起動

    for (;;) {
        /* Place your application code here. */
    }
}

まず、三つのコンポーネントの初期化を行います。 GPIO の初期化は Pin_Red_GpioInitOut() 関数で、 RT と DSTC の初期化は、それぞれの初期化関数で行います。

続いて、 DSTC を起動します。 Dstc_SetCommand() 関数で、あらかじめフラグをクリアしておきます。 そして、 Dstc_SetHwdesp() 関数で割り込み信号がきたら、ディスクリプタのどの部分を実行すれば良いかを指定します。 今回の場合には、 Base Timer #0 (BT0) の IRQ0 がきたら、0番目のディスクリプタを実行します。

ここで、割り込み信号をインデックス DSTC_IRQ_NUMBER_BT0_IRQ0 で表現していますが、 RT コンポーネントが BT0 に割り当てられるのかどうかは、 Build してみなくてはわかりません。 にもかかわらず、このような表記になっているのは、 RT コンポーネントから特定の BT を指し示す方法が無いからです。 ぜひ、 PSoC Creator が生成するファイルにマクロを用意してほしい所です。

最後に Dstc_SetDreqenbBit() 関数で割り込み信号を CPU の代わりに DSTC に接続します。 これで、割り込み信号により DSTC が動作を始めるようになります。

最後のステップは、 RT の起動です。 すでに、割り込み信号が DSTC に接続されていますので、 Bt_Rt_EnableSwTrig() 関数で RT にトリガをかけると、周期的に DSTC に対して転送要求を行います。

初期設定のあと、プログラムは無限ループに入ります。 割り込み要求は発生しないので、 CPU は割り込みを受けることも無くループを回り続けます。 これで、ソースコードは終わりです。 "Build" して、 "Program" すると、 LED が点滅を始めます。 「Lチカ」できました。


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

nice! 0

コメント 0

コメントを書く

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

トラックバック 0

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

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