SPIRIT1トランシーバ入門編

はじめに

STMicroelectronicsのSPIRIT1サブGHzトランシーバは、ワイヤレスセンサネットワーク、ホームオートメーション、検針など、多くのワイヤレスアプリケーションに対応する低消費電力でコスト効率の高い製品です。1GHz以下の幅広い周波数帯域で動作するように設計されており、2-FSK、GFSK、MSK、GMSK、OOK、およびASK変調方式をサポートしています。受信時(約9mA)、送信時(+11dBmで約21mA)ともに消費電力は非常に小さくなっています。FIFOとレジスタの内容を保持する最低電力モードでは、消費電流はわずか600nAです。SPIRIT1は、AES 128ビット暗号化コプロセッサ、自動CRC処理、CSMA/CSプロトコル内蔵など、システム全体のコストをさらに削減する機能を備えています。

SPIRIT1トランシーバをベースとしたRFモジュールと評価プラットフォームは、無線接続を必要とする製品の開発および製造を簡素化するために利用できます。SPSGRFモジュールは、SPIRIT1トランシーバ、チップアンテナ、フィルタ、バランを含み、すべて小型で使いやすいフォームファクタになっています。このモジュールは、868MHzと915MHzのいずれかの周波数帯に対応しています。SPIRIT1のデジタルインターフェースピン(GPIOピン、SPIピン、シャットダウンピン)は全て外部に出ており、アナログ回路(電源、水晶、アンテナ接続)は適切なコンポーネントに配線されています。

このチュートリアルでは、2つのSPSGRFモジュールを使用して、ポイントツーポイントの通信ネットワークを確立するアプリケーションを作成します。最初のステップは、STM32Cubeプロジェクトを作成し、モジュールと対話するためにMCUペリフェラルを初期化することです。その後、X-CUBE-SUBG1ドライバをプロジェクトに追加します。そのドライバは、豊富なAPIを開発者に公開しています。このAPIを使用して、モジュールは初期化され、通信が確立されます。最後に、単純なメッセージを含むパケットを一方のノードが送信し、他方のノードが受信します。

必要な部品・資料

このチュートリアルの指示どおりに進めるには、以下のハードウェアとソフトウェアを利用する必要があります。ただし、ターゲットのSTM32が異なるNucleoボード、異なるSPIRIT1モジュールによる拡張ボード、あるいはサードパーティ製IDEの使用の場合でも、以下の手順を少し変更することで代替が可能です。

1. STM32CubeIDEプロジェクトを作成する

このチュートリアルでは、STM32ラインのペリフェラル設定、コード生成、コードコンパイル、およびデバッグ機能を提供するオールインワンツールのSTM32CubeIDEを使用します。なお、他の環境(Keil、IAR、Atollicなど)が望ましい場合は、STM32CubeMXコンフィギュレータをスタンドアロンツールとしてインストールすると、サードパーティ製ツールチェーン/IDEにプロジェクトをエクスポートすることができます。いずれの場合も、同じグラフィカルコンフィギュレータを使用して、必要なペリフェラルをシンプルかつポータブルに初期化することができます。上記と異なるハードウェア構成を使用する場合でも、以下の手順を参考に、簡単にカスタム構成を作成することができます。

1.1 新規にSTM32CubeIDEプロジェクトを開始する

最初のステップは、ターゲットハードウェア用の新しいプロジェクトを作成することです。このチュートリアルでは、NUCLEO-L152RE 評価ボードを使用しています。

a. STM32CubeIDEを開き、目的のワークスペースを選択select(または作成create)します。次に、
File > New > STM32 Projectを選択します。

b. Target Selectionウィンドウが表示されます。 MCU/MPU SelectorまたはBoard Selector タブのいずれかを使用して、希望するSTM32ターゲットを選択します。図1は、 Board Selectorタブのオプションで、左側のMCU/MPU Seriesフィルタを利用して、STM32L1シリーズに基づくボードのみを表示したものです。これにより、ボードリストでNUCLEO-L152REボードを簡単に見つけることができます。ボードを選択し、Nextをクリックします。

図1: プロジェクトのターゲットとなるSTM32デバイスを選択する

