モダンOSのお砂場(70) UNO R4、ルネサスRA4M1でFreeRTOS、サスペンド

Joseph Halfmoon

タスクの一時停止(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 States

さて今回練習してみるのは、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の端子機能を反映した図面を起こしました。今回の実験回路が以下に。ArduinoUNO_R4_DUTschematic

 

今回実験のソース

タスクは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が緑っと。

suspendedTask

箱の奥底から発掘した実験用の治具をUNO R4に接続して実験しております。タスクがsuspendされていないときは、それぞれの周期で時折LEDが消灯しますが、suspendされるとLEDは点灯状態または消灯状態のままフリーズします。

ArduinoUNO_R4_DUT

停止も再開も思うがまま。ホントか?

モダンOSのお砂場(69) UNO R4、ルネサスRA4M1でFreeRTOS、ヒープメモリ へ戻る

モダンOSのお砂場(71) UNO R4、RA4M1でFreeRTOS、ISRからQUEUE へ進む