LoginSignup
8
7

More than 3 years have passed since last update.

Chapter4 キュー管理(FreeRTOS チュートリアル日本語訳)

Last updated at Posted at 2020-08-01

この記事について

Mastering the FreeRTOS Real Time Kernel-A Hands-On Tutorial Guildの日本語訳

Chpater 4 キュー管理

4.2 キューの特徴

データストレージ

キューは固定データの大きさが 'size'のアイテムを持ちます。保持できるアイテムの最大数を 'length' と呼びます。
sizeとlengthはキューを生成するときに定義します。

キューは先入先出(FIFO)です。Figure 31にFIFOとしてのデータ書き→読みの例を示します。
キューは先頭のデータを上書きして、先頭に書き込むこともできます。

キューには2つの実装方法があります。

  1. コピー キューにデータを送るときには、元のデータをコピーしてキューに入れる
  2. リファレンス キューにデータを送るときには、元のデータのポインターをキューに入れる

FreeRTOSではコピーしています。以下の理由でリファレンスよりシンプルだからです。

  • スタック変数がキュ-されて、関数を抜けてもスタックがなくなっても問題ない
  • データを保持するための領域を最初にallocateする必要がない。
  • 送信するタスクは送ったデータをすぐ再利用することができる
  • 送信タスク、受信タスクは完全に分離しているアプリケーションなので、それそぞれどちらがデータを保持しているのかを気にする必要がない。リリース責務もない。
  • コピーすることは、リファレンス手法の実現を妨げるものではない。ポインタをキューにコピーすることもできる。
  • RTOSが保存データのアロケート責務を完全に持つことができる
  • メモリプロテクトシステムではタスクのRAMへのアクセスは制限されることがある。リファレンスで渡すと送信、受信両タスクがアクセスできるRAMにデータを置く必要がある。コピーであればそのような制約はない。カーネルは常に特権状態で動作するのでキューにメモリ保護境界を越えてアクセスさせることができる

複数タスクからのアクセス

キューは、存在するタスク、ISRからアクセスできるオブジェクトです。何個のタスクでも同じキューにWrite, Readすることができます。実際は複数の書き手の場合が多く、読み手が複数いる場合は稀です。

公式文書は見つけられなかったが、どのポートの仕様にもSMPをサポートしているFreeRTOSはなかった。3rdパーティであるとの情報もあり。
すくなくともこの文章はAMPの話と思われる。
queue.cの中身をみると、クリティカルセクションでtaskENTER_CRITICALを使って割り込み禁止にしているだけだった。

キューを読むときのブロッキング

タスクがキューを読むときには、オプションでブロックタイムを設定することができる。
これはキューのデータが利用可能になるまでどれくらいBlocked状態で待つかを設定するものです。
タスクはBlocked状態でデータを待ち、データが利用可能になると自動的にReady状態に遷移します。
また、データが利用可能になる前にブロック時間を超えると、同様にReady状態に遷移します。

キューは複数の読み手がいても良いので、1つのキューが複数のタスクをブロックできます。この場合は、データが利用可能になると優先度の高いタスクからブロック解除されます。優先度が同じ場合は長い時間待っていたタスウからブロック解除されます。

キューを書く時のブロッキング

書く場合のブロックタイムは、キューが一杯の場合に空くまで待つ最大時間となります。
キューは複数の書き手がいてもよいので、1つのfullとなっているキューが複数のタスクをブロックできます。この場合も、キューが空くと優先度の高いタスクからブロック解除されます。優先度が同じ場合は長い時間待っていたタスウからブロック解除されます。

複数のキューに対するブロッキング

キューはセットのグループ化できる。タスクはセットにあるキューどれかが利用可能になるまでBlocked状態に入ることができる。
キューセットは4.6章で説明します。

4.3 キューの使用

xQueueCreate() API関数

キューは使用前に明示的に生成されなければならない。
キューはハンドルで参照されます。型はQueueHandle_tとなります。
xQeuueCreateはキューを生成して、そのキューを参照できるハンドルを返します。

FreeRTOS V9.0.0はxQueueCreateStatic関数(compile時にstaticなメモリでキューをcreateできる)を含んでいます。
FreeRTOSはqueueをcreateするときに、RAMをFreeRTOS heapから確保します。RAMはキューのデータ構造とキューに含まれるアイテムを保持するために使われます。
xQueueCreate()は十分なheapがないとNULLを返します。heapの詳細に関してはChapter2を参照してください。

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
/*
uxQueueLength : 一度に保持できるitemの数
uxItemSize    : item 1つ当たりのバイト数
Return Value  : NULLを返す場合は、heapメモリが足りない場合
                non-NULLを返す場合はキューの生成に成功して、ハンドルを返す
*/

キューが作られたあとは、xQueueReset APIで初期の空の状態へ戻せます。

