Open4

【Wio Terminal】FreeRTOS and WiFi (or BLE) -> Hard Fault

amenaruyaamenaruya

Wio TerminalでFreeRTOS使用時、無線機能が使えない

FreeRTOSとWiFi機能(正確にはWiFiとBLEの無線通信機能)が、なぜか両立できないという問題が存在する。Wio Terminalの製造元であるSeeed Studioのフォーラムにも、そのような質問が幾つか見られ、しかし何れも解決したようには見えなかった。既に数年間放置されているものと理解している。

https://forum.seeedstudio.com/c/products/wio-terminal/85

問題の概要

厳密には、FreeRTOSとWiFiは両立して動作している。

両立する場合

以下は、Wio Terminalの無線に関わるrpcUnifiedの一部である。rpcUnifiedを基に、WiFiについて詳細なrpcWiFiと、BLEについて詳細なrpcBLEが成立している。

https://github.com/Seeed-Studio/Seeed_Arduino_rpcUnified/blob/master/src/erpc_Unified_init.cpp#L106-L107

rpcUnifiedには、既にRTOSに関する記述が含まれている。erpc_Unified_init.cppを見ると、serverThreadclientThreadというthreadを作り、start()している様子がある。Threadの定義はerpc/erpc_threading_freertos.cppにある。Thread::start関数の定義を見れば、FreeRTOSのタスクを作っていることがわかる。

https://github.com/Seeed-Studio/Seeed_Arduino_rpcUnified/blob/master/src/erpc/erpc_threading_freertos.cpp#L47-L69

従って、次のようなプログラムは動作する。

動作する場合
#include "rpcWiFi.h"

#define STACK_SIZE  256

using namespace erpc;

TaskHandle_t    taskHandlerA,
                taskHandlerB;

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread A\tStarted");

    while (1) {
        Serial.println("Thread A\tHi");
        delay(1000);
    }
}

static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread B\tStarted");

    for (int i = 0; i < 10; i++) {
        Serial.println("Thread B\tHello");
        // dump_tasks(); // fault
        delay(2000);
    }
    Serial.println("Thread B\tBye");
    vTaskDelete(NULL);
}

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);
    while(!Serial);

    Serial.println("\n******************************");
    Serial.println("        Program start         ");
    Serial.println("******************************\n");


    Thread TaskA(
        &ThreadA,
        configMAX_PRIORITIES - 10,
        STACK_SIZE,
        "Task A"
    );

    Thread TaskB(
        &ThreadB,
        configMAX_PRIORITIES - 11,
        STACK_SIZE,
        "Task B"
    );

    Thread* tasksArray[2] = {&TaskA, &TaskB};

    for (Thread* t : tasksArray) {
        t -> start();
    }
}

void loop() {}

serial monitor

******************************
        Program start         
******************************

Thread A	Started
Thread A	Hi
Thread B	Started
Thread B	Hello
Thread A	Hi
Thread B	Hello
Thread A	Hi
Thread A	Hi
Thread B	Hello
Thread A	Hi

︙

Wio Terminalの電源を切るまで、表示は際限なく続く。
此の通り、タスクの作り方を真似ると動く。通常、タスクを開始するにはvTaskStartScheduler()setup()内に記述するが、ここには記述していない。ここで、コメントアウトした箇所にあるerpc::dump_tasks()を実行すると停止する。

両立しない場合

二者が両立しないとき、多くの場合は「タスクが全く動かない」。setup()の内容が実行された後、本来実行されるべきタスクの処理は一切確認できない。先のプログラムを例に取ると、次の通り。

動作しない例
#include "rpcWiFi.h"

#define STACK_SIZE  256

TaskHandle_t    taskHandlerA,
                taskHandlerB;

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread A\tStarted");

    while (1) {
        Serial.println("Thread A\tHi");
        delay(1000);
    }
}

static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread B\tStarted");

    for (int i = 0; i < 10; i++) {
        Serial.println("Thread B\tHello");
        delay(2000);
    }
    Serial.println("Thread B\tBye");
    vTaskDelete(NULL);
}

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);
    while(!Serial);

    Serial.println("\n******************************");
    Serial.println("        Program start         ");
    Serial.println("******************************\n");

    xTaskCreate(
        ThreadA,
        "Task A",
        STACK_SIZE,
        NULL,
        tskIDLE_PRIORITY + 2,
        &taskHandlerA
    );
    xTaskCreate(
        ThreadB,
        "Task B",
        STACK_SIZE,
        NULL,
        tskIDLE_PRIORITY + 1,
        &taskHandlerB
    );

    vTaskStartScheduler(); // コメントアウトしても同様
}

