So-net無料ブログ作成
検索選択

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


continue 文の次に実行されるのは? [PSoC]このエントリーを含むはてなブックマーク#

本日のお題

Twitterで、お題をいただきました。 これは、いっちょ試してみなくては。 題材に取り上げたのは、 Cortex-M0 搭載の PSoC 4 M-Series です。

GCC の場合

まず、 PSoC Creator のデフォルトツールチェインである GCC で試しました。 使用したコードは以下の通りです。

int main(void) {
    CyGlobalIntEnable; /* Enable global interrupts. */

    for (;;) {
        int i;
        for (i = 0; i < 10; ++i) {
            if (i == 0) continue;
        }
    }
}

for 行の中に変数宣言が入れられないので、外で宣言しています。 あれ、もしかしたら、これは C のお題じゃなかったのかな? 気にせず、 LST ファイルを見てみます。

  30 0000 62B6     		CPSIE   i
  33              	.L4:
  37 0002 0023     		mov	r3, #0
  38 0004 00E0     		b	.L2
  40              	.L3:
  42 0006 0133     		add	r3, r3, #1
  44              	.L2:
  46 0008 092B     		cmp	r3, #9
  47 000a FCDD     		ble	.L3
  48 000c F9E7     		b	.L4

変数 i は、 r3 に割り当てられています。 i を 0 と比較する部分は、存在しません。 すでに最適化されてしまっているようです。

continue 文の次に実行されるものは、そもそも if 文さえ存在しないので、問い自体が無意味である。

最適化しない GCC の場合

