🤔

FreeRTOS and WiFi on Wio Terminal

2024/07/27に公開

Wi-Fi機能とFreeRTOSによる並行処理との両立

Wio Terminalでは、しばしばFreeRTOSによって並行処理を行う。

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

multi-threading with FreeRTOS
#include <Seeed_Arduino_FreeRTOS.h>

TaskHandle_t Handle_aTask;
TaskHandle_t Handle_bTask;

static void ThreadA(void* pvParameters) {
    Serial.println("Thread A: Started");

    while (1) {
        Serial.println("Hello World!");
        delay(1000);
    }
}

static void ThreadB(void* pvParameters) {
    Serial.println("Thread B: Started");

    for (int i = 0; i < 10; i++) {
        Serial.println("---This is Thread B---");
        delay(2000);
    }
    Serial.println("Thread B: Deleting");
    vTaskDelete(NULL);
}

void setup() {

    Serial.begin(115200);

    vNopDelayMS(1000); // prevents usb driver crash on startup, do not omit this
    while(!Serial);  // Wait for Serial terminal to open port before starting program

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

    // Create the threads that will be managed by the rtos
    // Sets the stack size and priority of each task
    // Also initializes a handler pointer to each task, which are important to communicate with and retrieve info from tasks
    xTaskCreate(ThreadA,     "Task A",       256, NULL, tskIDLE_PRIORITY + 2, &Handle_aTask);
    xTaskCreate(ThreadB,     "Task B",       256, NULL, tskIDLE_PRIORITY + 1, &Handle_bTask);

    // Start the RTOS, this function will never return and will schedule the tasks.
    vTaskStartScheduler();
}

void loop() {
    // NOTHING
}
serial monitor

******************************
        Program start         
******************************
Thread A: Started
Hello World!
Thread B: Started
---This is Thread B---
Hello World!
Hello World!
---This is Thread B---
Hello World!

しかし、この手法を用いてWi-Fiの機能を利用せんとするとき、タスクが全く実行されないという問題が生じる。

add the WiFi library and construct a instance
#include <Seeed_Arduino_FreeRTOS.h>
#include "rpcWiFi.h" // WiFi library

// construct a instance
WiFiUDP wifiUdp;

TaskHandle_t Handle_aTask;
TaskHandle_t Handle_bTask;

static void ThreadA(void* pvParameters) {
    Serial.println("Thread A: Started");

    while (1) {
        Serial.println("Hello World!");
        delay(1000);
    }
}

static void ThreadB(void* pvParameters) {
    Serial.println("Thread B: Started");

    for (int i = 0; i < 10; i++) {
        Serial.println("---This is Thread B---");
        delay(2000);
    }
    Serial.println("Thread B: Deleting");
    vTaskDelete(NULL);
}

void setup() {

    Serial.begin(115200);

    vNopDelayMS(1000);
    while(!Serial);

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

    xTaskCreate(ThreadA,     "Task A",       256, NULL, tskIDLE_PRIORITY + 2, &Handle_aTask);
    xTaskCreate(ThreadB,     "Task B",       256, NULL, tskIDLE_PRIORITY + 1, &Handle_bTask);

    vTaskStartScheduler();
}

void loop() {}
serial monitor

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

この問題は既に認知され、forumにも複数の質問が寄せられているが、これについて明確な対策は示されていない。

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

対策

次のようにすればよい。

no error
// remove Seeed_Arduino_FreeRTOS.h
// remove task handlers
#include "rpcWiFi.h" // only WiFi library

using namespace erpc; // from Seeed_Arduino_rpcUnified

// construct a instance
WiFiUDP wifiUdp;

static void ThreadA(void* pvParameters) {
    Serial.println("Thread A: Started");

    while (1) {
        Serial.println("Hello World!");
        delay(1000);
    }
}

