FreeRTOS理解その４（ボタン押下によるタスク切り替えその３）

# ボタン押下によるタスク切換えの実現
今回は、改良版かつ少々実用的なケースをトライ。

## vTaskSuspend()とvTaskResume()
[FreeRTOS API](https://www.freertos.org/a00106.html)の[Task Control](https://www.freertos.org/a00112.html)にある
- [vTaskSuspend()](https://www.freertos.org/a00130.html)
- [vTaskResume()](https://www.freertos.org/a00131.html)
とを使う。vTaskSuspend()の引数に”NULL”を指定すると自らSleep(Suspend)するところもミソの一つ。

## 用いたデバイスおよび開発環境
ESP32を搭載する[M5Stack](https://www.switch-science.com/catalog/3647/)。最近は安くなった。このサイトでは、現時点で3575円。だいぶ前に買った時は5000円くらいもした。今は、[第２世代](https://www.switch-science.com/catalog/6530/)も発売されているようだ。開発環境はArduino IDEを利用。

## ソースコード
### タスク構成
タスクは全部で３つ（切り替え対象となるタスクが２つ、ボタンを扱うタスク）。

```c++
#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してくれる。

### （切り替えの対象となる）タスク２つ

```c++
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()が呼び出されるらしい。たとえば、こんな[記事](https://www.esp32.com/viewtopic.php?t=2379)、他にも類似の記事が複数見つかる。

### ボタンを扱うタスク

```c++
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お決まり部分

```c++
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()を呼び出す必要はないらしい（[参考記事](https://hamayan.blog.ss-blog.jp/2018-02-18-1)）。また、loop()もタスクの一つらしく、タスクスイッチを起こすためのdelay()（=vTaskDelay()）を入れた方がよいようだ（loop()自体がタスク、[参考記事](https://lang-ship.com/blog/work/esp32-freertos-l03-multitask/)）。

## 実験
シリアルコンソールの表示は下記のとおり。

```
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している。成功。


## 終わりに
もっと美しい方法（フラグを使わない方法）もあるだろう。ここでは、あえてフラグを使った。