xQueueSendToBack, xQueueSendToFront API関数

予想される通りに、xQueueSendToBack() はqueueのback(tail)にデータを送る。xQueueSendToFrontはfront(head)にデータを送る。
xQueueSend()はxQueueSendToBackとまったく同じです。
注意:xQueueSendToFront または xQueueSendToBackは割り込みルーティーンからは呼び出さないでください。interrupt-safeバージョンはxQueueSendToFromISR(), xQueueSendBackToBackFromISR()となります。これらはChapter6で説明します。

BaseType_t xQueueSendToFront(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);
BaseType_t xQueueSendToBack(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);
/*
xQueue : xQueueCreateで帰ってきたハンドル
pvItemToQueue : キューにコピーされるデータのポインタ
                各itemで保持できるサイズはキューが生成されたときに決まっている。ここではpvItemToQueueの指すデータがキューのストレージエリアにコピーされる
xTicksToWait  : タスクがキューがfullの場合に空くのを待つBlocked状態の最大時間。
                xTicksToWaitが0であればキューがfullの場合はすぐにreturnされる。
                ブロック時間はティック間隔で規定されるので、絶対時間はティック周波数に依存する
                pdMS_TO_TICKSマクロが変換に使える
Return value  : pdPass
                データ送信が正常に終了した。
                xTicksToWaitを設定してでBlock状態遷移した場合は、設定時間前にキューが利用可能になって送信した場合も正常終了となる
                errQUEUE_FULL
                キューがfullで送信できなかった場合に返す。
                xTicksToWaitを設定して時間切れになった場合もこれを返す
                xTicksToWaitにportMAX_DELAYを設定すると、タイムアウトなしで待つ
                FreeRTOSConfig.hでvTaskSuspendを1にすると使える
*/

xQueueReceive() API関数

xQueueReceive()はキューからデータを受信し、受信したデータをキューから削除する。
注意:xQueueReceiveは割り込みルーティーンからは呼び出さないでください。interrupt-safeバージョンはxQueueReceiveFromISR(), となります。これらはChapter6で説明します。

BaseType_t xQueueReceive(QueueHandle_t, void * const pvBuffer, TickType_t xTicksToWait);
/*
xQueue : xQueueCreateで帰ってきたハンドル
pvBuffer : 受信データをコピーするバッファのポインタ。
           各itemで保持できるサイズはキューが生成されたときに決まっている。pvBufferは少なくともそのサイズ以上にすること。
xTicksToWait  : タスクがキューがemptyの場合にデータが来る待つBlocked状態の最大時間。
                xTicksToWaitが0であればキューがemptyの場合はすぐにreturnされる。
                ブロック時間はティック間隔で規定されるので、絶対時間はティック周波数に依存する
                pdMS_TO_TICKSマクロが変換に使える
                xTicksToWaitにportMAX_DELAYを設定すると、タイムアウトなしで待つ
                FreeRTOSConfig.hでvTaskSuspendを1にすると使える
Return value  : pdPass
                データ受信が正常に終了した。
                xTicksToWaitを設定してでBlock状態遷移した場合は、設定時間前に受信できた場合も正常終了となる
                errQUEUE_FULL
                キューがemptyで受信できなかった場合に返す。
                xTicksToWaitを設定して時間切れになった場合もこれを返す
*/

uxQueueMessagesWaiting() API関数

uxQueueMessagesWaiting()は今キューに入っているアイテムの数を返します
注意:uxQueueMessagesWaitingは割り込みルーティーンからは呼び出さないでください。interrupt-safeバージョンはuxQueueMessagesWaitingFromISR(), となります。これらはChapter6で説明します。

UBaseType_t uxQueueMessagesWaiting(QueueHandle_t xQueue);
/*
xQueue : xQueueCreateで帰ってきたハンドル
Return Value : 今保持されているitem数。0であればキューは空ということ。
*/

Example 10. キュー受信時のBlocking

この例は
 キューが生成され(int32_tのデータが保持できる)
 複数のタスクからデータが送信され
 キューからデータが受信される
タスクはブロックタイムを設定しないで送信する。一方のタスクはキューを受信する
送信タスクのプライオリティは受信より低い。これはキューは1つを超えるitemを含まないことを意味する。
データ送信するとすぐに受信タスクはunblockされ、送信タスクをpre-emptする。キューのデータを消去したあとキューは空になる。
Listing 45に実装を示す。2つのタスクが生成され、1つはQueueに100を送信し続け、もう1つは200を送信し続ける。
タスクパラメータはそれぞれのタスクに引数として渡されている。

