FreeRTOSでマルチタスク (on ESP32)

ESP32上でFreeRTOSによるマルチタスクを使う必要があったのでメモ。

## 利点
- 複数のタスクを、他の処理時間に影響を受けずに実行できる
- 優先順位や実行時間の振り分けはRTOSにお任せできる
- 複数のタスクを、シンプルに記述することができる

## RTOSにおける最低限の主なAPI
- キュー：共有データの送受信
    - キューにデータをFIFOで積む
    - キューにデータがあった場合のみ処理が実行される
- セマフォ：動作許可とそれによる同期処理
    - バイナリセマフォ：ほぼミューテックスと一緒 (FreeRTOSはこっち)
    - カウンティングセマフォ：アクセス数監視と排他制御
- ミューテックス：複数スレッドからの共有資源アクセスの排他制御

### API

####キューデータ通信
- xQueueCreate()
- xQueueSend()
- xQueueReceive()

#### セマフォ同期
- xSemaphoreCreateBinary()
- xSemaphoreGiveFromISR()
- xSemaphoreTake()

#### ミューテックス排他制御 
- xSemaphoreCreateMutex()
- xSemaphoreGive()
- xSemaphoreTake()

## FreeRTOSにおけるセマフォとミューテックス

- 動作の許可を示すバイナリセマフォを同期で使う時はセマフォ
- 排他制御で使う時はミューテックスと呼ぶ
- 使用するAPIは、どちらもxSemaphoreGive()とxSemaphoreTake()
- 正しくは違うがざっくりいうとこんな感じ

これら2つの明確な違いは、

- セマフォ同期のvSemaphoreCreateBinary()では、初期値で動作許可が無い
- ミューティックス排他制御のxSemaphoreCreateMutex()では、初期値で動作許可がある

## サンプルコード

### マルチタスクを作成するAPI
``` xtaskCreatePinnedToCore.cpp
 BaseType_t xTaskCreatePinnedToCore(
    TaskFunction_t pvTaskCode,        // task_func_name
    const char *constpcName,          // task_name
    const uint32_t usStackDepth,      // stack_memory_size
    void *constpvParameters,          // ptr that will be used as the param for the task being created
    UBaseType_t uxPriority,           // task_priority (0-25, priority is 0 < 25)
    TaskHandle_t *constpvCreatedTask, // task_handle_pointer
    const BaseType_t xCoreID          // core ID
 )
```

### シンプルなマルチタスク
``` simple_multitask.cpp
 void task0(void* arg)
 {
     while (1)
     {
         static int count = 0;
         Serial.print("task 0 : ");
         Serial.println(count++);
         delay(1000);
     }
 }
 
 void task1(void* arg)
 {
     while (1)
     {
         static int count = 0;
         Serial.print("task 1 : ");
         Serial.println(count++);
         delay(2000);
     }
 }
 
 void setup()
 {
     Serial.begin(115200);
 
     // create tasks
     xTaskCreatePinnedToCore(task0, "Task0", 4096, NULL, 1, NULL, 0);
     xTaskCreatePinnedToCore(task1, "Task1", 4096, NULL, 1, NULL, 1);
 }
 
 void loop()
 {
     static int count = 0;
     Serial.print("main   : ");
     Serial.println(count++);
     delay(500);
 }
``` 

### キューによる通信

``` queue.cpp
 QueueHandle_t xQueue;
 
 static void vSendTask (void *pvParameters)
 {
     BaseType_t xStatus;
     int32_t SendValue = 10;
 
     while (1)
     {
         xStatus = xQueueSend(xQueue, &SendValue, 0);
 
         if(xStatus != pdPASS) // send error check
         {
             while(1)
             {
                 Serial.println("rtos queue send error, stopped");
                 delay(1000);
             }
         }
         if(SendValue == 10) SendValue = 20;
         else                SendValue = 10;
 
         delay(2000);
     }
 }
 
 static void vRecvTask(void *pvParameters)
 {
     BaseType_t xStatus;
     int32_t ReceivedValue = 0;
     const TickType_t xTicksToWait = 500U; // [ms]
 
     while (1)
     {
         // loop in xTicksToWait and
         // when data is received, this func is triggerred
         xStatus = xQueueReceive(xQueue, &ReceivedValue, xTicksToWait);
 
         Serial.println("check if data is received");
 
         if(xStatus == pdPASS) // receive error check
         {
             Serial.print("received data : ");
             Serial.println(ReceivedValue);
         }
         else
         {
             if(uxQueueMessagesWaiting(xQueue) != 0)
             {
                 while(1)
                 {
                     Serial.println("rtos queue receive error, stopped");
                     delay(1000);
                 }
             }
         }
     }
 }
 
 void setup()
 {
     Serial.begin(115200);
 
     // create queue : type = int32_t, size = 5
     xQueue = xQueueCreate(5, sizeof(int32_t));
 
     if(xQueue != NULL)
     {
         xTaskCreate(vSendTask, "TaskA", configMINIMAL_STACK_SIZE, NULL, 1, (TaskHandle_t *) NULL);
         xTaskCreate(vRecvTask, "TaskB", configMINIMAL_STACK_SIZE, NULL, 2, (TaskHandle_t *) NULL);
         Serial.println("tasks registered");
     }
     else
     {
         while(1)
         {
             Serial.println("rtos queue create error, stopped");
             delay(1000);
         }
     }
 }
 
 void loop()
 {
 }
```

