前回はOSタイマ、前々回はTask(プリエンプティブな)、並行処理を行う方法はもうお腹一杯な気がしないでもないです。しかし、FreeRTOSにはもう一つあり。CoRoutineです。本格的なTaskが使えるFreeRTOSでわざわざコルーチンする意味があるのか不明ですが、今回は「あるものは使ってみる」ということで。
※「モダンOSのお砂場」投稿順Indexはこちら
コルーチン
ザックリしたいい加減なことを書いてしまうと、コルーチンは、コルーチン同士の間では、制御権を「譲り合って」互いに並行に動作するかのごとくに見せかける?仕組みであります。コルーチンの誰かが「ちょっと休みますわ」と制御権を自主的に返納したら、スケジューラがよきにはからってくれて次のコルーチンが呼び出されると。呼び出されたコルーチンはCPUを占有しつづけることなく、適宜、制御権を自主返納することが求められます。譲り合いの精神?ね。
実行最中にもRTOSの思し召しのままに制御権を召し上げられる可能性のあるタスクに比べたら「ゆるゆる」な感じ?違うか。
まあ、プリエンプティブなタスクに比べると「軽い」ということもあり、多くのマイコン処理系、例えばMicroPythonなどで採用されてます。
さて、Arduino IDE環境で、ルネサスRA4M1搭載のArduino UNO R4でFreeRTOSしてますが、デフォルトではFreeRTOSのコルーチンは鍵かかってました。いろいろあるし、無理にコルーチン使わなくても他の方法で良いだろ、という思し召しか。
そういう思し召しを踏みにじり、今回は無理やりFreeRTOSのcoroutineを使ってみました。FreeRTOSのコルーチンに関するAPIドキュメントは以下のところから。
FreeRTOSConfig.h
FreeRTOSには FreeRTOSConfig.h というファイルがあります。手元のArduino IDE 2.1.1(Windows 11)の場合、UNO R4用のルネサス用のファイル類が格納されている以下のフォルダ内にありました。なお、なおArduino IDE 2.1.1の場合、ソース冒頭の #include <Arduino_FreeRTOS.h>から、右クリックの「定義へ移動」で2つ下まで開いていけばFreeRTOSConfig.hを開くことができます。ただし、勝手に変更するなよ、ということでリードオンリになってました。
~Packageのインストールパス~\packages\arduino\hardware\renesas_uno\1.0.2\libraries\Arduino_FreeRTOS
FreeRTOSConfig.h は、FreeRTOS内のどの機能を組み込んで、どの機能を組み込まないか選択するためのヘッダであります。必要な最低限のものを組み込むことでRTOSが不必要にメモリを食わないようにするためのもののようです。
このファイル内で、CoRoutineの組み込みを選択しているのが
configUSE_CO_ROUTINES
というマクロです。デフォルトではこれが0(組み込まない)になってました。外のファイルで指定することもできそうな感じですが、「面倒」だったので今回は FreeRTOSConfig.hを直接弄ってしまいました(そういう荒業のときには伝統の秀丸様に御出馬願うです。)
さらに、configMAX_CO_ROTUINE_PRORITIESというマクロも定義しておく必要があったので、ついでにここに書き込んでしまいました。暴挙?
まあ、後でCoRoutineはデフォルトに戻すつもりなので、今回だけね。
実験に使用したソースコード (.ino 「スケッチ」)
上で参照した、FreeRTOSのコルーチンに関するAPIドキュメントに掲載されているサンプルプログラムをほぼほぼそのままパクらせていただいた雰囲気のソースが以下に。
2つのコルーチンを走らせて、緑と青のLEDを異なる周期で光らせる何度目かのダブル・Lチカです。
#include <Arduino_FreeRTOS.h> #define LED_GREEN (2) #define LED_BLUE (1) #define LED_MAX (2) #define LED_PRIORITY (0) #define LED_ACTIVE (0x1) #define LED_INACTIVE (0x0) TaskHandle_t loop_task; CoRoutineHandle_t Handle_CoRoutine; void ledCoRoutine(CoRoutineHandle_t Handle_CoRoutine, UBaseType_t uxIndex) { static const pin_size_t ledPins[LED_MAX] = {LED_GREEN, LED_BLUE}; static const TickType_t coWaitRate[LED_MAX] = {1000, 2000}; static int ledStatus[LED_MAX] = {LED_INACTIVE, LED_INACTIVE}; crSTART(Handle_CoRoutine); for (;;) { if (ledStatus[uxIndex] == LED_INACTIVE) { digitalWrite(ledPins[uxIndex], LED_ACTIVE); ledStatus[uxIndex] = LED_ACTIVE; Serial.print(uxIndex); Serial.println(" ON"); } else { digitalWrite(ledPins[uxIndex], LED_INACTIVE); ledStatus[uxIndex] = LED_INACTIVE; Serial.print(uxIndex); Serial.println(" OFF"); } crDELAY(Handle_CoRoutine, coWaitRate[uxIndex]); } crEND(); } void initLEDs() { pinMode(LED_GREEN, OUTPUT); digitalWrite(LED_GREEN, LED_INACTIVE); pinMode(LED_BLUE, OUTPUT); digitalWrite(LED_BLUE, LED_INACTIVE); } void setup() { Serial.begin(115200); while (!Serial) { } Serial.println("Arduino UNO R4, FreeRTOS CoRoutine extercise."); initLEDs(); auto const rc_loop = xTaskCreate ( loop_thread_func, static_cast<const char*>("Loop Thread"), 512 / 4, nullptr, 1, &loop_task ); if (rc_loop != pdPASS) { Serial.println("Failed to create 'loop' thread"); return; } for(UBaseType_t uxIndex = 0; uxIndex < LED_MAX; uxIndex++) { xCoRoutineCreate(ledCoRoutine, LED_PRIORITY, uxIndex); } Serial.println("Starting scheduler ..."); vTaskStartScheduler(); for( ;; ); /* Never! */ } void loop() { //Serial.println(millis()); vCoRoutineSchedule(); vTaskDelay(configTICK_RATE_HZ/2); } void loop_thread_func(void *pvParameters) { for(;;) { loop(); taskYIELD(); } }
実機実行結果
Arduino UNO R4 MINIMAの実機にLEDを接続しCoRoutineで点滅させているところが以下に。
Taskだろうと、OSタイマだろうと、CoRoutineだろうと、Lチカには変わりなし。