static void ThreadB(void* pvParameters) {
    Serial.println("Thread B: Started");

    for (int i = 0; i < 10; i++) {
        Serial.println("---This is Thread B---");
        delay(2000);
    }
    Serial.println("Thread B: Deleting");
    vTaskDelete(NULL);
}

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

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

    // xTaskCreate → erpc::Thread
    // tskIDLE_PRIORITY → configMAX_PRIORITIES
    Thread TaskA(&ThreadA, configMAX_PRIORITIES - 10, 256, "Task A");
    Thread TaskB(&ThreadB, configMAX_PRIORITIES - 11, 256, "Task B");
    // configMAX_PRIORITIES - x
    // it will not work if x is small
    // Thread TaskA(&ThreadA, configMAX_PRIORITIES - 5, 256, "Task A");
    // Thread TaskB(&ThreadB, configMAX_PRIORITIES - 6, 256, "Task B");

    Thread* tasksArray[2] = {&TaskA, &TaskB};
    // start new tasks
    for (Thread* pTask : tasksArray) {
        pTask -> start();
    }
}

void loop() {}
serial monitor

******************************
        Program start         
******************************
Thread A: Started
Hello World!
Thread B: Started
---This is Thread B---
Hello World!
---This is Thread B---
Hello World!
Hello World!
---This is Thread B---
Hello World!

用例

NTPで時刻を取得する例を示す。

https://wiki.seeedstudio.com/Wio-Terminal-Wi-Fi/#wi-fi-ntp-example-code

udp test
#include "rpcWiFi.h"
#include <millisDelay.h>
#include "RTC_SAMD51.h"

#define STACK_SIZE 256
#define WIFI_SSID "SSID"
#define WIFI_PASSWORD "PASSWORD"

using namespace erpc;

const unsigned int  LOCAL_PORT      = 2390;
const char* const   TIME_SERVER     = "time.nist.gov";
const int           NTP_PORT        = 123;
const int           NTP_PACKET_SIZE = 48;
const unsigned long NTP_TO_UNIX     = 2208988800UL;
const long          JST             = 32400UL;
const unsigned long FIVE_SECONDS    = 5000UL;

millisDelay         updateDelay;
byte                packetBuffer[NTP_PACKET_SIZE];
DateTime            currentDateTime;
WiFiUDP             wifiUdp;
unsigned long       ntpTime;
RTC_SAMD51          rtcSamd51;

void                connectToWiFi();
unsigned long       getNtpTime();
void                sendNTPpacket(const char* const address);

static void         ThreadA(void* pvParameters);
static void         ThreadB(void* pvParameters);

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");

    while (1) {
        Serial.println("Thread B\tHello");
        delay(2000);
    }
}

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();
    }

    connectToWiFi();
    ntpTime = getNtpTime();
    if (ntpTime == 0) {
        Serial.println("Failed to get time from network time server.");
    }
    if (!rtcSamd51.begin()) {
        Serial.println("Couldn't find RTC");
        while (1);
    }
    currentDateTime = rtcSamd51.now();
    Serial.print("RTC time is: ");
    Serial.println(currentDateTime.timestamp(DateTime::TIMESTAMP_FULL));
    DateTime dtNtp = DateTime(ntpTime);
    Serial.printf(
        "NTP\t%04d/%d/%d %02d:%02d:%02d\n",
        dtNtp.year(),
        dtNtp.month(),
        dtNtp.day(),
        dtNtp.hour(),
        dtNtp.minute(),
        dtNtp.second()
    );
    Serial.println(dtNtp.timestamp(DateTime::TIMESTAMP_FULL));
    rtcSamd51.adjust(dtNtp);
    Serial.println("RTC (boot) time updated.");
    currentDateTime = rtcSamd51.now();
    Serial.printf("Adjusted RTC (boot) time is: ");
    Serial.println(currentDateTime.timestamp(DateTime::TIMESTAMP_FULL));
    updateDelay.start(FIVE_SECONDS);
}