c. プロジェクトの名前(例えば、図2のように「spirit1_helloWorld」)を指定します。Finishをクリックします。ウィンドウに「Initialize all peripherals with their default Mode?」というプロンプトが表示されます。Yesを選択します。

図2: STM32CubeIDEプロジェクトのオプションを設定する

1.2 ターゲットMCUを構成する

新しいプロジェクトをワークスペースに追加すると、STM32CubeMXの設定ファイル(.ioc)が自動的にDevice Configuration Toolのパースペクティブで開かれます。この時点で、ターゲットMCUは
X-NUCLEO-IDS01A5シールドとのインターフェースとして簡単に設定することができます。

a. まず、Pinout ViewでPB3ピンをクリックし、リストからReset_Stateを選択します(図3のステップ1~3)。

b. 次に、ウィンドウ左側のコンポーネントリストで、SPI1ペリフェラルを選択し、ドロップダウンでModeオプションをDisableからFull-Duplex Masterに変更します。次に、Configurationパネルで、Prescalerパラメータを4に変更します。これは、図3のステップ4~6で示されています。

図3: デバイスコンフィギュレータを使用してSPIペリフェラルを有効にする

c. Pinout Viewで、ピンPB6PA10をクリックし、リストからGPIO_Outputを選択して、GPIO出力として設定します。PC7ピンをクリックし、GPIO_EXTI7を選択して、外部割り込みとして設定します。その結果は、図4のようになります。

図4: SPIRIT1とのインターフェースに使用される残りのGPIOピンを有効にする

d. PinoutメニューからPins/Signals Options…を選択します(図5)。ポップアップした表で、左側の列の空のボックスをクリックし、すべての信号がピン止めされていることを確認します。また、表1に示すように、GPIOピンにユーザーラベルを付けます。

図5: Pins/Signals Options… ウィンドウを開く

表1: X-NUCLEO-IDS01A5シールドGPIOピンのユーザーラベル

PIN User Label
PB6 SPIRIT1_SPI_CSn
PA10 SPIRIT1_SDN
PC7 SPIRIT1_GPIO3

図6に示すような構成が表示されます。OKをクリックします。

1_userLabels

図6: ターゲットデバイスのすべてのイネーブルピンを設定する

e. コンポーネントリストで、GPIOペリフェラルを選択します。次に、ConfigurationパネルのGPIOタブで、ピンPC7に対応する表の行を選択します。ドロップダウンを使用して、GPIOモードをExternal Interrupt Mode with Falling edge trigger detection(立ち下がりエッジトリガ検出付き外部割り込みモード)に変更します。ガイドとして図7を参照してください。

図7: 立ち下がりエッジでトリガする外部割り込みを設定する

f. コンポーネントリストで、NVICペリフェラルを選択します。次に、ConfigurationパネルのNVICタブで、図8に示すように、EXTI line[9:5] interruptsを有効にするボックスをチェックします。

図8: Nested Vectored Interrupt Controller(NVIC)の外部割り込みラインを有効化する

g. Project Managerビューで、Code Generatorタブを選択し、Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheralの左のボックスにチェックを入れます(図9)。

図9: コード構成の設定を変更して、ペリフェラルごとに.c/.hファイルのペアを生成する

h. 最後に、.iocファイルを保存して、プロジェクトのコードを生成します。

2. SPIRIT1ドライバを追加する

SPIRIT1ドライバをプロジェクトに追加する基本的な手順は、1)ファイルをプロジェクトにコピーまたはリンクする、2)MCU_Interface.hで宣言された関数の定義を作成する、3)新しいファイルをプロジェクトのインクルードパスに追加する、です。この後の説明は、この3つの基本ステップの1つの実装に過ぎません。シンプルでわかりやすく、すぐに実行できるように設計されています。しかし、最終的なアプリケーションの要件や個人的な好みに基づいて、これらを変更することはもちろん可能です。

a. 図10に示すように、Project Explorerで、現在のプロジェクトのDriversディレクトリを右クリックし、Import…を選択します。

図10: Importウィザードのセレクタを開き、Driversディレクトリにリソースを追加する

b. GeneralカテゴリのFile Systemインポートウィザードを選択します(図11参照)。Nextをクリックします。

図11: File Systemインポートウィザードを選択する

