モダンOSのお砂場(28) FreeRTOS、スケジューラの起動、SAMD21

Joseph Halfmoon

前回は、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についてのドキュメントへのリンクを貼り付けておきます。

FreeRTOS vTaskStartScheduler

上記は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ファイル書き込みも可能だと思います(が、確かめてないデス。)

モダンOSのお砂場(27) FreeRTOS、ESP-IDFで定番のLチカをタスクで実装 へ戻る

モダンOSのお砂場(29) FreeRTOS、プリエンプティブなスケジューリング へ進む