こんにちは、ナナです。
イベントフラグに続く2つ目のタスク間同期・通信機能を紹介します。
データキューもイベントフラグと同様にタスク間で通信を行うための機構です。イベントフラグとは異なる特性を持つこの機能を解説します。
イベントフラグについて知りたい方はこちらの記事を参照してください。
本記事では次の疑問点を解消する内容となっています。
では、データキューの仕組みを学んでいきましょう。
データキューの概要
イベントフラグではイベントという事象の発生をタスク間で通信しあう機能でした。データキューではイベントフラグではできない「データ」の送信が可能です。
では、ITRON仕様を確認しましょう。
データキューは「タスク」や「イベントフラグ」などと同じように「オブジェクト」として存在するとされています。そのためオブジェクトを生成するためのサービスコールがあります。
データキューで使用する代表的なサービスコール
データキューではよくこれらのサービスコールを使用します。
- CRE_DTQ
- cre_dtq
- snd_dtq
- rcv_dtq
まずは、このサービスコールを覚えましょう。
データキューの生成方法(CRE_DTQ)
データキューを生成するための静的APIを見てみましょう。
CRE_DTQ(ID dtqid, { ATR dtqatr, UINT dtqcnt, VP dtq});
引数パラメータを解説しておきます。
ID | dtqid | 生成するデータキューのID。整数として一意のIDを割り付ける。 |
ATR | dtqatr | データキューの属性。 |
UINT | dtqcnt | データキュー領域の個数。0指定も可能。 |
VP | dtq | データキュー領域の先頭番地。NULLを指定すると自動メモリ割り付け。 |
dtqcntというのがが特徴的です。データキューはオブジェクトの中にデータを複数貯めることが可能であり、貯められる最大個数を指定する必要があります。
イベントフラグのように同一事象に対して上書きが発生しません。同じデータ内容でも最大個数まで溜まっていきます。
CRE_DTQによるデータキューの生成定義
では、データキューを生成してみましょう。system.cfgにデータキューを1つ生成定義してみてください。
追加するデータキュー生成情報
- IDは「DTQID_DTQ1」を指定する。
- 属性は「TA_TFIFO」を指定する。
- データ個数は「3」を指定する。
- データキュー領域の番地はNULLを指定する。
system.cfg(一部抜粋)
//------------------------------------------------
// イベントフラグ定義
//------------------------------------------------
CRE_FLG(FLGID_FLG1, {(TA_TPRI | TA_WSGL | TA_CLR), 0});
//------------------------------------------------
// データキュー定義
//------------------------------------------------
//------------------------------------------------
// データキュー定義
//------------------------------------------------
CRE_DTQ(DTQID_DTQ1, {TA_TFIFO, 3, NULL});
データキューを使ったタスクの待ち方(rcv_dtqの使い方)
データキューを使って受信待ちをするタスク処理を作ってみましょう。受信待ちをするタスクはTASK1とします。
データ受信待ちのサービスコールであるrcv_dtqの仕様
では、rcv_dtqのITRON仕様を見てみましょう。
ER rcv_dtq(ID dtqid, VP_INT * p_data);
rcv_dtqはタスクをデータ受信待ち状態に遷移させるサービスコールです。データキューにデータが入っていない場合rcv_dtqを呼び出すとタスクは受信待ち状態へ遷移します。
別タスクからデータキューにデータが送信されることで受信待ち状態が解除されます。
第2引数が受信したデータを格納するための変数へのポインタです。型がVP_INT型になっていますね。VP_INT型はlong型がtypedefされたものですので、4Byteのデータを受信することができることになります。
TASK1タスクからrcv_dtqを使ったミッション内容
rcv_dtqを行うTASK1のミッション内容を決めます。次のミッションに従い処理を行いましょう。
データキューによるデータの送受信は4Byteというデータの塊でしかありません。そのため、そのデータをどのように解釈するべきかというデータフォーマットの設計が必要となります。
本課題のようにビット単位でデータに意味付けを行い、送信/受信側でフォーマットに従ったデータを構築/解析するといった手法はよく行われます。
ビット演算に詳しくない方は『C言語 ビット演算を扱うための本当の視点と実践的な使用例を図解』の記事を見ておくとよいでしょう。
それではこの内容に従い、TASK1のプログラムを作ってみましょう。
main.c(一部抜粋)
void TASK1(VP_INT exinf)
{
while(1)
{
}
return;
}
main.c(一部抜粋)
void TASK1(VP_INT exinf)
{
VP_INT data; // データキュー受信データ
int ledKind; // LED種別
int ledOnoff; // ON/OFF種別
RELTIM time; // 待ち時間
while(1)
{
// イベントフラグ待ち処理
rcv_dtq(DTQID_DTQ1, &data);
// データフォーマット解析処理
ledKind = (data >> 24) & 0xFF;
ledOnoff = (data >> 16) & 0xFF;
time = data & 0xFFFF;
Led_setLight(ledKind, ledOnoff);
dly_tsk(time);
}
return;
}
受信したデータキューのデータをフォーマットに従い、ビット演算を使って抽出する。抽出したデータに従いLEDへの指示とdly_tskの待ち処理を行う。
データキューへのデータ送信方法(snd_dtq)
では、続いて外部からデータキューを送信する処理を作ってみましょう。TASK2から送信することにします。
データキューを送信するsnd_dtqの仕様
では、snd_dtqのITRON仕様を見てみましょう。
ER snd_dtq(ID dtqid, VP_INT data);
第2引数で指定した4Byteのデータをデータキューに送信します。データキューで受信待ちをしているタスクはそれを受け取ることができます。これがデータキューの送受信です。
TASK2タスクからsnd_dtqを使ったミッション内容
snd_dtqを行うTASK2のミッション内容を決めます。次のミッションに従い処理を行いましょう。
TASK2のデータキュー送信のプログラム作成
では、TASK2からデータキューを送信してみましょう。次のプログラムをベースにミッション内容をプログラミングしてみてください。
main.c(一部抜粋)
//------------------------------------------------
// 概 要:練習用TASK2関数
//------------------------------------------------
void TASK2(VP_INT exinf)
{
while(1)
{
}
return;
}
main.c(一部抜粋)
// LED制御用の構造体定義
typedef struct
{
int ledKind; // 橙/緑LED
int ledOnoff; // ON/OFF
RELTIM time; // 制御時間
} S_LED_CTR;
// データキュー送信用のテーブルデータ定義
const static S_LED_CTR gLedCtrTbl[] =
{
{ D_LED_KIND_ORANGE, D_LED_LIGHT_ON, 1000 },
{ D_LED_KIND_GREEN, D_LED_LIGHT_ON, 1000 },
{ D_LED_KIND_ORANGE, D_LED_LIGHT_OFF, 2000 },
{ D_LED_KIND_GREEN, D_LED_LIGHT_OFF, 2000 },
};
//------------------------------------------------
// 概 要:練習用TASK2関数
//------------------------------------------------
void TASK2(VP_INT exinf)
{
VP_INT data;
int i = 0;
while(1)
{
// データフォーマット構築処理
data = ((VP_INT)gLedCtrTbl[i].ledKind << 24) |
((VP_INT)gLedCtrTbl[i].ledOnoff << 16) |
((VP_INT)gLedCtrTbl[i].time);
// データキューの送信
snd_dtq(DTQID_DTQ1, data);
// テーブルデータの参照位置変更
i++;
if (i >= sizeof(gLedCtrTbl) / sizeof(gLedCtrTbl[0]))
{
i = 0;
}
// 100ミリ秒待ち
dly_tsk(100);
}
return;
}
LED制御用の情報を構造体と定数テーブルにまとめている。定数テーブルは仕様に従いデータを初期化しておく。
データ作成処理では定数テーブルから情報を取り出し、シフト演算とOR演算を利用し指定のデータフォーマットに合わせこんでいる。
ループ処理で定数テーブルの参照位置を変化させながら100ミリ秒の待ち処理を行う。
MAINタスク、CYC1、ALM1に関しては特にやるべきことはないので、次のようにしておきましょう。
main.c(一部抜粋)
//------------------------------------------------
// 概 要:MAINタスク
//------------------------------------------------
void MAIN(VP_INT exinf)
{
while(1)
{
dly_tsk(1000);
}
return;
}
//------------------------------------------------
// 概 要:練習用周期ハンドラ関数
//------------------------------------------------
void CYC1(VP_INT exinf)
{
}
//------------------------------------------------
// 概 要:練習用アラームハンドラ関数
//------------------------------------------------
void ALM1(VP_INT exinf)
{
}
ここまでできたら、ビルドして動かしてみましょう。LEDが1秒毎に順に点灯し、2秒毎に消灯する動きになるはずです。データを適宜変更し、データキューによる情報の受け渡しのイメージを掴んでください。
データキュー送信時の送信待ち状態へのタスク状態遷移について
先ほど作成したデータキューの送受信処理ですが、不思議なことに気が付きましたでしょうか。
送信する側は100ミリ秒周期で情報を送信しています。これに対して、受信側はデータ受信直後にLED制御のため1000~2000ミリ秒の待ち時間が発生しています。
つまり、送る側のペースと受ける側のペースが異なっているのです。一体どのように動いているのでしょうか?
ここでsnd_dtqの仕様書をもう少し見てみましょう。次の記述が書いてあります。
データキューには最大個数というパラメータがありました。このデータキューに空きがなかった場合、「自タスクを送信待ち状態へ移行させる」とされています。
つまり、送信側は100ミリ秒周期でデータを送っていると思っていたら、受信側の受け取りが遅延しているため送信側のタスクは定期的に待ち状態に遷移しているのです。
イベントフラグの時は決して発生しない、送信側における待ち状態というのがデータキューには起こりえます。そのため、送信側/受信側においてデータの送受信頻度を見極めて最大個数やタスク優先度の設計を行う必要があります。
データキューによるタスク間同期・通信機能のまとめ
それではデータキューの特徴をまとめます。
- データキューはオブジェクトとして生成が必要。
- データキューで送れる情報は4Byteの情報のみ。
- データキューの情報は上書きがなく、データキューの最大個数まで貯めることができる。
- データのフォーマットは皆さんが設計し、送受信データの構築・解析を行う必要がある。
- データ送信側が場合により「送信待ち」という待ち状態に遷移する可能性がある。
送信側が待ち状態になる可能性に関しては、ケアしておく必要があります。
また、データキューは扱いは簡単ではあるもののデータサイズが小さいため、限られたデータの中で如何に情報を伝えるかという設計スキルが求められます。