🈳

【Wio Terminal】Wi-Fi use on the FreeRTOS sub-thread

に公開

Wi-FiFreeRTOSの両立について進展があった

夫れWio Terminalにおいて、Wi-FiFreeRTOSが両立できないという問題を聞くことがあります。このことについて、解決策を記事にまとめたことがありました。

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

しかしこの時点では、Wi-Fiに関する記述を全てloop()に隔離していました。

このプログラムは先の記事を投稿した日(2024/7/27)に作成・編集されていたものです。SSIDのみ隠しているものの、コメント含め当時のまま載せています。

thread_start_wifi_scan.ino
/*
実際の審らかなる状態こそ知らね
setup()に於ける使用は赦され
他のThreadに於ける使用は赦されざる結果を得しより
WiFiはsetup()とloop()に幽閉すれば次の如く結果を得


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

Thread A	Started
Thread A	Hi
Thread B	Started
Thread B	Hello?
Wifi	set mode
Wifi	done settings
scan start
Thread A	Hi
Thread A	Hi
Thread B	Hello?
Thread A	Hi
Thread A	Hi
Thread B	Hello?
scan done
33 networks found
1: ⋯スキャンされたSSID名⋯ (-34)*
2: ⋯スキャンされたSSID名⋯ (-62)*
3: ⋯スキャンされたSSID名⋯ (-67)*
4: ⋯スキャンされたSSID名⋯ (-68)*
5: ⋯スキャンされたSSID名⋯ (-68)*
6: ⋯スキャンされたSSID名⋯ (-72)*
7: ⋯スキャンされたSSID名⋯ (-81)*
8: ⋯スキャンされたSSID名⋯ (-81)*
9: ⋯スキャンされたSSID名⋯ (-81)*
10: ⋯スキャンされたSSID名⋯ (-84)*
11: ⋯スキャンされたSSID名⋯ (-84)*
12: ⋯スキャンされたSSID名⋯ (-84)*
13: ⋯スキャンされたSSID名⋯ (-85)*
14: ⋯スキャンされたSSID名⋯ (-86)*
15: ⋯スキャンされたSSID名⋯ (-87)*
16: ⋯スキャンされたSSID名⋯ (-87)*
17: ⋯スキャンされたSSID名⋯ (-87)*
18: ⋯スキャンされたSSID名⋯ (-87)*
19: ⋯スキャンされたSSID名⋯ (-87)*
20: ⋯スキャンされたSSID名⋯ (-87)*
21: ⋯スキャンされたSSID名⋯ (-87)*
22:  (-88)*
23: ⋯スキャンされたSSID名⋯ (-89)*
24: ⋯スキャンされたSSID名⋯ (-89)*
25: ⋯スキャンされたSSID名⋯ (-90)*
26:  (-90)*
27: ⋯スキャンされたSSID名⋯ (-91)*
28: ⋯スキャンされたSSID名⋯ (-91)*
29: ⋯スキャンされたSSID名⋯ (-92)*
30: ⋯スキャンされたSSID名⋯ (-92)*
31: ⋯スキャンされたSSID名⋯ (-93)*
32: ⋯スキャンされたSSID名⋯ (-96)*
33: ⋯スキャンされたSSID名⋯ (-99)*

Thread A	Hi
Thread A	Hi
Thread B	Hello?
Thread A	Hi
*/

#include "rpcWiFi.h"

#define STACK_SIZE  256

using namespace erpc;

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) {
        for (int i = 0; i < 10; i++) {
            Serial.println("Thread B\tHello?");
            delay(2000);
        }
    }
}

void setup() {
    Serial.begin(115200);
    /* 起動時にUSBドライバーが故障しないようにする */
    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();
    }

    Serial.println("Wifi\tset mode");
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    delay(100);
    Serial.println("Wifi\tdone settings");
}

