So-net無料ブログ作成
検索選択
前の8件 | -

BLE Beacon の初歩 [PSoC]このエントリーを含むはてなブックマーク#

PSoC Advent Calendar 2016の3日目の記事です。

BLE Beacon というアプリケーションは、複数の BLE Beacon からの電波を受信して、受信場所の座標を推定するというものです。 そのため、 BLE Beacon は、 BLE Beacon の座標または座標を調べる事のできる一意な ID を発信します。 この記事では、可能な限り簡単に BLE Beacon を作成する方法をさぐります。

使用するハードウェア

CY8CKIT-145-40XX

今回使用するハードウェアは、 CY8CKIT-145-40XX PSoC® 4000S CapSense Prototyping Kit です。 今回は、このキットの裏側に搭載された EZ-BLE PRoC Module だけを使います。

BLEコンポーネントの設定

回路図

回路図に配置するのは、 BLE コンポーネントだけです。 今回作成する BLE Beacon は、データを入力せず、電波以外の物を出力しないので、端子の設定も必要ありません。


General タブ

まず、 General タブで作成するデバイスの振る舞いを決定します。 BLE Beacon は、他のデバイスとデータのやり取りをするものではなく、一方的にパケットを送信します。 このような用途で使われるデバイスを Broadcaster と呼んでいます。

これに対して、 Broadcaster の電波を受け取る事を目的としているデバイスは Observer と呼ばれます。 BLE Beacon の受信側はスマートフォンなどで行う事が多いのですが、 EZ-BLE PRoC Module を Observer として受信専用にする事もできます。


GAP Settings - General

GAP Setting タブでは、主に送信するパケットの詳細を設定します。 General ノードでは、デバイス ID とデバイス名を設定しています。 ここで設定されたデバイス名は、 BLE Beacon では使われないのでデフォルトのままにしています。


GAP Settings - Advertisement settings

Advertisement settings ノードでは、 Advertisement パケットの送信方法を規定します。 データのやり取りをするわけではないので、 Non-connectable に設定します。 Advertisement パケットの送信周期が指定されていますが、実際には、この周期は使用されていません。

BLE Beacon のパケット構成

GAP Settings - Advertisement packet

Advertisement packet では、BLE Beacon として送信するパケットの構成を指定します。 このパケットは、必須の Flags フィールドと Manufacturer Specific Data フィールドから構成されています。


Manufactrer Specific Data

Manufacturer Specific Data は、 Company ID と Data から構成されています。 Company ID には、 "Apple Inc." の 0x004C が入っています。 別の ID を使うと Android 版のアプリケーションでは認識できるのですが、 iOS 版のアプリケーションでは認識できないようです。 なんでかな?


Data フォーマット

Data には、 BLE Beacon で定められたフォーマットでデータを格納します。 CYALKIT-E02 Solar-Powered BLE Sensor Beacon Reference Design Kit (RDK) が Wireless Sensor Node (WSN) として使用される場合、16バイトの UUID には、アプリケーションの識別子として定められた値 "00050001-0000-1000-8000-00805F9B0131" が入ります。 それ以外に BLE Beacon からの情報を入れられるのは、 Major と Minor と呼ばれる合わせて4バイトのフィールドだけです。

WSN では、 Major 部分に識別 ID が入り、 Minor 部分にセンサから得られた値を入れるようになっています。 サイプレスのアプリケーション BLE-Beacon では、決められた UUID を持つパケットだけを検出するように作成されています。

クロックの設定

高速クロック設定

CPU および周辺回路は、24MHz外部発振子で作られた 24MHz のクロックをそのまま使用します。


低速クロック設定

32.768kHz 外部発振子で作られた WCO クロックは、 Timer0 で 16384 分周され 500ms ごとに割り込みを発生させ、ファームウェアのステートマシンを駆動します。

ソフトウェア

ソフトウェア

ソフトウェアは、以下のようになっています。

#include <project.h>