static void vSenderTask( void *pvParameters )
{
int32_t lValueToSend;
BaseType_t xStatus;

    /* Two instances are created of this task so the value that is sent to the
    queue is passed in via the task parameter rather than be hard coded.  This way
    each instance can use a different value.  Cast the parameter to the required
    type. */
    lValueToSend = ( int32_t ) pvParameters;

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* The first parameter is the queue to which data is being sent.  The
        queue was created before the scheduler was started, so before this task
        started to execute.

        The second parameter is the address of the data to be sent.

        The third parameter is the Block time ・the time the task should be kept
        in the Blocked state to wait for space to become available on the queue
        should the queue already be full.  In this case we don稚 specify a block
        time because there should always be space in the queue. */
        xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );

        if( xStatus != pdPASS )
        {
            /* We could not write to the queue because it was full ・this must
            be an error as the queue should never contain more than one item! */
            vPrintString( "Could not send to the queue.\r\n" );
        }
    }
}

Listing 46は受信タスクの実装です。ブロクタイムを100msに設定しているので、キューにデータがない場合はBlocked状態で待ちます。
データ受信できた場合、または100msを過ぎるとBlocked状態は解除されます。例では送信データが書き続けられるのでタイムアウトすることはありません。

Listing46
static void vReceiverTask( void *pvParameters )
{
/* Declare the variable that will hold the values received from the queue. */
int32_t lReceivedValue;
BaseType_t xStatus;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );

    /* This task is also defined within an infinite loop. */
    for( ;; )
    {
        /* As this task unblocks immediately that data is written to the queue this
        call should always find the queue empty. */
        if( uxQueueMessagesWaiting( xQueue ) != 0 )
        {
            vPrintString( "Queue should have been empty!\r\n" );
        }

        /* The first parameter is the queue from which data is to be received.  The
        queue is created before the scheduler is started, and therefore before this
        task runs for the first time.

        The second parameter is the buffer into which the received data will be
        placed.  In this case the buffer is simply the address of a variable that
        has the required size to hold the received data.

        the last parameter is the block time ・the maximum amount of time that the
        task should remain in the Blocked state to wait for data to be available should
        the queue already be empty. */
        xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );

        if( xStatus == pdPASS )
        {
            /* Data was successfully received from the queue, print out the received
            value. */
            vPrintStringAndNumber( "Received = ", lReceivedValue );
        }
        else
        {
            /* We did not receive anything from the queue even after waiting for 100ms.
            This must be an error as the sending tasks are free running and will be
            continuously writing to the queue. */
            vPrintString( "Could not receive from the queue.\r\n" );
        }
    }
}

mainは前回の例から少し変えただけです。キューを3つのint32_tを保持できように生成して、送信、受信タスクの優先度を逆転させます。
実装をListing 51に示します。

Listing51
int main( void )
{
    /* The queue is created to hold a maximum of 5 long values. */
    xQueue = xQueueCreate( 5, sizeof( int32_t ) );

    if( xQueue != NULL )
    {
        /* Create two instances of the task that will write to the queue.  The
        parameter is used to pass the value that the task should write to the queue,
        so one task will continuously write 100 to the queue while the other task
        will continuously write 200 to the queue.  Both tasks are created at
        priority 1. */
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );

        /* Create the task that will read from the queue.  The task is created with
        priority 2, so above the priority of the sender tasks. */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );

        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }
    else
    {
        /* The queue could not be created. */
    }

    /* The following line should never be reached because vTaskStartScheduler()
    will only return if there was not enough FreeRTOS heap memory available to
    create the Idle and (if configured) Timer tasks.  Heap management, and
    techniques for trapping heap exhaustion, are described in the book text. */
    for( ;; );
    return 0;
}

以下のような結果となります

Figure 36はこの優先度下の実行シーケンスになります。

4.4 複数のソースからの受信

FreeRTOSにとって1つの受信タスクから複数のソースからのデータの受信は普通のことです。
受信タスクはは処理を決めるためにどこからデータが来たのかを知る必要があります。
簡単な解決方法は、1つのキューの構造に、データとソース情報を含めることです。このスキームをFigure 34に示します。

  • キューはData_tの型を持つ構造体を保持する。構造体はenumを含み、1つのメッセージが何の意味を持って送られてきたのかを示すようにする
  • Contorollerは最上位の機能を実行するタスクで、入力に反応してシステムの状態を変化させる必要がある。キューで通信しながら。
  • CAN busタスクはCAN busインターフェースをカプセル化する。CAN busタスクがデコードメッセージを受信すると、デコードされたメッセージをData_t型で送信する。eDataIDはControllerタスクに何のデータかを教える必要がある。描写例だとモーターのスピード値であること。IDataValueはそのスピード値。
  • Human Machie Interface(HMI)はHMI機能をカプセル化する。オペレーターはコマンドを入れて、HMIタスクによって検出、解釈された値を問い合わせできる。コマンドが入力されたときに、HMIタスクはControllerタスクにコマンドを Data_tで送信する。eDataIDはこの場合"new set point value"にする。IDataValudはそのpoint value値。

