Open3

【Wio Terminal】Queue and WiFi

amenaruyaamenaruya

WiFiを使う時、Queueが使えない

元来Wio Terminalには、WiFiを使いながらFreeRTOSを使えないという問題があったが、宛ら吐哺握髪の執着的調査により、これに関しては一応の解決を示した。

https://zenn.dev/amenaruya/articles/801a76734428ec

但し、上記事で成し得たのは単なるマルチタスクであった。然らば次に問題となるのは、マルチタスク以外の機能である。そこでQueueを試したところ、案の定、従来の手法では正常に動かなかった。

https://zenn.dev/link/comments/a68bcc2ea29c1a

StaticQueue

https://github.com/Seeed-Studio/Seeed_Arduino_rpcUnified/blob/master/src/erpc/erpc_static_queue.h#L25-L114

探してみれば、すぐにStaticQueueというクラスの定義が見つかる。

しかし、茲から幾許の問題が露見した。

StaticQueueの問題

現状、私の把握するものを臚列する。

  1. 定義以外に何もない
  2. 認識されない
  3. エラーコード2

定義以外に何もない

マルチタスク実現の手掛かりとなったThreadは、用例が存在したこともあって扱うまでに時間を要さなかった。

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

しかしStaticQueueは、先に示した定義一箇所を除き、何処にも出現しない。全く記述がない。

とは言え、その構造は比較的単純であるため、用法はソースコードを読めば分かった。

認識されない

Threaderpc名前空間に定義されているため、erpc::Threadあるいはusing namespace erpc;と記述することで利用することができている。

ino
#include "rpcWiFi.h"
using namespace erpc;
Thread TaskA(&ThreadA, configMAX_PRIORITIES - 10, 256, "Task A");

しかしStaticQueueは違った。

ino
#include "rpcWiFi.h"
erpc::StaticQueue<int, 4> queue;
error
Compilation error: 'StaticQueue' in namespace 'erpc' does not name a template type

曰く、これはtemplateではないと。この指摘は、定義に矛盾するものに見える。
試みに、指摘の通りテンプレートの記述を取り払う。

ino
#include "rpcWiFi.h"
erpc::StaticQueue queue;
error
Compilation error: 'StaticQueue' in namespace 'erpc' does not name a type

曰く、typeではないと。
'StaticQueue'はテンプレートクラスとして定義されながら、テンプレートでもなく、型でもないと。では何だというのか。

認識させる方法

定義のあるファイルを名指しすると認識する。

ino
#include "rpcWiFi.h"
#include "erpc/erpc_static_queue.h"
erpc::StaticQueue<int, 4> queue;

エラーコード2

辛くも以上の問題を回避してきたが、完全に動作するには至らず、途中で停止するのが現状である。その際、User LEDの点滅回数から、エラーコードは2と判明している。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/src/error_hooks.cpp#L137-L156

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/src/FreeRTOSVariant.c#L29-L47

malloc faliedstack over flowなどと見える。

現況

小手先の対策により、Queueが動く様子は観測された。しかし、すぐに停止してしまう。

ソースコード1

現状、最も動作の良いものを示す。

#include "rpcWiFi.h"
#include "erpc/erpc_static_queue.h"

#define STACK_SIZE 256
#define QUEUE_SIZE 4

erpc::StaticQueue<int, QUEUE_SIZE> queue;

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    int iValue = 0;
    while (1) {
        if (queue.add(iValue)) {
            Serial.printf("ThreadA\tsend: %d\n", iValue);
            iValue++;
        }
        delay(2000);
    }
}
static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    int iValueReceived = 0;
    while (1) {
        Serial.printf("ThreadB\tnumber of elements in queue: %d\n", queue.size());
        
        if (queue.get(&iValueReceived)) {
            Serial.printf("ThreadB\treceive: %d\n", iValueReceived);
        }
        delay(1000);
    }
}

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

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

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

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

void loop() {}

serial monitor
ThreadA	send: 0
ThreadB	number of elements in queue: 1
ThreadB	receive: 0

この表示で停止する。

ソースコード2

上では、#define STACK_SIZE 256にてその値を256に設定しているが、これを変じると動作に影響が確認される。

倍の#define STACK_SIZE 512、あるいは半分の#define STACK_SIZE 128とした場合に於いて、実行結果は次の通りであった。

serial monitor
ThreadA	send: 0
amenaruyaamenaruya

Mutexの検討

Seeed_Arduino_rpcUnifiedには、Mutexに関する定義もある。

https://github.com/Seeed-Studio/Seeed_Arduino_rpcUnified/blob/master/src/erpc/erpc_threading.h#L276-L378