c. From Directory: フィールドに、展開したX-CUBE-SUBG1 ドライバの場所を入力します。図12に示すように、パスは以下のようなものになります。

<extraction_location>/en.x-cube-subg1_firmware/STM32CubeExpansion_SUBG1_V3.3.0/Drivers 

ファイルエクスプローラウィンドウで、SPIRIT1_Libraryディレクトリに移動し、該当するボックスにチェックをいれますFinishをクリックします。

図12: ソースのディレクトリ構造を維持したまま、SPIRIT1ライブラリファイルを
      Driversディレクトリにコピーする(つまり親ディレクトリを作成する)

d. プロジェクトエクスプローラを使用して、Drivers/BSP/Components/spirit1/SPIRIT1_Library/Inc/MCU_Interface.hファイルに移動し開きます。115行目から117行目まで、以下のように修正します。

void RadioEnterShutdown(void); //void EnterShutdown(void);
void RadioExitShutdown(void); //void ExitShutdown(void);
SpiritFlagStatus RadioCheckShutdown(void); //SpiritFlagStatus CheckShutdown(void);

e. Drivers/BSP/Components/spirit1ディレクトリに、radio_target.hradio_target.cという2つの新しいファイルを作成します。これらのファイルには、MCU_Interface.hファイルで宣言された関数の実装が含まれます。動作例として、これらのradio_target.hradio_target.cファイルの内容を、今作成したファイルにコピーしてください。

注:繰り返しになりますが、私とは異なる方法で実装を構成することができます。ファイルはプロジェクト内の別の場所に置いたり、別の名前をつけたり、いくつかのファイルに分けたりすることができます。これは、作業を開始するのに役立つ簡単な例です。

f. 最後に、ドライバファイルをプロジェクトのインクルードパスに追加する必要があります。プロジェクトエクスプローラで、図13に示すように、<project_name>/Drivers/BSP/Components/spirit1/SPIRIT1_Library/Incフォルダを右クリックして、Add/remove include path…を選択するだけです。ポップアップウィンドウで、DebugReleaseの両方の構成にチェックが入っていることを確認し、OKをクリックします。<project_name>/Drivers/BSP/Components/spirit1フォルダ(radio_target.hが含まれているため)についても同様の処理を繰り返します。

図13: SPIRIT1ライブラリをプロジェクトのインクルードパスに追加する

コードで SPIRIT1 API 関数を利用するには、ソース ファイルにヘッダファイルSPIRIT_Config.hを含めるだけです。

3. SPIRIT1を初期化する

ドライバをプロジェクトに追加すると、SPIRIT1 APIを使用してトランシーバを初期化することができます。この例では SPSGRF-915モジュールを使用しているため、すべての初期化コードはspsgrf.hspsgrf.cという 2 つのファイルにあります。このセクションで使用する定数はすべてspsgrf.hで定義されており、そのうちのいくつかの値(XTAL_FREQUENCYなど)はSPSGRFデータシート.で指定されています。言うまでもなく、異なるハードウェアコンポーネントを使用する場合は、これらのファイルを適切に変更する必要があります。

SPIRIT1ライブラリのユーザーマニュアルの1.23項に、SPIRIT1デバイスの設定に必要な手順が記載されています。以下に、これらの手順と、アプリケーションの例から得られたいくつかの他の手順を説明します。

3.1 トランシーバをリセットする

既知の状態から始めるために、初期化プロセスを開始する前にSPIRIT1をリセットする必要があります。これは、シャットダウンピン(SDN)をトグルすることで簡単に行えます。このピンをロジックハイにすると、SPIRIT1は完全にシャットダウンされ、レジスタの内容は失われます。 SpiritEnterShutdown()関数(その定義はステップ2dのradio_target.cファイルで提供されています)がこれを実現します。逆に、SpiritExitShutdown()関数は、ピンをデアサートしてSPIRIT1をREADY状態にします。このコードは以下のリスト1に示されています。また、 エラッタの1.2項に記載されているデバイスの制限に対応するSpiritManagementWaExtraCurrent()関数を使用していることに注意してください。

リスト1: SHUTDOWN状態を遷移させることによりSPIRIT1をリセットし、READY状態になるのを待つ