Example 11. 送信時のブロッキング、構造体の送信

Example11はExample10に似ているが、タスク優先度が逆転している。受信タスクが送信タスクより優先度が低い。
また、キューにintではなくstructureを使っている。

List48
typedef enum
{
    eSender1,
    eSender2
} DataSource_t;

/* Define the structure type that will be passed on the queue. */
typedef struct
{
    uint8_t ucValue;
    DataSource_t eDataSource;
} Data_t;

/* Declare two variables of type Data_t that will be passed on the queue. */
static const Data_t xStructsToSend[ 2 ] =
{
    { 100, eSender1 }, /* Used by Sender1. */
    { 200, eSender2 }  /* Used by Sender2. */
};

Example10では受信タスクが最優先度だったため、キューは1つ以上貯まることはなかったが。これは送信されたデータがすぐに受信できていたためだ。
Example11では送信タスクが高優先度なので、キューはfullになる。これは受信タスクがキューを消去したらすぐにpre-emptされて送信タスクがまたキューをfullにすることを意味する。
送信タスクはまたキューが空くまでブロックされる。

List49に送信タスクの実装を示す。送信タスクはブロック時間100msでキューが使用可能になるまでBlocked状態となる。
この例ではタイムアウトすることはない。受信タスクがキューがfullになるとすぐにキューからデータを受信するので。

List49
static void vSenderTask( void *pvParameters )
{
BaseType_t xStatus;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );

    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* The first parameter is the queue to which data is being sent.  The
        queue was created before the scheduler was started, so before this task
        started to execute.

        The second parameter is the address of the structure being sent.  The
        address is passed in as the task parameter.

        The third parameter is the Block time - the time the task should be kept
        in the Blocked state to wait for space to become available on the queue
        should the queue already be full.  A block time is specified as the queue
        will become full.  Items will only be removed from the queue when both
        sending tasks are in the Blocked state.. */
        xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );

        if( xStatus != pdPASS )
        {
            /* We could not write to the queue because it was full - this must
            be an error as the receiving task should make space in the queue
            as soon as both sending tasks are in the Blocked state. */
            vPrintString( "Could not send to the queue.\r\n" );
        }
    }
}

受信タスクは優先度が低いので、送信タスクがBlocked状態のときに動作する。送信タスクはキューがfullのときだけBlocked状態になる。
そのため受信タスクはキューがfullのときのみ動けることになる。そのためブロックタイムを設定しなくても、いつもデータを受信することになる。
受信データの実装をList 50に示す。

List50
static void vReceiverTask( void *pvParameters )
{
/* Declare the structure that will hold the values received from the queue. */
Data_t xReceivedStructure;
BaseType_t xStatus;

    /* This task is also defined within an infinite loop. */
    for( ;; )
    {
        /* As this task only runs when the sending tasks are in the Blocked state,
        and the sending tasks only block when the queue is full, this task should
        always find the queue to be full.  3 is the queue length. */
        if( uxQueueMessagesWaiting( xQueue ) != 3 )
        {
            vPrintString( "Queue should have been full!\r\n" );
        }

        /* The first parameter is the queue from which data is to be received.  The
        queue is created before the scheduler is started, and therefore before this
        task runs for the first time.

        The second parameter is the buffer into which the received data will be
        placed.  In this case the buffer is simply the address of a variable that
        has the required size to hold the received structure.

        The last parameter is the block time - the maximum amount of time that the
        task should remain in the Blocked state to wait for data to be available
        should the queue already be empty.  A block time is not necessary as this
        task will only run when the queue is full so data will always be available. */
        xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );

        if( xStatus == pdPASS )
        {
            /* Data was successfully received from the queue, print out the received
            value and the source of the value. */
            if( xReceivedStructure.eDataSource == eSender1 )
            {
                vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue );
            }
            else
            {
                vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue );
            }
        }
        else
        {
            /* We did not receive anything from the queue.  This must be an error
            as this task should only run when the queue is full. */
            vPrintString( "Could not receive from the queue.\r\n" );
        }
    }
}

main()は前の例と少ししか変えていない。Data_t型を保持できるようにして、送信、受信タスクの優先度を逆転させた。
List51に実装を示す

List51
int main( void )
{
    /* The queue is created to hold a maximum of 3 structures of type Data_t. */
    xQueue = xQueueCreate( 3, sizeof( Data_t ) );

    if( xQueue != NULL )
    {
        /* Create two instances of the task that will write to the queue.  The
        parameter is used to pass the structure that the task should write to the
        queue, so one task will continuously send xStructsToSend[ 0 ] to the queue
        while the other task will continuously send xStructsToSend[ 1 ].  Both
        tasks are created at priority 2, which is above the priority of the receiver. */
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) &( xStructsToSend[ 0 ] ), 2, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) &( xStructsToSend[ 1 ] ), 2, NULL );

        /* Create the task that will read from the queue.  The task is created with
        priority 1, so below the priority of the sender tasks. */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );

        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }
    else
    {
        /* The queue could not be created. */
    }

    /* The following line should never be reached because vTaskStartScheduler()
    will only return if there was not enough FreeRTOS heap memory available to
    create the Idle and (if configured) Timer tasks.  Heap management, and
    techniques for trapping heap exhaustion, are described in the book text. */
    for( ;; );
    return 0;
}

