【Wio Terminal】millisDelay and WiFi with FreeRTOS
Wio TerminalとmillisDelay
millisDelayとは、カウントダウンができるようになるものである。一定周期で同じ処理を繰り返す場合に用いるほか、カウントダウンが終わるまでwhileで進行を防御すれば、規定時間待機するdelayと同じように用いることもできる。実装の仕方によっては、タイマーを作ることもできるだろう。
millisDelayを用いているのは、Wio TerminalのWikiで次のように紹介されているためである。
FreeRTOS(WiFi)との相性が悪い
正確には、erpc::Threadとの相性が悪い。なおerpc::Threadについては、記事及びスクラップにて既に述べている。
delayが使えない
上の記事にある通り、erpc::Threadで作成したthread内ではdelayを使うことができている。しかしmillisDelayを使用している場合において、delayが原因で停止することがあった。
delayが使えないならば、millisDelayを使うしかなかった。
millisDelayが使えない (?)
erpc::Threadで作成したthreadにて、millisDelayの定義及び処理を記述したとき、当該threadを起動した後の処理が全て停止することが確認された。
🤔🤔🤔
delay検証
-
delay()のみ - ただの関数で
millisDelay -
erpc::ThreadでmillisDelay -
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::Threadで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
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 |
delay() |
millisDelay |
|---|---|---|
delay() |
◯ | ◯ |
millisDelay |
✕ | ✕ |
この結果を要約すれば、erpc::ThreadでmillisDelayは使えないということになる。となれば、erpc::Threadの関与しない単なる関数、即ちメインスレッドで管轄する外ない。
浮上する問題
以上の結果を受けたならば、メインスレッドでmillisDelayを管理すれば宜しい事となる。しかし、この手段を却下或いは留保せざるを得ない、一つの、極めて根本的な問題がある。
抑々、FreeRTOSには標準の機能が周く備わっているにも関わらず、それらを全て忌避してerpc::Threadなどという得体の知れぬ機能を使っている。荊棘の道を征くの発端たる動機は、WiFiを使いながら、マルチスレッドを実現するためであった。
但し、erpc::Threadで起動したthreadには、WiFiに関する機能を使うことができないという欠陥が確認される。従って、WiFiに関する機能はメインスレッドに集約せざるを得ず、言い換えれば、メインスレッドは既にWiFiの独擅場として、他の機能が入り込む余地は無いのである。故に、millisDelayをメインスレッドで管理することは難しい。
現状この問題を解決する手立てとして、WiFiとmillisDelayをメインスレッドで同時に管理し、この二者についてのマルチスレッドを諦める以外に思い当たらない。若しくは、少なくとも一方をerpc::Thread或いは別の何かで扱う方法を探すことである。何と情趣の無い覓句であろうか。
millisとxTaskGetTickCount
millisDelayの定義には、millis()という関数が含まれている。
この関数は、Arduinoで時間計測に使われるものである。
しかし調べるところによれば、FreeRTOSを使う際には異なる手法を用いるという。
つまりmillisDelayは、そもそもOSでの使用を想定していないと考えるのが自然だろうか。然らば推移的に、xTaskGetTickCountで同様の実装をする必要がある。
xTaskGetTickCountで時間を計測できるか
一応警戒を怠らず、erpc::Mutexでの排他制御を併せた。また、時間を保持するportTickType型変数は、関数外に大域変数として宣言した。
#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);
}
}
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
停止せず動作した。
解決
while (!msDelay.justFinished());が間違っていた。これでは、一つの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);
}
}
}
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
未解決
上での解決を要約すると、「millisDelayでdelayと同様の働きを実現する場合、delayを使わなければならない」という話である。
結局delayを使うのだから、端からdelayを使えばよい。
本末転倒
そもそも「millisDelayでdelayと同様の働きを実現する」必要があったのは、delayが使えない現象に遭遇したためである。しかし、未だこの現象の再現には至って居らず、更にはmillisDelayでの代用も不適切であることが言えた。
(表面上の)原因はSerial.print
本スクラップにはmillisDelayが良く表れたが、これは本源ではない。delayで停止する問題こそが本源であり、その問題をmillisDelayで回避しようとしたところ、失敗したのであった。
問題の原因は、シリアル出力である。しかし、なぜdelayで停止していたのかまでは分からず。
既出の問題
WiFiとFreeRTOSを両立するとき、なぜかシリアル出力に制限が掛かることがこれまでにも発覚していた。formatを使った文字列を表示すると、エラーコード2(malloc failed)で停止する。前回のスクラップを参照。
新出の問題
今回発覚したのは、小数値を表示することでも停止するということである。エラーコードは同じく2である。
-
float型
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);
}
}
Thread A Started
Thread A Hi
Thread A flag: 10
Thread A float: 3.03
Serial.printlnをSerial.printに換えても結果は同じであった。
-
double型
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);
}
}
Thread A Started
Thread A Hi
Thread A flag: 10
Thread A double: 3.03
事の深刻程度
formatが使えないという問題に就いては、使わなければ宜かったので然程困るものではなかった。しかし小数を表示できない問題では、表示しないことを除いて、対策が分からない。表示しなければ動くとはいえ、愈愈不便を齎すところまできた。
続き