FreeRTOS理解その12（タスクプライオリティ）

# タスクプライオリティ（優先度）による動作の振る舞いを知る
今回のお題は、FreeRTOSにてタスクプライオリティにより、複数のタスクがどのように振る舞うかを検証する。

## 環境、タスク構成
Arduino利用。３つのタスクを設ける。
- シリアルタスク（SerialIntr）：PCからの入力時に、キューにメッセージを送信
- タスクA（TaskA）：0から63までインクリメントした数値を「常に」PCに出力（63の次は0に戻る）
- タスクB（TaskB）：前述したキューでメッセージを受信したとき「のみ」PCにメッセージを出力

PCに出力するタイミングがポイント。

## ソースコード
#### FreeRTOS導入
```c++
#include <Arduino_FreeRTOS.h>
#include <queue.h>

TaskHandle_t xSerial, xTaskA, xTaskB;
QueueHandle_t qMsg;
```

タスクハンドラ、キューの定義。

#### シリアルタスク
```c++
void SerialIntr(void *arg) {
  static uint8_t val = 1;
  for (;;) {
    if (Serial.available()) {
      (void)Serial.readString(); 
      xQueueSend(qMsg, &val, portMAX_DELAY);
      val++;
    }
    vTaskDelay(1);  
  }
}
```

PCから入力されたデータは読み捨てる。キューにメッセージ（数値）を積む。数値は1から始まり、キューに積むたびにインクリメントする。

#### タスクA
```c++
void TaskA(void *arg) {
  uint8_t val;
  char buf[8];
  static uint8_t count = 0;
  uint32_t i, sum;
  for (;;) {
    sprintf(buf, "%2d ", count);
    Serial.print(buf);
    count++;
    if (!(count % 32)) {
      Serial.println("");
    }
    if (count == 64) {
      count = 0;
    }
    vTaskDelay(1);    // ①
  }
}
```

単に、数値（0-63）をPCへ出力。63まで出力したら0に戻る。①のvTaskDelay(1)でタスクスイッチが行われる（後述）。

#### タスクB
```c++
void TaskB(void *arg) {
  uint8_t val;
  char buf[32];
  static uint32_t count = 1;
  for (;;) {
    if (xQueueReceive(qMsg, &count, portMAX_DELAY) == pdPASS) { // Sleep(= Task Switch)    ②
      Serial.println("");
      sprintf(buf, "TaskB Start: N=%d", count);
      Serial.println(buf);
      for (int i = 0; i < 10; i++) {
        sprintf(buf, " Cnt=%d", i);
        Serial.println(buf);
        //vTaskDelay(1); // Task Switch    ③
      }
      count++;    
    }
  }
}
```

キューにメッセージが積まれるまでにずっと待つ（②）。メッセージを受けると、タスクBが開始したこと及びカウンタ10回分をPCに出力する。ここでも、③のvTaskDelay(1)でタスクスイッチが行われるが、ケースにより未使用（後述）。

#### Arduinoお決まり部分
```c++
void setup() {
  Serial.begin(9600);
  qMsg = xQueueCreate(1, sizeof(uint8_t));
  xTaskCreate(SerialIntr, "Serial", 256, NULL, 3, &xSerial);    // ④
  xTaskCreate(TaskA, "TaskA", 256, NULL, 1, &xTaskA);    // ⑤
  xTaskCreate(TaskB, "TaskB", 256, NULL, 2, &xTaskB);    // ⑥
  vTaskStartScheduler();
}

void loop() {
  // Nothing
}
```

タスクプライオリティ（④、⑤、⑥の'3'、'2'、'1'）、数値が大きいほど優先度が高い。ここでは、⑤や⑥の値を変化させる（後述）。

## 結果
### Case 1：　タスクBのプライオリティ2、タスクAのプライオリティ1
タスクBの実行中、タスクAは動作しない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/d99eb375-ed7c-0581-82bc-0e89f7e25f2f.png)
タスクAが'54'出力後、次の'55'が出力されるまで、タスクB出力しかされていないことがわかる。