// Major/Minor フィールドの位置
#define     MAJOR_OFFSET    (26u)
#define     MINOR_OFFSET    (28u)

// Minor フィールドの最小・最大値および変化ステップ
#define     MINOR_MIN       (69u)   //  0℃相当の値
#define     MINOR_MAX       (126u)  // 40℃相当の値
#define     MINOR_STEP      (1u)    // Minor値の変化量

最初は、定数が定義されます。 このプログラムでは、温度情報が入っている場所にある値を変化させて、データの取りこぼしが無いかを確認しています。 MAJOR_MIN から MAJOR_MAX まで MAJOR_STEP 刻みで値を変化させます。 ここでは、0℃相当の値から40℃相当の値まで変化させているので、受信側ではノコギリ波が観測されるはずです。

// Major/Minor フィールドの値
uint8   f_minor = MINOR_MIN;        // Minorフィールドの値
uint8   f_major = 0;                // Majorフィールドの値

// BLE の初期化終了フラグ
uint8   initialized = 0;

// ソフトウェアタイマのカウンタ
uint8   tick = 0;

これだけの大域変数が宣言されています。 f_major/f_minor は、それぞれ Major/Minor フィールドに与えられる値を示しています。 initialized は、 BLE デバイスの初期化が終わったかどうかのフラグになっています。 tick は、タイマ割り込みで使用されるソフトウェアタイマのカウンタになっています。

// 500msごとに Advertisement パケットを開始・停止する
void Wdt_Callback(void) {
    if (initialized) {
        // 初期化がされた
        if (tick == 0) {
            // 最初の500msで Advertisement パケットを送信する
            
            // Major フィールドの設定
            cyBle_discoveryData.advData[MAJOR_OFFSET] = f_major;
            
            // Minor フィールドの設定
            if ((f_minor < MINOR_MIN) || (f_minor > MINOR_MAX)) {
                f_minor = MINOR_MIN;
            }
            cyBle_discoveryData.advData[MINOR_OFFSET] = f_minor;
            f_minor += MINOR_STEP;

            // Advertisement パケットの送信を開始する
            CyBle_GappStartAdvertisement(CYBLE_ADVERTISING_FAST);
            tick = 1;
        } else {
            // 次の500msで Advertisement ポケットの送信を止める
            CyBle_GappStopAdvertisement();
            tick = 0;
        }
    }
}

500ms ごとに割り込みがかかると、このコールバックルーチンが呼び出されます。 コールバック関数の設定は、メインループで行われています。

BLE の初期化が終わっていたら、 BLE の動作処理をおこないます。 一周期は二つの 500ms 割り込みで構成されています。 前半の 500ms では、 Advertisement パケットの送信を開始します。 送信内容は、コンポーネントの設定で使用した値に Major/Minor フィールドの値を加えたものです。 最後に CyBle_GappStartAdvertisement() 関数で Advertisement パケットの送信を開始します。 実際には、割り込み処理が終了してメインループに戻った時にパケットの送信が行われます。

一方、後半の 500ms では、 Advertisement パケットの送信を禁止しています。 通常 Advertisement パケットを送信する時には、コンポーネントで設定された周期にしたがって、 Advertisement パケットを送信します。 このアプリケーションでは、 Advertisement パケットの送信間隔をタイマで管理することで、精密な送信間隔を実現しようとしています。

こうして、1秒間に一回、 BLE Beacon から Advertisement パケットが送信されます。

// BLE スタックの処理ハンドラ
void StackEventHandler(uint32 event, void *eventParam) {
    switch (event) {
        // スタックが立ちあがったまたはデバイスが切断されたら初期化終了とみなす
        case CYBLE_EVT_STACK_ON:
        case CYBLE_EVT_GAP_DEVICE_DISCONNECTED:
            initialized = 1;
            break;
        // それ以外のイベントは無視する
        default:
            break;
    }
}

BLE が状態の変化を検出した時に呼び出されるのがイベント処理ハンドラですが、ここで対応しているのは、ふたつのイベントのみです。 いずれも、 BLE の初期化が終わった時に発生するイベントなので、 initialized フラグを立てて、初期化終了を知らせます。

