モダンOSのお砂場(73) UNO R4でFreeRTOS、割り込みISRからNotify

Joseph Halfmoon

前回は2つのTask間でGiveしてTakeして相互にNotify。今回は割り込みサービスルーチン(ISR)からNotifyです。ISRからTaskへの一方通行なんだけれども。やらないわけにもいかないけれどもチョイ変のチョイ変。でもこれで割り込みが何か解決してくれることを待っているTaskというのも簡単。ホントか?

※「モダンOSのお砂場」投稿順Indexはこちら

※Arduino IDE上で「スケッチ」形式のソースからFreeRTOSを使って実験してみてます。ターゲット機はArduino UNO R4 Minima。泣く子も黙る?Armコアのルネサスマイコン搭載機です。

割り込みからのTask Notification

前回やったTask Notificationは、第70回でやったTaskのSuspend、Resumeと比べると、止まりたいところで止まって待つことができる仕組みでした。

    • 止まりたいところでTakeするとTakeしたTaskはそこで待つことになる(誰も起こしてくれない可能性があるならばタイムアウトを設定しておけば、自主的に実行再開することも可能)
    • 誰か別のTaskがGiveしてくれると、待っていたTaskは起き上がってTakeの次から実行を再開する(ただしGiveする条件などを細かく指定することもできる。条件があわなければ再開しない。)

この仕組みを使えば「なにか条件がそろうまで」待つという作業が結構簡単にできそうです。しかし前回試用してみたAPIは、Task間でNotificationするためのものでした。割り込みサービスルーチン内で「使ってはいけない」APIデス。

例によって割り込みサービスルーチン(ISR)内の場合は「こちらを使え」というAPIが用意されております。今回は以下を使ってみることにいたします。

vTaskNotifyGiveFromISR

なお、上記APIのドキュメンテーションは以下に。

vTaskNotifyGiveFromISR / vTaskNotifyGiveIndexedFromISR

実験に使用したソース

前回ソースのチョイ変です。前回は2個のTask間でのNotifyでしたが今回はISRとTask間のNotifyなので、ISRを1個作り、Taskを1減してます。例によって無関係にLチカをやり続けるループタスクは健在。

#include <Arduino_FreeRTOS.h>
#define LED_RED   (7)
#define LED_GREEN (5)
#define BUTTON    (3)
#define LOOP_W    (1000)
#define LED_W     (500)
#define PRIOBASE  (1)
#define ON        (0)
#define OFF       (1)

TaskHandle_t loop_task;
TaskHandle_t task1;
volatile int32_t counter;

void initPins() {
  pinMode(LED_RED, OUTPUT);
  digitalWrite(LED_RED, OFF);
  pinMode(LED_GREEN, OUTPUT);
  digitalWrite(LED_GREEN, OFF);
}

void loop_thread_func(void *pvParameters)
{
  while (1) {
    vTaskDelay(LOOP_W);
    digitalWrite(LED_GREEN, ON);
    vTaskDelay(LOOP_W);
    digitalWrite(LED_GREEN, OFF);
  }
}

void task_func1(void *pvParameters)
{
  while (1) {
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    while(counter-- > 0) {
      digitalWrite(LED_RED, ON);
      vTaskDelay(LED_W);
      digitalWrite(LED_RED, OFF);
      vTaskDelay(LED_W);
    }
  }
}

void buttonHandler() {
  BaseType_t xHigerPriorityTaskWoken = pdFALSE;
  counter = 3;
  vTaskNotifyGiveFromISR(task1, &xHigerPriorityTaskWoken);
}

void setup()
{
  Serial.begin(115200);
  while (!Serial) { }
  initPins();
  counter = 0;

  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;
  }

  BaseType_t xReturned = xTaskCreate ( task_func1, "TASK1", 512 / 4, nullptr, PRIOBASE, &task1 );
  if (xReturned != pdPASS) {
      Serial.println("Failed to create task1.");
      return;
  }

  attachInterrupt(digitalPinToInterrupt(BUTTON), buttonHandler, FALLING);

  Serial.println("Starting scheduler ...");
  vTaskStartScheduler();
  for( ;; ); /* Never! */
}

/* NEVER CALLED! */
void loop() {
}
実験結果

実機写真が以下に。前回の実機写真とほぼほぼクリソツ。変化なし。。。

なお、Serial通信使っているので、Arduino IDE付属のシリアルモニタなどでSerialチャンネルを開くまで待っています。

NOTIFY_ISR_DUTa

今回は赤い頭のプッシュスイッチを押すと、割り込みが発生(フォーリングエッジ)するようになっており、割り込みハンドラ中でTASK1にNotifyすると同時に点滅カウンタを3に設定しています。TASK1はNotifyされて起きると赤色LEDを3回消し(デフォルトは点灯状態)た後、再びTakeして停止します。

赤色LEDの点滅にかかわらず、最後にプッシュスイッチを押してから3回赤色LEDは消灯する挙動を示します。まあ、予定通りかね。

モダンOSのお砂場(72) UNO R4、RA4M1でFreeRTOS、Nortify へ戻る

モダンOSのお砂場(74) UNO R4、RA4M1でFreeRTOS、バイナリ・セマフォ へ進む