実行結果を示す。
image.png

実行シーケンスを示す
image.png

4.5 大きいまたは可変サイズデータの受信

キューに保存するデータサイズが大きい場合は、データそのもののコピーよりもポインタを送信するのが良い。
ポインタの送信は、実行時間、RAM使用量ともに効率化できる。しかし以下のことを保証しなければならない。

1. ポインタが示すRAMの所有者を明らかにする

ポインタを通じてデータを共有した場合は、2つのタスクが同時にメモリをmodifyしないことが求められる。さもなければメモリ内容の正当性や一貫性が失われることがある。
理想的には送信タスクのみがアクセスを許可されるべきで、受信タスクはアクセスだけ許されるようにすべきだ。

2. ポインタが示すRAMは有効であり続けること

メモリを動的または、事前に確保したバッファから取得する場合には、1つのタスクがフリーする責務がある。フリーした後はどのタスクもアクセスするべきではない。
ポインタが示すメモリはスタックとして確保されるべきではない。スタックフレームが変化すると無効となってしまうので。

Listing 52, 53, 54の例ではどのようにポインタを他のタスクに送るべきかを示す

  • List 52は5つのポインタを保持できるキューを生成する
  • List 53ではバッファを確保して文字列を書き込み、ポインタをキューに送信している
  • List 54ではポインタを受信して文字列をprintしている。
Listing52
/* Declare a variable of type QueueHandle_t to hold the handle of the queue being created. */ 
QueueHandle_t xPointerQueue; 
/* Create a queue that can hold a maximum of 5 pointers, in this case character pointers. */ 
xPointerQueue = xQueueCreate( 5, sizeof( char * ) ); 
Listing53
/* A task that obtains a buffer, writes a string to the buffer, then sends the address of the 
buffer to the queue created in Listing 52. */ 
void vStringSendingTask( void *pvParameters ) 
{ 
char *pcStringToSend; 
const size_t xMaxStringLength = 50; 
BaseType_t xStringNumber = 0; 

    for( ;; ) 
    { 
        /* Obtain a buffer that is at least xMaxStringLength characters big.  The implementation  
        of prvGetBuffer() is not shown – it might obtain the buffer from a pool of pre-allocated  
        buffers, or just allocate the buffer dynamically. */ 
        pcStringToSend = ( char * ) prvGetBuffer( xMaxStringLength ); 

        /* Write a string into the buffer. */ 
        snprintf( pcStringToSend, xMaxStringLength, "String number %d\r\n", xStringNumber ); 

        /* Increment the counter so the string is different on each iteration of this task. */ 
        xStringNumber++; 

        /* Send the address of the buffer to the queue that was created in Listing 52. The 
        address of the buffer is stored in the pcStringToSend variable.*/ 
        xQueueSend( xPointerQueue,   /* The handle of the queue. */  
                    &pcStringToSend, /* The address of the pointer that points to the buffer. */  
                    portMAX_DELAY ); 
    } 
} 
Listing54
/* A task that receives the address of a buffer from the queue created in Listing 52, and 
written to in Listing 53.  The buffer contains a string, which is printed out. */ 
void vStringReceivingTask( void *pvParameters ) 
{ 
char *pcReceivedString; 

    for( ;; ) 
    { 
        /* Receive the address of a buffer. */ 
        xQueueReceive( xPointerQueue,     /* The handle of the queue. */ 
                       &pcReceivedString, /* Store the buffer’s address in pcReceivedString. */ 
                       portMAX_DELAY ); 

        /* The buffer holds a string, print it out. */ 
        vPrintString( pcReceivedString ); 

        /* The buffer is not required any more - release it so it can be freed, or re-used. */ 
        prvReleaseBuffer( pcReceivedString ); 
    } 
} 

異なるタイプと長さのデータ送信

前のセクションでは、2つの有用な設計パターンを示した。構造体の送信とポインタの送信だ。
この2つのテクニックを組み合わせると、どんなソースから、どんなデータでも受信できるようになる。
FreeRTOS+TCP TCP/IPスタックの実装は、どうのよう達成するかの実践例を示してくれる。