// メインループ
int main() {
    CYBLE_API_RESULT_T apiResult;       // エラーコード

    // 割り込みを許可する
    CyGlobalIntEnable;
    
    // 低速タイマ Timer0 のコールバック関数を設定する
    CySysWdtSetInterruptCallback(CY_SYS_WDT_COUNTER0, Wdt_Callback);

    // BLE デバイスの初期化を行う
    apiResult = CyBle_Start(StackEventHandler);
    // 初期化が正常に終わったかを確認する
    CYASSERT(apiResult == CYBLE_ERROR_OK);

    for(;;){
        // BLE スタックへのイベントを処理する
        CyBle_ProcessEvents();
    }
}

メインループでは、タイマのコールバック関数を指定し、 BLE コンポーネントの初期化を行ったあと、 BLE のイベント処理を行うループに突入します。 これだけで、 BLE デバイスの出来上がりです。

観測結果

観測結果

プロジェクトが完成したら、スマートフォンで観測してみましょう。 使用するのは、 Cypress が提供する BLE-Beacon というアプリケーションで、 Android 版は Google Play から、 iOS 版は Apple Store から入手できます。

私の Android 端末では、このように表示されました。 ここで作成したデバイスは、1秒ごとにパケットを送信しています。 Android 端末でも Log 上では、1秒ごとにパケットを受信している事がわかるのですが、 Graph 表示にすると5秒に一回しかプロットしてくれません。 どうも、描画処理が間に合っていないそうです。 一方、 iOS 版では、1秒ごとにプロットが出力されます。

プロジェクトアーカイブ

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

参考サイト

S6SAE101A00SA1002 Solar-Powered IoT Device Kit
太陽電池で動作する Wireless Sensor Node (WSN) を開発するためのキットです。 BLE Beacon としての動作には代わりがありませんが、サイプレスの Company ID が使用されています。
CYALKIT-E02 Solar-Powered BLE Sensor Beacon Reference Design Kit (RDK)
WSN をばらまく時に使われるハードウェアを備えた設計例です。 今回の記事は、このキットから BLE Beacon の部分を抜き出しています。
CY8CKIT-145-40XX PSoC® 4000S CapSense Prototyping Kit
今回の記事で使用したハードウェアです。 本来は、 PSoC 4 S-Series の開発キットなのですが、裏側に EZ-BLE PROC Module が付いているので、これを利用させてもらいました。
EZ-BLE PRoC Module (Bluetooth Smart)
今回の記事のターゲットとなったデバイスです。 無線モジュールとして供給されていますが、 PSoC Creator では、単体のデバイスと同等に扱われます。

参考商品

PSoC 4 BLE

PSoC 4 BLE

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

PSoC 5LP で BULK 転送 (2) [PSoC]このエントリーを含むはてなブックマーク#

回路図

前回の記事では、 USBFS コンポーネントを "Manual" モードで使ってみました。 今回は、 "DMA" モードを使ってみます。 DMA 使ったら、もっと速くなるよね。

コンポーネントの設定

転送モード設定

回路図は、前回と同じなのですが、コンポーネントの設定を一か所だけ変えました。 "Device Descriptor" タブの "Descriptor Root" ノードで "Endpoint Buffer Management" を "DMA with Manual Buffer Management" に設定しています。

"Manual" モードでは、エンドポイントバッファと RAM 上のバッファとのデータ転送を CPU で行っていました。 "DMA" モードでは、この転送に DMA を用います。 CPU の代わりに DMA を使うのだから、当然、早くなるよね。

プログラム

プログラム

DMA を使用すると、 CPU が介在することなく処理が進む時間が生まれます。 このような時間をムダにしないために、プログラムは、 "BULK-IN" と "BULK-OUT" を取り扱う二つのステートマシンとして実装しました。

#include "project.h"