void loop() {
    if (updateDelay.justFinished()) {
        updateDelay.repeat();
        ntpTime = getNtpTime();
        if (ntpTime == 0) {
            Serial.println("Failed to get time from network time server.");
        }
        else {
            rtcSamd51.adjust(DateTime(ntpTime));
            Serial.println("\nrtcSamd51 time updated.");
            currentDateTime = rtcSamd51.now();
            Serial.print("Adjusted RTC time is: ");
            Serial.println(currentDateTime.timestamp(DateTime::TIMESTAMP_FULL));
        }
    }
}


void connectToWiFi() {
    Serial.println("Connecting to WiFi");
    WiFi.disconnect(true);
    Serial.println("Waiting for Wi-Fi connection...");
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    while (WiFi.status() != WL_CONNECTED) {
        WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
        delay(500);
    }
    Serial.println("Connected.");
}
void sendNTPpacket(const char* const address) {
    for (int i = 0; i < NTP_PACKET_SIZE; ++i) {
        packetBuffer[i] = 0;
    }
    packetBuffer[0] = 0b11100011;
    packetBuffer[1] = 0;
    packetBuffer[2] = 6;
    packetBuffer[3] = 0xEC;
    packetBuffer[12] = 49;
    packetBuffer[13] = 0x4E;
    packetBuffer[14] = 49;
    packetBuffer[15] = 52;
    wifiUdp.beginPacket(address, NTP_PORT);
    wifiUdp.write(packetBuffer, NTP_PACKET_SIZE);
    wifiUdp.endPacket();
}
unsigned long getNtpTime() {
    if (WiFi.status() == WL_CONNECTED) {
        wifiUdp.begin(WiFi.localIP(), LOCAL_PORT);
        sendNTPpacket(TIME_SERVER);
        delay(1000);
        if (wifiUdp.parsePacket()) {
            Serial.println("wifiUdp packet received\n");
            wifiUdp.read(packetBuffer, NTP_PACKET_SIZE);
            unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
            unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
            unsigned long secsSince1900 = highWord << 16 | lowWord;
            unsigned long epoch = secsSince1900 - NTP_TO_UNIX;
            unsigned long adjustedTime = epoch + JST;
            return adjustedTime;
        }
        else {
            wifiUdp.stop();
            return 0;
        }
        wifiUdp.stop();
    }
    else {
        return 0;
    }
}
serial monitor

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

Thread A	Started
Thread A	Hi
Thread B	Started
Thread B	Hello
Connecting to WiFi
Waiting for Wi-Fi connection...
Thread A	Hi
Thread B	Hello
Thread A	Hi
Thread A	Hi
Thread B	Hello
Thread A	Hi
Thread A	Hi
Thread B	Hello
Thread A	Hi
Thread A	Hi
Thread B	Hello
Thread A	Hi
Thread A	Hi
Thread B	Hello
Thread A	Hi
Thread A	Hi
Connected.
Thread B	Hello
Thread A	Hi
wifiUdp packet received

RTC time is: 2000-01-01T00:00:00
NTP	2024/7/27 05:57:53
2024-07-27T05:57:53
RTC (boot) time updated.
Adjusted RTC (boot) time is: 2024-07-27T05:57:53
Thread A	Hi
Thread B	Hello
Thread A	Hi
Thread A	Hi
Thread B	Hello
Thread A	Hi
Thread A	Hi
Thread B	Hello
Thread A	Hi
wifiUdp packet received


rtcSamd51 time updated.
Adjusted RTC time is: 2024-07-27T05:57:59
Thread A	Hi
Thread B	Hello
Thread A	Hi
Thread A	Hi
Thread B	Hello
Thread A	Hi
Thread A	Hi
wifiUdp packet received


rtcSamd51 time updated.
Adjusted RTC time is: 2024-07-27T05:58:04
Thread B	Hello
Thread A	Hi
Thread A	Hi
Thread B	Hello
Thread A	Hi

︙

参考

https://zenn.dev/amenaruya/scraps/200ee489a5b7ac

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

https://zenn.dev/amenaruya/scraps/6cc9a5024f5060

Discussion