FreeRTOSのvTaskResumeは、タスクのどこから再開するのか（結論: suspendしたところから)

`vTaskSuspend()` で suspend となったタスクを `vTaskResume()` で再開すると、どこから再開するのか？という素朴な疑問を確認しました。

検証環境:

* ボード: Wio LTE JP Version (STM32F405RG)
* FreeRTOS: Arduino_FreeRTOS_Library 11.1.0

## 結論

suspend したところから再開します。タスクの先頭からの再開ではありません。

※ 挙動や目的を考えると、当たり前と言えば当たり前ですが。

### コードと結果

* `pushToQueueTask`  `popFromQueueTask` `startAndStopTask` という３つのタスクを作成
* `pushToQueueTask` からキューを通して `popFromQueueTask` へカウントデータを1秒毎に送信、受信側でデータを表示
* `startAndStopTask` が不定期(1.0秒～2.5秒毎)に `pushToQueueTask` を suspend/resume している

```c++:where_is_resuming_from.ino
#include <WioLTEforArduino.h>
#define CONSOLE SerialUSB
#include <STM32FreeRTOS.h>
#include <STM32FreeRTOSConfig_extra.h>  // ref: https://qiita.com/ma2shita/items/88036fa3a75ffa684ec2

WioLTE Wio;
TaskHandle_t pushToQueueTask_h = NULL;
QueueHandle_t xQueue;
struct QUEUE_PAYLOAD {
  uint8_t data;
};

void pushToQueueTask(void *pvParameters) {
  CONSOLE.println("(pushToQueueTask) init.");
  QueueHandle_t q = (QueueHandle_t)pvParameters;
  struct QUEUE_PAYLOAD q_payload;
  for (;;) {
    for (uint8_t i = 0; i < 100; i++) {
      q_payload.data = i;
      xQueueSend(q, &q_payload, portMAX_DELAY);
      vTaskDelay(pdMS_TO_TICKS(1000));
    }
  }
}

void popFromQueueTask(void *pvParameters) {
  QueueHandle_t q = (QueueHandle_t)pvParameters;
  struct QUEUE_PAYLOAD q_payload;
  for (;;) {
    xQueueReceive(q, &q_payload, portMAX_DELAY);
    CONSOLE.printf("(popFromQueueTask) q_payload.data: %d\r\n", q_payload.data);
  }
}

void startAndStopTask(void *pvParameters) {
  for (;;) {
    if (eTaskGetState(pushToQueueTask_h) == eSuspended) {
      CONSOLE.println("(startAndStopTask) vTaskResume()");
      vTaskResume(pushToQueueTask_h);
    } else {
      CONSOLE.println("(startAndStopTask) vTaskSuspend()");
      vTaskSuspend(pushToQueueTask_h);
    }
    vTaskDelay(pdMS_TO_TICKS(random(10, 26)*100));
  }
}

void setup() {
  CONSOLE.begin(9600);
  delay(2000);
  Wio.Init();

  xQueue = xQueueCreate(1, sizeof(QUEUE_PAYLOAD));
  xTaskCreate(popFromQueueTask, nullptr, 1024, xQueue, 1, nullptr);
  xTaskCreate(pushToQueueTask, nullptr, 1024, xQueue, 1, &pushToQueueTask_h);
  xTaskCreate(startAndStopTask, nullptr, 1024, nullptr, 1, nullptr);
  vTaskStartScheduler();

  for (;;) __asm__("wfi");
}

void loop() {
  // Unnecessary Impl.
}
```

結果は以下の通りです。
init は `xCreateTask()` の直後１回のみ。以降は suspend したところから resume しています。

```
(pushToQueueTask) init.
(startAndStopTask) vTaskSuspend()
(popFromQueueTask) q_payload.data: 0
(startAndStopTask) vTaskResume()
(popFromQueueTask) q_payload.data: 1
(popFromQueueTask) q_payload.data: 2
(popFromQueueTask) q_payload.data: 3
(startAndStopTask) vTaskSuspend()
(startAndStopTask) vTaskResume()
(popFromQueueTask) q_payload.data: 4
(popFromQueueTask) q_payload.data: 5
(startAndStopTask) vTaskSuspend()
(startAndStopTask) vTaskResume()
(popFromQueueTask) q_payload.data: 6
(popFromQueueTask) q_payload.data: 7
```

## タスクの初期処理をしたければ vTaskDelete/xCreateTask を使う

タスクを初期処理から動かしたければ、`vTaskDelete()`でタスクを削除し、`xCreateTask()` でタスクを作ることで実現できます。

`startAndStopTask()` を、以下のように差し替えます。

```c++:
void startAndStopTask(void *pvParameters) {
  for (;;) {
    if (eTaskGetState(whereIsResumingFromTask_h) == eDeleted) {
      CONSOLE.println("(startAndStopTask) xTaskCreate");
      xTaskCreate(pushToQueueTask, nullptr, 1024, xQueue, 1, &pushToQueueTask_h);
    } else {
      CONSOLE.println("(startAndStopTask) vTaskDelete");
      vTaskDelete(pushToQueueTask_h);
    }
    vTaskDelay(pdMS_TO_TICKS(random(10, 26)*100));
  }
}
```

結果は以下の通り。

```
(pushToQueueTask) init.
(startAndStopTask) vTaskDelete
(popFromQueueTask) q_payload.data: 0
(startAndStopTask) xTaskCreate
(pushToQueueTask) init.
(popFromQueueTask) q_payload.data: 0
(popFromQueueTask) q_payload.data: 1
(popFromQueueTask) q_payload.data: 2
(startAndStopTask) vTaskDelete
(startAndStopTask) xTaskCreate
(pushToQueueTask) init.
(popFromQueueTask) q_payload.data: 0
(popFromQueueTask) q_payload.data: 1
(startAndStopTask) vTaskDelete
(startAndStopTask) xTaskCreate
(pushToQueueTask) init.
(popFromQueueTask) q_payload.data: 0
(popFromQueueTask) q_payload.data: 1
(startAndStopTask) vTaskDelete
```

このままだと、xTaskCreate の引数設定が冗長なので、関数で共通化したほうが良いかもしれません。

EoT