void loop() {
    //Serial.println("loop");
    Serial.println("scan start");
    int n = WiFi.scanNetworks();
    Serial.println("scan done");
    if (n == 0) {
        Serial.println("no networks found");
    } else {
        Serial.print(n);
        Serial.println(" networks found");
        for (int i = 0; i < n; ++i) {
            Serial.print(i + 1);
            Serial.print(": ");
            Serial.print(WiFi.SSID(i));
            Serial.print(" (");
            Serial.print(WiFi.RSSI(i));
            Serial.print(")");
            Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
            delay(10);
        }
    }
    Serial.println("");
    delay(5000);
}

ThreadAThreadBとがThreadとして作られていますが、そのどちらにもWi-Fiに関する記述はありません。

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) {
        for (int i = 0; i < 10; i++) {
            Serial.println("Thread B\tHello?");
            delay(2000);
        }
    }
}

初期設定を除き、全てloop()にあります。

void loop() {
    //Serial.println("loop");
    Serial.println("scan start");
    int n = WiFi.scanNetworks();
    Serial.println("scan done");
    if (n == 0) {
        Serial.println("no networks found");
    } else {
        Serial.print(n);
        Serial.println(" networks found");
        for (int i = 0; i < n; ++i) {
            Serial.print(i + 1);
            Serial.print(": ");
            Serial.print(WiFi.SSID(i));
            Serial.print(" (");
            Serial.print(WiFi.RSSI(i));
            Serial.print(")");
            Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
            delay(10);
        }
    }
    Serial.println("");
    delay(5000);
}

/*
︙
他のThreadに於ける使用は赦されざる結果を得しより
WiFiはsetup()とloop()に幽閉すれば次の如く結果を得
︙
*/

その結果の記録は残っておらず仔細は未詳ですが、「ThreadWi-Fiに関する記述を含めてはならない」という解釈に至っている様子が伺えます。

loop()を使うのでは、折角のマルチスレッドが台無しになるような感がありました。とはいえ、動くか否か定かでない(しかも当時は動かないと判断していた)方法は、動くことが確かめられた方法に及びません。そうしてこのような不格好な方法を取ってきました。

main-threadを別の事に使いたくなった

扨、いつか困るだろうと危惧していたことが起こりました。LvGLを使いたくなったのです。

しかしLvGLRTOSとの相性にはやや不安があります。

  1. thread-safeでない
  2. バージョン7.0.2FreeRTOSについて公式情報が無い
    Wio Terminal用のライブラリーSeeed_Arduino_LvGLにおけるバージョンは7.0.2

一応main-threadで動作することは確認できており、記事にもまとめています。

https://zenn.dev/amenaruya/articles/d7b1c677bbefce

実はこの裏で、一度sub-threadでも試しています。試行錯誤を尽くしたとは言えないため記事では然程触れていませんが、sub-threadでは動作させることができませんでした。

main-threadで動き、sub-threadで動かないという現象を目の当たりにしたこと、そしてLvGLの実装は大規模になることを踏まえた結果、次のように実装できることを望むに至ったというのが事の顚末です。

main-thread sub-thread
LvGL Wi-Fi

本題

sub-threadWi-Fiの実装が有効に働く現象を確認しました。(不安なので「可能である」抔とは申しません。)

一つ主観から意外であった結果を挙げると、排他制御を要することすらなかったという点でありましょう。何らかの制約が下でしか使えない可能性も覚悟していましたが、寧ろ望ましいことです。

スキャンする

最も簡単なのは、接続も通信もせず、周辺のアクセスポイント(AP)を検索することです。

プログラム

プログラム
#include "rpcWiFi.h"

constexpr uint16_t STACK_SIZE = 4096;

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);          // 故障対策
    while(!Serial);             // シリアルモニターが開かれるまで待機する
    vSetErrorSerial(&Serial);   // エラー出力を有効にする

    WiFi.mode(WIFI_STA);        // station mode
    WiFi.disconnect();          // 切断状態にする
    delay(100);

    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* pTask : tasks) {
        pTask -> start();
    }
}
void loop() {}

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("ThreadA\tbegin");

    int iValue = 1;

    while (1) {
        Serial.printf("ThreadA\tvalue: %d\n", iValue);
        if (iValue % 5 == 0) {
            uint16_t n = WiFi.scanNetworks();
            if (n == 0) {
                Serial.println("ThreadA\tno networks found");
            } else {
                Serial.printf("ThreadA\t%d networks found\n", n);
            }

        }
        vTaskDelay(1000);
        iValue++;
    }
}