#define     IN_EP               (0x02u)
#define     OUT_EP              (0x01u)
#define     BUFFER_SIZE         (64u)

uint8       buffer_in[BUFFER_SIZE] = "@@ABCDEFGIHJKLMNOPQRSTUVWXYZ";
uint8       buffer_out[BUFFER_SIZE];
uint16      length_out;

冒頭の定数とバッファの定義は、前回と同じです。

#define     ST_ACKWAIT          (1u)
#define     ST_READING          (2u)
#define     ST_GETWAIT          (3u)
#define     ST_DISCARDING       (4u)
#define     ST_PREPARING        (5u)

uint8       state_in;           // State code for BULK-IN
uint8       state_out;          // State code for BULK-OUT

今回のプロジェクトは、ステートマシンを取り入れたので、状態コードと状態変数を定義しています。 ふたつのステートマシンをひとつの状態コードで取り扱う横着な構成になっています。

int main(void) {
    CyGlobalIntEnable;                  // 割り込みの有効化    
    USB_Start(0, USB_5V_OPERATION);     // 動作電圧5VにてUSBFSコンポーネントを初期化

    for (;;) {
        // 初期化終了まで待機
        while (USB_GetConfiguration() == 0);

        USB_IsConfigurationChanged();   // CHANGEフラグを確実にクリアする

デバイスが SET_CONFIGURATION を受けて初期化を行う所までは、前回と同じです。

        // BULK-OUT: OUTエンドポイントでホストからデータを受信する
        state_out = ST_DISCARDING;

        // BULK-IN: 初期状態を決定する
        state_in = ST_PREPARING;

その後、双方のステートマシンの初期状態を決定しています。 前回のプロジェクトでは、 "BULK-OUT" エンドポイントをイネーブルする処理が入っていましたが、この処理もステートマシンの中に取り込んでしまっているので、完全に初期状態を決めるだけになりました。

        for (;;) {
            // 設定が変更されたら、再初期化をおこなう
            if (USB_IsConfigurationChanged()) {
                break;
            }

内側のループで、 SET_CONFIGURATION を検出してループを脱出するところは前回と同じです。

            // BULK-OUT ステートマシン
            switch (state_out) {
                case ST_DISCARDING:
                    // OUTバッファのデータを破棄する
                    USB_EnableOutEP(OUT_EP);
                    state_out = ST_ACKWAIT;
                    break;
                case ST_ACKWAIT:
                    // ホストからのパケットの到着を待つ
                    if (USB_GetEPState(OUT_EP) == USB_OUT_BUFFER_FULL) {
                        state_out = ST_READING;
                    }
                    break;
                case ST_READING:
                    // 受信バイト数を取得する
                    length_out = USB_GetEPCount(OUT_EP);
                    // OUTバッファからデータを取り出す
                    USB_ReadOutEP(OUT_EP, &buffer_out[0], length_out);
                    state_out = ST_GETWAIT;
                    break;
                case ST_GETWAIT:
                    // OUTバッファからの転送を待つ
                    if (USB_GetEPState(OUT_EP) != USB_OUT_BUFFER_FULL) {
                        state_out = ST_DISCARDING;
                    }
                    break;
                default:
                    break;
            }

"BULK-OUT" のステートマシンは、よっつの状態から構成されています。 まず、 ST_DISCARDING では、"BULK-OUT" エンドポイントを有効にして次のパケットを受信できるようにします。 "Manual" モードでは、エンドポイントに届いたデータを USB_ReadOutEP() 関数で引き取るだけで次のパケットを受信できるようになりました。 "DMA" モードでは、明示的に USB_EnableOutEP() を呼んで、次のパケットを受け入れます。

ST_ACKWAIT では、ホストからパケットが到着するのを待ちます。 ステートマシンで構成してあるので、パケットが到着していない場合は到着を待たずに別の処理を行う事が出来るようになっています。

ST_READING では、到着したパケットを buffer_out[] に転送を始めるため USB_ReadOutEP() 関数を呼び出します。 この後、データの転送が DMA で実行されますが、その間、 CPU は別の処理を行う事ができます。

ST_GETWAIT では、 DMA 転送の終了を待ちます。 転送の終了は、 USB_GetEPState() で示されます。 転送が完了したら、次のパケットを待つために状態遷移します。

            // BULK-IN ステートマシン
            switch (state_in) {
                case ST_PREPARING:
                    // 空きバッファにデータを準備する
                    buffer_in[0]++;
                    // INバッファのデータを送信する
                    USB_LoadInEP(IN_EP, &buffer_in[0], BUFFER_SIZE);
                    state_in = ST_ACKWAIT;
                    break;
                case ST_ACKWAIT:
                    // ホストからのデータ受信確認を待つ
                    if (USB_GetEPState(IN_EP) == USB_IN_BUFFER_EMPTY) {
                        state_in = ST_PREPARING;
                    }
                    break;
                default:
                    break;
            }
        }
    }
}

