Open7

【Wio Terminal】millisDelay and WiFi with FreeRTOS

amenaruyaamenaruya

Wio TerminalとmillisDelay

millisDelayとは、カウントダウンができるようになるものである。一定周期で同じ処理を繰り返す場合に用いるほか、カウントダウンが終わるまでwhileで進行を防御すれば、規定時間待機するdelayと同じように用いることもできる。実装の仕方によっては、タイマーを作ることもできるだろう。

millisDelayを用いているのは、Wio TerminalのWikiで次のように紹介されているためである。

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

https://wiki.seeedstudio.com/Wio-Terminal-RTC/#dependent-libraries

FreeRTOS(WiFi)との相性が悪い

正確には、erpc::Threadとの相性が悪い。なおerpc::Threadについては、記事及びスクラップにて既に述べている。

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

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

delayが使えない

上の記事にある通り、erpc::Threadで作成したthread内ではdelayを使うことができている。しかしmillisDelayを使用している場合において、delayが原因で停止することがあった。

delayが使えないならば、millisDelayを使うしかなかった。

millisDelayが使えない (?)

erpc::Threadで作成したthreadにて、millisDelayの定義及び処理を記述したとき、当該threadを起動した後の処理が全て停止することが確認された。

🤔🤔🤔

amenaruyaamenaruya

delay検証

  1. delay()のみ
  2. ただの関数でmillisDelay
  3. erpc::ThreadmillisDelay
  4. millisDelayのみ

1. delay()のみ

#include "rpcWiFi.h"
#include <millisDelay.h>

constexpr uint16_t  PRIORITY_CRITERION  = configMAX_PRIORITIES - 10;
const uint16_t      STACK_SIZE          = 256;

static void         Thread1(void* pvParameters);
void                ThreadWifiArea(void);

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

    Serial.println("setup\tcreate threads");

    erpc::Thread thread1(
        &Thread1,
        PRIORITY_CRITERION,
        STACK_SIZE,
        "Thread 1"
    );

    Serial.println("setup\tstart threads");

    erpc::Thread* pThreadsArray[] = {
        &thread1
    };
    for (erpc::Thread* pt : pThreadsArray) {
        pt -> start();
    }

    Serial.println("setup\tstart ThreadWifiArea()");

    ThreadWifiArea(); /* 発散 */
    Serial.println("exception: loop avoided or end: ThreadWifiArea");

    Serial.println("setup\tend setup");
}

void loop() {}

void ThreadWifiArea(void) {
    Serial.println("WiFi\tstart");

    /* delay */
    //millisDelay             msDelay;

    while (1) {
        Serial.println("WiFi\trunning");

        //msDelay.start(3000);
        //while (!msDelay.justFinished());
        delay(3000);
    }
}

static void Thread1(void* pvParameters) {
    (void) pvParameters;

    Serial.println("1\tstart");

    /* delay */
    //millisDelay             msDelay;

    while (1) {
        Serial.println("1\trunning");

        //msDelay.start(4000);
        //while (!msDelay.justFinished());
        delay(4000);
    }

}

setup	create threads
setup	start threads
1	start
1	running
setup	start ThreadWifiArea()
WiFi	start
WiFi	running
WiFi	running
1	running
WiFi	running
1	running
WiFi	running
1	running
WiFi	running
WiFi	running
1	running
WiFi	running
1	running
WiFi	running
1	running
WiFi	running
WiFi	running
1	running
WiFi	running
1	running
WiFi	running
1	running
WiFi	running

2. ただの関数でmillisDelay

#include "rpcWiFi.h"
#include <millisDelay.h>

constexpr uint16_t  PRIORITY_CRITERION  = configMAX_PRIORITIES - 10;
const uint16_t      STACK_SIZE          = 256;

static void         Thread1(void* pvParameters);
void                ThreadWifiArea(void);

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

    Serial.println("setup\tcreate threads");

    erpc::Thread thread1(
        &Thread1,
        PRIORITY_CRITERION,
        STACK_SIZE,
        "Thread 1"
    );

    Serial.println("setup\tstart threads");

    erpc::Thread* pThreadsArray[] = {
        &thread1
    };
    for (erpc::Thread* pt : pThreadsArray) {
        pt -> start();
    }

    Serial.println("setup\tstart ThreadWifiArea()");

    ThreadWifiArea(); /* 発散 */
    Serial.println("exception: loop avoided or end: ThreadWifiArea");

    Serial.println("setup\tend setup");
}