StaticQueueの実体

StaticQueueの実体は、下に示す通りStaticQueueクラスのprotectedメンバー変数であり、FreeRTOSのQueueとは独立している。

https://github.com/Seeed-Studio/Seeed_Arduino_rpcUnified/blob/master/src/erpc/erpc_static_queue.h#L105-L111

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/master/FreeRTOS/Source/queue.c#L93-L135

xQueueGenericSend

FreeRTOSのQueueに於ける送信の実装は、xQueueGenericSend()にある。これを見ると、taskENTER_CRITICAL()taskEXIT_CRITICAL()によって、他の割り込みが起こらない時間を設けている。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/master/FreeRTOS/Source/queue.c#L771-L980

StaticQueueにそのような実装は見えない。

https://github.com/Seeed-Studio/Seeed_Arduino_rpcUnified/blob/master/src/erpc/erpc_static_queue.h#L53-L70

排他制御

そもそも、FreeRTOS側で定義される機能(xTaskCreate()またはvTaskStartScheduler())を使うことで動作が停止していた。Queueに関しても同様である。以上から、taskENTER_CRITICAL()及びtaskEXIT_CRITICAL()の使用は避けておき、Seeed_Arduino_rpcUnifiedに定義されている機能の中から、それらしいものを選択して試す。

Mutex and StaticQueue
#include "rpcWiFi.h"
#include "erpc/erpc_static_queue.h"

#define STACK_SIZE 256
#define QUEUE_SIZE 4

erpc::StaticQueue<int, QUEUE_SIZE> g_QUEUE;

erpc::Mutex g_MUTEX;

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    int iValue = 0;
    bool bSucceedAddition = false;
    while (1) {
        {
            /*
            Resource Acquisition Is Initialization
            GuardのconstructorがMutexのlock()に対応し、
            GuardのdestructorがMutexのunlock()に対応する
            */
            erpc::Mutex::Guard guardA(g_MUTEX);
            bSucceedAddition = g_QUEUE.add(iValue);
        }
        if (bSucceedAddition) {
            Serial.printf("ThreadA\tsend: %d\n", iValue);
            iValue++;
        }
        delay(2000);
    }
}
static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    int iValueReceived = 0;
    int iQueueSize = 0;
    bool bSucceedAcquisition = false;
    while (1) {
        {
            erpc::Mutex::Guard guardB1(g_MUTEX);
            iQueueSize = g_QUEUE.size();
        }
        Serial.printf("ThreadB\tnumber of elements in queue: %d\n", iQueueSize);

        {
            erpc::Mutex::Guard guardB2(g_MUTEX);
            bSucceedAcquisition = g_QUEUE.get(&iValueReceived);
        }
        if (bSucceedAcquisition) {
            Serial.printf("ThreadB\treceive: %d\n", iValueReceived);
        }
        delay(1000);
    }
}

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

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

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

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

void loop() {}

serial monitor
ThreadA	send: 0
ThreadB	number of elements in queue: 1
ThreadB	receive: 0
ThreadB	number of elements in queue: 0

エラーコードは4であった。

少なくともこの記述では、停止を防ぐことはできなかった。

amenaruyaamenaruya

解決

前稿の解決法と用例を示す。

原因と対処法

諸悪の根源は、シリアル出力で用いるフォーマットであった。

🤔🤔🤔

なんで?

動作する場合

print()とprintln()での表示
static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread A\tStarted");

    unsigned int flag = 0;

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

        Serial.print("Thread A\tflag: ");
        Serial.println(flag);

        flag++;
        delay(1000);
    }
}

停止する場合

次のように記述するときは、シリアル出力の後に停止する様子が見られる。この際、エラーコードは2となる。

  1. Serial.printf()を使う
printfでの表示
static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread A\tStarted");

    unsigned int flag = 0;

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

        Serial.printf("Thread A\tflag: %u\n", flag); // errorBlink(2)

        flag++;
        delay(1000);
    }
}
  1. snprintf()を使う
snprintfとprintln
static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread A\tStarted");

    unsigned int flag = 0;

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

        int printed;
        char string[24];
        printed = snprintf(
            string,
            24,
            "Thread A\tflag: %u\n",
            flag
        );
        if (printed >= 0)
            Serial.println(string); // errorBlink(2)

        flag++;
        delay(1000);
    }
}

StaticQueueの用例

この対処により、StaticQueueが動作した。

StaticQueueSample.ino
#include "rpcWiFi.h"
#include "erpc/erpc_static_queue.h"

#define STACK_SIZE 256
#define QUEUE_SIZE 4