static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    Serial.println("ThreadB\tbegin");
    while (1) {
        Serial.println("ThreadB\tdelay 1sec");
        vTaskDelay(1000);
    }
}

実行結果

実行結果
ThreadA	begin
ThreadA	value: 1
ThreadB	begin
ThreadB	delay 1sec
ThreadA	value: 2
ThreadB	delay 1sec
ThreadA	value: 3
ThreadB	delay 1sec
ThreadA	value: 4
ThreadB	delay 1sec
ThreadA	value: 5
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	31 networks found
ThreadB	delay 1sec
ThreadA	value: 6
ThreadB	delay 1sec
ThreadA	value: 7
ThreadB	delay 1sec
ThreadA	value: 8
ThreadB	delay 1sec
ThreadA	value: 9
ThreadB	delay 1sec
ThreadA	value: 10
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	32 networks found
ThreadB	delay 1sec
ThreadA	value: 11

接続する

次はアクセスポイント、つまるところ自宅のWi-Fiルーターに接続します。

プログラム

当然乍らアクセスポイントの名前(SSID)とパスワードが必要です。

プログラム
SsidAndPassword.h
#ifndef __SSID_AND_PASSWORD__
#define __SSID_AND_PASSWORD__

/* WPA3-Personalのものには繋がらない */

#define WIFI_SSID       "~~SSID~~"
#define WIFI_PASSWORD   "~~password~~"

#endif

#include "rpcWiFi.h"

#include "SsidAndPassword.h"

constexpr uint16_t STACK_SIZE = 4096;

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);          // 故障対策
    while(!Serial);             // シリアルモニターが開かれるまで待機する
    vSetErrorSerial(&Serial);   // エラー出力を有効にする

    WiFi.mode(WIFI_STA);        // station mode
    WiFi.disconnect();          // 切断状態にする
    delay(100);

    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* pTask : tasks) {
        pTask -> start();
    }
}
void loop() {}

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("ThreadA\tbegin");

    int iValue = 1;

    while (1) {
        Serial.printf("ThreadA\tvalue: %d\n", iValue);
        if (iValue % 5 == 0) {
            Serial.println("ThreadA\tbegin Wi-Fi");
            WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
            while (WiFi.status() != WL_CONNECTED) {
                vTaskDelay(500);
                Serial.println("ThreadA\tConnecting..");
            }
            Serial.println("ThreadA\tConnected");
            IPAddress ipAddress = WiFi.localIP();
            Serial.println(ipAddress);

            WiFi.disconnect();
            Serial.println("ThreadA\tDisconnected");
            vTaskDelay(100);
        }
        vTaskDelay(1000);
        iValue++;
    }
}

static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    Serial.println("ThreadB\tbegin");
    while (1) {
        Serial.println("ThreadB\tdelay 1sec");
        vTaskDelay(1000);
    }
}

実行結果