"BULK-IN" もステートマシンで構成されています。 ST_PREPARING では、ホストに送信すべきデータをバッファに準備します。 USB_LoadInEP() によって DMA 転送が開始されて、 buffer_in[] に準備されたデータがエンドポイントに送られます。

ST_ACKWAIT では、ホストからの受信確認を待ちます。 DMA 転送が終わって、ホストから受信確認が到着したら、次のパケットの準備を行います。

転送速度の測定

BULK IN 転送速度

プロジェクトが出来たら、転送速度を測定します。 "BULK-IN" は、 860kB/s でした。 前回の 850kB/s とほとんど変わりませんね。


BULK OUT 転送速度

"BULK-OUT" は、 690kB/s でした。 前回の 700kB/s よりも遅めになっています。

DMA を使っても転送速度が上がらなかったのは、扱っているデータが64バイトと DMA にとっては小さすぎるのが原因と考えられます。 もっと、パケットサイズが大きければ、効果があるのでしょう。


送受信転送速度

"BULK-IN" と "BULK-OUT" を同時に動かしたところ、双方とも 560kB/s になりました。 ステートマシンを使っても代わる代わる転送が行われる状況に変化はないようです。

以上、 "DMA" モードを使った場合の転送速度を測定しましたが、単純にパケットを送受信するだけでは、 "Manual" モードとの差は出ないという事がわかりました。 きっと、演算を行わせながら使うと、全体のスループットに貢献するのでしょう。

プロジェクトアーカイブ

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

関連商品

CY8CKIT-059 PSoC 5LP Prototyping Kit

CY8CKIT-059 PSoC 5LP Prototyping Kit

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

PSoC 5LP で BULK 転送 (1) [PSoC]このエントリーを含むはてなブックマーク#

回路図

PSoC 5LP には、 Full-Speed USB のインターフェイスが搭載されています。 今回からの記事では、 PSoC 5LP でどのくらいの転送速度が実現できるか、実験してみます。

USBFS コンポーネントの設定

CY8CKIT-059 Pioneer kit

実験に使用するハードウェアは、 CY8CKIT-059 PSoC® 5LP Prototyping Kit です。 USB のコネクタも実装されていますから、今回の実験にはピッタリです。

回路は、この記事のタイトルに掲げた通りです。 USBFS コンポーネントを配置して、インスタンス名を "USB" に変更しています。


Descriptor Root ノード

USBFS コンポーネントの設定ダイアログでは、デバイス・ディスクリプタを設定します。 すべて、 "Device Descriptor" タブの中で設定できます。

最初は、 "Descriptor Root" ノードの設定です。 ここには、エンドポイントバッファの取り扱い方法を四つの選択肢から選びます。 まずは、 "Manual (Static Allocation)" を試してみます。 他の方法は、続く記事で取り扱います。


Device Descriptor ノード

次は、 "Device Descriptor" ノードの設定です。 ここで重要なのは、 "Vendor ID" と "Product ID" です。 それぞれ "4B4" と "F1" に設定しています。

