前回はタスク・リストを一覧して何故か満足してしまいました。今回は、割り込みとタスクの間の通信を試みてみたいと思います。割り込みサービスルーチン側では、処理の依頼をするだけで、時間のかかる処理はタスクにお任せするという仕組み。とりあえず動くプロトタイプを作ってみました。
※「モダンOSのお砂場」投稿順Indexはこちら
※末尾に実験に使用したコード全文を掲げました。
※実験には、Seeed Studio社製 Seeeduino Xiaoボード(Microchip製 ATSAMD21マイコン<Arm Cortex-M0+>搭載)を使用しています。PC上のVSCode上のPlatformIOを使用させていただき、Arduinoフレームワークでビルドを行っています。
QUEUE
今回、使用するのは Queue という構造です。以前に以下の投稿で取り扱ったことがあります。
モダンOSのお砂場(20) FreeRTOS、キュー構造。でも別件が気になってしまうっ!
根本的な差異はないのですが、上記ではタスク間にQueueを張っていました。今回は、割り込みが絡んでくるのでチトAPIが異なってまいります。使用するAPIのマニュアルページは以下です。
今回のサンプルコードの目論見
今回作成したプログラムの動作は以下のとおりです。
-
- キーLとキーRがある。
- キーLを押すとカウントアップ、キーRを押すとカウントダウンするカウンタがある
- カウンタが増減する度にその値をコンソールに出力する。またカウント処理中LEDを点灯する。
FreeRTOSを使っての処理は以下の図のとおりです。
2つのキーにそれぞれ対応する割り込みハンドラがあり、その中でキューにカウントUP/DOWNすべき数を書き込んでいく。基本周期100msで動作している定期タスクRed Taskが、Queueに書き込まれている数を読み取って実際にカウントUP/DOWNを行い、コンソール出力、LED点滅を行う。Queueに数が書き込まれていた場合、10ms後にもQueueに値が溜まっていないか確認する。溜まっている場合は処理する。
難しいことはなんもありゃしませんが、しかし、今回手を抜いている部分おおありです。
-
- キーは人間が押す前提なので、Queue(8段確保)があふれるような頻度での動作は想定していない。80ms中に9回のキー入力など無いだろ~という手抜き。
- 割り込みハンドラからキューイングするときに、タスクが待機状態であった場合、「プライオリティを上げてたたき起こす」機能があるが使用していない。
実験に使用した回路
以下に、実験に使用した回路図を示します。以前から使用していた赤、青のLEDが左に、今回とりつけたプッシュスイッチ2個が右という簡単な構成です。
スタック・オーバーフロー発生
今回のコードも前回使用のコードを改造して作ったのですが、RAMに制約(32KB)のあるATSAMD21G18では、ちょっとコードを増やすとエラーが起こります。こんな感じ。
メモリ量に制限がでやすい「組み込みらしい」挙動なので、スタックの割り当て量などを調整する「事案」かとも思ったのです。でもタイマサービスは今回使わなくても済むので削除して対応してしまいました(本当はメンドイから。)
ただし、一度このようなエラーに落ち込むと更新したオブジェクトコードをUSB経由で書き込めなくなります。その時は、Seeeduino XiaoボートのUSBソケットの脇にある小さなRSTパッドを素早く2回短絡して(勿論導電性のピンなどで)ハングしているアプリコードから制御を奪う必要があります。結構、何度もやっているのでツツクのにも慣れました。
実行結果
スタック・オーバーフロー件を対処後は問題なく動作しました。そのときの標準出力に現れた結果が以下に。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、タスク・リストを一覧する へ戻る
モダンOSのお砂場(33) Mbed OS6、bare metal profile、Lチカ へ進む
実験に使用したソース全文
#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); } }