erpc::StaticQueue<int, QUEUE_SIZE> g_QUEUE;

erpc::Mutex g_MUTEX;

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("ThreadA\tbegin");
    int iValue = 0;
    bool bSucceedAddition = false;
    while (1) {
        {
            erpc::Mutex::Guard guardA(g_MUTEX);
            bSucceedAddition = g_QUEUE.add(iValue);
        }

        if (bSucceedAddition) {
            Serial.print("ThreadA\tsend: ");
            Serial.println(iValue);

            iValue++;
        }
        delay(2000);
    }
}
static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    Serial.println("ThreadB\tbegin");
    int iValueReceived = 0;
    int iQueueSize = 0;
    bool bSucceedAcquisition = false;
    while (1) {
        {
            erpc::Mutex::Guard guardB1(g_MUTEX);
            iQueueSize = g_QUEUE.size();
        }
        Serial.print("ThreadB\tnumber of elements in queue: ");
        Serial.println(iQueueSize);

        if (iQueueSize != 0) {
            erpc::Mutex::Guard guardB2(g_MUTEX);
            bSucceedAcquisition = g_QUEUE.get(&iValueReceived);

        } else {
            bSucceedAcquisition = false;
        }
        if (bSucceedAcquisition) {
            Serial.print("ThreadB\treceive: ");
            Serial.println(iValueReceived);
        }
        delay(1000);
    }
}

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

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

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

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

void loop() {}

serial monitor
ThreadA	begin
ThreadA	send: 0
ThreadB	begin
ThreadB	number of elements in queue: 1
ThreadB	receive: 0
ThreadB	number of elements in queue: 0
ThreadA	send: 1
ThreadB	number of elements in queue: 1
ThreadB	receive: 1
ThreadB	number of elements in queue: 0
ThreadA	send: 2
ThreadB	number of elements in queue: 1
ThreadB	receive: 2
ThreadB	number of elements in queue: 0
ThreadA	send: 3
ThreadB	number of elements in queue: 1
ThreadB	receive: 3
ThreadB	number of elements in queue: 0
ThreadA	send: 4
ThreadB	number of elements in queue: 1
ThreadB	receive: 4
ThreadB	number of elements in queue: 0
ThreadA	send: 5
ThreadB	number of elements in queue: 1
ThreadB	receive: 5
ThreadB	number of elements in queue: 0
ThreadA	send: 6
ThreadB	number of elements in queue: 1
ThreadB	receive: 6
ThreadB	number of elements in queue: 0
ThreadA	send: 7
ThreadB	number of elements in queue: 1
ThreadB	receive: 7
ThreadB	number of elements in queue: 0
ThreadA	send: 8
ThreadB	number of elements in queue: 1
ThreadB	receive: 8
ThreadB	number of elements in queue: 0
ThreadA	send: 9
ThreadB	number of elements in queue: 1
ThreadB	receive: 9
ThreadB	number of elements in queue: 0
ThreadA	send: 10
ThreadB	number of elements in queue: 1
ThreadB	receive: 10
ThreadB	number of elements in queue: 0
ThreadA	send: 11
ThreadB	number of elements in queue: 1
ThreadB	receive: 11
ThreadB	number of elements in queue: 0
ThreadA	send: 12
ThreadB	number of elements in queue: 1
ThreadB	receive: 12
ThreadB	number of elements in queue: 0
ThreadA	send: 13
ThreadB	number of elements in queue: 1
ThreadB	receive: 13
ThreadB	number of elements in queue: 0
ThreadA	send: 14
ThreadB	number of elements in queue: 1
ThreadB	receive: 14
ThreadB	number of elements in queue: 0
ThreadA	send: 15
ThreadB	number of elements in queue: 1
ThreadB	receive: 15
ThreadB	number of elements in queue: 0
ThreadA	send: 16
ThreadB	number of elements in queue: 1
ThreadB	receive: 16
ThreadB	number of elements in queue: 0
ThreadA	send: 17
ThreadB	number of elements in queue: 1
ThreadB	receive: 17
ThreadB	number of elements in queue: 0
ThreadA	send: 18
ThreadB	number of elements in queue: 1
ThreadB	receive: 18
ThreadB	number of elements in queue: 0
ThreadA	send: 19
ThreadB	number of elements in queue: 1
ThreadB	receive: 19
ThreadB	number of elements in queue: 0
ThreadA	send: 20
ThreadB	number of elements in queue: 1
ThreadB	receive: 20
ThreadB	number of elements in queue: 0
ThreadA	send: 21
ThreadB	number of elements in queue: 1
ThreadB	receive: 21

// power off