【Wio Terminal】FreeRTOS and WiFi (or BLE) -> Hard Fault
Wio TerminalでFreeRTOS使用時、無線機能が使えない
FreeRTOSとWiFi機能(正確にはWiFiとBLEの無線通信機能)が、なぜか両立できないという問題が存在する。Wio Terminalの製造元であるSeeed Studioのフォーラムにも、そのような質問が幾つか見られ、しかし何れも解決したようには見えなかった。既に数年間放置されているものと理解している。
問題の概要
厳密には、FreeRTOSとWiFiは両立して動作している。
両立する場合
以下は、Wio Terminalの無線に関わるrpcUnifiedの一部である。rpcUnifiedを基に、WiFiについて詳細なrpcWiFiと、BLEについて詳細なrpcBLEが成立している。
rpcUnifiedには、既にRTOSに関する記述が含まれている。erpc_Unified_init.cpp
を見ると、serverThread
とclientThread
というthreadを作り、start()
している様子がある。Thread
の定義はerpc/erpc_threading_freertos.cpp
にある。Thread::start
関数の定義を見れば、FreeRTOSのタスクを作っていることがわかる。
従って、次のようなプログラムは動作する。
#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() {}
******************************
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でも、そのように指南している。
この手法に背戻したのが先の両立例であったが、そのコメントアウトを外し、erpc::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() {}
******************************
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
これまでとは異なり、タスクの途中まで動いている様子が確認されたのである。この後は何も表示されていない。
停止時の挙動とエラーコード
何らかの異常があって動作が止まるものと思われるが、その異常の何たるかをエラーメッセージで知ることは叶わない。しかし、Wio TerminalのUser LEDが決まって点滅することに着目した。
青色に光っている箇所がUser LED(引用:https://wiki.seeedstudio.com/Wio-Terminal-CircuitPython/)
User LEDの点滅
Wio Terminalでは、Seeed_Arduino_FreeRTOSによってFreeRTOSを利用する。この中に、User LEDの点滅に関する定義がある。
つまるところ、点滅の回数がそのままエラーコードを表している。
点滅の回数を数えたところ4回であったから、エラーコードは4であると分かった。エラーコード4については次に記述がある。
よって、エラーコード4の示す内容は、「Hard Fault」であることが判明した。
Hard Faultとは?🤔🤔🤔
一応解決
記事に概要を纏めました。
setup()
やloop()
を使っている点で、思ったものとは乖離があるものの、一応両立したので解決。ただし、詳しい原因などはよくわからないまま。
Queueが使えない
案の定、従来のやり方ではQueueが使えない。User LEDの点滅から、やはりエラーコード4が出ている。
#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() {}
ThreadA send: 0
続き