この結論じゃ面白くないので、最適化されないように -O0 オプションを付けました。

  28 0000 80B5     		push	{r7, lr}
  32 0002 82B0     		sub	sp, sp, #8
  34 0004 00AF     		add	r7, sp, #0
  38 0006 62B6     		CPSIE   i
  41              	.L5:
  44 0008 0023     		mov	r3, #0
  45 000a 7B60     		str	r3, [r7, #4]
  46 000c 06E0     		b	.L2
  47              	.L4:
  49 000e 7B68     		ldr	r3, [r7, #4]
  50 0010 002B     		cmp	r3, #0
  51 0012 00D1     		bne	.L3
  52 0014 C046     		mov	r8, r8
  53              	.L3:
  55 0016 7B68     		ldr	r3, [r7, #4]
  56 0018 0133     		add	r3, r3, #1
  57 001a 7B60     		str	r3, [r7, #4]
  58              	.L2:
  60 001c 7B68     		ldr	r3, [r7, #4]
  61 001e 092B     		cmp	r3, #9
  62 0020 F5DD     		ble	.L4
  65 0022 F1E7     		b	.L5

すると、変数をレジスタに割り当てる事もしないコンパイル結果となりました。 i を 0 と比較する部分は、50行目にあります。 その後、51行目の "bne" で条件分岐を行います。 continue 文に相当するのは、52行目の "mov r8, r8" です。 その後は、55行目から始まる ++i が実行されます。 つまり、 continue 文の次に実行されるのは、 "++i" であるという事がわかりました。

continue 文の次に実行されるものは、 ++i である。

MDK-ARM の場合

同じプロジェクトを MDK-ARM でコンパイルしてみました。

0x000006E4 BF00      NOP      
0x000006E6 B662      CPSIE    I
0x000006E8 BF00      NOP      
0x000006EA BF00      NOP      

0x000006EC 2000      MOVS     r0,#0x00
0x000006EE E004      B        0x000006FA

0x000006F0 2800      CMP      r0,#0x00
0x000006F2 D100      BNE      0x000006F6
0x000006F4 E000      B        0x000006F8

0x000006F6 BF00      NOP      
0x000006F8 1C40      ADDS     r0,r0,#1

0x000006FA 280A      CMP      r0,#0x0A
0x000006FC DBF8      BLT      0x000006F0

0x000006FE E7F5      B        0x000006EC

機能制限版では、 LST ファイルが出力されないようなので、デバッガの "Disassembly" ウィンドウから取ってきました。 変数 i は、 r0 に割り当てられています。

if 文は 0x000006F0 番地にあります。 continue 文に相当するのは、 0x000006F4 番地の "B 0x000006F8" です。 分岐先の 0x000006F8 番地には、 ++i があります。

continue 文の次に実行されるものは、 ++i である。

IAR Embedded Workbench の場合

IAR のコンパイラでも、同じようにコンパイルしてみました。

   \                     main: (+1)
   \   00000000   0xB500             PUSH     {LR}

   \   00000002   0xB662             CPSIE   i
   \                     ??main_0: (+1)
   \   00000004   0x2000             MOVS     R0,#+0
   \                     ??main_1: (+1)
   \   00000006   0x280A             CMP      R0,#+10
   \   00000008   0xDAFC             BGE      ??main_0
   \   0000000A   0x2800             CMP      R0,#+0
   \                     ??main_2: (+1)
   \   0000000C   0x1C40             ADDS     R0,R0,#+1
   \   0000000E   0xE7FA             B        ??main_1

変数 i は、 R0 レジスタに割り当てられています。 変数 i と 0 を比較する部分は、 0000000A 番地の "CMP R0,#+0" です。 しかし、比較をしているだけで後に続く条件分岐が見当たりません。 次に実行されるのは、 0000000C 番地の ++i です。

continue 文に相当する部分が存在しないが、結果的に次に実行されるものは、 ++i である。

最適化をがんばったら

これまでは、 "-O0" オプションで最適化を抑制していました。 普通は、多少の最適化を行わせますので、ためしに GCC の再弱最適化オプションである "-O1" を使ってコンパイルさせてみました。

  30 0000 62B6     		CPSIE   i
  33              	.L2:
  35 0002 FEE7     		b	.L2

すると、最適化の結果、何も残りませんでした。 後にあるのは、一番外側の無限ループのみです。

最適化されると、 for 文がまるごと省略される。

関連商品

PSoC 4200M CY8CKIT-043 Prototyping Kit

PSoC 4200M CY8CKIT-043 Prototyping Kit

  • 出版社/メーカー: スイッチサイエンス
  • メディア: エレクトロニクス

Clock_Start() メソッドでクロックが起動しない [PSoC]このエントリーを含むはてなブックマーク#

ボタンでブザーを操作する回路

PSoC 4200 を使って、ブザーをボタンにより操作しようとしているのだけど、うまく動かないと相談を受けました。 さて、何が原因でしょうね。

ソフトウェアでで Clock を制御する

使われた回路は、上にある通りです。 ソフトウェアで入力端子の状態を読み出し、それにしたがって、 Clock コンポーネントを動かしたり止めたりするというコンセプトです。 ソフトウェアでの判断は、以下のようになっています。

#include <project.h>

uint8   SW2_state;

int main() {
    CyGlobalIntEnable; /* Enable global interrupts. */

    /* Place your initialization/startup code here (e.g. MyInst_Start()) */

    for (;;) {
        /* Place your application code here. */
        SW2_state = Pin_SW2_Read();
        if (SW2_state) {
            // SW2 OFF
            Clock_880Hz_Stop();
        } else {
            // SW2 ON
            Clock_880Hz_Start();
        }
    }
}

メインループの中で入力端子の状態を読み取り、状態にしたがって Stop() メソッドまたは Start() メソッドを呼び出すという仕組みです。 ボタンを押し続けた場合、このループでは繰り返し Start() メソッドが呼び出されます。 「 Start() メソッドにより、 Clock コンポーネントが起動される」という目的で作られているのであれは、これでも問題ないのですが、どうも起動する以外の副作用が有りそうです。

実験開始

実験回路

そこで、実験回路を作成しました。

左半分の回路では、 Pwm_Start タイマにより周期的な割り込み int_Start を発生させて Pin_LED 端子に接続されている Clock コンポーネントの Start() メソッドを発行しています。 LED は、 Clock コンポーネントの周期 200ms で点滅を行います。 タイマに PWM コンポーネントを使用しているのは、出力を波形で見るためです。

一方、右側の回路は、左の回路で生成された信号を取りこむロジック・アナライザとして機能します。 信号を取りこむタイミングは、割り込み int_Sample で決定します。 Status Register で取りこんだ信号は、 UART コンポーネントで送り出します。 UART で送信されたデータは Bridge Control Panel で波形として表示します。 この動作は、 CY8CKIT-042 でロジアナを作った ~UART編~ で紹介した通りです。

// Re-start Clock component ISR
CY_ISR(int_Start_isr) {
    Clock_5Hz_Start();
}

// Logic sampling ISR
CYBIT  int_Sample_flag = 0;
uint8   probe = 0;

CY_ISR(int_Sample_isr) {
    probe = SR_Probe_Read();
    int_Sample_flag = 1;    
}

int main() {
    CyGlobalIntEnable; /* Enable global interrupts. */

    // Start method calling timing
    Pwm_Start_Start();
    Pwm_Start_WritePeriod(489);
    int_Start_StartEx(int_Start_isr);
    
    // Logic sampling timing
    int_Sample_StartEx(int_Sample_isr);
    
    // Logic analyzer output
    UART_Start();

    // Main loop
    for(;;) {
        // Sample logic level periodically        
        if (int_Sample_flag) {
            UART_UartPutChar(probe);
            int_Sample_flag = 0;
        }
    }
}

ソフトウェアは、上記のとおりです。 Start() メソッドを発行する周期を Pwm_Start タイマで規定しています。 この実験では、周期を変化させて動作を確認するために、周期をソフトウェアで決定しています。 こうすると、プロジェクトを Build した時に回路の配置配線を行う必要が無いため、実験に必要な時間を減らす事ができます。

実験の結果

490ms周期

まず、 Start() メソッドの周期を 490 とした場合、このような波形になりました。 Start() メソッドを発行してから、ちょうど 100ms 後に LED 出力が立ち下がり、さらに 100ms 後に立ちあがっているのがわかります。 100ms は、 LED を駆動する Clock コンポーネントの周期の半分です。 この様子から、 Start() メソッドの発行により、 Clock コンポーネントの内部カウンタがリセットされたと考えられます。 内部カウンタがリセットされたのに Clock 出力そのものがリセットされないのは妙です。 そこで、 Start() メソッドの周期を 390ms としてみました。


390ms周期

すると、 Start() メソッド発行後 100ms の位置にあった立ち下がりエッジが無くなりました。 さらに 100ms あと、つまり Start() メソッド発行後 200ms には立ち上がりエッジがあります。 これらの実験から以下の推論が導き出されます。

  1. Start() メソッドを発行すると Clock の内部カウンタがリセットされるが、クロック出力は以前の状態を保持する。
  2. Start() メソッドの発行からクロック半周期分の時間が経過すると、クロック出力が "0" に設定される。
  3. さらに半周期分の時間が経過するとクロック出力は "1" に設定される。
  4. 以降、クロック出力を "0" と "1" で交互に設定するとクロック出力が変化する。

以上の推測から、 Start() メソッドの発行周期を 200ms よりも短くすると、クロック出力は "0" に設定されたままで、クロックとしては機能しなくなると考えられます。


199ms周期

そこで、 Start() メソッドの周期を 199ms にしました。 すると、みごとに LED が点滅しなくなりました。 ブザーの音が出なくなったのは、 Start() メソッドの周期が短すぎて、クロック出力が出なくなってしまったものと推測されます。

解決策

ここからは、どうやったらブザー出力が出てくるかという解決策を探ります。 実験の結果から、 Start() メッソドの呼び出し周期が 1.2ms (880Hz) よりも短ければ、クロックは出力されます。 そこで、ボタンの判定の後、20ms ほど遅延を入れて Start() メッソドの呼び出しが頻繁に発生しないようにします。

    for (;;) {
        /* Place your application code here. */
        SW2_state = Pin_SW2_Read();
        if (SW2_state) {
            // SW2 OFF
            Clock_880Hz_Stop();
        } else {
            // SW2 ON
            Clock_880Hz_Start();
        }
        CyDelay(20);
    }

これで、ボタンに連動してブザーが鳴るようになります。 しかし、音が「にごる」ようになってしまいました。 これは、追加した遅延時間が短すぎて、クロック出力が歪んでしまったのが原因です。

遅延時間を長くすると、音の濁りが少なくなりますが、ボタンを押してから音が出る・止まるまでの遅延も大きくなり、ボタンに対する反応が遅くなったように感じます。

#include <project.h>

uint8   SW2_state;
uint8   SW2_last_state = 1u;    // OFF as default

int main() {
    CyGlobalIntEnable; /* Enable global interrupts. */

    /* Place your initialization/startup code here (e.g. MyInst_Start()) */

    for (;;) {
        /* Place your application code here. */
        SW2_state = Pin_SW2_Read();
        if (SW2_state && !SW2_last_state) {
            // SW2 ON to OFF
            Clock_880Hz_Stop();
        } else if (!SW2_state && SW2_last_state) {
            // SW2 OFF to ON
            Clock_880Hz_Start();
        }
        SW2_last_state = SW2_state;
    }
}

根本的な解決法として、以前のボタンの状態を記憶しておいて、状態が変化したら Clock コンポーネントの制御を行うようにしました。 ボタンの状態が変化した時だけ Start()/Stop() メソッドを呼び出すので、頻繁にメソッドの呼び出しが起こる事も無くなりました。 また、ボタンに対する反応も良くなりました。

参考商品

PSoC 4200 Prototyping Kit

PSoC 4200 Prototyping Kit

  • 出版社/メーカー: スイッチサイエンス
  • メディア: エレクトロニクス

なぜ、 PSoC 5LP はクロックを LED に直結できるのか。 [PSoC]このエントリーを含むはてなブックマーク#

クロック直結 LED

PSoC 5LP で L チカをやるとき、一番簡単なのはクロック出力を出力端子に接続する事です。 これだけで、 LED を点滅させることが出来ます。 でも、このワザは、 PSoC 4 では使えません。 この記事では、なぜ PSoC 5LP だけがクロックを LED に直結できるのかを探ります。

クロックは、どこへ行く

まず、クロックを出力端子に直結して、回路を合成してみます。 合成の結果は、 "rpt" ファイルに詳しく書かれています。

Clock group 0: 
    Clock Block @ F(Clock,0): 
    clockblockcell: Name =ClockBlock
        PORT MAP (
            imo => ClockBlock_IMO ,
            pllout => ClockBlock_PLL_OUT ,
            ilo => ClockBlock_ILO ,
            clk_100k => ClockBlock_100k ,
            clk_1k => ClockBlock_1k ,
            clk_32k => ClockBlock_32k ,
            xtal => ClockBlock_XTAL ,
            clk_32k_xtal => ClockBlock_XTAL_32KHZ ,
            clk_sync => ClockBlock_MASTER_CLK ,
            clk_bus_glb => ClockBlock_BUS_CLK ,
            clk_bus => ClockBlock_BUS_CLK_local ,
            dclk_glb_0 => Net_1 ,
            dclk_0 => Net_1_local );
        Properties:
        {
        }

ディジタルクロックブロックが一つ使用されており、 dclk_0 出力に 2Hz のクロック信号 Net_1_local が出てくるようになっています。 この信号を出力端子に引きこむ事で、 LED を点滅させることが出来ます。

ペリフェラルクロック

クロックシステムのブロック図

PSoC 5LP で任意のクロックを生成する場合、8系統のクロック生成器でペリフェラルクロックと呼ばれるクロックを生成します。 クロック生成器の入力は、7種類のクロック源に加えて、一般のディジタル信号 (Digital Signal Interconnect: DSI) からも取り入れる事が出来ます。 これを使うと、ユーザの作成した回路から出てくる信号を別のシステムのクロックに使ったりなど、かなり融通の利く(ゆるい)取りまわしが出来ます。


クロック生成器

入力されたクロックは、16ビットの分周器で分周されます。 この出力は、一般のディジタル信号と同じように出てゆきます。 つまり、クロックと一般の信号の区別が非常にゆるい構成になっているのです。

出て行った信号が出力端子に接続されれば、 LED に直結する事も出来ます。 非常に簡単です。

PSoC 42xx のクロック生成器

PSoC 42xx のクロックシステム

一方、 PSoC 42xx の場合、 HFCLK をクロック源とする分周器が4系統あります。 クロックを必要とするペリフェラルは、16個のブロックに分割されます。 そして、それぞれのペリフェラルが、この4系統からクロックを選んで使用するという構成になっています。


ペリフェラルクロックの行き先

それぞれのペリフェラルに供給されたクロックの用途は、クロックに限られます。 PSoC 5LP のように一般の信号としては使用できません。 これが、クロックを LED に直結できない最大の理由です。 直結するルートが無いので直結できないのです。

PSoC 42xxM のクロック生成器

PSoC 42xxM のクロックシステム

クロックシステムは、 PSoC 4 であっても品種によって少しずつ異なっています。 この図は、 PSoC 42xx M-Series のブロック図です。 25ブロックのクロックを21系統の分周器から選択するようになっています。 もちろん、これらの用途もクロックに限られていますので、一般の信号としては使用することが出来ません。 いずれにしても、ペリフェラルクロックは必ず HFCLK を分周した信号なので、 HFCLK で同期しやすくなっています。

それでも L チカしたい

PSoC 4 でも L チカ

それでも、お手軽に PSoC 4 で L チカをしたいとなったら、 Toggle Flip-Flop (TFF) を使ってクロックを一般の信号に変換する方法があります。 この方法を使うと、 TFF を実現するためのマクロセルを一個消費してしまいます。 さらに、この TFF が含まれる UDB は、このクロック以外は使う事ができません。 条件によっては、厳しくなるでしょう。

同様にクロックを Interrupt コンポーネントに接続して周期割り込みを構成する場合なども、この手法が使えます。 もし、 UDB が足りなくなったら、別の方法が無いか考えましょう。

参考文献

PSoC® 5LP Architecture TRM
PSoC の内部構成に関する情報は、 Technical Reference Manual (TRM) という文書に記述されています。 これは、 PSoC 5LP の Architecture TRM です。
PSoC 4100 and 4200 Family: PSoC® 4 Architecture Technical Reference Manual (TRM)
これは、 PSoC 42xx の Architecture TRM です。 兄弟である PSoC 41xx の情報も入っています。
PSoC 4100M/4200M Family: PSoC® 4 Registers Technical Reference Manual (TRM)
これは、 PSoC 41xx M-SeriesPSoC 42xx M-Series の Architecture TRM です。

関連商品

SparkFun FreeSoC2 開発ボード - PSoC5LP

SparkFun FreeSoC2 開発ボード - PSoC5LP

  • 出版社/メーカー: Sparkfun
  • メディア: エレクトロニクス
PSoC 4200 Prototyping Kit

PSoC 4200 Prototyping Kit

  • 出版社/メーカー: スイッチサイエンス
  • メディア: エレクトロニクス
PSoC 4200M CY8CKIT-043 Prototyping Kit

PSoC 4200M CY8CKIT-043 Prototyping Kit

  • 出版社/メーカー: スイッチサイエンス
  • メディア: エレクトロニクス

サイクルタイムを測定しよう (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サイクルのままです。 この事実から推測されるのは、分岐実行の前にプリフェッチが行われていないのか、プリフェッチバッファが多数存在するかのどちらかです。 この測定結果からは、どちらとも言えません。


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