タスクの一時停止(suspend)と再開(resume)を実験しようとしたのですが、勝手な割り込み端子への思い込みに足をすくわれました。Arduino UNO R4は32ビット機になったわけだし、その辺の端子はみな割り込みに使えるんじゃね?大間違い!Arduino UNOの伝統にのっとり、2番、3番だけなのね。律儀。
※「モダンOSのお砂場」投稿順Indexはこちら
※Arduino IDE上で「スケッチ」形式のソースからFreeRTOSを使って実験してみてます。ターゲット機はArduino UNO R4 Minima。泣く子も黙る?Armコアのルネサスマイコン搭載機です。
FreeRTOSのタスクステート
FreeRTOSのタスクステートは忘却力の年寄にも覚えやすいシンプルな4ステートです。実行中のRunning、実行可能だけれどまだCPUの割り当てがないReady、ブロッキングされるAPIを使って「何かを待っている」Blocked、そして一時停止中のSuspendedです。詳しいことは御本家の以下ドキュメントなどご参照くだされ。
さて今回練習してみるのは、Taskの一時停止と再開です。使用するAPIは2つだけ。どちらも引数はタスクハンドルのみのシンプルさです。
-
- vTaskSuspend()、Running, Ready, Blockedのどの状態からでもSuspendedへ遷移させる
- vTaskTaskResume()、Suspended状態からReady状態に遷移させる
これを使えばTaskの一時停止と再開など思うがままだと。知らんけど。
UNO R4 attachinterrupt
例によって3つのTaskを走らせておいて、4つめのTaskから前の3つのうちの一つを一時停止させたり、再開させたりしようということにいたしました。実際にAPIを発行するのはタスクからなのですが、停止、再開の切っ掛けは割り込みハンドラで作ることにいたしました。要はボタンを1回押したら最初のタスクが止まり、2回目で最初が再開、2つ目が停止、3回目で2つ目再開、3つ目停止、4回目で全部動作で最初に戻るってな塩梅です。
FreeRTOSといってもArduino環境です。外部端子の割り込みハンドラは以下の関数でOK。
attachInterrupt()
その筈が勝手な思い込みでちと手間取りました。「UNO R4は32ビット機だし、その辺のデジタルピンは皆割り込みに使えるんじゃね」嘘です。UNO R4でもattachInterrupt()できるのは以下の2端子に限られてました。
D2, D3のみ
なんだ8ビットのUNO R3と一緒なのね。勝手な思い込みを正すべく、以下のチートシートを読み直しましたです。
Arduino UNO R4 Minima Cheat Sheet
そして、UNO R4の端子機能を反映した図面を起こしました。今回の実験回路が以下に。
今回実験のソース
タスクは4つですが、3つが suspend/resumeの対象です。それぞれ「働いている」ところを見せるため、赤、青、緑のLEDのうち1色を担当して、それぞれ別々の周期で点滅(ほとんどの時間点灯していて、時々消灯する)するようにしてあります。
4つのタスクとは別に割り込みハンドラがD3端子のフォーリング・エッジに反応するようになっており、この中で0から3の数字をsuspendedという大域変数に格納してます。割り込みハンドラの仕事はこれだけです。
4個目のタスクはクルクル回りつつ、上記のsuspended変数に変化が現れたら、それに応じたTASKを停止させ、suspendedから外れたTASKを再開させます。0なら0番タスク停止という塩梅。3の場合は全部稼働状態。
#include <Arduino_FreeRTOS.h> #define NTASKS (3) #define LED_RED (7) #define LED_BLUE (6) #define LED_GREEN (5) #define BUTTON (3) #define LOOP_W (100) #define LED_W (200) #define PRIOBASE (1) TaskHandle_t loop_task; TaskHandle_t tasks[NTASKS] = {NULL, NULL, NULL}; const char taskname[NTASKS][6] = {"Task1", "Task2", "Task3"}; pin_size_t taskParam[NTASKS] = {LED_RED, LED_BLUE, LED_GREEN}; volatile int suspended; void initPins() { pinMode(LED_RED, OUTPUT); digitalWrite(LED_RED, 0); pinMode(LED_BLUE, OUTPUT); digitalWrite(LED_BLUE, 0); pinMode(LED_GREEN, OUTPUT); digitalWrite(LED_GREEN, 0); } void loop_thread_func(void *pvParameters) { int oldsuspended = suspended; while (1) { vTaskDelay(LOOP_W); if (oldsuspended != suspended) { Serial.print("OLD: "); Serial.print(oldsuspended); Serial.print(" NEW: "); Serial.println(suspended); if (oldsuspended < 3) { vTaskResume(tasks[oldsuspended]); } if (suspended < 3) { vTaskSuspend(tasks[suspended]); } oldsuspended = suspended; } } } void task_func(void *pvParameters) { TickType_t xLastWakeTime; pin_size_t pinNumber = *(pin_size_t *)pvParameters; const TickType_t xTimeInc = pinNumber * LED_W; while (1) { vTaskDelay(xTimeInc); digitalWrite(pinNumber, 0); vTaskDelay(LED_W); digitalWrite(pinNumber, 1); } } void selTask() { suspended = suspended < NTASKS ? suspended + 1 : 0; } void setup() { Serial.begin(115200); while (!Serial) { } initPins(); pinMode(BUTTON, INPUT_PULLUP); suspended = NTASKS; attachInterrupt(digitalPinToInterrupt(BUTTON), selTask, FALLING); auto const rc_loop = xTaskCreate ( loop_thread_func, static_cast<const char*>("Loop Thread"), 512 / 4, nullptr, PRIOBASE, &loop_task ); if (rc_loop != pdPASS) { Serial.println("Failed to create 'loop' thread"); return; } TaskHandle_t xHandle = NULL; BaseType_t xReturned; for (int i=0; i<NTASKS; i++) { xReturned = xTaskCreate ( task_func, taskname[i], 512 / 4, (void *)&taskParam[i], PRIOBASE, &xHandle ); if (xReturned != pdPASS) { Serial.println("Failed to create task."); return; } else { tasks[i] = xHandle; } } Serial.println("Starting scheduler ..."); vTaskStartScheduler(); for( ;; ); /* Never! */ } /* NEVER CALLED! */ void loop() { }
実機動作確認
Arduino IDEでビルドした上記の「スケッチ」をArduino UNO R4 Minima機に書き込んで走らせたときの、シリアルモニタの状況が以下に。
ボタンを押す度に、OLDとNEWでresumeするタスクとsuspendされるタスクが報告されます。OLDは suspended状態だったタスク番号(3は停止中はなし)、NEWは新たにsuspendされるタスク番号。0は赤色LEDをつかさどるタスク、1は青で、2が緑っと。
箱の奥底から発掘した実験用の治具をUNO R4に接続して実験しております。タスクがsuspendされていないときは、それぞれの周期で時折LEDが消灯しますが、suspendされるとLEDは点灯状態または消灯状態のままフリーズします。
停止も再開も思うがまま。ホントか?