【Wio Terminal】millisDelay and WiFi with FreeRTOS
millisDelay
Wio Terminalと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
のみ
delay()
のみ
1. #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
millisDelay
2. ただの関数で#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
erpc::Thread
でmillisDelay
3. #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
millisDelay
のみ
4. #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が使えないという問題に就いては、使わなければ宜かったので然程困るものではなかった。しかし小数を表示できない問題では、表示しないことを除いて、対策が分からない。表示しなければ動くとはいえ、愈愈不便を齎すところまできた。