TCP/IPスタックは多くの異なるソースからのイベントによって処理をする。異なるイベントは異なるデータタイプに関連する
TCP/IPタスクの外で発生するすべてのイベントはIPStakcEvent_t構造体として送信される。IPStakcEvent_tに構造を示す。
pvDataは値を直接指すポインタまたは、バッファへのポインタである

Listing55
/* A subset of the enumerated types used in the TCP/IP stack to identify events. */ 
typedef enum 
{ 
    eNetworkDownEvent = 0, /* The network interface has been lost, or needs (re)connecting. */ 
    eNetworkRxEvent,       /* A packet has been received from the network. */ 
    eTCPAcceptEvent,       /* FreeRTOS_accept() called to accept or wait for a new client. */ 

    /* Other event types appear here but are not shown in this listing. */ 

} eIPEvent_t; 


/* The structure that describes events, and is sent on a queue to the TCP/IP task. */ 
typedef struct IP_TASK_COMMANDS 
{ 
    /* An enumerated type that identifies the event.  See the eIPEvent_t definition above. */ 
    eIPEvent_t eEventType; 

    /* A generic pointer that can hold a value, or point to a buffer. */ 
    void *pvData; 

} IPStackEvent_t; 

TCP/IPイベントは関連する以下のデータを含む

eNetworkRxEvent : ネットワークから受信したデータパケット

受信したデータはIPStackEvent_tを使ってTCP/IPタスクに送られる。この構造体のeEventTypeはeNetworkRxEventとなり、pvDataは受信データを含んだバッファへのポインタとなる。
疑似コードをList56に示す。

Listing56
void vSendRxDataToTheTCPTask( NetworkBufferDescriptor_t *pxRxedData ) 
{ 
IPStackEvent_t xEventStruct; 

    /* Complete the IPStackEvent_t structure.  The received data is stored in  
    pxRxedData. */ 
    xEventStruct.eEventType = eNetworkRxEvent; 
    xEventStruct.pvData = ( void * ) pxRxedData; 

    /* Send the IPStackEvent_t structure to the TCP/IP task. */ 
    xSendEventStructToIPTask( &xEventStruct ); 
} 
eTCPAcceptEvent : クライアントからの接続をaccept または waitするソケット 

FreeRTOS_accept()を呼ぶタスクから送られたAcceptイベントはIPStackEvent_tを使ってTCP/IPタスクに送信する
eEventTypeはeTCPAcceptEvent, pvDataはコネクションをacceptしたソケットのポインタとなる。
疑似コードをListing57に示す。

Listing57
void vSendAcceptRequestToTheTCPTask( Socket_t xSocket ) 
{ 
IPStackEvent_t xEventStruct; 

    /* Complete the IPStackEvent_t structure. */ 
    xEventStruct.eEventType = eTCPAcceptEvent; 
    xEventStruct.pvData = ( void * ) xSocket; 

    /* Send the IPStackEvent_t structure to the TCP/IP task. */ 
    xSendEventStructToIPTask( &xEventStruct ); 
} 
eNetworkDownEvent : 接続、再接続を要求するネットワーク

Network downイベントは、ネットワークインターフェースからTCP/IPタスクにIPStackEvent_tをつかって送信する
eEventTypeはeNetworkDownEvent, pvDataは使われない。
疑似コードをListing57に示す。

Listing58
void vSendNetworkDownEventToTheTCPTask( Socket_t xSocket ) 
{ 
IPStackEvent_t xEventStruct; 

    /* Complete the IPStackEvent_t structure. */ 
    xEventStruct.eEventType = eNetworkDownEvent; 
    xEventStruct.pvData = NULL; /* Not used, but set to NULL for completeness. */ 

    /* Send the IPStackEvent_t structure to the TCP/IP task. */ 
    xSendEventStructToIPTask( &xEventStruct ); 
} 

これらのイベントを受信するタスクをListing 59に示す。
IPStackEvent_tをどのイベント化を解釈するために使っている。

Listing59
IPStackEvent_t xReceivedEvent; 

    /* Block on the network event queue until either an event is received, or xNextIPSleep ticks  
    pass without an event being received.  eEventType is set to eNoEvent in case the call to  
    xQueueReceive() returns because it timed out, rather than because an event was received. */ 
    xReceivedEvent.eEventType = eNoEvent; 
    xQueueReceive( xNetworkEventQueue, &xReceivedEvent, xNextIPSleep ); 

    /* Which event was received, if any? */ 
    switch( xReceivedEvent.eEventType ) 
    { 
        case eNetworkDownEvent : 
            /* Attempt to (re)establish a connection.  This event is not associated with any  
            data. */ 
            prvProcessNetworkDownEvent(); 
            break; 

        case eNetworkRxEvent: 
            /* The network interface has received a new packet.  A pointer to the received data  
            is stored in the pvData member of the received IPStackEvent_t structure.  Process  
            the received data. */ 
            prvHandleEthernetPacket( ( NetworkBufferDescriptor_t * )( xReceivedEvent.pvData ) ); 
            break; 

        case eTCPAcceptEvent: 
            /* The FreeRTOS_accept() API function was called.  The handle of the socket that is  
            accepting a connection is stored in the pvData member of the received IPStackEvent_t  
            structure. */ 
            xSocket = ( FreeRTOS_Socket_t * ) ( xReceivedEvent.pvData ); 
            xTCPCheckNewClient( pxSocket ); 
            break; 

        /* Other event types are processed in the same way, but are not shown here. */ 

    } 

