こんにちは、ナナです。
ここからはマルチタスクならではの機能を解説していきます。まずは代表的な機能であるイベントフラグを紹介しましょう。
本記事では次の疑問点を解消する内容となっています。
では、イベントフラグの仕組みを学んでいきましょう。
イベントフラグで学ぶタスク間同期・通信機能とは
タスクとは「労働者」であり、マルチタスクとは「複数の労働者」でした。
システムという同じ場所で働く労働者は、それぞれのミッションを遂行していますが、場合によっては他の労働者とコミュニケーションを取る必要性があります。
例えばお店で働く料理人とウェイターでは次のようなシーンがありますね。
料理人が作成した料理ができたため、ウェイターに口頭で連絡して運んでもらうといったシーンです。会話をすることで料理が冷める前に、運ぶことができます。
このようにお互いのタイミングを合わせることを「同期」と呼びます。タスク間同期・通信機能はタスク同士で同期を取るための通信機能を提供します。
このようにITRON仕様というのはタスクというものを中心にして、どのような機能があればシステム構築が便利になるかという考え方で仕様が検討されています。
ITRONが定義するタスク間同期・通信機能とは
タスク同士が会話をするためのこの機能にはいくつかの種類が設けられています。
ITRON仕様「4.4 同期・通信機能」で定義される4つ。
- セマフォ
- イベントフラグ
- データキュー
- メールボックス
さらに高機能な「拡張同期・通信機能」で定義される3つ。
- ミューテックス
- メッセージバッファ
- ランデブ
同期・通信機能はそれぞれに特徴があるため、自分が求めるものがどのようなものなのかを見極めて最適なものを選択できるスキルが求められます。
本記事ではイベントフラグを解説します。
イベントフラグの概要
イベントフラグは同期・通信機能の中でおそらく一番使われている機能だと思います。それほど使い勝手がよいということです。
イベント(事象)+フラグ(有無)という組み合わせで、とあるイベントの発生をタスク間で通知することができる機能です。
では、ITRON仕様におけるイベントフラグの概要を見てみましょう。
イベントフラグは「タスク」や「周期ハンドラ」などと同じように「オブジェクト」として存在するとされています。そのためオブジェクトを生成するためのサービスコールがあります。
イベントフラグで使用する代表的なサービスコール
イベントフラグではよくこれらのサービスコールを使用します。
- CRE_FLG
- cre_flg
- set_flg
- wai_flg
まずは、このサービスコールを覚えましょう。
イベントフラグの生成方法(CRE_FLG)
イベントフラグを生成するための静的APIを見てみましょう。
CRE_FLG(ID flgid, {ATR flgatr, FLGPTN iflgptn});
引数パラメータを解説しておきます。
ID | flgid | 生成するイベントフラグのID。整数として一意のIDを割り付ける。 |
ATR | flgatr | イベントフラグの属性。 |
FLGPTN | iflgptn | イベントフラグパターンの初期値。基本は0を設定。 |
flgatrが特殊です。 ((TA_FIFO || TA_TPRI)|(TA_WSGL||TA_WMUL)|[TA_CLR])の指定ができます。
このイベントフラグというオブジェクトは複数のタスクで共有してイベントを待つということができる少し特殊な機能を持っています。
ただ、私はこの複数待ちを使っているアプリケーションには出会ったことがありません。使うなとは言いませんが、使うならしっかりと理解した上で使わないと思わぬ動きになるため注意しましょう。
TA_CLRはイベントフラグ待ち状態から待ち解除する時に、受信したイベントフラグのビットパターンを全てクリアするためのオプションであり、基本的には付けておくことが推奨です。
CRE_FLGによるイベントフラグの生成定義
では、皆さんにイベントフラグの定義をsystem.cfgに追加していただきましょう。前回までの設定はそのままで、イベントフラグの定義を1つ追加しましょう。
追加するイベントフラグ生成情報
- IDは「FLGID_FLG1」を指定する。
- 属性は「(TA_TPRI | TA_WSGL | TA_CLR)」を指定する。
- 初期値は「0」を指定する。
system.cfg(一部抜粋)
//------------------------------------------------
// アラームハンドラ定義
//------------------------------------------------
CRE_ALM(ALMID_ALM1, {TA_HLNG, 0, ALM1});
//------------------------------------------------
// イベントフラグ定義
//------------------------------------------------
system.cfg(一部抜粋)
//------------------------------------------------
// イベントフラグ定義
//------------------------------------------------
CRE_FLG(FLGID_FLG1, {(TA_TPRI | TA_WSGL | TA_CLR), 0});
イベントフラグの設定値はこの値が最も基本形である。
イベントフラグを1つのタスクからしか待たない場合は、TA_TPRIでもTA_FIFOでもどっちでもよい。
イベントフラグを使ったタスクの待ち方(wai_flgの使い方)
イベントフラグを使ってイベント待ちをするタスク処理を作ってみましょう。イベント待ちをするタスクはTASK1とします。
イベント待ちのサービスコールであるwai_flgの仕様
では、wai_flgのITRON仕様を見てみましょう。
ER wai_flg(ID flgid, FLGPTN waiptn, MODE wfmode, FLGPTN * p_flgptn);
wai_flgはタスクをイベント待ち状態に遷移させるサービスコールです。イベントフラグがセットされることで待ち状態が解除されます。
wfmodeという待ち方のオプション指定が必要であり、TWF_ANDWかTWF_ORWのどちらかを指定します。これはイベントフラグをANDで待つかORで待つかという違いであり、イベントを独立して待つことができるTWF_ORWを指定するのが一般的です。
TASK1タスクからwai_flgを使ったミッション内容
wai_flgを行うTASK1のミッション内容を決めます。次のミッションに従い処理を行いましょう。
wai_flg関数の呼び出し方は、少し慣れが必要です。定型の形があるのですが、この形を知っていないとどのように指定するのかわかりづらいです。順に使い方を解説しましょう。
イベント種類の定義
今回は4種類のイベントがあるため、これらをmain.cにマクロ定義にて行います。
main.c(追記)
// 各イベント定義
#define D_FLG1_GREEN_LED_ON (0x0001) // 緑LED点灯
#define D_FLG1_GREEN_LED_OFF (0x0002) // 緑LED消灯
#define D_FLG1_ORANGE_LED_ON (0x0004) // 橙LED点灯
#define D_FLG1_ORANGE_LED_OFF (0x0008) // 橙LED消灯
// 全イベントパターン
#define D_FLG1_ALLPTN ( D_FLG1_GREEN_LED_ON | \
D_FLG1_GREEN_LED_OFF | \
D_FLG1_ORANGE_LED_ON | \
D_FLG1_ORANGE_LED_OFF )
イベント定義時の注意点①
各イベントをビット単位の数値で定義します。この時に定義値は0x01、0x02、0x04、0x08、0x10…といった2進数においてどこか1つのビットのみが立っている値にすることです。
これを1、2、3、4…といった連番で定義するとイベントフラグは意図した動きにならないため注意してください。イベントフラグとは各ビットにイベントを割り付ける機能なのです。
HOSにおいてフラグパターンのデータ型であるFLGPTN型はunsigned short型のため全16個まで定義することができます。
イベント定義時の注意点②
各イベントをまとめたマクロであるD_FLG1_ALLPTNを用意します。これはwai_flgの引数で後程使用します。
この定義は各イベントのマクロ値をOR演算で合体させています。これにより次のように対象イベントのビットが全て立った値を定義することになります。
イベントフラグはビット演算との関係が非常に強い機能です。
ビット演算について詳しくない方は『C言語 ビット演算を扱うための本当の視点と実践的な使用例を図解』の記事を見ておくとよいでしょう。
イベントの待ち方
タスク処理の中からwai_flgを呼び出します。TASK1の関数では次のように呼び出します。
main.c(一部抜粋)
void TASK1(VP_INT exinf)
{
FLGPTN flg = 0;
while(1)
{
// イベントフラグ待ち処理
wai_flg(FLGID_FLG1, D_FLG1_ALLPTN, TWF_ORW, &flg);
// イベント処理
}
return;
}
wai_flgの第2引数には待つべきイベントフラグのパターンを指定する必要があります。今回は4つ全てのイベントを待つため、D_FLG1_ALLPTNを指定しています。
第3引数にはTWF_ORWを指定し、いづれかのイベントが1つでも発生したら待ちを解除します。TWF_ANDWにすると全てのビットが立たないと待ちを解除できません。
待ちが解除されると第4引数の中には発生したイベントフラグの内容が格納されます。引数がポインタになっているのはそのためですね。
ITRON入門編を受講されている方はよほど大丈夫だと思いますが、ポインタについて詳しく知りたい方は『C言語 ポインタを使いこなせ【身に付けるための9の極意】』の記事を参照してください。
さあ皆さん、それではTASK1で不足している各種イベント処理の実装を行ってみましょう。各イベントが発生したときにLEDを適宜点灯、消灯させてください。
main.c(一部抜粋)
//------------------------------------------------
// 概 要:練習用TASK1関数
//------------------------------------------------
void TASK1(VP_INT exinf)
{
FLGPTN flg = 0;
while(1)
{
// イベントフラグ待ち処理
wai_flg(FLGID_FLG1, D_FLG1_ALLPTN, TWF_ORW, &flg);
// イベント処理
if ((flg & D_FLG1_GREEN_LED_ON) == D_FLG1_GREEN_LED_ON)
{
Led_setLight(D_LED_KIND_GREEN, D_LED_LIGHT_ON);
}
if ((flg & D_FLG1_GREEN_LED_OFF) == D_FLG1_GREEN_LED_OFF)
{
Led_setLight(D_LED_KIND_GREEN, D_LED_LIGHT_OFF);
}
if ((flg & D_FLG1_ORANGE_LED_ON) == D_FLG1_ORANGE_LED_ON)
{
Led_setLight(D_LED_KIND_ORANGE, D_LED_LIGHT_ON);
}
if ((flg & D_FLG1_ORANGE_LED_OFF) == D_FLG1_ORANGE_LED_OFF)
{
Led_setLight(D_LED_KIND_ORANGE, D_LED_LIGHT_OFF);
}
}
return;
}
flg変数に対してAND演算によるマスク処理を実施し、ビットが立っているかをチェックしています。
イベント待ち処理の注意点①
イベント受信時の各イベントは独立したif文を使うのが基本です。if-elseif文ではつなげないようにしましょう。
イベントのフラグは同時に複数のビットが立つことがあり得ます。
そのため、独立したif文で各イベントに対する処理を行うのが基本になります。if-elseif文にすると1つのイベントしか処理できなくなります。
イベントの待ち方注意点②
イベントフラグは同時に複数立つことを想定します。
イベントフラグが同時に複数立った場合に、どちらを先に実施するかで処理の意味合いが変化するケースがあります。
もちろん順番に依存しないケースも多いのですが、イベントフラグを使うときは処理順番は検証する必要があることを覚えておきましょう。
イベントフラグへのイベント発行方法(set_flgの使い方)
では、続いて外部からイベントを発行する処理を作ってみましょう。イベントはTASK2とCYC1から発行することにします。
イベントフラグをセットするset_flgの仕様
では、set_flgのITRON仕様を見てみましょう。
ER set_flg(ID flgid, FLGPTN setptn);
第2引数で指定したイベントをイベントフラグにセットするサービスコールです。setptnの値を論理和、つまりOR演算にて更新するとされています。
これは待つ側のタスクが動きだすまでの間に、イベントフラグを複数立てられる可能性を示唆しています。
TASK2タスクとCYC1周期ハンドラからset_flgを使ったミッション内容
set_flgを行うTASK2とCYC1のミッション内容を決めます。次のミッションに従い処理を行いましょう。
TASK2のイベントのセットのプログラム作成
では、TASK2からイベントを発行してみましょう。次のプログラムをベースにミッション内容をプログラミングしてみてください。
main.c(一部抜粋)
//------------------------------------------------
// 概 要:練習用TASK2関数
//------------------------------------------------
void TASK2(VP_INT exinf)
{
while(1)
{
}
return;
}
main.c(一部抜粋)
//------------------------------------------------
// 概 要:練習用TASK2関数
//------------------------------------------------
void TASK2(VP_INT exinf)
{
while(1)
{
if (Sw_getPressed() == D_SW_PRESSED_ON)
{
// 緑LED点灯イベント
set_flg(FLGID_FLG1, D_FLG1_GREEN_LED_ON);
}
else
{
// 緑LED消灯イベント
set_flg(FLGID_FLG1, D_FLG1_GREEN_LED_OFF);
}
dly_tsk(100);
}
return;
}
スイッチモジュールのSw_getPressed関数を利用し、スイッチONとOFFの場合でset_flgを呼び出している。指定するのはグリーンLED用イベントのマクロ定数である。
CYC1のイベントのセット方法
続いて、CYC1からイベントを発行してみましょう。次のプログラムをベースにミッション内容をプログラミングしてみてください。
main.c(一部抜粋)
//------------------------------------------------
// 概 要:練習用周期ハンドラ関数
//------------------------------------------------
void CYC1(VP_INT exinf)
{
// 5秒毎に周期起動する
}
main.c(一部抜粋)
//------------------------------------------------
// 概 要:練習用周期ハンドラ関数
//------------------------------------------------
void CYC1(VP_INT exinf)
{
// 5秒毎に周期起動する
static int led = 1; // LEDは最初に点灯
if (led == 1)
{
// 橙LED点灯イベント
iset_flg(FLGID_FLG1, D_FLG1_ORANGE_LED_ON);
led = 0;
}
else
{
// 橙LED消灯イベント
iset_flg(FLGID_FLG1, D_FLG1_ORANGE_LED_OFF);
led = 1;
}
}
周期ハンドラが呼び出される度にLEDの発行イベントを切り替えるため、static変数を利用している。最初は点灯イベント、2回目は消灯イベント…となるようにプログラムする。
MAINタスクとALM1に関しては特にやるべきことはないので次のようにしておきましょう。
main.c(一部抜粋)
//------------------------------------------------
// 概 要: MAINタスク
//------------------------------------------------
void MAIN(VP_INT exinf)
{
while(1)
{
dly_tsk(1000);
}
return;
}
//------------------------------------------------
// 概 要:練習用アラームハンドラ関数
//------------------------------------------------
void ALM1(VP_INT exinf)
{
}
ここまでできたら、ビルドして動かしてみましょう。オレンジLEDは5秒周期で点滅し、グリーンLEDはスイッチの押下に従い点滅するはずです。
全体の動きのシーケンス図は次のようになっています。
イベントフラグによるタスク間同期・通信機能の特徴まとめ
それではイベントフラグの特徴をまとめます。
- イベントフラグはオブジェクトとして生成が必要。
- イベントとして最大16種類のイベントを管理できる。
- イベント受信側において複数の種類のイベントフラグが同時に立っていることがある。
- イベントの待ち方としてAND待ちとOR待ちがあるが、基本形はOR待ちである。
- イベント受信時の処理はif文を単独で連ねる形が基本形である。
- イベントの処理順番は場合により意識しなければならないケースがある。
- 待つ側のタスクが動く前に、セット側が同一のイベントを連続発行した場合は上書きされる。
イベントフラグをタスク間同期・通信で選択する場合、特に注意すべきなのが7.になります。
同一イベントを複数連続で発行した場合、待つ側のタスク次第でイベントは上書きされ、1回のイベントとして処理される可能性があります。
この動きが許容できない場合はイベントフラグの利用は控えた方がよいでしょう。