// restart the radio
SpiritEnterShutdown();
SpiritExitShutdown();
SpiritManagementWaExtraCurrent(); // To be called at the SHUTDOWN exit. It avoids extra current consumption at SLEEP and STANDBY.

// wait for the radio to enter the ready state
do
{
  for (volatile uint8_t i = 0; i != 0xFF; i++); // delay for state transition
  SpiritRefreshStatus(); // reads the MC_STATUS register
} while (g_xStatus.MC_STATE != MC_STATE_READY);

3.2 無線部の構成

モジュールがリセットされると、まずベース周波数、データレート、変調などの基本的な無線パラメータを設定します。これを行うには、単にSRadioInit構造体を宣言し、必要な設定パラメータをそれに合わせます。そして、リスト2に示すように、SpiritRadioInit()関数の引数として、その構造体へのポインタを渡します。ただし、SpiritRadioSetXtalFrequency()関数を最初に呼び出さないと、期待通りの構成にならないことに注意してください。

この時点で、送信電力も設定することができます。SPIRIT1には8つのレジスタのバンクがあり、そこに異なる電力レベルを保存することができます。ただし、ASK変調を使用しない場合は、1つの電力レベルのみを設定する必要があります。まず、SpiritRadioSetPALeveldBm()関数を使用して、バンク内の特定のレジスタで希望の電力レベルを指定します。次に、SpiritRadioSetPALevelMaxIndex()関数が呼ばれ、どのレジスタから電力レベルを取得するかをトランシーバに伝えます。

リスト2: 無線部のRFパラメータを設定し、希望の電力レベルを設定する

SRadioInit xRadioInit;

// Initialize radio RF parameters
xRadioInit.nXtalOffsetPpm = XTAL_OFFSET_PPM;
xRadioInit.lFrequencyBase = BASE_FREQUENCY;
xRadioInit.nChannelSpace = CHANNEL_SPACE;
xRadioInit.cChannelNumber = CHANNEL_NUMBER;
xRadioInit.xModulationSelect = MODULATION_SELECT;
xRadioInit.lDatarate = DATARATE;
xRadioInit.lFreqDev = FREQ_DEVIATION;
xRadioInit.lBandwidth = BANDWIDTH;
SpiritRadioSetXtalFrequency(XTAL_FREQUENCY); // Must be called before SpiritRadioInit()
SpiritRadioInit(&xRadioInit);

// Set the transmitter power level
SpiritRadioSetPALeveldBm(POWER_INDEX, POWER_DBM);
SpiritRadioSetPALevelMaxIndex(POWER_INDEX);

3.3 パケットの構成

SPIRIT1では、幅広いアプリケーションに対応するため、3つの設定可能なパケット構造を用意しています。これらは、Basic、STack、およびWM-BUSのパケットフォーマットです。これらの構造の詳細、相違点、および使用可能な構成オプションについては、SPIRIT1データシートの9.7項を参照してください。クイックリファレンスとして、以下の図14-16を参照してください。

図14: Basicパケットの構造

図15: STackパケットの構造

図16: WM-BUSパケットの構造


どのフォーマットを使用するかの選択は、対応するAPI構造体を宣言して適合させ、適切な初期化関数に引数として渡すことによって行われます。3つのパケットフォーマットのそれぞれについて、これらの構造体と関数の名称を以下の表2に示します。

表2: 3つのパケットフォーマットそれぞれに使用しなければならない初期化構造と初期化関数

Packet Type Initialization Structures Initialization Functions
Basic * PktBasicInit
  • PktBasicAddressInit|* SpiritPktBasicInit()
  • SpiritPktBasicAddressInit()|
    |STack|* PktStackInit
  • PktStackAddressInit
  • PktStackLlpInit|* SpiritPktStackInit()
  • SpiritPktStackAddressesInit()
  • SpiritPktStackLlpInit()|
    |WM-BUS|* PktMbusInit|* SpiritPktMbusInit()|

