FreeRTOSにて通知(Notify)とタスク状態表示を実施
FreeRTOSの本家ページのxTaskNotifyGive()とulTaskNotifyTake()のサンプルを試すと同時にタスク状態遷移を確認してみた。
環境
ESP32搭載のM5Stack。ESP32でのFreeRTOSの紹介はこちら。Arduino IDE利用。
FreeRTOSタスク状態遷移
ここに説明がある。
ESP32でのArduino IDE環境のtask.hでの状態定義は次のようになっている。
/** 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;
確認内容
3つのタスク構成。1つのタスクはボタン押下を検知し、すべてのタスク状態を表示する。2つのタスクでは、タスク間で通知を行い、別タスクから通知が来るまでは動作しないようなもの。同時にタスク状態を表示。
ソースコード
最初
# define BTN 38 // Middle button in M5Stack
xTaskHandle xTaskBtn;
xTaskHandle xTask1;
xTaskHandle xTask2;
char *task_state[] = {"Running", "Ready", "Blocked", "Suspended", "Deleted"};
ここはほぼ自明。
ボタンハンドリングタスク
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);
}
}
ボタン押下を検知し、各タスクの状態を表示する。
2つのタスク
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お決まり部分
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となっている。
実験
起動直後
タスク作成直後で、Task1とTask2とが”Ready”となっているが、TaskBtnが”Blocked”となっている。これはタスク作成時のPriority設定に関わっているようで、仮に、すべてのタスクPriorityを”0”とすると
xTaskCreate(&TaskBtn, "TaskBtn", 2048, NULL, 0, &xTaskBtn);
xTaskCreate(&Task1, "Task1", 2048, NULL, 0, &xTask1);
xTaskCreate(&Task2, "Task2", 2048, NULL, 0, &xTask2);
タスクPriority値が大きいほうが優先度が高いらしいが、このあたりの詳細は調べていない(勘弁)。いずれにせよ、Task1が最初に動作状態”Running”となっている。
ボタン押下なし状態
以降、TaskBtnのタスクPriorityは”1”となっている。
xTaskNotifyGive()を実行すると、もう一つのタスク状態が、”Suspended” -> ”Ready” -> "Running"と変化していることがわかる。また、ulTaskNotifyTake()の前後で、もう一つのタスク状態が、”Running” -> ”Suspended”と変化していることがわかる。(”Ready”などを経由している可能性もあるが、深追いしていない。こちらも勘弁。)
ボタン押下状態
TaskBtnが動作する。Task1動作中のボタン押下時の状態は次のようになった。
TaskBtnは当然ながら”Running”状態、Task1の状態は”Running” -> ”Blocked”へと変化している。これは、Task1内でコールされたvTaskDelay()で、タスク状態が”Blocked”に移行しており、この間にボタンが押下されたからである。
感想
タスク状態および遷移は奥が深い。