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チカ」できました。


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

FM0+ 評価ボード

前回の記事では、精密な「Lチカ」を行うために PWM を使って LED を直接駆動していました。 今回は、周期割り込みを使って、「Lチカ」してみます。

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

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

GPIO と RT を配置する

プロジェクトを作成するところまでは、前回までと同じです。 今回は、 GPIO コンポーネントと Reload Timer (RT) コンポーネントを配置します。 GPIO は "Component Catalog" の "Ports and Pins" から回路図に Drag&Drop します。 また、 Reload Timer は "Component Catalog" の "Digital" から Drag&Drop します。 GPIO は、名前を "Pin_Red" に変更しておきます。


Interrupt タブ

RT コンポーネントは、ダウンカウンタで構成されたタイマで、カウンタがゼロになったらあらかじめ設定された値をカウンタに書き込みます。 書き込みイベントが周期的に発生するため、周期割り込みを発生するのに使用されます。

このコンポーネントは、割り込みの機能だけを使い、入出力は使いません。 そのため、「Basic タブ」 は変更しません。

「Interrupt タブ」では、どのような種類の割り込みを発生させ、その時にどの関数を呼び出すかを設定します。 "bRtUnderflowIrq" を "true" に設定すると、 Reload Timer の一周期ごとに割り込みが発生します。 その時に "pfnRtUnderflowIrqCb" に設定されたコールバック関数が呼び出されます。 ここでは、 "UnderflowCb" としました。 実際には、この名前の初めにコンポーネントの名前を付けた "RT_UnderflowCb()" が呼び出されます。

"bTouchNvic" も "true" に設定します。 このパラメータを "true" にすると、割り込み受付モジュール Nested Vector Interrupt Controller (NVIC) の設定が一緒に行われます。 以上で、周期割り込みを受け付けてコールバック関数を呼び出すようになります。 フラグをクリアするなどの操作は、自動的に生成されるので、ユーザが気にする必要はありません。


RT タブ

「RT タブ」では、一項目を設定します。 "enRtPres" で RT のクロックに使用されるプリスケーラの分周比を設定します。 PWM コンポーネントの "enPwmPres" と考え方は同じです。 今回も、最大分周比である "1/2048" を使用します。 デフォルト状態でのクロック周波数は 8MHz なので、 RT の駆動クロックは、約4kHzとなります。

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

端子を割り当てる

端子の設定

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

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

ソースコードの記述

ソースコード

次は、ソースコードの記述です。

#include "project.h"

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

void RT_UnderflowCb(void) {
    Pin_Red_GpioPut(!Pin_Red_GpioGet());    // GPIO 出力を反転する
}