以下のコード例では、basicのパケット形式を利用するようにデバイスを構成しています。また、デバイスにアドレス(例:「MY_ADDRESS」)を割り当てるとともに、マルチキャストおよびブロードキャストアドレスを割り当てています。パケットフィルタリングは、各アドレスに対して有効です。つまり、パケットを受信し、その宛先アドレスがマイアドレス、マルチキャストアドレス、あるいはブロードキャストアドレスのいずれかの値と一致しない場合、そのパケットは破棄されることになります。

リスト3: SpiritPktBasicInit()およびSpiritPktBasicAddressInit()関数を呼び出して、SPIRIT1 がBasicパケット形式を使用するように設定する

PktBasicInit xBasicInit;
PktBasicAddressesInit xBasicAddress;
  
// Configure packet handler to use the Basic packet format
xBasicInit.xPreambleLength = PREAMBLE_LENGTH;
xBasicInit.xSyncLength = SYNC_LENGTH;
xBasicInit.lSyncWords = SYNC_WORD;
xBasicInit.xFixVarLength = LENGTH_TYPE;
xBasicInit.cPktLengthWidth = LENGTH_WIDTH;
xBasicInit.xCrcMode = CRC_MODE;
xBasicInit.xControlLength = CONTROL_LENGTH;
xBasicInit.xAddressField = EN_ADDRESS;
xBasicInit.xFec = EN_FEC;
xBasicInit.xDataWhitening = EN_WHITENING;
SpiritPktBasicInit(&xBasicInit);

// Configure destination address criteria for automatic packet filtering
xBasicAddress.xFilterOnMyAddress = EN_FILT_MY_ADDRESS;
xBasicAddress.cMyAddress = MY_ADDRESS;
xBasicAddress.xFilterOnMulticastAddress = EN_FILT_MULTICAST_ADDRESS;
xBasicAddress.cMulticastAddress = MULTICAST_ADDRESS;
xBasicAddress.xFilterOnBroadcastAddress = EN_FILT_BROADCAST_ADDRESS;
xBasicAddress.cBroadcastAddress = BROADCAST_ADDRESS;
SpiritPktBasicAddressesInit(&xBasicAddress);

3.4 GPIOとIRQの構成

SPIRIT1には4つのGPIOピンがあり、各ピンはいくつかの機能の1つとして設定することが可能です。割り込み要求、電池低下検出、およびウェイクアップタイマ終了信号などが含まれます。各ピンはロジックハイまたはローに設定することもでき、MCUの追加GPIOをエミュレートすることができます。利用可能な機能の完全なリストについては、SPIRIT1データシートの表37と表38を参照してください。リスト4のコード例では、SGpioInit構造体を当てはめてSpiritGpioInit()関数に渡すことで、GPIO3が割り込み要求信号として設定されます。

割り込み要求を発生させることができるイベントは数多く存在します。完全なリストについては、SPIRIT1データシートの表36を参照してください。リスト4では、TX data sentRX data readyRX data discarded、およびRX operation timeoutの各イベントが有効になっています。ここで、これらのイベントのいずれかが発生すると、SPIRIT1 GPIO3ピンの信号はロジックハイからロジックローに切り替わります。なお、MCUのPC7ピンは、これが発生したときに外部割り込みをトリガするように設定されています(上記1.2項)。これらのイベントを処理する例として、次項のリスト10を参照ください。

リスト4: TX/RXオペレーションが完了し、RXデータがフィルタリングされて破棄され、RXオペレーションがタイムアウトしたときに、GPIO3が割り込み信号を送信するように設定する

SGpioInit xGpioInit;

// Configure GPIO3 as interrupt request pin (active low)
xGpioInit.xSpiritGpioPin = SPIRIT_GPIO_3;
xGpioInit.xSpiritGpioMode = SPIRIT_GPIO_MODE_DIGITAL_OUTPUT_LP;
xGpioInit.xSpiritGpioIO = SPIRIT_GPIO_DIG_OUT_IRQ;
SpiritGpioInit(&xGpioInit);

// Generate an interrupt request for the following IRQs
SpiritIrqDeInit(NULL);
SpiritIrq(TX_DATA_SENT, S_ENABLE);
SpiritIrq(RX_DATA_READY, S_ENABLE);
SpiritIrq(RX_DATA_DISC, S_ENABLE);
SpiritIrq(RX_TIMEOUT, S_ENABLE);
SpiritIrqClearStatus();