4.6 複数のキューからの受信

Queue Sets

通常、1つのタスクは異なるデータサイズ、異なるソースからくるデータを受信するように設計される。
前のセクションでは1つのキューを使って構造体を受信することによる効率的な方法を示したが、時にはアプリケーション設計者が設計制約(いくつかのソースから異なるキューを使う)を課されることがある。例えば、サードパーティのコードが導入されて、専用キューの存在が予想される時だ。このようなときには'queue set'が使える。

Queue setsはタスクが1つ以上のキューからデータを受信できるようにする。その際には各キューへのポーリング(どれがデータを含んでいるのか順番に決めるような)をする必要がない。
queue setを使う設計は1つのキューを使う場合に比べて、あまり好ましくないし、効率も良くない。
そのため本当に必要な時だけ使うように推奨されている。

次のセクションでどうやってqueue setを使うのかを以下の手順で示す

1. queue setを生成する

2. setにqueueを加える

セマフォをqueue setに加える。セマフォについてはこの本の後で説明する。

3. どののキューがデータを含むかを決めるためにキューを読む

キューが受信データのメンバに含まれている場合は、キューのハンドルがqueue setで送られ、queue setから読むとリターンされる。
そのためqueue setからキューハンドルが帰ってきた場合は、データが含まれていることが分かり、読むことができる。
注意:queue setから最初にキューハンドルを読むまでは、キューからデータを読んではならない
Queue Setを使うにはFreeRTOSConfig.hでconfigUSE_QUEUE_SETSを1にする

xQueueCreateSet() API関数

queue setは使う前に生成する必要がある。

QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength);
/*
uxEventQueueLength:
キューがqueue setのメンバーのときには、そのハンドルはqueue setにおくられる。uxEventQueueLengthは保持できるキューハンドルの最大数を示す。

set内のキューがデータを受信したときのみ、キューハンドルはqueue setに送ることができる。キューはfullの場合データを受信することができないため、
queue setがfullのときにはキューハンドラはqueue setに送るべきではない。

そのため、queue setのitem最大値、queue setに入るすべてのqueueのlength合計を保持できる必要がある。
例えば、1つのsetに3つのqueueがあり、各queueのlengthが5の場合にはsetがfullになるまでのitem数は15となる。
この場合uxEventQueueLengthは15にすれば、すべてのアイテムが受信できることを保証できる。

semaphoreをqueue setに追加することもできる。binary semaphore, counting semaphoreはこの本の後で説明される。
必要なuvEventQueueLenghtを計算する目的で、binary semaphoreのlengthは1, counting semaphoreのlengthはsemaphoreの最大カウント値となる

他の例として、queue setがlength 3のqueueとbinary semaphore(length 1) 含む場合は、uxEvenQueueLengthは4となる(3+1)

Retrun Value:
NULLの場合はheap不足で生成できなかった。
non-NULLの場合は生成されたqueue setを示すハンドラを返す。
*/

xQueueAddToSet() API関数

xQueueAddToSetはqueueかsemaphoreをqueue setに追加する。

BaseType_t xQueueAddToSet(QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet);
// XQueueOrSemaphore : queueかsemaphoreのhandleを設定する。QueueSetMemberHandle_tにキャストする必要がある。
// xQueueSet         : queue setのhandleを設定する

xQueueSelectFromSet() API関数

xQueueSelectFromSetはqueue setからqueue handleを読むことができる。

queueまたはsemaphoreがqueue setのメンバーの時、そのqueueとsemaphoreはqueue setに送られ、あるタスクがxQueueSelectFromSetを呼ぶとリターンされる。
もしxQueueSelectFromSetがhandleをリターンすれば、handleを使ってqueueまたはsemaphoreを参照でき、タスクは直接そのqueueとsemaphoreにアクセスできる。

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, const TickType_t xTicksToWait ); 
/*
*/

xQueueSelectFromSetによってhandleがリターンされていないときは、queue setのメンバーであるqueueまたはsemaphoreからデータを読んではならない。
xQueueSelectFromSetによってリターンされたhandleのみから読むこと

Listing62

4.7 メールボックスを生成するキューの使用

