FreeRTOS理解その５（Notifyとタスク状態表示）

# FreeRTOSにて通知（Notify）とタスク状態表示を実施
FreeRTOSの本家ページの[xTaskNotifyGive()](https://www.freertos.org/xTaskNotifyGive.html)と[ulTaskNotifyTake()](https://www.freertos.org/ulTaskNotifyTake.html)のサンプルを試すと同時にタスク状態遷移を確認してみた。

## 環境
ESP32搭載のM5Stack。ESP32でのFreeRTOSの紹介は[こちら](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos.html)。Arduino IDE利用。

## FreeRTOSタスク状態遷移
[ここ](https://www.freertos.org/RTOS-task-states.html)に説明がある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/66e5152e-580d-d6f5-a866-23eec9801975.png)
ESP32でのArduino IDE環境の[task.h](https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/freertos/freertos/task.h)での状態定義は次のようになっている。

```c++
/** Task states returned by eTaskGetState. */
typedef enum
{
	eRunning = 0,	/*!< A task is querying the state of itself, so must be running. */
	eReady,			/*!< The task being queried is in a read or pending ready list. */
	eBlocked,		/*!< The task being queried is in the Blocked state. */
	eSuspended,		/*!< The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
	eDeleted		/*!< The task being queried has been deleted, but its TCB has not yet been freed. */
} eTaskState;
```

## 確認内容
３つのタスク構成。１つのタスクはボタン押下を検知し、すべてのタスク状態を表示する。２つのタスクでは、タスク間で通知を行い、別タスクから通知が来るまでは動作しないようなもの。同時にタスク状態を表示。

## ソースコード
### 最初
```c++
#define BTN 38 // Middle button in M5Stack

xTaskHandle xTaskBtn;
xTaskHandle xTask1;
xTaskHandle xTask2;

char *task_state[] = {"Running", "Ready", "Blocked", "Suspended", "Deleted"};
```
ここはほぼ自明。

### ボタンハンドリングタスク
```c++
void TaskBtn(void *arg) {
  unsigned long t = 0, p;

  for(;;) {
    while (!digitalRead(BTN)) {
      if (!t) { t = millis(); }
    }
    if (t) {
      p = millis()-t;
      if (p > 100) {  // for prevention of chattering
        Serial.printf("\nTaskBtn: Task1 state = %s\n", task_state[eTaskGetState(xTask1)]);
        Serial.printf("TaskBtn: Task2 state = %s\n", task_state[eTaskGetState(xTask2)]);
        Serial.printf("TaskBtn: TaskBtn state = %s\n", task_state[eTaskGetState(xTaskBtn)]);
      }
      t = 0;
    }
    vTaskDelay(1);
  }
}
```
ボタン押下を検知し、各タスクの状態を表示する。

### ２つのタスク
```c++
void Task1(void *arg) {
  for(;;) {
    Serial.printf("\nTask1(1): Task1 state = %s\n", task_state[eTaskGetState(xTask1)]);
    Serial.printf("Task1(1): Task2 state = %s\n", task_state[eTaskGetState(xTask2)]);
    Serial.printf("Task1(1): TaskBtn state = %s\n", task_state[eTaskGetState(xTaskBtn)]);
    vTaskDelay(5000);
    xTaskNotifyGive(xTask2); // Send notification to xTask2
    Serial.printf("\nTask1(2): Task1 state = %s\n", task_state[eTaskGetState(xTask1)]);
    Serial.printf("Task1(2): Task2 state = %s\n", task_state[eTaskGetState(xTask2)]);
    Serial.printf("Task1(2): TaskBtn state = %s\n", task_state[eTaskGetState(xTaskBtn)]);
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Block to wait for xTask2
  }
}

void Task2(void *arg) {
  for(;;) {
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Block to wait for xTask1
    Serial.printf("\nTask2(1): Task1 state = %s\n", task_state[eTaskGetState(xTask1)]);
    Serial.printf("Task2(1): Task2 state = %s\n", task_state[eTaskGetState(xTask2)]);
    Serial.printf("Task2(1): TaskBtn state = %s\n", task_state[eTaskGetState(xTaskBtn)]);
    vTaskDelay(5000);
    xTaskNotifyGive(xTask1); // Send notification to xTask1
    Serial.printf("\nTask2(2): Task1 state = %s\n", task_state[eTaskGetState(xTask1)]);
    Serial.printf("Task2(2): Task2 state = %s\n", task_state[eTaskGetState(xTask2)]);
    Serial.printf("Task2(2): TaskBtn state = %s\n", task_state[eTaskGetState(xTaskBtn)]);
  }
}
```
まずは、Task1が動作し、Task2に制御を渡し（xTaskNotifyGive()）、その後Task2からの通知（ulTaskNotifyTake()）を無限に待つ。Task2はTask1からの通知（ulTaskNotifyTake()）を無限に待ち、通知があったら動き出し、その後Task1に制御を渡す（xTaskNotifyGive()）。同時にその時のタスク状態を表示する。

### Arduino IDEお決まり部分
```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);
  Serial.printf("\nsetup(): Task1 state = %s\n", task_state[eTaskGetState(xTask1)]);
  Serial.printf("setup(): Task2 state = %s\n", task_state[eTaskGetState(xTask2)]);
  Serial.printf("setup(): TaskBtn state = %s\n", task_state[eTaskGetState(xTaskBtn)]);
}

void loop() {
  vTaskDelay(1);
}
```
ボタン初期設定、タスク作成、起動時のタスク状態を表示。タスクPriorityは、TaskBtnが1、Task1および2が0となっている。

## 実験
### 起動直後
![起動直後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/49e78d21-de02-ecb2-5522-5a0d46264fff.png)
タスク作成直後で、Task1とTask2とが”Ready”となっているが、TaskBtnが”Blocked”となっている。これはタスク作成時のPriority設定に関わっているようで、仮に、すべてのタスクPriorityを”0”とすると

```c++
  xTaskCreate(&TaskBtn, "TaskBtn", 2048, NULL, 0, &xTaskBtn);
  xTaskCreate(&Task1, "Task1", 2048, NULL, 0, &xTask1);
  xTaskCreate(&Task2, "Task2", 2048, NULL, 0, &xTask2);
```
![起動直後２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/43860426-37a4-f5b0-29f9-13db7dd8b570.png)
と、すべてのタスク状態が”Ready”となる。

タスクPriority値が大きいほうが優先度が高いらしいが、このあたりの詳細は調べていない（勘弁）。いずれにせよ、Task1が最初に動作状態”Running”となっている。

### ボタン押下なし状態
以降、TaskBtnのタスクPriorityは”1”となっている。
![ボタンなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/b22068c9-aeee-a236-2b21-a805710811fe.png)
xTaskNotifyGive()を実行すると、もう一つのタスク状態が、”Suspended” -> ”Ready” -> "Running"と変化していることがわかる。また、ulTaskNotifyTake()の前後で、もう一つのタスク状態が、”Running” -> ”Suspended”と変化していることがわかる。（”Ready”などを経由している可能性もあるが、深追いしていない。こちらも勘弁。）

### ボタン押下状態
TaskBtnが動作する。Task1動作中のボタン押下時の状態は次のようになった。
![タスク１中のボタン押下.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/12f1950f-9624-8a36-c14a-8d4cfb18ac1d.png)
TaskBtnは当然ながら”Running”状態、Task1の状態は”Running” -> ”Blocked”へと変化している。これは、Task1内でコールされたvTaskDelay()で、タスク状態が”Blocked”に移行しており、この間にボタンが押下されたからである。

Task2動作中のボタン押下時の状態も同様。
![タスク２中のボタン押下.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/1a073eb6-c714-21ee-4cf5-461e57f1efa7.png)

## 感想
タスク状態および遷移は奥が深い。