### Case 2：　タスクBのプライオリティ2、タスクAのプライオリティ1、タスクBのfor文内でvTaskDelay()実行（③）
タスクB実行中でも、vTaskDelay()によるタスクスイッチがあれば、タスクAは動くことがわかる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/4e7dbd21-b32b-20b1-2db4-7a1d636cb5c9.png)
タスクB出力中でも、タスクAの出力（'38'-'46'）が見える。

### Case 3： タスクBのプライオリティ1、タスクAのプライオリティ2
上述の⑤および⑥の数値の変更。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/36d2e20c-dae2-5fb3-9c4b-ebe81df3bf73.png)

タスクB実行中に、タスクBにvTaskDelay()（上述③）がなくても、タスクAは動作する。ただし、タスクAとタスクBとの出力が同時に発生し、正しく表示されていない（上記の「6Cnt=6」および「6Cnt=8」、'60'と'61'とが隠れている）。

### Case 3：　番外
正しく表示させるために、PCへの出力（シリアル出力）に排他制御を設ける。

```c++:TaskPriorityR1.ino
#include <Arduino_FreeRTOS.h>
#include <queue.h>
#include <semphr.h>  // ⑦

TaskHandle_t xSerial, xTaskA, xTaskB;
QueueHandle_t qMsg;
SemaphoreHandle_t semSerial;  // ⑧

void SerialIntr(void *arg) {
  static uint8_t val = 1;
  for (;;) {
    if (Serial.available()) {
      (void)Serial.readString(); 
      xQueueSend(qMsg, &val, portMAX_DELAY);
      val++;
    }
    vTaskDelay(1);  
  }
}

void TaskA(void *arg) {
  uint8_t val;
  char buf[8];
  static uint8_t count = 0;
  uint32_t i, sum;
  for (;;) {
    sprintf(buf, "%2d ", count);
    count++;
    if (xSemaphoreTake(semSerial, portMAX_DELAY) == pdTRUE) {  // ⑨
      Serial.print(buf);
      if (!(count % 32)) {
        Serial.println("");
      }
      xSemaphoreGive(semSerial);  // ⑩
    }
    if (count == 64) {
      count = 0;
    }
    vTaskDelay(1);
  }
}

void TaskB(void *arg) {
  uint8_t val;
  char buf[32];
  static uint32_t count = 1;
  for (;;) {
    if (xQueueReceive(qMsg, &count, portMAX_DELAY) == pdPASS) { // Sleep(= Task Switch)
      sprintf(buf, "TaskB Start: N=%d", count);
      if (xSemaphoreTake(semSerial, portMAX_DELAY) == pdTRUE) {  // ⑪
        Serial.println("");
        Serial.println(buf);
        xSemaphoreGive(semSerial);  // ⑫
      }
      for (int i = 0; i < 10; i++) {
        sprintf(buf, " Cnt=%d", i);
        if (xSemaphoreTake(semSerial, portMAX_DELAY) == pdTRUE) {  // ⑬
          Serial.println(buf);
          xSemaphoreGive(semSerial);  // ⑭
        }
        //vTaskDelay(1); // Task Swtich
      }
      count++;    
    }
  }
}

void setup() {
  Serial.begin(9600);
  qMsg = xQueueCreate(1, sizeof(uint8_t));
  semSerial = xSemaphoreCreateMutex();  // ⑮
  xSemaphoreGive(semSerial);  // ⑯
  xTaskCreate(SerialIntr, "Serial", 256, NULL, 3, &xSerial);
  xTaskCreate(TaskA, "TaskA", 256, NULL, 2, &xTaskA);
  xTaskCreate(TaskB, "TaskB", 256, NULL, 1, &xTaskB);
  vTaskStartScheduler();
}

void loop() {
  // Nothing
}
```

- ⑦、⑧、⑮、⑯：シリアル出力用セマフォの導入および初期化
- ⑨、⑩：タスクAでのセマフォ取得待ちおよび開放
- ⑪、⑫、⑬、⑭：タスクBでのセマフォ取得待ちおよび開放

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/fd0cb7d9-4691-e40b-a408-dbbafd7fd7f2.png)

正しく表示されている。

## おわりに
期待どおりの結果。