組み込み業界の専門用語にはコンセンサスがない。
'mailbox'は異なるRTOSでは異なる意味となる。この本ではmailboxはlength 1のqueueを意味する。
queueはmailboxとして説明されることがある。基本的な機能の違いというよりかはアプリケーションの使い方によってそう呼ばれる。

  • queueはタスクまたは割り込みルーチンからデータをもう1つのタスクに送る。送りてはキューにアイテムをセットして、受けては受信した後にキューから削除する。
  • mailboxはどのタスク、割り込みルーチンからでも読まれるために保持される。データはmailboxを通り過ぎない代わりに、上書きされるまで保持される。送信側はデータをmailboxにセットする。受信側はデータを読むが削除はしない。

このチャプターでは2つのmailboxに関するAPIを説明する。

Listing67
/* A mailbox can hold a fixed size data item.  The size of the data item is set 
when the mailbox (queue) is created.  In this example the mailbox is created to 
hold an Example_t structure.  Example_t includes a time stamp to allow the data held 
in the mailbox to note the time at which the mailbox was last updated.  The time 
stamp used in this example is for demonstration purposes only - a mailbox can hold 
any data the application writer wants, and the data does not need to include a time 
stamp. */ 
typedef struct xExampleStructure 
{ 
    TickType_t xTimeStamp; 
    uint32_t ulValue; 
} Example_t; 


/* A mailbox is a queue, so its handle is stored in a variable of type  
QueueHandle_t. */ 
QueueHandle_t xMailbox; 

void vAFunction( void ) 
{ 
    /* Create the queue that is going to be used as a mailbox.  The queue has a  
    length of 1 to allow it to be used with the xQueueOverwrite() API function, which  
    is described below. */ 
    xMailbox = xQueueCreate( 1, sizeof( Example_t ) ); 

} 

xQueueOverwrite() API関数

xQueueSendToBackと同様に、xQueueOverWrite() API関数はqueueでデータを送る。もしqueueがfullの場合は既にqueueに入っているデータを上書きする。

xQueueOverwriteはLength 1のqueueのみに使われるべきである。
この制約はどちらのデータを上書きするかを判断する実装を避けるためである。

★注意:xQueueOverwriteは割り込みルーティーンからは呼び出さないでください。interrupt-safeバージョンはxQueueOverwriteFromISR(), となります。これらはChapter6で説明します。

Listing68
BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue ); 

Listing 69にxQueueOverWriteの使用例を示す

Listing69
void vUpdateMailbox( uint32_t ulNewValue ) 
{ 
/* Example_t was defined in Listing 67. */ 
Example_t xData; 

    /* Write the new data into the Example_t structure.*/ 
    xData.ulValue = ulNewValue; 

    /* Use the RTOS tick count as the time stamp stored in the Example_t structure. */ 
    xData.xTimeStamp = xTaskGetTickCount(); 

    /* Send the structure to the mailbox - overwriting any data that is already in the  
    mailbox. */ 
    xQueueOverwrite( xMailbox, &xData ); 
} 

xQueuePeek() API関数

xQueuePeek()はキューからitemを削除することのなく、itemを受信するために使われる。
xQueuePeek()はqueueの先頭からデータを受信して、データを変更したり順番を変えたりすることをしない。
★注意:xQueuePeekは割り込みルーティーンからは呼び出さないでください。interrupt-safeバージョンはxQueuePeekFromISR(), となります。これらはChapter6で説明します。

Listing70
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ); 

Listing 71に使用例を示す。

Listing71
BaseType_t vReadMailbox( Example_t *pxData ) 
{ 
TickType_t xPreviousTimeStamp; 
BaseType_t xDataUpdated; 

    /* This function updates an Example_t structure with the latest value received  
    from the mailbox.  Record the time stamp already contained in *pxData before it  
    gets overwritten by the new data. */ 
    xPreviousTimeStamp = pxData->xTimeStamp; 

    /* Update the Example_t structure pointed to by pxData with the data contained in  
    the mailbox.  If xQueueReceive() was used here then the mailbox would be left  
    empty, and the data could not then be read by any other tasks.  Using  
    xQueuePeek() instead of xQueueReceive() ensures the data remains in the mailbox. 
    A block time is specified, so the calling task will be placed in the Blocked  
    state to wait for the mailbox to contain data should the mailbox be empty.  An  
    infinite block time is used, so it is not necessary to check the value returned  
    from xQueuePeek(), as xQueuePeek() will only return when data is available. */ 
    xQueuePeek( xMailbox, pxData, portMAX_DELAY ); 

    /* Return pdTRUE if the value read from the mailbox has been updated since this  
    function was last called.  Otherwise return pdFALSE. */ 
    if( pxData->xTimeStamp > xPreviousTimeStamp ) 
    { 
        xDataUpdated = pdTRUE; 
    } 
    else 
    { 
        xDataUpdated = pdFALSE; 
    } 

    return xDataUpdated; 
} 
8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7