この VID/PID は、サイプレスが "Bulk Loop" というデバイスに設定した番号です。 そのため、新たに作成することなくデバイスドライバを使いまわすことができます。 デバイスドライバおよび関連するアプリケーションのインストールは、後ほど説明します。

"Manufacturing String" および "Product String" が設定されていますが、これらは必須ではありません。


Configuration Descriptor ノード

"Configuration Descriptor" ノードでは、 "Max Power (mA)" を 100 に変更しています。 また、 "Device Power" を "Bus Powered" にしています。 この実験の使い方では、消費電流の申告値を大きくする必要は、あまりありません。 また、 "Bus Powered" 申告をしてもしなくても、動作に影響はありません。 "100mA" であれば、一般的なバスパワーの USB ハブでも接続できるでしょう。


Alternate Setting 0 ノード

次は、 "Alternate Setting 0" ノードです。 このノードでは、設定はデフォルトのままですが、ひとつだけやることが有ります。 それは、エンドポイントを追加する事です。

これから作ろうとしているプロジェクトでは、 "BULK-IN" と "BULK-OUT" のエンドポイントをそれぞれひとつ使用します。 ところが、デフォルト状態では、エンドポイントが一つしか定義されていません。 ふたつ目のエンドポイントを追加するためには、 "Add Endpoint" ボタンをクリックします。


Endpoint Descriptor ノード (1)

ひとつめのエンドポイントは、 EP#1 を BULK-OUT で使用します。 パケットの最大サイズは、 Full-Speed 規格上の最大値である 64 とします。


Endpoint Descriptor ノード (2)

ふたつ目のエンドポイントは、 EP#2 を BULK-IN で使用します。 パケットの最大サイズは、 EP#1 と同じく 64 とします。

以上で USBFS コンポーネントの設定は終わりです。


この説明で使われた "BULK-IN" と "BULK-OUT" は、 USB インテーフェイスの用語です。

USB 規格では、ホストとファンクションと言う役割が有ります。 ほとんどの場合、 PC がホストになり、 PSoC 5LP で作るデバイスはファンクション側になります。 "BULK-IN" は、ホストから見た時のデータ方向を表していて、ホストがデータを受信する手順を表します。 同様に "BULK-OUT" はホストがデータを送信する手順を表します。

ファンクションから見るとデータの方向が逆になりますので、注意が必要です。

クロックの設定

クロック設定

USB を使用する上で気を付けなくてはならないのが、クロックの設定です。 USB の規格では、比較的高精度なクロックを要求されています。 ところが、 CY8CKIT-059 にはクリスタルなどの高精度なクロック源は搭載されていません。 こんな場合でも困らないように、 PSoC 5LP の場合、 USB ホストから送信された USB パケットのクロック情報を基準として、内蔵クロックである Internal Main Oscillator (IMO) を微調整し、最終的に ±0.25% の精度のクロックを得る機能があります。

微調整のタイミングをとるために 100kHz の低速クロックが使用されます。 このような事情から低速クロックもこの通りに設定しなくてはなりません。

IMO クロックは、 PLL に導入されて高速なバスクロックを生成しています。 このとき、 PLL の出力周波数を 80MHz に設定すると、 IMO が +0.25% 側に振れたときにバスクロックの最大値である 80.01MHz を超えてしまいます。 ここでは、 IMO が +0.25% の時でも最大値を超えない最大周波数設定ということで、 79.5MHz に設定しています。 これで、 IMO の誤差が大きい時でも安心して使用できます。

プログラム

プログラム

ハードウェアが整ったら、プロジェクトを Build して、ソースコードを記述します。 ソースコードは、すべて "main.c" に記述していきます。

#include "project.h"

#define     IN_EP               (0x02u)
#define     OUT_EP              (0x01u)
#define     BUFFER_SIZE         (64u)

ソースコードの最初の部分で、マクロにより定数を定義しています。 "IN_EP" は、 "BULK-IN" 転送に使用するエンドポイントの番号です。 エンドポイントは、 EP1 から EP8 までの最大8個を使用できます。 同様に、 "OU_EP" で "BULK-OUT" 転送に使用するエンドポイントを表します。