### セマフォによる割り込み同期

``` semaphore.cpp
 SemaphoreHandle_t xBinarySemaphore;
 
 void handleIRQ(void)
 {
     BaseType_t xHigherPriorityTaskWoken;
     xHigherPriorityTaskWoken = pdFALSE;
 
     xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken); // can be used in ISR
 }
 
 static void vISRTask (void *pvParameters)
 {
     BaseType_t xStatus;
     const TickType_t xTicksToWait = 1000U; // [ms]
 
     Serial.println("check for isr");
 
     while(1)
     {
         xStatus = xSemaphoreTake(xBinarySemaphore, xTicksToWait);
 
         Serial.println("check for interrupt");
 
         if(xStatus == pdTRUE)
         {
             static int count = 0;
             Serial.print("take : ");
             Serial.println(count++);
         }
     }
 }
 
 
 void setup()
 {
     Serial.begin(115200);
 
     xBinarySemaphore = xSemaphoreCreateBinary();
 
     if(xBinarySemaphore != NULL)
     {
         xTaskCreate(vISRTask, "Task", configMINIMAL_STACK_SIZE, NULL, 1, (TaskHandle_t *)NULL);
     }
     else
     {
         while(1)
         {
             Serial.println("rtos semaphore create error, stopped");
             delay(1000);
         }
     }
 }
 
 void loop()
 {
     // virtual interrupt
     Serial.println("give");
     handleIRQ();
     delay(2000);
 }
```

### ミューテックスによる排他制御

``` mutex.cpp
 SemaphoreHandle_t xMutex = NULL;
 int sharedResource = 0;
 
 static void vTask1(void *pvParameters)
 {
     BaseType_t xStatus;
     const TickType_t xTicksToWait = 1000UL; //
     xSemaphoreGive(xMutex);
 
     while(1)
     {
         xStatus = xSemaphoreTake(xMutex, xTicksToWait);
 
         Serial.println("check for mutex (task1)");
 
         if(xStatus == pdTRUE)
         {
             sharedResource = 100;
             Serial.print("shared resource change by task1 : ");
             Serial.println(sharedResource);
         }
 
         xSemaphoreGive(xMutex);
         delay(500);
     }
 }
 
 static void vTask2(void *pvParameters)
 {
     BaseType_t xStatus;
     const TickType_t xTicksToWait = 500UL;
     xSemaphoreGive(xMutex);
 
     while(1)
     {
         xStatus = xSemaphoreTake(xMutex, xTicksToWait);
 
         Serial.println("check for mutex (task2)");
 
         if(xStatus == pdTRUE )
         {
             sharedResource = 1;
             Serial.print("shared resource change by task2 : ");
             Serial.println(sharedResource);
         }
 
         xSemaphoreGive(xMutex);
         delay(1000);
     }
 }
 
 void setup()
 {
     Serial.begin(115200);
 
     xMutex = xSemaphoreCreateMutex();
 
     if( xMutex != NULL )
     {
         // higher priority task is done before lower priority task
         // if there are some same priority tasks, they are kicked in order of registration
         xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1,(TaskHandle_t *) NULL);
         xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2,(TaskHandle_t *) NULL);
     }
     else {
         while(1)
         {
             Serial.println("rtos mutex create error, stopped");
             delay(1000);
         }
     }
 }
 
 void loop()
 {
 }
```

## その他：マルチタスクにおけるdelay()について
- esp32-arduinoでは、delay() は vTaskDelay() で実装されてるので、タスク内でも使える

## その他：関数などのプレフィックスについて
- x：非 stdint 型以外の返り値、size_t の返り値を持つ関数
- v：void 型の関数
- u：unsigned な返り値
- ux：unsigned な 非 stdint 型以外の返り値
- etc.
- [Coding Standard and Style Guide > Naming Conventions](https://www.freertos.org/FreeRTOS-Coding-Standard-and-Style-Guide.html#NamingConventions)


## 参考

- [FreeRTOS](https://www.freertos.org/)
- [FreeRTOS API Reference](https://www.freertos.org/a00106.html)
- [FreeRTOS Manual/Tutrial](https://www.freertos.org/Documentation/RTOS_book.html)
- [ESPRESSIF FreeRTOS API](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/freertos.html)
- http://happytech.jp/bRTOS.html → こちらを参考にソースコードも記載させていただきました
- https://hamayan.blog.so-net.ne.jp/2018-02-26-3
- https://hamayan.blog.so-net.ne.jp/2018-03-08-1