前回は、ESP32上のFreeRTOSでした。しかしESP32だと出来なかったことがあるのです。スケジューラの起動。ESP32の場合FreeRTOSが動いているのが前提なので、こちらに制御がもらえる前にスケジューラが動いてます。一方、最近手に入れたSAMD21機Arduino環境では、スケジューラを自分で立ち上げればFreeRTOSできる、という仕組み。
※「モダンOSのお砂場」投稿順Indexはこちら
スケジューラは並行に動作する多数のタスクの実行の順序を管理するまさにRTOSの要であります。ESP32マイコンの場合、ESP-IDF環境であろうと、Arduino環境であろうと、裏で常にFreeRTOSのスケジューラは「動いている」ようです(プログラマに制御を渡す前にスタートアップ側で起動しているみたい。)コアを2個持ち、そのうち1個がWiFiなどの無線通信を制御する必要があるためかと思います。そのため、ESP32では、FreeRTOSのタスクをCreateすれば即座に走ります。便利ですが、裏で動いている筈のスケジューラのことは意識に上りませぬ。
その点、以下でLチカをやっているSeeeduino Xiaoボード、Microchip社製AT SAMD21マイコン搭載のスタンスは異なります。
AT SAMの部屋(1) SAMD21搭載、Seeeduino Xiaoで吉例Lチカ
上記では、Arduino環境でビルドしていますが、FreeRTOSのスケジューラは事前に動いてません。しかし、ライブラリの中を覗いてみると、
FreeRTOSのAPIは何時でも使える状態
ということが分かります。その気になったら使える、と。このSeeeduino Xiaoであれば、スケジューラの起動からできそうなので、やってみたいと思います。
スケジューラに関するドキュメント
以下に、スケジューラ起動のAPIについてのドキュメントへのリンクを貼り付けておきます。
上記はAPIの説明ですが、同じサイト上にはFreeRTOSの解説もあるので、いろいろ学べます(といってちゃんと読んでないケド。読んで勉強しろよ、自分。)
作製した実際のプログラム
Seeeduino Xiao、Arduino環境でビルド可能なソース全文を以下に掲げます。別投稿で使った「Lチカ」のコードをFreeRTOS上のタスクで書き換えたものです。当然、FreeRTOSのスケジューラを起動しています。
#include <Seeed_Arduino_FreeRTOS.h> int counter; TaskHandle_t Handle_blinkTask; static void blink_task(void * pvParameters) { const TickType_t xDelay = 250 / portTICK_PERIOD_MS; // 250ms while (1) { if ((counter++ % 2) == 0) { digitalWrite(PIN_LED_13, HIGH); } else { digitalWrite(PIN_LED_13, LOW); } vTaskDelay(xDelay); } } void setup() { Serial.begin(9600); vNopDelayMS(1000); while(!Serial); Serial.printf("ATSAMD21 FreeRTOS Blink.\r\n"); pinMode(PIN_LED_13, OUTPUT); digitalWrite(PIN_LED_13, LOW); counter = 0; xTaskCreate(blink_task, "BLINK", 2048, NULL, tskIDLE_PRIORITY + 1, &Handle_blinkTask); Serial.printf("Start FreeRTOS Scheduler.\r\n"); vTaskStartScheduler(); } void loop() { while(1) { printf("Counter: %d\n",counter); vTaskDelay(10000 / portTICK_RATE_MS); } }
ヘッダは、上記の1個をインクルードすればFreeRTOSのAPIもArduinoのAPIもOKのようです(内部で Arduino.hもインクルードされてました。)
Lチカの本体は、タスクとして起動される関数 blink_task()の中にまとめています。その部分のコードそのものは「素の」Arduinoと変わりません。
setup()の中で、上記のblink_task()関数を実行するタスクを前回も使った xTaskCreateというAPIを使って起動しています。必要なタスクをCreateした後、setup()の末尾でタスクスケジューラを呼び出しています。
スケジューラのAPIのマニュアルにも書いてありますが、スケジューラの起動に成功すれば制御は戻ってきません。行ったっきり。以降はスケジューラが、管理対象のタスクを「スケジュール」しながら走らせます。setup()関数からは戻らないので、本来ならsetup()の後の隠されたループの中で呼ばれ続ける筈のloop()関数に制御が移ることはありません、うまく行けば。
唯一loop()が呼ばれることになると思われるのが、スケジューラが十分な量のメモリを使えなかった等の理由から、起動に失敗した場合です。その場合、スケジューラから戻ってくるのできっとloop()が呼ばれることになるんではないかしらん(起動に失敗しなかったので、それは目撃してないです。)
でもloop()の中でvTaskDelay()を呼び出しているのはBUGっぽいです。スケジューラの起動を失敗したときしか loop()が呼ばれないのだから。
シリアルポートの初期化の後に置いてある
vNopDelayMS()
という関数ですが、これはスケジューラが動いていようがいまいが使える遅延ループです。中身を見たらNOPを実行する「只の」ソフトウエアループでした。loop()の中の遅延は、こちらの vNopDelayMS()こそ使うべきじゃね。
動作結果
LチカOKでした。別件投稿と変わらないので写真はつけてないです。一応、別件投稿から点滅周期を半分にしたので、今回のコードが走っていることが分かります(時々Flash書き換えたつもりが、書き換えてない早合点もあり。)
TIPS
スケジューラを起動せずに、taskをCreateしてしまったら、2度と制御が戻ってこなくなりました。まあ当然ちゃ当然ですが。しかし、それだけではすみません。
オブジェクトファイルのアップロード
すらできなくなってしまいました。困った。しかし、Seeeduino Xiaoのページにはそういうときの対処方法が書いてありました。RESET信号を素早く2回入れる、という技です。Seeeduino Xiaoは小型すぎるためかRESET端子はピンとしては出されていないのですが、USBコネクタのそばにランドとしておかれています。そしてその横にはGNDのランドもあります。ここをショートしてやればRESETがかかります。
ピンセットで同時に両方のランドを触ってやればRESETかけることができました。しかし「2回素早く」が難しいです。上記のページにあるようにジャンパ線等の片方をGNDのランドに押し付けつつ、他方をRESETのランドに「ちょん、ちょん」とするのが良いみたいです。すると、ブート用のモードに入ります。オブジェクトファイルのアップロードができる状態に回復しました。USB接続のストレージにも見えるようなので、UF2ファイル書き込みも可能だと思います(が、確かめてないデス。)