実行結果
ThreadA	begin
ThreadA	value: 1
ThreadB	begin
ThreadB	delay 1sec
ThreadA	value: 2
ThreadB	delay 1sec
ThreadA	value: 3
ThreadB	delay 1sec
ThreadA	value: 4
ThreadB	delay 1sec
ThreadA	value: 5
ThreadA	begin Wi-Fi
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	Connected
192.168.128.145
ThreadA	Disconnected
ThreadB	delay 1sec
ThreadA	value: 6
ThreadB	delay 1sec
ThreadA	value: 7
ThreadB	delay 1sec
ThreadA	value: 8
ThreadB	delay 1sec
ThreadA	value: 9
ThreadB	delay 1sec
ThreadA	value: 10
ThreadA	begin Wi-Fi
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	Connected
192.168.128.145
ThreadA	Disconnected
ThreadB	delay 1sec
ThreadA	value: 11
ThreadB	delay 1sec
ThreadA	value: 12
ThreadB	delay 1sec
ThreadA	value: 13
ThreadB	delay 1sec
ThreadA	value: 14
ThreadB	delay 1sec
ThreadA	value: 15
ThreadA	begin Wi-Fi
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	Connected
192.168.128.145
ThreadA	Disconnected
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	value: 16
ThreadB	delay 1sec
ThreadA	value: 17
ThreadB	delay 1sec
ThreadA	value: 18
ThreadB	delay 1sec
ThreadA	value: 19
ThreadB	delay 1sec
ThreadA	value: 20
ThreadA	begin Wi-Fi
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	Connected
192.168.128.145
ThreadA	Disconnected
ThreadB	delay 1sec
ThreadA	value: 21
ThreadB	delay 1sec
ThreadA	value: 22
ThreadB	delay 1sec
ThreadA	value: 23
ThreadB	delay 1sec
ThreadA	value: 24
ThreadB	delay 1sec
ThreadA	value: 25
ThreadA	begin Wi-Fi
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	Connected
192.168.128.145
ThreadA	Disconnected
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	value: 26
ThreadB	delay 1sec
ThreadA	value: 27

通信する

最後に、NTPサーバーから日時のデータを取得します。

NTPのプログラム元はこちら。

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

プログラム

プログラム
#ifndef __SSID_AND_PASSWORD__
#define __SSID_AND_PASSWORD__

/* WPA3-Personalのものには繋がらない */

#define WIFI_SSID       "~~SSID~~"
#define WIFI_PASSWORD   "~~password~~"

#endif

#include "rpcWiFi.h"

#include "SsidAndPassword.h"

constexpr uint16_t STACK_SIZE = 4096;

// Wio Terminalが送信時に使うポート番号
constexpr uint16_t LOCAL_UDP_PORT = 2390;

// NTPサーバーの情報
const char* const NTP_SERVER = "time.nist.gov";
constexpr uint16_t NTP_PORT = 123;

// NTPパケットのうち日時を知るためには先頭48バイトあればよい
constexpr uint16_t NTP_PACKET_SIZE = 48;

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);          // 故障対策
    while(!Serial);             // シリアルモニターが開かれるまで待機する
    vSetErrorSerial(&Serial);   // エラー出力を有効にする

    WiFi.mode(WIFI_STA);        // station mode
    WiFi.disconnect();          // 切断状態にする
    delay(100);

    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* pTask : tasks) {
        pTask -> start();
    }
}
void loop() {}

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("ThreadA\tbegin");

    int iValue = 1;

    byte packetBuffer[NTP_PACKET_SIZE];
    WiFiUDP wifiUdp;
    uint64_t ntpTime;

    while (1) {
        Serial.printf("ThreadA\tvalue: %d\n", iValue);
        if (iValue % 5 == 0) {
            Serial.println("ThreadA\tbegin Wi-Fi");
            WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
            while (WiFi.status() != WL_CONNECTED) {
                vTaskDelay(500);
                Serial.println("ThreadA\tConnecting..");
            }
            Serial.println("ThreadA\tConnected");
            IPAddress ipAddress = WiFi.localIP();
            Serial.println(ipAddress);

            if (WiFi.status() == WL_CONNECTED) {
                Serial.println("ThreadA\tbegin UDP");
                wifiUdp.begin(ipAddress, LOCAL_UDP_PORT);

                Serial.println("ThreadA\tcreate data (UDP packet)");
                for (int i = 0; i < NTP_PACKET_SIZE; ++i) {
                    packetBuffer[i] = 0;
                }
                packetBuffer[0] = 0b11100011;   // Leap Indicator, Version Number, Mode
                packetBuffer[1] = 0;            // Stratum
                packetBuffer[2] = 6;            // Polling Interval
                packetBuffer[3] = 0xEC;         // Peer Clock Precision
                packetBuffer[12] = 49;          // Reference ID
                packetBuffer[13] = 0x4E;
                packetBuffer[14] = 49;
                packetBuffer[15] = 52;

                Serial.println("ThreadA\tsend data");
                wifiUdp.beginPacket(NTP_SERVER, NTP_PORT);
                wifiUdp.write(packetBuffer, NTP_PACKET_SIZE);
                wifiUdp.endPacket();

                vTaskDelay(1000);

                if (wifiUdp.parsePacket()) {
                    Serial.println("ThreadA\treceived data");
                    wifiUdp.read(packetBuffer, NTP_PACKET_SIZE);

                    uint64_t highWord = word(packetBuffer[40], packetBuffer[41]);
                    uint64_t lowWord = word(packetBuffer[42], packetBuffer[43]);
                    uint64_t rowNtpData = highWord << 16 | lowWord;
                    Serial.printf("ThreadA\trow data: %lu\n", rowNtpData);
                }

                Serial.println("ThreadA\tstop UDP");
                wifiUdp.stop();
            }

            WiFi.disconnect();
            Serial.println("ThreadA\tDisconnected");
            vTaskDelay(100);
        }
        vTaskDelay(1000);
        iValue++;
    }
}