void loop() {}

タスクの動いた様子がない

******************************
        Program start         
******************************

このようなxTaskCreate()を使った方法は、FreeRTOSでタスクを作成するために取られるものである。Wio Terminalの公式wikiでも、そのように指南している。

https://wiki.seeedstudio.com/Software-FreeRTOS/#hello-world-example

この手法に背戻したのが先の両立例であったが、そのコメントアウトを外し、erpc::dump_tasks()を実行した際、異なる動作を確認した。

dump_tasks()を実行する場合
#include "rpcWiFi.h"

#define STACK_SIZE  256

using namespace erpc; // for dump_tasks()

TaskHandle_t    taskHandlerA,
                taskHandlerB;

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread A\tStarted");

    while (1) {
        Serial.println("Thread A\tHi");
        delay(1000);
    }
}

static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread B\tStarted");

    for (int i = 0; i < 10; i++) {
        Serial.println("Thread B\tHello");
        dump_tasks(); // fault
        delay(2000);
    }
    Serial.println("Thread B\tBye");
    vTaskDelete(NULL);
}

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);
    while(!Serial);

    Serial.println("\n******************************");
    Serial.println("        Program start         ");
    Serial.println("******************************\n");


    Thread TaskA(
        &ThreadA,
        configMAX_PRIORITIES - 10,
        STACK_SIZE,
        "Task A"
    );

    Thread TaskB(
        &ThreadB,
        configMAX_PRIORITIES - 11,
        STACK_SIZE,
        "Task B"
    );

    Thread* tasksArray[2] = {&TaskA, &TaskB};

    for (Thread* t : tasksArray) {
        t -> start();
    }
}

void loop() {}

dump_tasks()実行後に停止する様子
******************************
        Program start         
******************************

Thread A	Started
Thread A	Hi
Thread B	Started
Thread B	Hello
Task B   	X	8	20	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	36	5
Tmr Svc  	B	2	45	4

これまでとは異なり、タスクの途中まで動いている様子が確認されたのである。この後は何も表示されていない。

amenaruyaamenaruya

停止時の挙動とエラーコード

何らかの異常があって動作が止まるものと思われるが、その異常の何たるかをエラーメッセージで知ることは叶わない。しかし、Wio TerminalのUser LEDが決まって点滅することに着目した。

LED
青色に光っている箇所がUser LED(引用:https://wiki.seeedstudio.com/Wio-Terminal-CircuitPython/)

User LEDの点滅

Wio Terminalでは、Seeed_Arduino_FreeRTOSによってFreeRTOSを利用する。この中に、User LEDの点滅に関する定義がある。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/master/src/error_hooks.cpp#L161-L179

つまるところ、点滅の回数がそのままエラーコードを表している。
点滅の回数を数えたところ4回であったから、エラーコードは4であると分かった。エラーコード4については次に記述がある。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/master/src/FreeRTOSVariant.c#L86-L90

よって、エラーコード4の示す内容は、「Hard Fault」であることが判明した。

Hard Faultとは?🤔🤔🤔

amenaruyaamenaruya

Queueが使えない

案の定、従来のやり方ではQueueが使えない。User LEDの点滅から、やはりエラーコード4が出ている。

error
#include "rpcWiFi.h"

using namespace erpc;

#define STACK_SIZE 256
#define QUEUE_SIZE 4

QueueHandle_t   queue;

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    int iValue = 0;
    while (1) {
        xQueueSend(
            queue,
            &iValue,
            portMAX_DELAY
        );
        Serial.printf("ThreadA\tsend: %d\n", iValue);
        delay(2000);
        iValue++;
    }
}
static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    int iValueReceived = 0;
    while (1) {
        xQueueReceive(
            queue,
            &iValueReceived,
            portMAX_DELAY
        );
        Serial.printf("ThreadB\treceive: %d\n", iValueReceived);
        delay(1000);
    }
}

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);
    while(!Serial);

    queue = xQueueCreate(
        QUEUE_SIZE,
        sizeof(int)
    );

    Thread taskA(
        &ThreadA,
        configMAX_PRIORITIES - 10,
        STACK_SIZE,
        "Task A"
    );

    Thread taskB(
        &ThreadB,
        configMAX_PRIORITIES - 11,
        STACK_SIZE,
        "Task B"
    );

    Thread* tasks[] = {&taskA, &taskB};
    for (Thread* pt : tasks) {
        pt -> start();
    }
}

void loop() {}

serial monitor
ThreadA	send: 0

続き

https://zenn.dev/amenaruya/scraps/49c8513710cb78