ボタン押下によるタスク切換えの実現
今回は、改良版かつ少々実用的なケースをトライ。
vTaskSuspend()とvTaskResume()
- vTaskSuspend()
-
vTaskResume()
とを使う。vTaskSuspend()の引数に”NULL”を指定すると自らSleep(Suspend)するところもミソの一つ。
用いたデバイスおよび開発環境
ESP32を搭載するM5Stack。最近は安くなった。このサイトでは、現時点で3575円。だいぶ前に買った時は5000円くらいもした。今は、第2世代も発売されているようだ。開発環境はArduino IDEを利用。
ソースコード
タスク構成
タスクは全部で3つ(切り替え対象となるタスクが2つ、ボタンを扱うタスク)。
# define BTN 38 // Middle button in M5Stack
xTaskHandle xTask1;
xTaskHandle xTask2;
xTaskHandle xTaskBtn;
uint8_t t_flag = 0; // for which task to run
どのタスクが実行されるかのフラグ”t_flag”を設けた。なお、ESP32では、FreeRTOSを明示的に記載しなくても(#includeしなくても)、Buildしてくれる。
(切り替えの対象となる)タスク2つ
void Task1(void *arg) {
uint8_t i = 0;
while (1) { // infinite loop
vTaskSuspend(NULL); // Sleep by oneself
while (t_flag == 1) {
if (i == 0) {
Serial.println(" Task1 is running.");
i++;
}
delay(1);
}
Serial.println(" Task1 is stopped.");
i = 0;
}
}
void Task2(void *arg) {
uint8_t i = 0;
while (1) { // infinite loop
vTaskSuspend(NULL); // Sleep by oneself
while (t_flag == 2) {
if (i == 0) {
Serial.println(" Task2 is running.");
i++;
}
delay(1);
}
Serial.println(" Task2 is stopped.");
i = 0;
}
}
フラグ”t_flag”が'1'の時はTask1が、'2'の時はTask2が実行される。まず起動時に自らSleepする(vTaskSuspend(NULL))。別タスク(ボタンを扱うタスク)から起こされると(vTaskResume())とフラグと比較し、自分宛てであれば実行。そうでなければ、再度Sleepする。各タスクでは、タスクが実行されているメッセージ、止まっているメッセージを表示するのみ。
なお、ESP32では、delay()を呼び出すと、vTaskDelay()が呼び出されるらしい。たとえば、こんな記事、他にも類似の記事が複数見つかる。
ボタンを扱うタスク
void TaskBtn(void *arg) {
unsigned long t = 0, p;
while (1) { // infinite loop
while (!digitalRead(BTN)) {
if (!t) {
t = millis();
}
}
if (t) {
p = millis()-t;
if (p > 3000) {
Serial.println("Long Push");
t_flag = 2;
vTaskResume(xTask2);
} else if (p > 500) {
Serial.println("Short Push");
t_flag = 1;
vTaskResume(xTask1);
} else {
Serial.println("Very Short Push");
t_flag = 0;
}
t = 0;
}
delay(1);
}
}
ボタンの長押しでTask2を起こし(t_flagに"2"を代入してvTaskResume(xTask2)を呼び出し)、普通押しでTask1(t_flagに"1"を代入してvTaskResume(xTask1)を呼び出し)、クイック押しで動作中タスクをSuspend(何もしない、t_flagに"0"を代入)としている。また、それぞれ該当するメッセージを表示。
Arduinoお決まり部分
void setup() {
Serial.begin(9600);
pinMode(BTN, INPUT);
xTaskCreate(&TaskBtn, "TaskBtn", 2048, NULL, 1, &xTaskBtn);
xTaskCreate(&Task1, "Task1", 2048, NULL, 0, &xTask1);
xTaskCreate(&Task2, "Task2", 2048, NULL, 0, &xTask2);
}
void loop() {
delay(1); // Seem to have "Must"
}
初期設定およびタスク生成。ESP32のArduino環境では、vTaskStartScheduler()を呼び出す必要はないらしい(参考記事)。また、loop()もタスクの一つらしく、タスクスイッチを起こすためのdelay()(=vTaskDelay())を入れた方がよいようだ(loop()自体がタスク、参考記事)。
実験
シリアルコンソールの表示は下記のとおり。
Short Push
Task1 is running.
Very Short Push
Task1 is stopped.
Long Push
Task2 is running.
Very Short Push
Task2 is stopped.
Short Push
Task1 is running.
Long Push
Task2 is running.
Task1 is stopped.
Short Push
Task1 is running.
Task2 is stopped.
Very Short Push
Task1 is stopped.
Very Short Push
期待どおり、ボタンの長押しでTask2が実行され、、普通押しでTask1が実行され、クイック押しでタスクがStopしている。成功。
終わりに
もっと美しい方法(フラグを使わない方法)もあるだろう。ここでは、あえてフラグを使った。