static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    Serial.println("ThreadB\tbegin");
    while (1) {
        Serial.println("ThreadB\tdelay 1sec");
        vTaskDelay(1000);
    }
}

実行結果

実行結果
ThreadA	begin
ThreadA	value: 1
ThreadB	begin
ThreadB	delay 1sec
ThreadA	value: 2
ThreadB	delay 1sec
ThreadA	value: 3
ThreadB	delay 1sec
ThreadA	value: 4
ThreadB	delay 1sec
ThreadA	value: 5
ThreadA	begin Wi-Fi
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	Connected
192.168.128.145
ThreadA	begin UDP
ThreadA	create data (UDP packet)
ThreadA	send data
ThreadB	delay 1sec
ThreadA	received data
ThreadA	row data: 3954038511
ThreadA	stop UDP
ThreadA	Disconnected
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	value: 6
ThreadB	delay 1sec
ThreadA	value: 7
ThreadB	delay 1sec
ThreadA	value: 8
ThreadB	delay 1sec
ThreadA	value: 9
ThreadB	delay 1sec
ThreadA	value: 10
ThreadA	begin Wi-Fi
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadB	delay 1sec
ThreadA	Connected
192.168.128.145
ThreadA	begin UDP
ThreadA	create data (UDP packet)
ThreadA	send data
ThreadB	delay 1sec
ThreadA	received data
ThreadA	row data: 3954038523
ThreadA	stop UDP
ThreadA	Disconnected
ThreadB	delay 1sec
ThreadA	value: 11
ThreadB	delay 1sec
ThreadA	value: 12
ThreadB	delay 1sec
ThreadA	value: 13
ThreadB	delay 1sec
ThreadA	value: 14


一応、これらの値が正しいものなのか計算してみましょう。

最後に得られた値である3954038523を用います。唯の計算なのでPythonを使っています。

pythonで計算した様子
>>> data = 3954038523
>>> epoch = data - 2208988800
>>> epoch
1745049723
>>> jst = epoch + 32400
>>> jst
1745082123

2208988800を引くことでUNIX時間に変換されます。これで既にグリニッジ標準時となります。

日本標準時にするには、更に32400を足します。


こちらの計算サイトで確認してみましょう。

https://keisan.casio.jp/exec/system/1526004418

時差考慮あり
時差を考慮してくれる場合はepochの方(1745049723)を使う

しっかりした情報が出てきました。

ついでにPythonでも確認してみましょう。

https://phst.hateblo.jp/entry/2022/09/17/090000

Pythonは始めから時差を考慮してくれるようで、epochの方を使えば済みます。jstの方を使うと未来の日時になってしまいました。

