はじめに
Wio Terminal向けにTOPPERS/ASPがリリースされていまが、Wi-FiやBLEを使えるようにするにはFreeRTOSが必要になります。
そこで、FreeRTOSのAPIをTOPPERS/ASPで実装して、Wi-FiやBLEを使えるようにしてみました。
こちらの記事で使い方の紹介をしていますが、この記事では、FreeRTOSのAPIを実装した時に考慮した点などを書いておきたいと思います。
説明対象のソースコードはここにあります。
今回の実装ではFreeRTOSの全てのAPIでは無く、Wio TerminalでWi-FiやBLEの動作の必要なAPIのみ実装し、動作に支障の無い程度の再現度になります。
他のFreeRTOS向けアプリケーションでは足らない実装があると思いますが、悪しからず。
FreeRTOSの範囲外ですが、目的はWio Terminalで用意さている機能を使えるようにしたいので、Arduinoのスタートコードとのすり合わせた部分なども書いておきたいと思います。
FreeRTOSのAPI
まず、Wio TerminalのWi-FiやBLEでのFreeRTOSの使い方を調べました。
- BLEを使うにはrpcBLEライブラリが必要で、rpcUnifiedライブラリに依存しています。
- Wi-Fi使うにはrpcWiFiライブラリが必要で、同じくrpcUnifiedライブラリに依存しています。
- rpcUnifiedライブラリはFreeRTOSを使っていて、2つのタスクで動作しています。
- 一つのタスクは、Arduinoのユーザーが実装する関数の
setup
とloop
を実行するClientタスクです。 - もう一つのタスクは、BLE/Wi-FiのCPU(RTL8720DN)との通信用のServerタスクです。
Arduinoのユーザーは、他のプログラムと同様の使い方を維持しつつ、Wi-FiとBLEの通信処理も行うためには、マルチタスク処理が必要になりFreeRTOSが使われているようです。
簡単に図に表すと下記のようになります。
ちなみに、メインCPUとWi-Fi/BLEのCPUは、eRPC(Embedded RPC)というライブラリを使って通信しているようです。
FreeRTOSのOSオブジェクトはの使われ方は、下記の様になっていました。
- タスク
- メイン処理(Cleantタスク)とシリアル受信用(Serverタスク)の2つのタスク
- ミューテックス
- 通信路の排他処理
- カウンティング・セマフォ
- メッセージバッファの資源管理
- バイナリ・セマフォ
- eRPCメッセージの受信をメイン処理に通知
- イベント・グループ
- Wi-Fiイベントの通知
FreeRTOSのAPIの中で必要なAPIの抽出は、ビルド時にlibFreeRTOS.aを含めずにリンクし、未定義としてエラーになったAPIを対象としました。
APIの中には、プリプロセッサ・マクロでリネームされているものがありますが、リネーム前のマクロ名の方でAPIを実装しています。
ちなみに、こちらの記事にTOPPERS/ASPとFreeRTOSのAPIの比較をしています。
タスク
TOPPERS/ASPのタスクで実装しました。
typedef struct tskTaskControlBlock* TaskHandle_t;
typedef void (*TaskFunction_t)(void*);
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,
const char* const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void* const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t* const pxCreatedTask) PRIVILEGED_FUNCTION;
void vTaskDelete(TaskHandle_t xTaskToDelete) PRIVILEGED_FUNCTION;
void vTaskDelay(const TickType_t xTicksToDelay) PRIVILEGED_FUNCTION;
TaskHandle_t xTaskGetCurrentTaskHandle(void) PRIVILEGED_FUNCTION;
void vTaskSuspend(TaskHandle_t xTaskToSuspend) PRIVILEGED_FUNCTION;
TickType_t xTaskGetTickCount(void) PRIVILEGED_FUNCTION;
void vTaskStartScheduler(void) PRIVILEGED_FUNCTION;
void vPortEnterCritical(void) PRIVILEGED_FUNCTION;
void vPortExitCritical(void) PRIVILEGED_FUNCTION;
#define taskENTER_CRITICAL() vPortEnterCritical()
#define taskEXIT_CRITICAL() vPortExitCritical()
ミューテックス
TOPPERS/ASPのセマフォで実装しました。
セマフォを作成するときの設定は、最大値が1で、初期値が1です。
再入可能なように実装し、FreeRTOSでは対応している優先度継承は、未対応となっています。
struct QueueDefinition;
typedef struct QueueDefinition* QueueHandle_t;
typedef QueueHandle_t SemaphoreHandle_t;
SemaphoreHandle_t xSemaphoreCreateMutex(void) PRIVILEGED_FUNCTION;
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, TickType_t xTicksToWait) PRIVILEGED_FUNCTION;
BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex) PRIVILEGED_FUNCTION;
カウンティング・セマフォ
TOPPERS/ASPのセマフォで実装しました。
セマフォを作成するときの設定は、最大値が引数値の値で、初期値は最大値と同じ値です。
SemaphoreHandle_t xSemaphoreCreateCounting(const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount) PRIVILEGED_FUNCTION;
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore) PRIVILEGED_FUNCTION;
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait) PRIVILEGED_FUNCTION;
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore) PRIVILEGED_FUNCTION;
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, BaseType_t * const pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;
FreeRTOS実装ではキューという機能で、ミューテックスやセマフォを実装しています。
TOPPERS/ASPのデータ・キューに似ていいて、取得待ちと送信待ちの機能があります。
キューに積むデータサイズが指定可能で、0が設定できます。TOPPERS/ASPではデータサイズは固定でオブジェクト生成時に個別に設定することは出来ませんし、0も設定できません。
ですので、FreeRTOSと同じような実装には手間がかかるので、リネームされたキューのAPI名ではなく、リネーム前のセマフォのAPI名で実装しました。
#define xSemaphoreCreateBinary xQueueGenericCreate
FreeRTOSではミューテックスもセマフォもキューで実装しているので、セマフォで作ったハンドルで、ミューテックスのAPIを呼び出すことも出来そうですが、このような呼び出しには対応していません。
バイナリ・セマフォ
TOPPERS/ASPのセマフォで実装しました。
セマフォを作成するときの設定は、最大値が1で、初期値が1です。
SemaphoreHandle_t xSemaphoreCreateBinary( void ) PRIVILEGED_FUNCTION;
こちらは、カウンティング・セマフォと初期設定が違うだけで、Take/Giveなどの操作のAPIは共通です。
イベント・グループ
TOPPERS/ASPのイベント・フラグで実装しました。
EventGroupHandle_t xEventGroupCreate(void) PRIVILEGED_FUNCTION;
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet) PRIVILEGED_FUNCTION;
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear) PRIVILEGED_FUNCTION;
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup) PRIVILEGED_FUNCTION;
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait) PRIVILEGED_FUNCTION;
FreeRTOSのAPI呼び出し1つに対してTOPPERS/ASPのAPI呼び出しが2つになっています。
FreeRTOSのxEventGroupSetBits
は、TOPPERS/ASPのset_flg
とref_flg
を使って実装しています。それは、FreeRTOSでは戻り値に設定後のフラグの値を返しているのですが、TOPPERS/ASPでは設定のAPIでは現在の値は分からないため、参照のAPIを使って戻り値にしているためです。
この実装で気になるのは、2つのAPI呼び出しの狭間に別の処理が行われる可能性があるのでは無いかという点です。
別の処理が行われる可能性があるのは、割り込みで優先度の高いタスクが起きた時です。ところが、割り込み禁止状態ではTOPPERS/ASPのAPIが呼び出せないので、2つのAPIの呼び出しは割り込み解除の状態で行う必要があります。
ですので、FreeRTOSとして見せかけたAPIの呼び出し中に別のタスクが動き、期待した振る舞いにならない可能性がありますが、今回の使い方では問題無いと考えています。
動的メモリ
こちらはTOPPERS/ASPの機能では無く、標準Cライブラリのmalloc
/free
で実装しました。
void *pvPortMalloc( size_t xSize ) PRIVILEGED_FUNCTION;
void vPortFree( void *pv ) PRIVILEGED_FUNCTION;
Arduioライブラリとの起動手順の競合
FreeRTOSの実装ではないのですが、Arduinoライブラリとも、すり合わせが必要だったので、その内容も紹介します。
TOPPERS/ASPの起動手順はsta_ker
に書かれていています。FreeRTOSが入った状態のArduinoライブラリの起動手順は2通りあり、Wi-Fi/BLE(rpcUnified)を含んだ場合と、単にRTOS化した場合で違います。
この違いについては、ここでは詳しく説明しませんが、簡単に言うとタスクの使い方が違っています。
GCCがC++のコードをコンパイルすると、クラスのインスタンスをグローバル変数で定義した時は、__libc_init_array
に、コンストラクタの関数ポインタの配列が作られます。
そのコンストラクタはmain
関数よりも早く呼ぶため、標準Cライブラリ(newlib)から呼び出されます。
Arduinoのプログラムでsta_ker
を呼び出すのは、main
関数から呼ばれるsetup
の中を想定しています。
ここで不都合が起こります。Wi-Fi/BLE(rpcUnified)では、コンストラクタでFreeRTOSのAPIを呼び出し、タスク生成を行っています。
一方TOPPERS/ASPではsta_ker
を呼び出すまではAPI呼び出しが失敗します。OSが使う領域の初期化が済んでいないためです。
つまり、グローバル変数のクラスのコンストラクタでタスク生成など行うと、TOPPERS/ASPでは失敗してしまいます。
今回はArduinoライブラリの変更はしたくないので、sta_ker
の中身を個別に別のタイミングで呼ぶことにしました。
GCCでは、__libc_init_array
を呼び出す前に処理することが出来る__libc_preinit_array
というのが用意されています。
また、ArduinoライブラリではinitVariant
という上書き可能な関数が用意されています。
それぞれの初期化手順に対応できるよう、sta_ker
の中身を分割して呼ぶように変更しました。
それを上記の図で表しています。
最後に
今回は、FreeRTOS向けのアプリをTOPPERS/ASPでも使えるようにしましたが、用意してあるターゲットボードはWio TerminalとSeeeduino Xiaoとなっています。
FreeRTOSがサポートするデバイスの方が多いので、TOPPERSが対応するターゲットを増やすため、FreeRTOSのポーティング部からTOPPERSのRTOSのターゲット依存部の作り方に繋げる手順なども調べていきたいなと思っています。