モダンOSのお砂場(32) FreeRTOS、割り込みハンドラからタスクにキュー

Joseph Halfmoon

前回はタスク・リストを一覧して何故か満足してしまいました。今回は、割り込みとタスクの間の通信を試みてみたいと思います。割り込みサービスルーチン側では、処理の依頼をするだけで、時間のかかる処理はタスクにお任せするという仕組み。とりあえず動くプロトタイプを作ってみました。

※末尾に実験に使用したコード全文を掲げました。

※実験には、Seeed Studio社製 Seeeduino Xiaoボード(Microchip製 ATSAMD21マイコン<Arm Cortex-M0+>搭載)を使用しています。PC上のVSCode上のPlatformIOを使用させていただき、Arduinoフレームワークでビルドを行っています。

QUEUE

今回、使用するのは Queue という構造です。以前に以下の投稿で取り扱ったことがあります。

モダンOSのお砂場(20) FreeRTOS、キュー構造。でも別件が気になってしまうっ!

根本的な差異はないのですが、上記ではタスク間にQueueを張っていました。今回は、割り込みが絡んでくるのでチトAPIが異なってまいります。使用するAPIのマニュアルページは以下です。

xQueueSendFromISR

今回のサンプルコードの目論見

今回作成したプログラムの動作は以下のとおりです。

  1. キーLとキーRがある。
  2. キーLを押すとカウントアップ、キーRを押すとカウントダウンするカウンタがある
  3. カウンタが増減する度にその値をコンソールに出力する。またカウント処理中LEDを点灯する。

FreeRTOSを使っての処理は以下の図のとおりです。

InterruptHandler_Queue_TASK
2つのキーにそれぞれ対応する割り込みハンドラがあり、その中でキューにカウントUP/DOWNすべき数を書き込んでいく。基本周期100msで動作している定期タスクRed Taskが、Queueに書き込まれている数を読み取って実際にカウントUP/DOWNを行い、コンソール出力、LED点滅を行う。Queueに数が書き込まれていた場合、10ms後にもQueueに値が溜まっていないか確認する。溜まっている場合は処理する。

難しいことはなんもありゃしませんが、しかし、今回手を抜いている部分おおありです。

  • キーは人間が押す前提なので、Queue(8段確保)があふれるような頻度での動作は想定していない。80ms中に9回のキー入力など無いだろ~という手抜き。
  • 割り込みハンドラからキューイングするときに、タスクが待機状態であった場合、「プライオリティを上げてたたき起こす」機能があるが使用していない。
実験に使用した回路

以下に、実験に使用した回路図を示します。以前から使用していた赤、青のLEDが左に、今回とりつけたプッシュスイッチ2個が右という簡単な構成です。

SeeeduinoXiao_DUT_Schematic

スタック・オーバーフロー発生

今回のコードも前回使用のコードを改造して作ったのですが、RAMに制約(32KB)のあるATSAMD21G18では、ちょっとコードを増やすとエラーが起こります。こんな感じ。StackOverflowTmrSvc

メモリ量に制限がでやすい「組み込みらしい」挙動なので、スタックの割り当て量などを調整する「事案」かとも思ったのです。でもタイマサービスは今回使わなくても済むので削除して対応してしまいました(本当はメンドイから。)

ただし、一度このようなエラーに落ち込むと更新したオブジェクトコードをUSB経由で書き込めなくなります。その時は、Seeeduino XiaoボートのUSBソケットの脇にある小さなRSTパッドを素早く2回短絡して(勿論導電性のピンなどで)ハングしているアプリコードから制御を奪う必要があります。結構、何度もやっているのでツツクのにも慣れました。

RST_PAD

実行結果

スタック・オーバーフロー件を対処後は問題なく動作しました。そのときの標準出力に現れた結果が以下に。Lボタンを押すとカウントが上がり、Rボタンを押すとカウントが下がります。一応、意図どおりの動作だね。