3.5 受信機品質指標の構成

信号の受信中および受信後、SPIRIT1は3つの品質インジケータに基づき動作します。それらは、受信信号強度インジケータ(RSSI)、プリアンブル品質インジケータ(PQI)、同期品質インジケータ(SQI)です。PQI値とSQI値はいずれも、受信したプリアンブルと同期バイトの「正しさ」にそれぞれ対応します(図14~16参照)。PQIチェックまたはSQIチェックは、インジケータが設定された閾値を下回った場合、パケット復調を中止することが可能です。SPIRIT1データシートの9.10.4項に「SQIチェックを常に有効にすることを推奨する」とあるため、リスト5のサンプルコードでは、まさにそのようにしています。SQI閾値を0に設定することで、期待される同期バイトと受信した同期バイトが完全に一致することが要求されます。

RSSI測定により、CS(Carrier Sense)機能で信号が受信されているかどうかを検出することができます。CS信号は、RSSI値が事前定義された閾値(デフォルトでは -112 dBm)を超えるとアサートされ、信号の復調の開始、RXタイマの停止、またCSMA アルゴリズムに使用されます。リスト5に示すように、SpiritQiSetRssiThresholddBM()関数を使用して、この閾値を上げたり下げたりすることができます。

リスト5: RSSI/SQIの閾値を設定し、受信した同期フィールドが破損している場合にパケット復調を中止するSQIチェックを有効にする

// Enable the synchronization quality indicator check (perfect match required)
// NOTE: 9.10.4: "It is recommended to always enable the SQI check."
SpiritQiSetSqiThreshold(SQI_TH_0);
SpiritQiSqiCheck(S_ENABLE);

// Set the RSSI Threshold for Carrier Sense (9.10.2)
// NOTE: CS_MODE = 0 at reset
SpiritQiSetRssiThresholddBm(RSSI_THRESHOLD);

3.6 タイマの構成

SPIRIT1トランシーバで消費されるエネルギーを最小限に抑えるために、タイムアウト時間が経過すると自動的に受信を中止するようにタイマを設定することができます。このタイムアウト時間はSpiritTimerSetRxTimoutMs()関数で設定可能です。また、SET_INFINITE_RX_TIMEOUT()マクロを使用してタイムアウトを無効にすることができ、この場合、パケットを受信するかSABORTコマンドストローブが発行されたときに受信が停止されます。リスト6では、RECEIVE_TIMEOUTがユーザーによって定義されているかどうかによって、タイムアウトが2秒に設定されるか、完全に無効化されます。

有効なパケットの受信中にタイムアウトが発生するのを防ぐために、受信品質指標(上記参照)の1つ以上が対応する閾値を超えたことを条件に、RXタイマを停止することができます。リスト6では、SQIがリスト5で上記定義した閾値を超えた場合に、RXタイマを停止しています。

リスト6: RXのタイムアウトを2秒または無限大に条件設定し、タイマの停止条件も設定する

// Configure the RX timeout
#ifdef RECEIVE_TIMEOUT
SpiritTimerSetRxTimeoutMs(2000.0);
#else
SET_INFINITE_RX_TIMEOUT();
#endif /* RECIEVE_TIMEOUT */
SpiritTimerSetRxTimeoutStopCondition(SQI_ABOVE_THRESHOLD);

4. シンプルなアプリケーション

SPIRIT1によるパケットの送受信を実証するために、この項では極めてシンプルな単方向通信の例をご紹介します。これは、あるノードが定期的に短いメッセージを送信し、別のノードがそれを受信してシリアルコンソールに出力するものです。より高度な双方向通信の例については、X-CUBE-SUBG1ファームウェアパッケージに含まれているポイントツーポイントの例を参照してください(en.x-cube-subg1_firmware/STM32CubeExpansion_SUBG1_V3.3.0/Projects/STM32L152RE-NUCLEO/Examples/SPIRIT1/P2P_demoディレクトリにあります)。

4.1 ヘルパー関数

メインアプリケーションの移植性を高めるため、SPIRIT1 APIの関数呼び出しはいくつかのヘルパー関数にまとめられています。

SPSGRF_StartTx()