void loop() {}

void ThreadWifiArea(void) {
    Serial.println("WiFi\tstart");

    /* delay */
    millisDelay             msDelay;

    while (1) {
        Serial.println("WiFi\trunning");

        msDelay.start(3000);
        while (!msDelay.justFinished());
        //delay(3000);
    }
}

static void Thread1(void* pvParameters) {
    (void) pvParameters;

    Serial.println("1\tstart");

    /* delay */
    //millisDelay             msDelay;

    while (1) {
        Serial.println("1\trunning");

        //msDelay.start(4000);
        //while (!msDelay.justFinished());
        delay(4000);
    }

}

setup	create threads
setup	start threads
1	start
1	running
setup	start ThreadWifiArea()
WiFi	start
WiFi	running
WiFi	running
1	running
WiFi	running
1	running
WiFi	running
1	running
WiFi	running
WiFi	running
1	running
WiFi	running
1	running

3. erpc::ThreadmillisDelay

#include "rpcWiFi.h"
#include <millisDelay.h>

constexpr uint16_t  PRIORITY_CRITERION  = configMAX_PRIORITIES - 10;
const uint16_t      STACK_SIZE          = 256;

static void         Thread1(void* pvParameters);
void                ThreadWifiArea(void);

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

    Serial.println("setup\tcreate threads");

    erpc::Thread thread1(
        &Thread1,
        PRIORITY_CRITERION,
        STACK_SIZE,
        "Thread 1"
    );

    Serial.println("setup\tstart threads");

    erpc::Thread* pThreadsArray[] = {
        &thread1
    };
    for (erpc::Thread* pt : pThreadsArray) {
        pt -> start();
    }

    Serial.println("setup\tstart ThreadWifiArea()");

    ThreadWifiArea(); /* 発散 */
    Serial.println("exception: loop avoided or end: ThreadWifiArea");

    Serial.println("setup\tend setup");
}

void loop() {}

void ThreadWifiArea(void) {
    Serial.println("WiFi\tstart");

    /* delay */
    //millisDelay             msDelay;

    while (1) {
        Serial.println("WiFi\trunning");

        //msDelay.start(3000);
        //while (!msDelay.justFinished());
        delay(3000);
    }
}

static void Thread1(void* pvParameters) {
    (void) pvParameters;

    Serial.println("1\tstart");

    /* delay */
    millisDelay             msDelay;

    while (1) {
        Serial.println("1\trunning");

        msDelay.start(4000);
        while (!msDelay.justFinished());
        //delay(4000);
    }

}

setup	create threads
setup	start threads
1	start
1	running
1	running
1	running
1	running

4. millisDelayのみ

#include "rpcWiFi.h"
#include <millisDelay.h>

constexpr uint16_t  PRIORITY_CRITERION  = configMAX_PRIORITIES - 10;
const uint16_t      STACK_SIZE          = 256;

static void         Thread1(void* pvParameters);
void                ThreadWifiArea(void);

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

    Serial.println("setup\tcreate threads");

    erpc::Thread thread1(
        &Thread1,
        PRIORITY_CRITERION,
        STACK_SIZE,
        "Thread 1"
    );

    Serial.println("setup\tstart threads");

    erpc::Thread* pThreadsArray[] = {
        &thread1
    };
    for (erpc::Thread* pt : pThreadsArray) {
        pt -> start();
    }

    Serial.println("setup\tstart ThreadWifiArea()");

    ThreadWifiArea(); /* 発散 */
    Serial.println("exception: loop avoided or end: ThreadWifiArea");

    Serial.println("setup\tend setup");
}

void loop() {}

void ThreadWifiArea(void) {
    Serial.println("WiFi\tstart");

    /* delay */
    millisDelay             msDelay;

    while (1) {
        Serial.println("WiFi\trunning");

        msDelay.start(3000);
        while (!msDelay.justFinished());
        //delay(3000);
    }
}

static void Thread1(void* pvParameters) {
    (void) pvParameters;

    Serial.println("1\tstart");

    /* delay */
    millisDelay             msDelay;

    while (1) {
        Serial.println("1\trunning");

        msDelay.start(4000);
        while (!msDelay.justFinished());
        //delay(4000);
    }

}

setup	create threads
setup	start threads
1	start
1	running
1	running
1	running
1	running