"BUFFER_SIZE" は、エンドポイントバッファのサイズを表します。 コンポーネントの設定で使用した最大サイズである "64" を使用しています。

uint8       buffer_in[BUFFER_SIZE] = "@@ABCDEFGIHJKLMNOPQRSTUVWXYZ";
uint8       buffer_out[BUFFER_SIZE];
uint16      length;

"BULK-IN" 転送では、 "buffer_in" に格納されたデータを少しずつ変化させながらホストに送信します。 一方 "BULK-OUT" 転送では、ホストから受信したデータの長さを "length" に受け取り、データ本体を "buffer_out" に引き取ります。 今回のプロジェクトは転送速度の上限を探ることを目的としています。 そのため、送受信するデータの加工は行われません。

int main(void) {
    CyGlobalIntEnable;                  // 割り込みの有効化    
    USB_Start(0, USB_5V_OPERATION);     // 動作電圧5VにてUSBFSコンポーネントを初期化

"main()" 関数の戦闘で割り込みを許可し、 USB コンポーネントの初期化を行っています。 CY8CKIT-059 評価ボードでは、ホストから供給された VBUS がそのまま PSoC 5LP に供給されます。 そのため、電源電圧が 5V である事を明記して初期化を行っています。

    for (;;) {
        // 初期化終了まで待機
        while (USB_GetConfiguration() == 0);

        USB_IsConfigurationChanged();   // CHANGEフラグを確実にクリアする

        USB_EnableOutEP(OUT_EP);        // OUTエンドポイントでホストからデータを受信する

初期化が終わったら、無限ループに入ります。 この無限ループでは、ホストから SET_CONFIGURATION で USB デバイスとしての初期設定が行われるのを待ちます。 "USB_GetConfiguration()" 関数で、 SET_CONFIGURATION が行われるのを待ちます。

さらに "USB_IsConfigurationChanged()" 関数が呼ばれて返り値が捨てられています。 この関数により、設定が変更された事を示す内部フラグが確実にクリアされます。 一部のオペレーティングシステムでは、 USB デバイスの検出後、二回 SET_CONFIGURATION が発行されます。 この関数呼び出しは、そういった SET_CONFIGURATION が複数回発行される場合に対処するためのものです。

デバイスが初期化された後は、受信が禁止されているため、 "BULK-OUT" データが受信できません。 そのため、最後の "USB_EnableOutEp()" 関数で "BULK-OUT" データの受信を許可しています。

        for (;;) {
            // 設定が変更されたら、再初期化をおこなう
            if (USB_IsConfigurationChanged()) {
                break;
            }

さらに内側にループが構成されています。 このループでは、実際のデータの送受信を行いますが、ホストから SET_CONFIGURATION が発行されたら外側のループに脱出して、初期設定を行います。 SET_CONFIGURATION を "USB_IsConfigurationChenged()" 関数で検出したら break でループを脱出します。

            // BULK-OUT : データが受信されたか調べる
            if (USB_GetEPState(OUT_EP) & USB_OUT_BUFFER_FULL) {
                // 受信バイト数を取得する
                length = USB_GetEPCount(OUT_EP);

                // OUTバッファからデータを取り出す
                USB_ReadOutEP(OUT_EP, &buffer_out[0], length);
            }

次に "BULK-OUT" データの受信を行います。 "BULK-OUT" からデータを受信すると、 "USB_GetState()" 関数で返される USB_OUT_BUFFER_FULL フラグがセットされます。 フラグがセットされているのを検出したら、受信したデータの長さを "USB_GetEPCount()" 関数から受け取り、その長さ分のデータを "USB_ReadOutEP()" 関数で受け取ります。 受け取ったデータは "buffer_out[]" に格納されますが、格納されたデータは使用されず破棄されます。 以上でデータの受信は終わりです。

もし、データをバッファに受け取る前に次の "BULK-OUT" パケットが届いてしまったら、 PSoC 5LP の USB インターフェイスハードウェアは自動的に NAK を返し、受信できない事をホストに示します。 NAK を受けたホストは、次の機会を待って、ふたたび "BULK-OUT" パケットを送信し、データが受信できた事を示す ACK をファンクションが返すまでデータを送り続けます。

このような動作を行っているので、 "BULK-OUT" 転送ではホストから送信されたデータが確実にファンクションに届くことが保証できます。

            // BULK-IN : 送信バッファが空いたか調べる
            if (USB_GetEPState(IN_EP) & USB_IN_BUFFER_EMPTY) {
                // INバッファにデータを送り込む
                buffer_in[0]++;
                USB_LoadInEP(IN_EP, &buffer_in[0], BUFFER_SIZE);
            }
        }
    }
}

