ArduinoでFreeRTOS（FreeRTOS理解その１：セマフォ、キューなど）

# Arduino上でFreeRTOSの基本的な動作を試す
## FreeRTOS
[本家サイトはこちら](https://www.freertos.org/index.html)
## Arduinoでの準備
FreeRTOSライブラリをインストールする。
![ライブラリインストール.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/876ecc30-baed-a085-cff5-309f5801fcb4.png)
インストールするとFreeRTOSに関するいくつかのスケッチサンプルが見つかる。今回は、タスク間通信を試したかったので、次のサンプルを利用した（組み合わた）。
    - AnalogRead_DigitalRead　セマフォ
    - integerQueue　　　　　　　キュー
    - TaskStatus　　　　　　　　タスクSuspend/Resume

## 具体的に実現したいこと
- PCからの入力数値の回数分、LEDのオンオフを行う
- PCからの入力により、LEDのオンオフを一時的に止める、その後再開する
- LEDのオンオフのメッセージおよびボタン押下のメッセージをPCに送るが、共有資源（ここではシリアル回線）の競合でメッセージが送れないケースがあることの確認

## ソースコード
### インクルード、関数定義など
``` C++
#include <Arduino_FreeRTOS.h>  // Include Arduino FreeRTOS library
#include <queue.h>             // Include queue support
#include <semphr.h>            // add the FreeRTOS functions for Semaphores (or Flags).

//define task handles
TaskHandle_t TaskBlink_Handler;
TaskHandle_t TaskButton_Handler;
TaskHandle_t TaskSerial_Handler;

QueueHandle_t InterTaskQueue; // define Queue
SemaphoreHandle_t SerialSemaphore; // define Semaphore

// define tasks for Blink & Button & Serial
void TaskBlink(void *pvParameters);
void TaskButton(void *pvParameters);
void TaskSerial(void* pvParameters);
```
- タスクはArduino上のコンポネートを扱う次の３つ
 1. LED（TaskBlink）
 2. ボタン（TaskButton）
 3. PCとの通信（TaskSerial）
- タスク間のデータ通信用のキュー（InterTaskQueue）
- 共有資源（ここではSerial回線）をコントロールするセマフォ（SerialSemaphore）

その他は上記のとおり。

### Arduinoお決まりのsetup(),loop()
``` C++
void setup() {
  Serial.begin(9600);
  
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.
  }

  if ( SerialSemaphore == NULL )  // Check to confirm that the Serial Semaphore has not already been created.
  {
    SerialSemaphore = xSemaphoreCreateMutex();  // Create a mutex semaphore we will use to manage the Serial Port
    if ( ( SerialSemaphore ) != NULL )
      xSemaphoreGive( ( SerialSemaphore ) );  // Make the Serial Port available for use, by "Giving" the Semaphore.
  }

  // Create a queue
  InterTaskQueue = xQueueCreate(1, // Queue length
                        sizeof(int)); // Queue item size
                                
  if (InterTaskQueue != NULL) {
     xTaskCreate(
      TaskBlink
      ,  "Blink"   // A name just for humans
      ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
      ,  NULL //Parameters passed to the task function
      ,  2  // Priority, with 2 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
      ,  &TaskBlink_Handler );//Task handle

    xTaskCreate(TaskButton, "Button", 128, NULL, 1, &TaskButton_Handler);
    xTaskCreate(TaskSerial, "Serial", 128, NULL, 0, &TaskSerial_Handler);
    
    vTaskStartScheduler();  // start scheduler, however, if it doesn't exist, it seems to work.
  }
}  
  
void loop()
{
  // Empty. Things are done in Tasks.
}
```
1. setup()
 - セマフォ作成
 - キュー作成
 - タスク作成（優先度Priorityには注意が必要のようです。）
2. loop()
 - なし

### TaskSerial
``` C++
void TaskSerial(void* pvParameters){
  (void) pvParameters;
  
   for (;;) // A Task shall never return or exit.
   {
    while(Serial.available()>0){
      String str = Serial.readString();
      if (str[0] == 's') {
        vTaskSuspend(TaskBlink_Handler); // Make TaskBlink Suspend
        Serial.println("Suspend!");
      } else if (str[0] == 'r') {
        vTaskResume(TaskBlink_Handler);  // Make TaskBlink Resume
        Serial.println("Resume!");
      } else { // not considering all error cases
        int val = str.toInt();
        xQueueSend(InterTaskQueue, &val, portMAX_DELAY);  // Send value to TaskBlink
      }
      vTaskDelay(1);
    }
   }
}
```
シリアル回線から受け取るデータによるアクションの定義
- 's'を受け取ったら、TaskBlinkを一時中断
- 'r'を受け取ったら、TaskBlinkを再開
- それ以外（ここでは数値、細かいエラー制御は省略）は数値に変換してキューに積む

vTaskDelay()で他のタスクに制御が確実移行する（ようだ）。

### TaskButton
``` C++
void TaskButton(void *pvParameters)
{
  (void) pvParameters;
  const int DIN_PIN = 7;  // Pin from 5V is connected to one leg of button. The other leg is connected to GND.
  int value;
  
  pinMode( DIN_PIN, INPUT_PULLUP );  // External resistor is not necessary. 
  for (;;) // A Task shall never return or exit.
  {
    value = digitalRead( DIN_PIN );
    if ( value == LOW ){
      // If the semaphore is not available, wait 5 ticks of the Scheduler to see if it becomes free.
      if ( xSemaphoreTake( SerialSemaphore, ( TickType_t ) 5 ) == pdTRUE )
      {
        Serial.println("Button Pushed");
        delay(1500); // sleep at purpose to make other tasks delay getting the Serial Semaphore (prevention of Serial.println)
        xSemaphoreGive( SerialSemaphore ); // Now free or "Give" the Serial Port for others.
      }
    }
    vTaskDelay(1); // one tick delay (15ms) in between reads for stability??
  }
}
```
- PIN 7をPULL_UPとして入力に設定
- PIN 7から入力があったらセマフォ取得
- シリアル回線にメッセージを送る
- **わざと**1500ms眠る（TaskBlinkのセマフォ取得を妨げるため）
- セマフォ解放

### TaskBlink
``` C++
void TaskBlink(void *pvParameters)
{
  (void) pvParameters;
  pinMode(LED_BUILTIN, OUTPUT);
  
  for (;;) // A Task shall never return or exit.
  {
    int count;
    if (xQueueReceive(InterTaskQueue, &count, portMAX_DELAY) == pdPASS) {
      for (int i=0; i<count; i++) {
        if ( xSemaphoreTake( SerialSemaphore, ( TickType_t ) 5 ) == pdTRUE )
        {
          char buf[10];
          sprintf(buf, "N=%d", i+1);
          Serial.println(buf);
          xSemaphoreGive( SerialSemaphore ); // Now free or "Give" the Serial Port for others.
        }
        digitalWrite(LED_BUILTIN, HIGH);
        vTaskDelay( 500 / portTICK_PERIOD_MS ); // wait for 500ms
        digitalWrite(LED_BUILTIN, LOW);
        vTaskDelay( 500 / portTICK_PERIOD_MS ); // wait for 500ms
      }
    }
  }
}
```
- Arduino搭載LEDの設定
- キューからデータ（LEDのオンオフを行う回数）を受け取る
- セマフォ取得
- 何回目のLEDオンオフかのメッセージをシリル回線に送る
- LEDのオンオフ

## 実験
### TaskBlinkの一時中断および再開
![Suspend&Resume.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/933348d4-14ae-23fb-f40b-fc33df9d0d26.png)
再開（Resume）されるまで、TaskBlinkのメッセージ送信が止まる。

### セマフォロック中のためメッセージが送れないケース
![資源ロック中のSleepの結果.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/c7d385e2-dfd2-0242-66d4-2b8bc73807f2.png)
TaskButtonにてボタン押下後1500msもSleepし、TaskBlinkにてセマフォ取得できず、"N=4"および"N=6"メッセージが送ることができていない。

## おわりに
次は、タスク優先度（Priority）の実験を行う。