リスト7に示すこの関数は、引数として与えられたデータバッファを使用して送信動作を開始します。まずTX FIFOをフラッシュして予期せぬデータの送信を回避し、TX FIFOの長さを超えないようにバッファの長さに上限を設定します。その後、FIFOにTXバッファの内容をロードし、ペイロード長を設定します。最後にTXコマンドを送信して送信を開始します。

リスト7: SPSGRF_StartTx()の定義

void SPSGRF_StartTx(uint8_t *txBuff, uint8_t txLen)
{
  // flush the TX FIFO
  SpiritCmdStrobeFlushTxFifo();

  // Avoid TX FIFO overflow
  txLen = (txLen > MAX_BUFFER_LEN ? txLen : MAX_BUFFER_LEN);
  
  // start TX operation
  SpiritSpiWriteLinearFifo(txLen, txBuff);
  SpiritPktBasicSetPayloadLength(txLen);
  SpiritCmdStrobeTx();
}

この関数は、送信操作の完了を待たずに戻ってくることに注意してください。その責任は、呼び出し側のモジュールに委ねられています。操作が完了したことを判断する方法はたくさんありますが、最も一般的なのは、TX data sent割り込みイベントを有効にすることです(上記リスト4で示したように)。

SPSGRF_StartRx()

受信を開始するには、次のようにRXコマンドを送信するだけで可能です。上記の関数と同様に、この関数は操作が完了するまでブロックしません。 呼び出し側のモジュールは、いくつかの方法を用いてデータが受信されたかどうかを判断できますが、最も簡単なのは、RX data ready割り込み要求を構成することです(上記のリスト4で示されているように)。

リスト8: SPSGRF_StartRx()の定義

void SPSGRF_StartRx(void)
{
  SpiritCmdStrobeRx();
}

SPSGRF_GetRxData()

SPIRIT1 が受信したデータは、RX FIFOから読み取る必要があります。この関数は、まずFIFOに現在格納されているバイト数を取得し、そのバイト数をFIFOから読み出すことでこれを行います。データは、引数として関数に供給されたRXバッファに格納されます。

リスト9: SPSGRF_GetRxData()の定義

uint8_t SPSGRF_GetRxData(uint8_t *rxBuff)
{
  uint8_t len;

  len = SpiritLinearFifoReadNumElementsRxFifo();
  SpiritSpiReadLinearFifo(len, rxBuff);

  return len;
}

4.2 割り込みハンドラ

リスト4では、SPIRIT1のGPIO3が、指定された4つのイベントのいずれかが発生した場合に、割り込み信号を生成する(すなわち、ロジックハイからロジックローにトグルする)ように構成されています。1.2項では、GPIO3に接続されたMCUピンを、立ち下がりエッジトリガの外部割り込みラインとして設定しました。したがって、SPIRIT1のGPIO3がローになると、MCU上でHAL_GPIO_EXTI_Callback() 関数が実行されることになります。今回のアプリケーションでは、以下のリスト10に示すようにコールバックを定義しています。GPIO3が割り込みのソースであることを確認した後、SPIRIT1デバイスのステータスレジスタを読み、割り込み要求のトリガとなったイベントを特定します。TX data sentイベントとRX data sentイベントのいずれかであった場合、それぞれのフラグが設定されます。RX data discardedイベントまたはRX timeoutイベントであった場合、RXコマンドを再発行し、SPIRIT1を受信状態に戻します。

リスト10: HAL_GPIO_EXTI_Callback()の定義

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  SpiritIrqs xIrqStatus;

  if (GPIO_Pin != SPIRIT1_GPIO3_Pin)
  {
    return;
  }

  SpiritIrqGetStatus(&xIrqStatus);
  if (xIrqStatus.IRQ_TX_DATA_SENT)
  {
    xTxDoneFlag = S_SET;
  }
  if (xIrqStatus.IRQ_RX_DATA_READY)
  {
    xRxDoneFlag = S_SET;
  }
  if (xIrqStatus.IRQ_RX_DATA_DISC || xIrqStatus.IRQ_RX_TIMEOUT)
  {
    SpiritCmdStrobeRx();
  }
}

4.3 アプリケーション