ATSAMD21 FreeRTOS INTR+QUEUE test. 001
Start FreeRTOS Scheduler.
UpDownCounter: 1
UpDownCounter: 2
UpDownCounter: 1
UpDownCounter: 2
UpDownCounter: 3
UpDownCounter: 4
UpDownCounter: 5
UpDownCounter: 6
UpDownCounter: 5
UpDownCounter: 4
UpDownCounter: 3
UpDownCounter: 2
UpDownCounter: 1
UpDownCounter: 0
UpDownCounter: -1

一応、割り込みからのキューイングが動作しているので叩き台くらいにはなるか。次は?

モダンOSのお砂場(31) FreeRTOS、タスク・リストを一覧する へ戻る

実験に使用したソース全文
#include <Seeed_Arduino_FreeRTOS.h>

// D2/A2=PA10, D3/A3=PA11, D10/A10=PA6
// D7/A7=PB9(EINT9), D8/A8=PA7(EINT7)
#define LED_RED   (2)
#define LED_BLUE  (3)
#define KEY_L     (8)
#define KEY_R     (7)
#define LED_ACTIVE    (0x0)
#define LED_INACTIVE  (0x1)
#define STACK_DEPTH (256)
#define ENABLE_NOP_LOOP (0)
#define QUEUE_DEPTH (8)

volatile int upDownCounter;
int counter;
int blueFlag;
TaskHandle_t  Handle_RedTask;
QueueHandle_t isrQueue;

void waitLoop(int ms) {
  #if ENABLE_NOP_LOOP > 0
    vNopDelayMS(ms);
  #else
    vTaskDelay(ms / portTICK_RATE_MS);
  #endif
}

static void RedTask(void * pvParameters) {
  int item;
    while (1) {
    item = 0;
    if (xQueueReceive(isrQueue, (void*)&item, 0) == pdTRUE) {
      upDownCounter += item;
      Serial.printf("UpDownCounter: %d\r\n", upDownCounter);
      digitalWrite(LED_RED, LED_ACTIVE);
      waitLoop(10);
    } else {} 
      digitalWrite(LED_RED, LED_INACTIVE);
      waitLoop(100);
  }
}

void keyLhandler() {
  int sendData = 1;
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  xQueueSendToBackFromISR(isrQueue, &sendData, &xHigherPriorityTaskWoken);
}

void keyRhandler() {
  int sendData = -1;
  BaseType_t xHigherPriorityTaskWoken = pdFALSE; 
  xQueueSendToBackFromISR(isrQueue, &sendData, &xHigherPriorityTaskWoken);
}

void setupLEDpins() {
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);
  digitalWrite(LED_RED, LED_INACTIVE);
  digitalWrite(LED_BLUE, LED_INACTIVE);
}

void setupINTR() {
  pinMode(KEY_L, INPUT);
  pinMode(KEY_R, INPUT);
  attachInterrupt(digitalPinToInterrupt(KEY_L), keyLhandler, FALLING);
  attachInterrupt(digitalPinToInterrupt(KEY_R), keyRhandler, FALLING);
}

void setup() {
  Serial.begin(9600);
  vNopDelayMS(1000);
  while(!Serial);
  Serial.printf("ATSAMD21 FreeRTOS INTR+QUEUE test. 001\r\n");
  setupLEDpins();
  setupINTR();
  isrQueue = xQueueCreate(QUEUE_DEPTH, sizeof(int));
  counter = 0;
  upDownCounter = 0;
  blueFlag = 0;
  xTaskCreate(RedTask, "RED", STACK_DEPTH, NULL, tskIDLE_PRIORITY + 3, &Handle_RedTask);
  Serial.printf("Start FreeRTOS Scheduler.\r\n");
  vTaskStartScheduler();
}

// NEVER CALLED, if scheduler OK.
void loop() {
    while(1) {
        Serial.printf("NEVER loop: %d\r\n",counter++);
        delay(1000);
    }
}