FreeRTOSのAPIをTOPPERS/ASPで実装してみた

# はじめに

[Wio Terminal](https://wiki.seeedstudio.com/Wio-Terminal-Getting-Started/)向けにTOPPERS/ASPが[リリース](https://github.com/exshonda/Arduino_TOPPERS_ASP)されていまが、Wi-FiやBLEを使えるようにするには[FreeRTOS](https://freertos.org/)が必要になります。
そこで、FreeRTOSのAPIをTOPPERS/ASPで実装して、Wi-FiやBLEを使えるようにしてみました。
[こちらの記事](https://qiita.com/h7ga40/items/11e3860967bd7549d38c)で使い方の紹介をしていますが、この記事では、FreeRTOSのAPIを実装した時に考慮した点などを書いておきたいと思います。
説明対象のソースコードは[ここ](https://github.com/h7ga40/Arduino_TOPPERS_ASP_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ライブラリ](https://github.com/Seeed-Studio/Seeed_Arduino_rpcBLE)が必要で、rpcUnifiedライブラリに依存しています。
- Wi-Fi使うには[rpcWiFiライブラリ](https://github.com/Seeed-Studio/Seeed_Arduino_rpcWiFi)が必要で、同じくrpcUnifiedライブラリに依存しています。
- [rpcUnifiedライブラリ](https://github.com/Seeed-Studio/Seeed_Arduino_rpcUnified)はFreeRTOSを使っていて、２つのタスクで動作しています。
- 一つのタスクは、Arduinoのユーザーが実装する関数の``setup``と``loop``を実行するClientタスクです。
- もう一つのタスクは、BLE/Wi-FiのCPU（RTL8720DN）との通信用のServerタスクです。

Arduinoのユーザーは、他のプログラムと同様の使い方を維持しつつ、Wi-FiとBLEの通信処理も行うためには、マルチタスク処理が必要になりFreeRTOSが使われているようです。

簡単に図に表すと下記のようになります。

![Wio Terminalのタスク](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/2e6594eb-0e2d-1c3c-623d-5423a2cacf5d.png)

ちなみに、メインCPUとWi-Fi/BLEのCPUは、[eRPC(Embedded RPC)](https://github.com/EmbeddedRPC/erpc)というライブラリを使って通信しているようです。

FreeRTOSのOSオブジェクトはの使われ方は、下記の様になっていました。

- タスク
  - メイン処理（Cleantタスク）とシリアル受信用(Serverタスク)の２つのタスク
- ミューテックス
  - 通信路の排他処理
- カウンティング・セマフォ
  - メッセージバッファの資源管理
- バイナリ・セマフォ
  - eRPCメッセージの受信をメイン処理に通知
- イベント・グループ
  - Wi-Fiイベントの通知

FreeRTOSのAPIの中で必要なAPIの抽出は、ビルド時にlibFreeRTOS.aを含めずにリンクし、未定義としてエラーになったAPIを対象としました。
APIの中には、プリプロセッサ・マクロでリネームされているものがありますが、リネーム前のマクロ名の方でAPIを実装しています。
ちなみに、[こちらの記事](https://qiita.com/h7ga40/items/9e24c824dd2d83fa5d4a)にTOPPERS/ASPとFreeRTOSのAPIの比較をしています。

## タスク

TOPPERS/ASPのタスクで実装しました。

```c
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のセマフォで実装しました。
セマフォを作成するときの設定は、最大値が１で、初期値が１です。
再入可能なように実装し、FreeRTOSでは対応している優先度継承は、未対応となっています。

```c
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のセマフォで実装しました。
セマフォを作成するときの設定は、最大値が引数値の値で、初期値は最大値と同じ値です。

```c
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のデータ・キューに似ていいて、取得待ちと送信待ちの機能があります。
キューに積むデータサイズが指定可能で、０が設定できます。TOPPERS/ASPではデータサイズは固定でオブジェクト生成時に個別に設定することは出来ませんし、０も設定できません。
ですので、FreeRTOSと同じような実装には手間がかかるので、リネームされたキューのAPI名ではなく、リネーム前のセマフォのAPI名で実装しました。

```c
#define xSemaphoreCreateBinary xQueueGenericCreate
```

FreeRTOSではミューテックスもセマフォもキューで実装しているので、セマフォで作ったハンドルで、ミューテックスのAPIを呼び出すことも出来そうですが、このような呼び出しには対応していません。

## バイナリ・セマフォ

TOPPERS/ASPのセマフォで実装しました。
セマフォを作成するときの設定は、最大値が１で、初期値が１です。

```c
SemaphoreHandle_t xSemaphoreCreateBinary( void ) PRIVILEGED_FUNCTION;
```

こちらは、カウンティング・セマフォと初期設定が違うだけで、Take/Giveなどの操作のAPIは共通です。

## イベント・グループ

TOPPERS/ASPのイベント・フラグで実装しました。

```c
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呼び出し１つに対してTOPPERS/ASPのAPI呼び出しが２つになっています。
FreeRTOSの``xEventGroupSetBits``は、TOPPERS/ASPの``set_flg``と``ref_flg``を使って実装しています。それは、FreeRTOSでは戻り値に設定後のフラグの値を返しているのですが、TOPPERS/ASPでは設定のAPIでは現在の値は分からないため、参照のAPIを使って戻り値にしているためです。
この実装で気になるのは、２つのAPI呼び出しの狭間に別の処理が行われる可能性があるのでは無いかという点です。
別の処理が行われる可能性があるのは、割り込みで優先度の高いタスクが起きた時です。ところが、割り込み禁止状態ではTOPPERS/ASPのAPIが呼び出せないので、２つのAPIの呼び出しは割り込み解除の状態で行う必要があります。
ですので、FreeRTOSとして見せかけたAPIの呼び出し中に別のタスクが動き、期待した振る舞いにならない可能性がありますが、今回の使い方では問題無いと考えています。

## 動的メモリ

こちらはTOPPERS/ASPの機能では無く、標準Cライブラリの``malloc``/``free``で実装しました。

```c
void *pvPortMalloc( size_t xSize ) PRIVILEGED_FUNCTION;
void vPortFree( void *pv ) PRIVILEGED_FUNCTION;
```

## Arduioライブラリとの起動手順の競合

FreeRTOSの実装ではないのですが、Arduinoライブラリとも、すり合わせが必要だったので、その内容も紹介します。
TOPPERS/ASPの起動手順は``sta_ker``に書かれていています。FreeRTOSが入った状態のArduinoライブラリの起動手順は２通りあり、Wi-Fi/BLE(rpcUnified)を含んだ場合と、単にRTOS化した場合で違います。
この違いについては、ここでは詳しく説明しませんが、簡単に言うとタスクの使い方が違っています。

![起動手順](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/89d786b1-2445-779d-be40-0e7b0867b366.png)

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``の中身を個別に別のタイミングで呼ぶことにしました。

![対応後の起動手順](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/89fdc0ca-4354-e287-6337-007f41bb8454.png)

GCCでは、``__libc_init_array``を呼び出す前に処理することが出来る``__libc_preinit_array``というのが用意されています。
また、Arduinoライブラリでは``initVariant``という上書き可能な関数が用意されています。
それぞれの初期化手順に対応できるよう、``sta_ker``の中身を分割して呼ぶように変更しました。
それを上記の図で表しています。

# 最後に

今回は、FreeRTOS向けのアプリをTOPPERS/ASPでも使えるようにしましたが、用意してあるターゲットボードはWio Terminalと[Seeeduino Xiao](https://wiki.seeedstudio.com/jp/Seeeduino-XIAO/)となっています。
FreeRTOSがサポートするデバイスの方が多いので、TOPPERSが対応するターゲットを増やすため、FreeRTOSのポーティング部からTOPPERSのRTOSのターゲット依存部の作り方に繋げる手順なども調べていきたいなと思っています。