結果

erpc::Thread \backslash 関数 delay() millisDelay
delay()
millisDelay

この結果を要約すれば、erpc::ThreadmillisDelayは使えないということになる。となれば、erpc::Threadの関与しない単なる関数、即ちメインスレッドで管轄する外ない。

浮上する問題

以上の結果を受けたならば、メインスレッドでmillisDelayを管理すれば宜しい事となる。しかし、この手段を却下或いは留保せざるを得ない、一つの、極めて根本的な問題がある。

抑々、FreeRTOSには標準の機能が周く備わっているにも関わらず、それらを全て忌避してerpc::Threadなどという得体の知れぬ機能を使っている。荊棘の道を征くの発端たる動機は、WiFiを使いながら、マルチスレッドを実現するためであった。

但し、erpc::Threadで起動したthreadには、WiFiに関する機能を使うことができないという欠陥が確認される。従って、WiFiに関する機能はメインスレッドに集約せざるを得ず、言い換えれば、メインスレッドは既にWiFiの独擅場として、他の機能が入り込む余地は無いのである。故に、millisDelayをメインスレッドで管理することは難しい。

現状この問題を解決する手立てとして、WiFiとmillisDelayをメインスレッドで同時に管理し、この二者についてのマルチスレッドを諦める以外に思い当たらない。若しくは、少なくとも一方をerpc::Thread或いは別の何かで扱う方法を探すことである。何と情趣の無い覓句であろうか。

amenaruyaamenaruya

millisxTaskGetTickCount

millisDelayの定義には、millis()という関数が含まれている。

https://github.com/ansonhex/millisDelay/blob/master/src/millisDelay.cpp#L25-L30

この関数は、Arduinoで時間計測に使われるものである。

https://elchika.com/article/94c942fd-e864-4ce1-8784-b5bc77d7a671/

しかし調べるところによれば、FreeRTOSを使う際には異なる手法を用いるという。

https://qiita.com/eggman/items/58a772c0669781863ca9

つまりmillisDelayは、そもそもOSでの使用を想定していないと考えるのが自然だろうか。然らば推移的に、xTaskGetTickCountで同様の実装をする必要がある。

xTaskGetTickCountで時間を計測できるか

一応警戒を怠らず、erpc::Mutexでの排他制御を併せた。また、時間を保持するportTickType型変数は、関数外に大域変数として宣言した。

xTaskGetTickCount with Thread
#include "rpcWiFi.h"

constexpr uint16_t  PRIORITY_CRITERION  = configMAX_PRIORITIES - 10;
const uint16_t      STACK_SIZE          = 256;

erpc::Mutex         g_Mutex;

portTickType        g_StartTick, g_EndTick;

static void         Thread1(void* pvParameters);
void                Thread0(void);

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

    Serial.println("setup\tcreate threads");

    erpc::Thread thread1(
        &Thread1,
        PRIORITY_CRITERION,
        STACK_SIZE,
        "Thread 1"
    );

    Serial.println("setup\tstart threads");

    erpc::Thread* pThreadsArray[] = {
        &thread1
    };
    for (erpc::Thread* pt : pThreadsArray) {
        pt -> start();
    }

    Serial.println("setup\tstart Thread0()");

    Thread0(); /* 発散 */
    Serial.println("exception: loop avoided or end: Thread0");

    Serial.println("setup\tend setup");
}

void loop() {}

void Thread0(void) {
    Serial.println("0\tstart");

    while (1) {
        Serial.println("0\trunning");

        delay(3000);
    }
}

static void Thread1(void* pvParameters) {
    (void) pvParameters;

    Serial.println("1\tstart");

    while (1) {
        Serial.println("1\trunning");

        {
            erpc::Mutex::Guard guard(g_Mutex);
            g_StartTick = xTaskGetTickCount();
        }

        delay(4000);

        {
            erpc::Mutex::Guard guard(g_Mutex);
            g_EndTick = xTaskGetTickCount();
        }

        Serial.print("1\t");
        Serial.println((g_EndTick - g_StartTick) * portTICK_RATE_MS);

        delay(4000);
    }

}

serial monitor
setup	create threads
setup	start threads
1	start
1	running
setup	start Thread0()
0	start
0	running
0	running
1	4000
0	running
1	running
0	running
1	4000
0	running
0	running
1	running
0	running
1	4000
0	running

停止せず動作した。

amenaruyaamenaruya