>>> from datetime import datetime
>>> jst
1745082123
>>> epoch
1745049723
>>> dt = datetime.fromtimestamp(jst)
>>> dt.strftime('%Y年%m月%d日 %H時%M分%S秒')
'2025年04月20日 02時02分03秒'
>>> dt2 = datetime.fromtimestamp(epoch)
>>> dt2.strftime('%Y年%m月%d日 %H時%M分%S秒')
'2025年04月19日 17時02分03秒'

対してCはこれらと異なり、時差の考慮をしてくれるほど親切ではないようで、自分で時差分調整する必要があります。

時差考慮なし
時差を考慮してくれない場合はjstの方(1745082123)を使う

このために、わざわざ自分で時差を調整した値を算出する必要があったのです。

更なる検証

動かなかったプログラム
#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()を実行したところで停止していました。

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

先ずは動いたものを示します。

動くもの
#include "rpcWiFi.h"

constexpr uint16_t STACK_SIZE = 4096;

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);          // 故障対策
    while(!Serial);             // シリアルモニターが開かれるまで待機する
    vSetErrorSerial(&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* pTask : tasks) {
        pTask -> start();
    }
}
void loop() {}

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

実行結果
Thread A	Started
Thread A	Hi
Thread B	Started
Thread B	Hello
Task B   	X	8	972	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	990	5
Tmr Svc  	B	2	45	4
Thread A	Hi
Thread A	Hi
Thread B	Hello
Task B   	X	8	794	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	990	5
Tmr Svc  	B	2	45	4
Thread A	Hi
Thread A	Hi
Thread B	Hello
Task B   	X	8	794	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	990	5
Tmr Svc  	B	2	45	4
Thread A	Hi
Thread A	Hi
Thread B	Hello
Task B   	X	8	794	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	990	5
Tmr Svc  	B	2	45	4
Thread A	Hi
Thread A	Hi
Thread B	Hello
Task B   	X	8	794	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	990	5
Tmr Svc  	B	2	45	4
Thread A	Hi
Thread A	Hi
Thread B	Hello
Task B   	X	8	794	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	990	5
Tmr Svc  	B	2	45	4
Thread A	Hi
Thread A	Hi
Thread B	Hello
Task B   	X	8	794	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	990	5
Tmr Svc  	B	2	45	4
Thread A	Hi
Thread A	Hi
Thread B	Hello
Task B   	X	8	794	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	990	5
Tmr Svc  	B	2	45	4
Thread A	Hi
Thread A	Hi
Thread B	Hello
Task B   	X	8	794	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	990	5
Tmr Svc  	B	2	45	4
Thread A	Hi
Thread A	Hi
Thread B	Hello
Task B   	X	8	794	6
runClient	R	7	5062	2
IDLE     	R	0	129	3
runServer	B	8	1965	1
Task A   	B	8	990	5
Tmr Svc  	B	2	45	4
Thread A	Hi
Thread A	Hi
Thread B	Bye
Thread A	Hi
Thread A	Hi
Thread A	Hi

元のプログラムとの主な違いは二点あります。

  1. TaskHandle_t型変数の宣言

    TaskHandle_t    taskHandlerA,
                    taskHandlerB;
    

    これは通常FreeRTOSを使う際に必要なものですが、erpc::Threadを使う場合には不要になるため、使用していません。

    なお、この宣言の有無は関係ありませんでした。

  2. stackサイズ

    元のプログラムでは256しかないのに対して、動く方では4096配当しています。

    動いたプログラムにおいてconstexpr uint16_t STACK_SIZE = 256;と変更したところ、記憶の通り停止しました。エラーコード(LEDの点滅回数)は4、即ちHard Faultでした。Hard Faultであるためなのか、シリアルモニターへのエラー出力はありませんでした。

つまり、このように停止する問題の中には、stackサイズ不足を原因とするものがあるようです。

以上をまとめると、「stackサイズに余裕を持たせることで解決する」ということになります。

これで、Wi-FiFreeRTOSの使い勝手が更に改善されるでしょう。

Discussion