前回Mutex(2つあるうちのRTOS Mutexと呼ばれている方)を使ってみました。スレッド間の同期には有効なAPIですが、残念ながらISR(割り込みサービスルーチン)との間では使用できません。そこで今回はSemaphoreを使って、ISRとThreadの間で排他制御を行ってみます。
※「モダンOSのお砂場」投稿順Indexはこちら
※実験コードのビルドは、Arm社のWeb開発環境 Mbed Compilerを使わせていただいております。登録すればインストールせずに即使える開発環境です。実機ターゲットは、ST Microelectronics社のNucleo-F401REボード(STM32F401REマイコン搭載。コアは Arm Cortex M4F)です。
今回の実験コード
後に今回使用した実験コード(動作OKの方)の全文を掲げております。コードの目論見は以下のようです。
-
- 前回も使用した赤、緑のLED2個の「点灯権」を排他制御の必要なリソースと想定する。
- スレッド1は、点灯権を取得して赤色LED(A)を点灯(同時に緑色LED(B)を消灯)し、一定時間後制御権を手放した後赤色LEDを消灯する。そして一定時間待つ。この動作を繰り返す。
- キー割り込みハンドラは、キーが押されていたら起動され、点灯権の取得を試み、権利を取得できたら緑色LEDを点灯する。取得に失敗したらその回は流してしまう(あきらめるなよハンドラ。)
- メイン関数は、キー割り込みハンドラを設定し、スレッド1を起動した後は、我関せずとオンボードのLEDを一定周期でLチカする。
さて使用した Semaphore のAPIの説明は以下にあります。
Semaphoreは複数個のリソースも管理できるのですが、今回は一つだけです。主眼は「ISRの内外で使える」という1点の確認。
上記のAPI文書を良く読んでプログラムを書けばよかったのですが、ほぼスルーのまま書いてしまいました。とりあえずビルドは成功。メモリは余裕。
Mbed OSのOSエラーに遭遇
さっそくNucleo F401REに書き込んでみます。起動後は、外付けのLEDもオンボードのLEDも予定通りにLチカしているのですが、割り込みハンドラを起動するためにボタン(Nucleo F401REのオンボードの青色SW)を押すと挙動が変です。
オンボードLEDが「チカ、チカ、チカ」と小刻みに点滅した後消灯を繰り返します。
USBシリアルに仮想端末を接続すると以下のごとし。MbedOS Error とな。みればSemaphoreのパラメータが不正みたいです。
問題と思われるソースコード、ISRのハンドラ部分が以下に。無謀にも割り込みハンドラの中で権利をゲットするまで無期限に待つ acquire() を使っています。
void b1UserHandler() { extLED_control.acquire(); ledB = 0; extLED_control.release(); }
上記ページの void acquire()のNoteから1文引用させていただきましょう。
You cannot call this function from ISR context.
確かに SemaphoreはISRからの呼び出しにも対応はしています。しかし、権利を取れるまで(無限に)待つ、とか、何秒待つとか、割り込みハンドラ内で許されない「待ち」については拒否されてしまうっと。
書き直して動作OKになったコード
割り込みハンドラ内で使っているAPIを、acquire()からtry_acquire()に変更し、権利をゲットできたときだけ操作をするように改めたものが以下です。
#include "mbed.h" #include "platform/mbed_thread.h" #include "stdlib.h" #define BLINKING_RATE_MS (500) InterruptIn B1_USER(PC_13, PullUp); DigitalOut ledA(PA_8); DigitalOut ledB(PB_10); Semaphore extLED_control(1); Thread t1; void b1UserHandler() { if (extLED_control.try_acquire()) { ledB = 0; extLED_control.release(); } } void t1_thread() { while (true) { extLED_control.acquire(); ledB = 1; ledA = 0; //LOW ACTIVE ThisThread::sleep_for(1000); extLED_control.release(); ledA = 1; ThisThread::sleep_for(1000); } } int main() { B1_USER.fall(b1UserHandler); printf("\r\n\r\n*** Semaphore Sample ***\r\n"); DigitalOut led(LED1); t1.start(t1_thread); while (true) { led = !led; thread_sleep_for(BLINKING_RATE_MS); } }
上記のコードですと以下のような動作が起こります。
-
- 赤色点灯時にユーザボタンを押しても無視される(タスク1が権利持っているので取得できない。)
- 赤色消灯時にユーザボタンを押すと緑色LEDが点灯する(割り込みハンドラ側で制御できた)
まあ、ISRの中から呼び出せることは確認できたと。