int main(void) {
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    Pin_Red_GpioInitOut(1);                 // GPIO を出力に設定する
    Bt_Rt_Init(&RT_HW, &RT_Config);         // RT の初期設定
    Bt_Rt_WriteCycleVal(&RT_HW, CYCLE);     // RT 周期
    Bt_Rt_EnableCount(&RT_HW);              // RT を起動する
    Bt_Rt_EnableSwTrig(&RT_HW);             // RT にトリガをかける

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

必要なのは、割り込みコールバック関数と初期設定だけです。 割り込みコールバック関数 "RT_UnderflowCb()" は、 RT のカウンタがゼロになるたびに呼び出される関数です。 この関数では、 GPIO "Pin_Red" の出力を反転させています。 周期割り込みが0.5秒ごとに発生するので、 LED は、 1Hz で点滅します。

GPIO コンポーネントは、 "main()" 関数内で "GpioInitOut" メソッドにより出力に設定します。 出力の初期設定値は、 "1" です。

"Bt_Rt_Init()" は RT の初期設定を行う関数です。 PWM の時と同じように第一引数に &RT_HW を渡して、設定すべきタイマブロックを指定しています。 第二引数の &RT_Config には、コンポーネントの設定ダイアログで設定したパラメータが格納されています。

"Bt_Rt_WriteCycleVal()" は、 RT の周期を設定します。 周期の単位は、設定ダイアログで指定した約4kHzの RT 駆動クロックです。 このプログラムでは、コンパイル時に設定値 (CYCLE) を計算させて、周期に0.5秒を設定しています。

"Bt_Rt_EnableCount()" で、 RT の動作を開始します。 ただし、この時には RT カウンタは動き出しません。 カウンタが動きだすのは、 "Bt_Rt_EnableSwTrig()" でトリガをかけた時です。 トリガを受けた RT は、継続して割り込みをさせます。

ここで使用されている関数群は、 Peripheral Driver Library (PDL) ライブラリそのものです。 このライブラリの設計思想は、従来の PSoC の API とは異なっているので、注意が必要です。

ソースコードは、以上です。 "Build" して、 "Program" すると、 LED が点滅を始めます。 「Lチカ」できました。


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

FM0+ 評価ボード

前回の記事では、時間稼ぎループをソフトウェアで実現していたために、精密な「Lチカ」にはなりませんでした。 今回は、ハードウェアの力を借りて、「Lチカ」してみます。

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

回路図に PWM を配置する

PWM を配置

プロジェクトを作成するところまでは、前回と同じです。 今回は、 PWM コンポーネントを配置します。 "Component Catalog" の "Digital" にある "PWM" を Drag&Drop で回路図に配置します。


Basic タブ

次にコンポーネントの設定を行います。 このプロジェクトでは、 PWM 出力を直接赤い LED に接続します。 その際に PWM 出力で使用される端子を Timer I/O A (TIOA) と呼びます。 Basic タブでは、"ConnectTIOA" を "true" に設定して TIOA 出力を有効にします。


PWM タブ

PWM タブでは、二項目を設定します。 まず、 "enPwmOutputPolarity" で出力の位相を設定します。 "Initially high" に設定すると、初期状態が HIGH になります。 TIOA に接続される LED は、電源とポート出力の間に接続されています。 そのため、負論理 (Active Low) 出力に設定します。

"enPwmPres" は、 PWM のクロックに使用されるプリスケーラの分周比を設定します。 今回は、クロックに比べて遅い点滅にするため、最大分周比である "1/2048" を使用します。 デフォルト状態でのクロック周波数は 8MHz なので、 PWM の駆動クロックは、約4kHzとなります。

これで回路図の設定は終わりです。 端子を表すコンポーネントがありませんが、設定で有効にした TIOA が使われることになります。

端子を割り当てる

端子の設定

次は、端子の割り当てを行います。 ここでは、 PWM の TIOA 端子をどこに割り当てるかを指定します。 割り当てるのは、赤い LED が接続されている P3D 端子です。 "PWM:TIOA" に "P3D" を設定します。

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

ソースコードの記述

ソースコード

次は、ソースコードの記述です。

#include "project.h"

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

int main(void) {
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    PWM_SetPinFunc_TIOA_OUT();                  // PWM の出力機能を有効にする
    Bt_Pwm_Init(&PWM_HW, &PWM_Config);          // PWM の初期設定
    Bt_Pwm_WriteCycleVal(&PWM_HW, CYCLE - 1);   // PWM 周期
    Bt_Pwm_WriteDutyVal(&PWM_HW, CYCLE / 2);    // PWM デューティー
    Bt_Pwm_EnableCount(&PWM_HW);                // PWM を起動する
    Bt_Pwm_EnableSwTrig(&PWM_HW);               // PWM にトリガをかける

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

必要なのは、初期設定だけです。 まず、 "PWM_SetPinFunc_TIOA_OUT()" で PWM の TIOA に割り当てられた端子、すなわち P3D 端子を出力に設定します。 この関数は、 PWM コンポーネントのメソッドのように記述されています。

ところが、これ以降の関数ではメソッドという扱いにはなっていません。 次の "Bt_Pwm_Init()" は PWM の初期設定を行う関数ですが、引数としてタイマのレジスタアドレスである &PWM_HW を渡して、設定すべきタイマブロックを指定しています。 このレジスタアドレスを使って、各種レジスタを関数で設定するという仕組みです。 第二引数の &PWM_Config には、 PSoC Creator の設定ダイアログで設定したパラメータが格納されています。 設定ダイアログを見てわかるように、このパラメータには PWM の周期やデューティーといった情報は含まれてはいません。 これらの情報は、別途設定をすることになります。

"Bt_Pwm_WriteCycleVal()" と "Bt_Pwm_WriteDutyVal()" は、 PWM の周期とデューティーを設定します。 ここでも引数にレジスタアドレスを渡しています。 周期とデューティーの単位は、設定ダイアログで指定した約4kHzの PWM 駆動クロックです。 このプログラムでは、コンパイル時に設定値を計算させて、周期に0.5秒、デューティーにその半分 (50%) を設定しています。

"Bt_Pwm_EnableCount()" で、 PWM の動作を開始します。 ただし、この時には PWM カウンタは動き出しません。 カウンタが動きだすのは、 "Bt_Pwm_EnableSwTrig()" でトリガをかけた時です。 トリガを受けた PWM は、継続して LED を点滅させます。

ここで使用されている関数群は、 Peripheral Driver Library (PDL) ライブラリそのものです。 このライブラリの設計思想は、従来の PSoC の API とは異なっているので、注意が必要です。

ソースコードは、以上です。 "Build" して、 "Program" すると、 LED が点滅を始めます。 「Lチカ」できました。


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

FM0+ 評価ボード

PSoC Creator が、 Revision 4.0 から FM0+ MCU にも対応するようになりました。 評価ボードも入手できるので、さっそく「Lチカ」してみましょう。

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

プロジェクトを作成する

ターゲット選択

プロジェクトの作成手順は、これまで PSoC のプロジェクトを作成した時と同じです。 まず、メニューから "File" → "New" → "Project..." を選んでダイアログを開きます。

ダイアログの最初の画面では、デバイスを選択します。 最近の PSoC Creator では、デバイスの型番を指定する代わりに、キットの型番を選択していましたが、 FM0+ のキットはリストに並んでいません。 そこで、 "Target Device" を選択しデバイスリストから "S6E1C32D0AGV20000" を選びます。


初期プロジェクト選択

次にプロジェクトの初期状態を選択します。 今回は、白紙の状態からプロジェクトを作成しますので、 "Empty Schematic" を選択します。


ディレクトリと名前の設定

最後に、プロジェクトを配置するディレクトリと Workspace 名および Project 名を選択します。 これで、プロジェクトが作成できました。

回路図に GPIO を配置する

GPIO コンポーネント

次は、回路図に GPIO コンポーネントを配置します。 "Project Explorer" から ".cysch" ファイルの回路図を開きます。 そして、 "Component Catalog" の "Ports and Pins" にある "GPIO" を Drag&Drop で回路図に配置します。


GPIOを配置

配置されたコンポーネントをダブルクリックして、設定ダイアログを開きます。


GPIOの設定ダイアログ

設定項目は、わずかです。 入出力の設定さえありません。 ここでは、コンポーネントの名前を "Pin_Red" に変更します。 これで、回路図の準備は終わりです。

端子を割り当てる

端子設定を呼び出す

回路図が出来たら、端子の割り当てを行います。 PSoC Creator 4.0 からは、 "Workspace Explorer" から直接端子設定画面を呼び出せるようになっています。 "Design Wide Resources" の下にある "Pins" を開きます。


端子の設定

端子の設定方法は、これまでと同じです。 この評価ボードでは、赤い LED が、 P3D 端子に接続されています。 "Pin_Red:GPIO" に "P3D" を設定します。

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

ソースコードの記述

ソースコード

次は、ソースコードの記述です。 PSoC 比べると、記述は多めです。

int main(void) {
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    Pin_Red_GpioInitOut(1);  // GPIO を出力に設定する

    for (;;) {
        /* Place your application code here. */
        uint32_t i;
        for (i = 0; i < 1000000; i++) ;  // ちょっと待つ
        Pin_Red_GpioPut(!Pin_Red_GpioGet());  // GPIO 出力を反転する
    }
}

GPIO コンポーネントでは、入出力の方向さえ定めていませんでした。 そのため、 GPIO を出力として設定する必要があります。 "GpioInitOut" メソッドで設定を行います。 引数には、出力の初期設定値が入ります。

PSoC では、待ち時間を作るための CyDelay() などの関数がありましたが、 FM0+ の場合には見つかりませんでした。 そこで、目分量で1000000回のループを作成し、待ち時間を作っています。 デフォルトの状態では、内部 8MHz クロックをそのまま CPU で使用していますので、これで1秒近辺の待ち時間が出来るでしょう。

GPIO の読み出しと書き込みには、メソッド "GpioGet" と "GpioPut" を使用します。 ここでは、読み出した値を論理反転して書き込み、 LED を点滅させます。

ここで使用されているメッソド群は、間接的に Peripheral Driver Library (PDL) と呼ばれるライブラリを呼び出しています。 どんな事ができるのかを知るためには、 PDL も知る必要がありそうです。

ソースコードは、以上です。 "Build" して、 HEX ファイルを作成します。

MCUに書き込む

CMSIS-DAPの検出

PSoC の場合、 MiniProgKitProg などの PSoC 固有のインターフェイスが使用されていました。 評価ボードの場合、標準的なインターフェイスは CMSIS-DAP です。

メニューから "Debug" → "Select Debug Target..." を選ぶとデバッガのインターフェイスが表示されます。 評価ボードを接続すると、 "CMSIS-DAP" が認識されます。 問題なくデバッガとして認識されるようです。

プログラムは、メニューの "Debug" → "Program" で書き込まれます。 書き込みが終わると、そのままプログラムが実行されて、 LED が点滅します。 「Lチカ」できました。


サイクルタイムを測定しよう (12) [FMx]このエントリーを含むはてなブックマーク#

FM0+ 評価ボード

前回は、ループの配置アドレスを変えながら、サイクル数を測定しました。 今回は、ループ内の命令数によってサイクル数に差が出るかについて調べました。

ソースコード

ループ内の命令数を変化させるために、ループ内に "nop" 命令を追加します。 以下のコードで、 "nop" 命令の数を調整します。

void func9(reg8_t *reg, uint8_t val0, uint8_t val1) @ ".text.func9" {
    for (;;) {
        __asm(
            "nop\n"     // 29
            "nop\n"     // 28
            "nop\n"     // 27
            "nop\n"     // 26
            "nop\n"     // 25
            "nop\n"     // 24
            "nop\n"     // 23
            "nop\n"     // 22
            "nop\n"     // 21
            "nop\n"     // 20
            "nop\n"     // 19
            "nop\n"     // 18
            "nop\n"     // 17
            "nop\n"     // 16
            "nop\n"     // 15
            "nop\n"     // 14
            "nop\n"     // 13
            "nop\n"     // 12
            "nop\n"     // 11
            "nop\n"     // 10
            "nop\n"     // 9
            "nop\n"     // 8
            "nop\n"     // 7
            "nop\n"     // 6
            "nop\n"     // 5
            "nop\n"     // 4
            "nop\n"     // 3
            "nop\n"     // 2
            "nop\n"     // 1
            "label_2:"
        );
        *reg = val0;
        *reg = val1;
    }
}

このコードでは、ループの配置アドレスは固定されますが、代わりに分岐命令のアドレスが変化していきます。

     50          void func9(reg8_t *reg, uint8_t val0, uint8_t val1) @ ".text.func9" {
   \                     func9: (+1)
   \   00000000   0xB500             PUSH     {LR}
     83              for (;;) {
     84                  __asm(
    110                      "nop\n"     // 4
    111                      "nop\n"     // 3
    112                      "nop\n"     // 2
    113                      "nop\n"     // 1
    114                      "label_2:"
    115                  );
   \                     ??func9_0: (+1)
   \   00000002   0xBF00             nop
   \   00000004   0xBF00             nop
   \   00000006   0xBF00             nop
   \   00000008   0xBF00             nop
    116                  *reg = val0;
   \                     ??label_2: (+1)
   \   0000000A   0x7001             STRB     R1,[R0, #+0]
    117                  *reg = val1;
   \   0000000C   0x7002             STRB     R2,[R0, #+0]
   \   0000000E   0xE7F8             B        ??func9_0
    118              }
    119          }

上記は、 "nop" 命令を4個入れた場合のコードです。 分岐先アドレスは "0002" のままですが、分岐命令は "000E" に移動しています。

測定結果

サイクル数を測定しました。 "nop" ひとつあたり1サイクルほど命令実行時間が増えますので、 "nop" による影響を除いたサイクル数を計算しました。

NOP数サイクル数
(除NOP)
分岐先アドレス分岐命令
040206
140208
24020A
34020C
44020E
560210
660212
760214
860216
960218
106021A
116021C
126021E
1370220
1470222
1570224
1670226
1770228
187022A
197022C
207022E
2180230
2280232
2380234
2480236
2580238
268023A
278023C
288023E
2990240

8命令(16バイト)ごとにサイクル数が一つずつ増えているのが分かります。 言い方を変えると、8個の "NOP" を実行するために9サイクルの時間を要しているのです。 ちょっと、効率が悪くありませんか?

推測するに、16バイト境界を超えるごとに命令フェッチに1サイクルの実行時間が必要となっていると考えられます。 通常、プリフェッチとパイプラインの構造がうまく働いていれば、命令フェッチの時間はパイプラインに隠れてしまうはずです。 しかし、この実験結果からは、命令フェッチが見えてしまっています。 しかも、命令フェッチサイクルが増えるのは、分岐命令アドレスの下4ビットが 0000 になった時です。 本当にプリフェッチしていないのでしょうか?


サイクルタイムを測定しよう (11) [FMx]このエントリーを含むはてなブックマーク#

FM0+ 評価ボード

前回は、最小ループのサイクル数を測定しました。 今回は、ループの配置アドレスによってサイクル数に差が出るかについて調べました。

ソースコード

配置アドレスを変更する考え方は、 PSoC で行った方法と同じです。 ループの前に "nop" 命令を追加して、ループのアドレスを移動します。 以下のコードで、 "nop" 命令の数を調整します。

void func9(reg8_t *reg, uint8_t val0, uint8_t val1) @ ".text.func9" {
    __asm(
        "nop\n"     // 29
        "nop\n"     // 28
        "nop\n"     // 27
        "nop\n"     // 26
        "nop\n"     // 25
        "nop\n"     // 24
        "nop\n"     // 23
        "nop\n"     // 22
        "nop\n"     // 21
        "nop\n"     // 20
        "nop\n"     // 19
        "nop\n"     // 18
        "nop\n"     // 17
        "nop\n"     // 16
        "nop\n"     // 15
        "nop\n"     // 14
        "nop\n"     // 13
        "nop\n"     // 12
        "nop\n"     // 11
        "nop\n"     // 10
        "nop\n"     // 9
        "nop\n"     // 8
        "nop\n"     // 7
        "nop\n"     // 6
        "nop\n"     // 5
        "nop\n"     // 4
        "nop\n"     // 3
        "nop\n"     // 2
        "nop\n"     // 1
        "label_1:"
    );
    for (;;) {
        *reg = val0;
        *reg = val1;
    }
}

func9() 関数は、 @ 指示子によって、セクション名が決められています。 このセクション名を "icf" ファイルで指定する事で、 func9() 関数の境界を定めることが出来ます。

define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };
define block FUNC9     with alignment = 32 { section ".text.func9" };
define block MAIN      with alignment = 32 { section ".text.main" };
:
:
place in ROM_region   { readonly, block FUNC9, block MAIN };

このプロジェクトの場合、上記の記述が入っています。 "define block" でブロック名を定義します。 FUNC9 ブロックには ".text.func9" セクションが入っており、 alignment を 32 と定義しています。 この記述により、 func9() 関数は 32 バイト境界に配置されます。

測定結果

サイクル数を測定した所、周期的に4サイクルと6サイクルのパターンを繰り返す事がわかりました。

NOP数サイクル数分岐先アドレス分岐命令
040206
140408
24060A
34080C
440A0E
560C10
660E12
741014
841216
941418
104161A
114181C
1241A1E
1361C20
1461E22
1542024
1642226
1742428
184262A
194282C
2042A2E
2162C30
2262E32
2343034
2443236
2543438
264363A
274383C
2843A3E
2963C40

サイクル数が6サイクルになるのは、分岐先アドレスの下4ビットが 11xx になるときです。 このとき、分岐先のアドレスと分岐命令アドレスの上位部分が異なっているのがわかります。 例えば、 NOP 数が 5 の時、分岐先アドレスの上位4ビットは 0000 ですが、分岐命令アドレスの上位4ビットは 0001 です。 このように、分岐先と分岐命令が16バイト境界を超えるとサイクル数が増えます。

これは、16バイト境界を超えると分岐先命令をフェッチするために追加サイクルが必要になり、サイクル数が伸びたと考えられます。 また、 Flash ROM のフェッチバッファのサイズは16バイトであるとも推測できます。

ひとつだけ腑に落ちない点もあります。 例えば、 NOP数が 4 の時、分岐命令は 0E にあります。 分岐命令が実行される時、命令がプリフェッチされているのであれば、バッファには 10 から 17 までの内容がロードされているはずです。 その場合、分岐先の命令がバッファに入っていないので、当然、再度フェッチが発生し、サイクル数が伸びるはずです。 しかし、実際には、4サイクルのままです。 この事実から推測されるのは、分岐実行の前にプリフェッチが行われていないのか、プリフェッチバッファが多数存在するかのどちらかです。 この測定結果からは、どちらとも言えません。


サイクルタイムを測定しよう (10) [FMx]このエントリーを含むはてなブックマーク#

FM0+ 評価ボード

今回は、 CypressFM0+ MCU S6E1A シリーズに焦点をあてて、サイクルタイムを測定してみます。 CPU は、 Cortex-M0+ だから、 PSoC 4 と大きな違いはないでしょう。

Fast GPIO を使ったループ

今回のプロジェクトでも、ループを回るごとに GPIO をトグルさせて外部からループの周期を測定します。 PSoC 4 の場合、 Control Register コンポーネントを配置して、データを書き込むだけで出力がトグルするハードウェアを組みました。 FM0+ の場合、ハードウェアを組む事はできませんので、すべてソフトウェアで GPIO を操作します。

FM0+ には、ハードウェアを作成する機能は有りませんが、ソフトウェアから高速に GPIO を操作する機能を持っています。 それが、 Fast GPIO (FGPIO) です。 FGPIO は、 CPU に近いバスに接続されているため、 GPIO の操作に1クロックしか要しません。 こういった、高速に GPIO 出力を操作する用途には最適です。

    Gpio1pin_InitOut(FGPIO1PIN_P61, Gpio1pin_InitVal(1));
    FM_GPIO->FPOER6_f.P1 = 1;  // Enable FGPIO

使用する端子は、評価ボードの LED が接続されている P61 です。 ディレイを入れると、 LED の明滅で動作を確認できます。 P61 を出力に設定するためには、上記のように Gpio1pin_InitOut() マクロを使用します。 このマクロは、 Peripheral Driver Library (PDL) と呼ばれる基本ライブラリで提供されています。

デフォルトの状態では、各端子は GPIO として機能します。 これを FPGIO で制御させるために FPOER6 レジスタの当該ビットを1に設定します。 これで、 P61 は、 FGPIO の出力端子として機能します。

      func9(&bFM_GPIO_FPDOR6_P1, 0u, 2u);

実際に FGPIO をトグルさせる部分は、前回同様 func9() 関数にまとめました。 P61 の出力値を変更するためのレジスタが、 bFM_GPIO_FPDOR6_P1 にあります。 今回は、書き込み動作一回でトグルさせるのではなく、 set/clear の二回の書き込みでトグルさせます。 そのため、 clear するための値と set するための値を引数に与えています。

typedef volatile uint8_t reg8_t;

void func9(reg8_t *reg, uint8_t val0, uint8_t val1) @ ".text.func9" {
    for (;;) {
        *reg = val0;
        *reg = val1;
    }
}

func9() 関数は、上記のようになっています。 "@" で示されている名前は、セクション名です。 リンカのスクリプトで、このセクションを 32 バイト境界から配置するように指定しています。 bFM_GPIO_FPDOR6_P1 レジスタのアドレスと書き込む値を引数で与えると、それぞれ CPU のレジスタに値が保持されて、アクセス時間が最短になります。

     83              for (;;) {
    116                  *reg = val0;
   \                     ??func9_0: (+1)
   \   00000002   0x7001             STRB     R1,[R0, #+0]
    117                  *reg = val1;
   \   00000004   0x7002             STRB     R2,[R0, #+0]
   \   00000006   0xE7FC             B        ??func9_0
    118              }

コンパイラで生成されたコードは、3命令になりました。

実行サイクル数

このプログラムのループ周期を測定したところ、4サイクルで実行されることがわかりました。 CPU クロックを40MHzで操作させているので、10MHzのパルスで LED が駆動されたことになります。 さらに実験したところ、 STRB 命令が1サイクル、 B 命令が2サイクルで実行されるということがわかりました。

参考サイト

FM0+ S6E1A Series 5V Robust ARM® Cortex®-M0+ MCU
今回実験に使用したのは、 FM0+ S6E1A というシリーズの MCU です。
FM0-V48-S6E1A1 ARM® Cortex®-M0+ FM0+ MCU Evaluation Board
実験には、評価ボードを使用しました。 書き込みツールとして CMSIS-DAP として機能する FM3 が搭載されています。
FM MCU Peripheral Driver Library (PDL)
FM0+ の各種ペリフェラルを操作するための基本ライブラリです。 今回は、 FGPIO の初期化で使用しましたが、高速にアクセスするためには、レジスタを直接操作した方が良さそうです。

参考文書

AN210985 - Getting Started with FM0+ Development
FM0+ でプログラム開発を行うための手順が書かれています。 この文書では、 Peripheral Driver Library (PDL) の使用を前提としています。

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