ITRON入門 データキューの概要と使い方【タスク間データ通信】

ITRON入門カリキュラム
この記事は約15分で読めます。

こんにちは、ナナです。

イベントフラグに続く2つ目のタスク間同期・通信機能を紹介します。

データキューもイベントフラグと同様にタスク間で通信を行うための機構です。イベントフラグとは異なる特性を持つこの機能を解説します。

イベントフラグについて知りたい方はこちらの記事を参照してください。

本記事では次の疑問点を解消する内容となっています。

本記事で学習できること
  • データキューとは何か?
  • データキューの作り方は?
  • データキューを使ったデータ送受信方法は?
  • データキューの特徴を知った上で採用すべきかの判断基準とは?

では、データキューの仕組みを学んでいきましょう。

スポンサーリンク

データキューの概要

イベントフラグではイベントという事象の発生をタスク間で通信しあう機能でした。データキューではイベントフラグではできない「データ」の送信が可能です。

データキューの送受信

では、ITRON仕様を確認しましょう。

4.4.3 データキュー

データキューは1ワードのメッセージ(これをデータと呼ぶ)を受け渡しすることにより、同期と通信を行うためのオブジェクトである。

データキュー機能にはデータキューを生成/削除する機能、データキューに対してデータを送信/強制送信/受信する機能、データキューの状態を参照する機能が含まれる。

データキューはID番号で識別されるオブジェクトである。データキューのID番号をデータキューIDと呼ぶ。

データキューは「タスク」や「イベントフラグ」などと同じように「オブジェクト」として存在するとされています。そのためオブジェクトを生成するためのサービスコールがあります。

データキューで使用する代表的なサービスコール

データキューではよくこれらのサービスコールを使用します。

  • CRE_DTQ
  • cre_dtq
  • snd_dtq
  • rcv_dtq

まずは、このサービスコールを覚えましょう。

スポンサーリンク

データキューの生成方法(CRE_DTQ)

データキューを生成するための静的APIを見てみましょう。

CRE_DTQ(ID dtqid, { ATR dtqatr, UINT dtqcnt, VP dtq});

引数パラメータを解説しておきます。

IDdtqid生成するデータキューのID。整数として一意のIDを割り付ける。
ATRdtqatrデータキューの属性。
UINTdtqcntデータキュー領域の個数。0指定も可能。
VPdtqデータキュー領域の先頭番地。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:データキューからの受信

dtqidで指定されるデータキューからデータを受信しdataに返す。具体的な処理内容は次のとおりである。

対象データキューにデータが入っている場合には、その先頭のデータを取り出しdataに返す。

データキューで送信を待っているタスクがある場合には、送信待ち行列の先頭のタスクが送信しようとしているデータをデータキューの末尾に入れそのタスクを待ち解除する。

rcv_dtqはタスクをデータ受信待ち状態に遷移させるサービスコールです。データキューにデータが入っていない場合rcv_dtqを呼び出すとタスクは受信待ち状態へ遷移します。

データキューが空っぽ

別タスクからデータキューにデータが送信されることで受信待ち状態が解除されます。

データ送信による受信側タスクの待ち解除

第2引数が受信したデータを格納するための変数へのポインタです。型がVP_INT型になっていますね。VP_INT型はlong型がtypedefされたものですので、4Byteのデータを受信することができることになります。

TASK1タスクからrcv_dtqを使ったミッション内容

rcv_dtqを行うTASK1のミッション内容を決めます。次のミッションに従い処理を行いましょう。

TASK1:データキュー受信待ちのミッション

TASK1にはLEDを制御するミッションを与える。外部から要求されるデータキューのデータに応じてLEDを制御する。

VP_INT型のデータフォーマットは次のとおりとする。

データフォーマット

データの意味

①:制御対象のLEDの種別を表す。0:橙LED、1:緑LED
②:制御内容の種別を表す。0:消灯、1:点灯
③:制御ミリ秒数を表す。0~65535ミリ秒

このデータフォーマットに従い、LEDを制御する。

データキューによるデータの送受信は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);
snd_dtq:データキューへの送信

dtqidで指定されるデータキューにdataで指定されるデータを送信する。

対象データキューで受信を待っているタスクがある場合には、受信待ち行列の先頭のタスクに送信するデータを渡し、そのタスクを待ち解除する。

この時、待ち解除されたタスクに対しては待ち状態に入ったサービスコールの返値としてE_OKを返し、データキューから受信したデータとしてdataの値を返す。

第2引数で指定した4Byteのデータをデータキューに送信します。データキューで受信待ちをしているタスクはそれを受け取ることができます。これがデータキューの送受信です。

TASK2タスクからsnd_dtqを使ったミッション内容

snd_dtqを行うTASK2のミッション内容を決めます。次のミッションに従い処理を行いましょう。

TASK2:データキューの送信ミッション

TASK2はスイッチの押下状態に応じてグリーンLEDの点灯、消灯イベントを発行するミッションを与える。

受信側のデータフォーマットに合わせて、次のようにLEDの点灯/消灯の情報を設定し送信する

データ内容と送信順番

  1. ①橙LED ②点灯 ③1000ミリ秒
  2. ①緑LED ②点灯 ③1000ミリ秒
  3. ①橙LED ②消灯 ③2000ミリ秒
  4. ①緑LED ②消灯 ③2000ミリ秒

これらをdly_tskによる100ミリ秒周期で1つずつ送信するものとする。4番を送った後は再度1番から繰り返すものとする。

送信データ作成時の特記事項

指定フォーマットのデータを構築する際にシフト演算とOR演算を利用し行う。

仮に①:0x12、②:0x34、③:0x5678の値をデータフォーマットに変換する際は、次のようにVP_INT型にキャストしなければならいことに注意。

VP_INT data = ((VP_INT)0x12 << 24) | ((VP_INT)0x34 << 16) | ((VP_INT)0x5678);

キャストを行うことで4Byteデータとしてデータの構築が可能となる。キャストをしないと2Byteとして演算されるため正しいデータが作成できない。

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の仕様書をもう少し見てみましょう。次の記述が書いてあります。

snd_dtq:データキューへの送信

【機能】
データキュー領域に空きがない場合には自タスクを送信待ち行列につなぎ、データキューへの送信待ち状態に移行させる。

データキューには最大個数というパラメータがありました。このデータキューに空きがなかった場合、「自タスクを送信待ち状態へ移行させる」とされています。

つまり、送信側は100ミリ秒周期でデータを送っていると思っていたら、受信側の受け取りが遅延しているため送信側のタスクは定期的に待ち状態に遷移しているのです。

送信待ち状態

イベントフラグの時は決して発生しない、送信側における待ち状態というのがデータキューには起こりえます。そのため、送信側/受信側においてデータの送受信頻度を見極めて最大個数やタスク優先度の設計を行う必要があります。

スポンサーリンク

データキューによるタスク間同期・通信機能のまとめ

それではデータキューの特徴をまとめます。

  1. データキューはオブジェクトとして生成が必要。
  2. データキューで送れる情報は4Byteの情報のみ。
  3. データキューの情報は上書きがなく、データキューの最大個数まで貯めることができる。
  4. データのフォーマットは皆さんが設計し、送受信データの構築・解析を行う必要がある。
  5. データ送信側が場合により「送信待ち」という待ち状態に遷移する可能性がある。

送信側が待ち状態になる可能性に関しては、ケアしておく必要があります。

また、データキューは扱いは簡単ではあるもののデータサイズが小さいため、限られたデータの中で如何に情報を伝えるかという設計スキルが求められます。