このシンプルなアプリケーションは、main.cファイル内にすべて含まれています。APPLICATION_TRANSMITTERマクロのおかげで、送信ノードと受信ノードの両方で同じプロジェクトが使用されています。送信ノードをプログラムするには、プロジェクトをビルドしてバイナリファイルを生成する前に、このマクロ定義のコメントを解除するだけで可能です。受信機については、定義のコメントアウト、再ビルドして、受信機ノードのプログラミングを行います。

無限ループに入る前の設定コードは両ノードで同じです。送受信が完了したことを示す2つのフラグがグローバルに定義されています。データバッファはローカルに定義され、MCUクロックとペリフェラルは初期化されます。最後に、SPSGRFモジュールのSPIRIT1デバイスを3項で説明したように初期化します。初期化後、SpiritPktBasicSetDestinationAddress()関数を使用して宛先アドレスを設定します。このシンプルな例では、送信側と受信側の両方が同じアドレスを持っており、宛先アドレスが一致している必要があることに注意してください。

メインループでは、2つのノードに対して異なるプロセスが定義されています。送信機は、SPIRIT1にデータバッファの内容を受信機に送信するよう指示する前に、TX doneフラグをFALSEに設定します。その後、TX doneフラグ(コールバック関数でTRUEに設定される)をポーリングして、送信の完了を待ちます。完了すると、シリアルコンソールに情報メッセージが出力され、2秒間の遅延が開始されます。

同様に、受信機はSPIRIT1に受信状態を指示する前に、RX doneフラグをFALSEに設定することから始めます。その後、送信機からデータを受信するとコールバック関数がRX doneフラグをTRUEに設定するため、継続してポーリングします。データを受信すると、SPIRIT1デバイスからデータを読み出し、シリアルコンソールに出力します。

main.cファイルの簡略版(余計なコメントや関数定義がない)をリスト11に示します。完全なファイルは、ここで見ることができます。

リスト11: main.cのアプリケーション専用の内容

#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"
#include <stdio.h>
#include "spsgrf.h"

#define APPLICATION_TRANSMITTER // comment out if programming the receiver

volatile SpiritFlagStatus xTxDoneFlag;
volatile SpiritFlagStatus xRxDoneFlag;

int main(void)
{
  char payload[20] = "Hello World!\r\n";
  uint8_t rxLen;
  
  HAL_Init();
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_SPI1_Init();

  SPSGRF_Init();
  SpiritPktBasicSetDestinationAddress(0x44);

  while (1)
  {
#ifdef APPLICATION_TRANSMITTER
    // Send the payload
    xTxDoneFlag = S_RESET;
    SPSGRF_StartTx(payload, strlen(payload));
    while(!xTxDoneFlag);

    HAL_UART_Transmit(&huart2, "Payload Sent\r\n", 14, HAL_MAX_DELAY);

    HAL_Delay(2000); // Block for 2000 ms
#else
    xRxDoneFlag = S_RESET;
    SPSGRF_StartRx();
    while (!xRxDoneFlag);

    rxLen = SPSGRF_GetRxData(payload);
    HAL_UART_Transmit(&huart2, "Received: ", 10, HAL_MAX_DELAY);
    HAL_UART_Transmit(&huart2, payload, rxLen, HAL_MAX_DELAY);
#endif // APPLICATION_TRANSMITTER
  }
}

まとめ

このチュートリアルの目的は、SPIRIT1 APIをSTM32ベースのアプリケーションで使用できるようにする方法を示すとともに、その基本的な使用例を紹介することです。構築するプロジェクトがない方のために、新しいSTM32CubeIDEプロジェクトを立ち上げ、SPIRIT1とのインターフェースに必要なペリフェラルを設定/初期化する方法を説明しています。そこから、ドライバファイルをプロジェクトにコピーし、あらかじめ定義されたインターフェースの実装を行い、新しいリソースをインクルードパスに追加することで、SPIRIT1ライブラリが組み込まれました。APIにアクセスできる状態で、SPIRIT1はアプリケーション用に適切に初期化され、ヘルパー関数と外部割り込みハンドラを使用して単方向通信が確立されました。その結果、さまざまなユースケースに簡単に拡張できる、シンプルで骨太なサンプルアプリケーションが完成しました。




オリジナル・ソース(English)