解決

while (!msDelay.justFinished());が間違っていた。これでは、一つのthreadが場を明け渡さないで居ることとなる。手法は関係なかった。莫迦〳〵しい。

millisDelay with Thread
#include "rpcWiFi.h"
#include <millisDelay.h>

constexpr uint16_t  PRIORITY_CRITERION  = configMAX_PRIORITIES - 10;
const uint16_t      STACK_SIZE          = 256;

static void         Thread1(void* pvParameters);
void                ThreadWifiArea(void);

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

    Serial.println("setup\tcreate threads");

    erpc::Thread thread1(
        &Thread1,
        PRIORITY_CRITERION,
        STACK_SIZE,
        "Thread 1"
    );

    Serial.println("setup\tstart threads");

    erpc::Thread* pThreadsArray[] = {
        &thread1
    };
    for (erpc::Thread* pt : pThreadsArray) {
        pt -> start();
    }

    Serial.println("setup\tstart ThreadWifiArea()");

    ThreadWifiArea(); /* 発散 */
    Serial.println("exception: loop avoided or end: ThreadWifiArea");

    Serial.println("setup\tend setup");
}

void loop() {}

void ThreadWifiArea(void) {
    Serial.println("WiFi\tstart");

    millisDelay     msDelay;

    while (1) {
        Serial.println("WiFi\trunning");

        msDelay.start(3000);
        while (!msDelay.justFinished()) {
            delay(1000);
        }
    }
}

static void Thread1(void* pvParameters) {
    (void) pvParameters;

    Serial.println("1\tstart");

    millisDelay     msDelay;

    while (1) {
        Serial.println("1\trunning");

        msDelay.start(4000);
        while (!msDelay.justFinished()) {
            delay(1000);
        }
    }
}

serial monitor
setup	create threads
setup	start threads
1	start
1	running
setup	start ThreadWifiArea()
WiFi	start
WiFi	running
WiFi	running
1	running
WiFi	running
1	running
WiFi	running
1	running
WiFi	running
WiFi	running
1	running

amenaruyaamenaruya

未解決

での解決を要約すると、「millisDelaydelayと同様の働きを実現する場合、delayを使わなければならない」という話である。

結局delayを使うのだから、端からdelayを使えばよい

本末転倒

そもそも「millisDelaydelayと同様の働きを実現する」必要があったのは、delayが使えない現象に遭遇したためである。しかし、未だこの現象の再現には至って居らず、更にはmillisDelayでの代用も不適切であることが言えた。

amenaruyaamenaruya

(表面上の)原因はSerial.print

本スクラップにはmillisDelayが良く表れたが、これは本源ではない。delayで停止する問題こそが本源であり、その問題をmillisDelayで回避しようとしたところ、失敗したのであった。

問題の原因は、シリアル出力である。しかし、なぜdelayで停止していたのかまでは分からず。

既出の問題

WiFiとFreeRTOSを両立するとき、なぜかシリアル出力に制限が掛かることがこれまでにも発覚していた。formatを使った文字列を表示すると、エラーコード2(malloc failed)で停止する。前回のスクラップを参照。

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

新出の問題

今回発覚したのは、小数値を表示することでも停止するということである。エラーコードは同じく2である。

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

    unsigned int flag = 10;

    float flag2 = static_cast<float>(flag) / 3.3f;

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

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

        Serial.print("Thread A\tfloat: ");
        Serial.println(flag2);

        flag++;
        flag2 = static_cast<float>(flag) / 3.3f;
        delay(1000);
    }
}
serial monitor
Thread A	Started
Thread A	Hi
Thread A	flag: 10
Thread A	float: 3.03

Serial.printlnSerial.printに換えても結果は同じであった。

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

    unsigned int flag = 10;

    double flag3 = static_cast<double>(flag) / 3.3d;

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

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

        Serial.print("Thread A\tdouble: ");
        Serial.println(flag3);

        flag++;
        flag3 = static_cast<double>(flag) / 3.3d;

        delay(1000);
    }
}
serial monitor
Thread A	Started
Thread A	Hi
Thread A	flag: 10
Thread A	double: 3.03

事の深刻程度

formatが使えないという問題に就いては、使わなければ宜かったので然程困るものではなかった。しかし小数を表示できない問題では、表示しないことを除いて、対策が分からない。表示しなければ動くとはいえ、愈愈不便を齎すところまできた。