最後は、 "BULK-IN" 転送です。 "BULK-IN" からデータを送信できるようになると、 "USB_GetState()" 関数で返される USB_IN_BUFFER_EMPTY フラグがセットされます。 フラグがセットされているのを検出したら、 "buffer_in[]" のデータを準備し "USB_LoadInEP()" 関数でデータを送信します。 ホスト側が受信したパケットをそれぞれ区別できるように、送信されるデータの最初の1バイトの値を変化させています。

準備されたデータは、ホストから要求が来たらホストに送信されます。 ファンクションのデータは、ホストが受信した事を示す ACK が来るまで保存されます。 このため、 "BULK-IN" のデータもホストに確実に届くことが保証されます。

ソースコードは以上です。 PSoC 5LP にプログラムしたら、 USB デバイスの出来上がりです。

転送速度の測定

デバイスが出来上がったら、転送速度を測定してみます。 PSoC 向けに測定を行うツールは、ありませんが、 EZ-USB FX3 向けに作られたツールならあります。 今回は、ありがたく、このツールを利用させてもらいます。

ツールは、 Cypress USB Suite というソフトウェア群に含まれており、 EZ-USB FX3 Software Development Kit からダウンロードできます。 インストールするには、 "EZ-USB FX3 SDK" インストーラをダウンロードして "" をまるごとインストールするか、 "USB Suite Zip" をダウンロードして使う方法があります。 ハードディスクに余裕が有れば、インストーラをご利用ください。

プログラムした PSoC 5LP の USB コネクタを PC に接続すると、デバイスドライバがインストールされます。 もし、自動的にインストールされない場合には、ダウンロードまたはインストールしたファイルからデバイスドライバを探してインストールしてください。


Streamer

転送速度を測定するツールは、 "Streamer" と呼ばれています。 ツールを起動すると、 "Connected Device" に "Cypress FX3 USB Streamer Example" というデバイスが見えます。 これが、さきほどプログラムした "PSoC 5LP" デバイスです。 "Endpoint" で "BULK IN" または "BULK OUT" を選び、 "Start" ボタンをクリックすると転送が始まり転送速度が表示されます。


BULK IN 転送速度

"BULK-IN" を選んだ場合、私の PC では 850kB/s (毎秒850kバイト)と表示されました。 Full-Speed のバス速度が 12Mb/s ですから妥当な値でしょう。


BULK OUT 転送速度

"BULK-OUT" の転送速度は 700kB/s と表示されました。


送受信転送速度

Streamer アプリケーションをふたつ開いて、 "BULK-IN" と "BULK-OUT" を同時に動かす事も出来ます。 この場合、 "BULK-IN" も "BULK-OUT" も転送速度が 560kB/s になりました。 ソースコードの構成から、転送速度が速い状態では、 "BULK-IN" と "BULK-OUT" が代わる代わる処理されます。 そのため、双方の転送速度が等しくなったと考えられます。

プロジェクトアーカイブ

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

関連商品

CY8CKIT-059 PSoC 5LP Prototyping Kit

CY8CKIT-059 PSoC 5LP Prototyping Kit

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

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


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

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

前の8件 | -