LoginSignup
3
2

More than 3 years have passed since last update.

Chapter 8 イベントグループ (Event Gropus)

Posted at

この記事について

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

重要と思われるポイント

  • イベントグループは複数のタスク、複数のイベント間で同期ができる
  • イベントグループに使用できるビット数はconfigUSE_16_BIT_TICKSで設定する
  • xEventGroupSetBitsで設定したbitは既に設定されている値とのORがとられる
  • ISRからイベントビットを設定した結果でBlocked状態から抜けるタスクはデーモンタスクのコンテキストで実行される
  • イベントの条件を満たしたあとに自動でビットをクリアする場合にはxClearOnExitを設定する
  • 複数のタスクを同期させるには、「イベントのセット」、「イベントが条件を満たしているかのテスト」を同時に行うxEventGroupSyncを使う必要がある

Chapter 8 イベントグループ (Event Gropus)

8.1 チャプターの紹介とスコープ

リアルタイム組み込みシステムではイベントに応答する必要がある。前のチャプターではタスクと通信するイベントを扱うFreeRTOSのfeatureを説明してきた。
セマフォやキューのような例を挙げた。両者の特徴としては以下となる。

  • 1つのイベントの発生をBlocked状態で待てる
  • イベントが発生したときに待っている中の最優先度のタスクをunblockする

イベントグループは複数のイベントと複数のタスクをコミュニケートすることができるFreeRTOS Featureである。
キューやsemaphoreとは違って

  • イベントグループは1つのタスクが複数のイベント発生をBlocked状態で待つことができる
  • イベントが発生したときに、同じイベントを待っている複数のタスクをunblockすることができる

イベントタスクのユニークな特徴としては、複数のタスクを同期することができる。イベントは1つ以上のタスクにブロードキャストすることができる。1つのタスクが、複数のイベントのセットの発生をBlocked状態で待つことができる。1つのタスクが複数のアクションの完了を待つこともできる。

イベントグループはまたアプリケーションのRAM使用量を減らすことができる。複数のバイナリセマフォを1つのイベントで置き換えらることがある。

イベントグループ機能はオプションとなる。FreeRTOSのファイルのevent_groups.cをビルドする必要がある。

スコープ

このチャプターでは以下を学ぶ

  • イベントグループの実践的な使用方法
  • 他のFreeRTOS FeatureとのPros, Cons
  • イベントグループのビット立て方
  • どのようにイベントグループでセットされるビットを待つのか
  • タスクのセットを同期させるための、イベントグループ使用方法

8.2 イベントグループの特徴

Event Groups, Event Flags, Event Bits

'flag'はイベントが発生したか、しないかったを示す1または0のBoolean値である。'group'はevent flagsのセットを示す。

イベントフラグは1bitの1 or 0を保存して状態を表す。イベントフラグの各状態は、1つのイベントグループの変数として保存される。
イベントグループのイベントフラグの状態は、EventBits_tの1bitで表される。このことから、イベントフラグはイベント「ビット」としても知られる。
EventBitsが1の場合はイベントが発生したことを示す。0であれば発生していないことを示す。

Figure71にEventBits_tに各ビットをアサインしたイベントフラグを示す。
image.png
もしイベントグループが0x92 (binary 1001 0010) のとき、1, 4, 7のビットがセットされている。1, 4, 7のイベントが発生したことを示す。
Figure72はEventBits_tの変数で、ビット1, 4, 7がセットされ、他のビットはクリアされて、0x92になっていることを示す。
image.png
ビットへの割り込みはアプリケーション開発者に依存する。
たとえば、開発者がイベントグループを生成して、

  • bit0は、'a'メッセージをネットワークから受信したことを意味する
  • bit1は、'a'メッセージをネットワークへ送信する準備ができていることを意味する
  • bit2は、ネットワーク接続がabortしたことを意味する

EventBits_tについて

イベントグループのイベントビット数はconfigUSE_16_BIT_TICKSで決まる

  • configUSE_16_BIT_TICKSが1の場合、各イベントグループは8bit扱う
  • configUSE_16_BIT_TICKSが0の場合、各イベントグループは24bit扱う

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

イベントグループはどのタスクやISRからもアクセスできることができる。同じイベントグループに対して、どんな数のタスクも読み書きできる

イベントグループの使用例

FreeRTOS+TCP TCP/IPの実装は、イベントグループの実践例となっていて、イベントグループが設計をシンプルに、リソース使用を最小にしている。

TCPソケットは多くの異なるイベントに応答する。イベント例は、accept, bind, read, closeイベントを含む。例えば、ソケットがcreateされたがアドレスにバインドされていない時には、bindイベント受信を待っているが、readイベントは待っていない状態となる。(アドレスがない場合は、readすることはできない)

FreeRTOS+TCPソケット状態は、FreeRTOS_Socket_tに保存されている。この構造体は各イベントが定義されているイベントグループを含んでいる。
イベントまたはイベントのグループを待つためのブロックするFreeRTOS+TCP APIコールは単純にイベントグループをブロックする。

8.3 イベントグループを使ったイベント管理

xEvnetGroup API Function

FreeRTOS V.9.0.0ではコンパイル時にアロケートするxEventGroupCreateStatic関数が含まれる。イベントグループは使用前に明示的に使うことができる。

イベントグループはEventGroupHandle_t型を参照する。xEventGroupCreate APIはイベントグループを生成して、それを示すハンドルを返す。

Listing132
EventGroupHandle_t xEventGroupCreate( void ); 

・戻り値
NULLはメモリ不足で生成失敗したことを示す
non-NULLは生成に成功したことを示し、ハンドルを返す

xEventGroupSetBits API Function

xEventGroupSetBits API関数は、イベントグループに1つか複数のビットをセットする。bitによってタスクにイベントを発生したことを伝えるために使用される。

ISRからの場合は、xEventGroupSetBitsFromISRを使う。

Listing133
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet ); 

・xEvnetGroup
xEventGroupCreateで生成したhandle
・uxBitsToSet
イベントグループに1をセットするために、ビットマスクを設定する。イベントグループの値は、すでに設定されている値とのORと取って設定される
・戻り値
xEventGroupSetBitsがリターンしたときのイベントグループの値を示す。他のタスクによって再度クリアされるので、uxBitsTosetで設定された値は戻り値は持つ必要がない。

xEventGroupSetBitsFromISR

xEventGroupSetBitsFromISRは割り込みセーフバージョンとなっている。

セマフォのgiveは決定的な動作をする。セマフォのgiveは最も高い1つのタスクをBlock状態から抜けさせる。イベントグループにビットがセットされたときには、何個のタスクがBlock状態を抜けるか事前にわからない。そのためイベントグループにビットをセットする動作は決定的ではない。

FreeRTOSの設計標準では、ISR内で動くまたは割り込みがdisable時の、非決定的な操作は許可していない。
そのため、xEventGroupBitsFromISRは直接ISRからイベントをセットせずに、代わりにRTOSデーモンタスクで遅延実行する。

Listing134
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, 
                                      const EventBits_t uxBitsToSet, 
                                      BaseType_t *pxHigherPriorityTaskWoken ); 

・pxHigherPriorityTaskWoken
xEventGroupSetBitsFromISRはISR内で直接イベントセットは行わないが、タイマーコマンドキューにコマンド送信してRTOS deamonタスクまで動作を遅延させる。
デーモンタスクがBlocked状態であれば、この動作でBlocked状態を抜ける。もし、デーモンタスクが現在動いているタスクより優先度が高い場合には*pxHigherPriorityTaskWokenはpdTRUEとなる。
もしpdTRUEであれば、割り込みが終わる前にコンテキストスイッチが発生する。これは割り込みが直接デーモンタスクへreturnすることを保証する。デーモンタスクがReady状態で最も優先度が高いためだ。

・戻り値
pdPASS : タイマーコマンドキューへの送信が成功した
pdFALSE : タイマーコマンドキューがfullだったので送信が失敗した。

xEventGroupWaitBits API Function

xEventGroupWaitBits API Functionはタスクがイベントグループの値を読ませられて、もしイベントビットがセットされていなときには、選択的にイベントグループにビットがセットされるまでBlocked状態で待つことができる。

Listing125
EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup, 
                                 const EventBits_t uxBitsToWaitFor, 
                                 const BaseType_t xClearOnExit, 
                                 const BaseType_t xWaitForAllBits, 
                                 TickType_t xTicksToWait ); 

この条件は、タスクがBlocked状態に入るかどうかや、いつBlocked状態を抜けるかを決めるために使われ、"unblock condition"と呼ばれる。
unblock conditionはuxBitsToWaitFor, xWaitForAllBitsパラメータの組み合わせで設定される。

  • uxBitsToWaitForはイベントグループでどのイベントをテストするのかを設定する
  • xWaitForAllBitsはbitをORかANDどちらで使用するのかを設定する

unblock条件がxEventGroupWaitBitsが呼ばれた条件を満たす場合は、タスクはBlocked状態には入らない。

タスクがBlocked状態またはBlocked状態を抜ける結果となる、conditionの例は、Table45に示す。Table45はイベントグループとuxBitsToWaitFor値の4bitをs召している。他のビットはゼロと仮定する。

Table 45

Event Group値 uxBitsToWaitFor xWaitForAllBits 振る舞い
0000 0101 pdFALSE callしたタスクはbit0, bit2がsetされていないのでBlocked状態になる。bit0またはbit2がsetされるとBlocked状態を抜ける
0100 0101 pdTRUE callしたタスクはbit0, bit2両方がsetされていないのでBlocked状態になる。bit0とbit2が両方setされるとBlocked状態を抜ける
0100 0110 pdFALSE callしたタスクはxWaitForAllBitsがpdFALSEで、uxBitsToWaitForで指定した2つのうちの1つのbitがsetされているのでBlocked状態に入らない
0100 0110 pdTRUE callしたタスクはxWaitForAllBitsがpdTRUEでuxBitsToWaitForで指定された2つのbitがsetされていないのでBlocked状態に入る。2つともsetされるとBlocked状態を抜ける

callしたタスクはuxBitsToWaitForパラメータでテストするビットを指定する。unblock conditionが条件を満たした後はそのビットをゼロにクリアする必要がありそうだ。
xEventGroupClearBit API functionを使うとビットをクリアできるが、以下のアプリケーションコードの場合は、競合を発生させる。

  • 同じイベントグループを複数のタスクが使っている
  • 異なるタスクまたはISRからビットがセットされる

xClearOnExitパラメータは、これらの潜在的な競合を回避する。もしxClearOnExitがpdTRUEであれば、アトミックな操作(割り込まれない)としてビットをテストして、クリアすることができる。

パラメータ名 説明
xEventGroup xEventGroupCreateでリターンされたhandle
uxBitsToWaitFor イベントグループでテストしたいビット設定。例えば、bit0, bit2をandまたはorでテストしたいときは0x05(binary 0101)とする。
xClearOnExit unblock conditionを満たしたときにxClearOnExitがpdTRUEのときはuxBitsToWaitForで指定されたイベントビットはxEventGroupWaitBits APIを終了する前に、0にクリアする
xWaitForAllBits uxBitsToWaitForの条件を指定する。pdFALSEの場合は、ORで待つ。pdTRUEの場合はandで待つ
xTicksToWait unblock conditionが条件を満たすまでにBlocked状態で待てる最大時間。xTicksToWaitがzeroまたは、unblock conditionがcallしたときにすでに満たしていれば、xEventGroupWaitBitsは即returnする。pdMS_TO_TICKSマクロでmsでして可能。portMAX_DELAYを指定するとタイムアウトなしで待つ(INCLUDE_vTaskSuspend(FreeRTOSConfig.h)が1の場合)
戻り値 xEventGroupWaitBitsがunblock conditionを満たしてリターンすると、戻り値はunblock conditionを満たしたときのイベントグループの値となる。値はxClearOnExitで設定できる自動クリアの前の値。この場合は戻り値もunblock conditionを満たしている。タイムアウトでリターンする場合は、タイムアウトしたときのイベントグループの値となる。このときの戻り値はunblock conditionを満たしていない

Example 22. イベントグループの実験

この例では以下のデモをする

  • イベントグループの生成
  • ISRからイベントグループのビットをセット
  • タスクからのイベントグループのビットをセット
  • イベントグループでのBlock

xEventGroupWaitBitsのxWaitForAllBitsパラメータの影響は、pdFALSEで最初の実験でデモする。そのあとpdTRUEの場合を示す。

イベントビット0, 1はタスクからセットする。イベントビット2はISRからセットする。これら3つのビットはListing136のdefine名で説明する。

Listing136
/* Definitions for the event bits in the event group. */ 
#define mainFIRST_TASK_BIT  ( 1UL << 0UL ) /* Event bit 0, which is set by a task. */ 
#define mainSECOND_TASK_BIT ( 1UL << 1UL ) /* Event bit 1, which is set by a task. */ 
#define mainISR_BIT         ( 1UL << 2UL ) /* Event bit 2, which is set by an ISR. */ 

Listing137では、bit0,1をセットするタスクの実装を示す。ループ内で繰り返し1つのビットをセットする。その後、xEventGroupSetBitとの間に200ms遅延を入れる。
各ビットがセットされるまえに、文字列をprint出力する

Listing137
static void vEventBitSettingTask( void *pvParameters ) 
{ 
const TickType_t xDelay200ms = pdMS_TO_TICKS( 200UL ), xDontBlock = 0; 

    for( ;; ) 
    { 
        /* Delay for a short while before starting the next loop. */ 
        vTaskDelay( xDelay200ms ); 

        /* Print out a message to say event bit 0 is about to be set by the task,  
        then set event bit 0. */ 
        vPrintString( "Bit setting task -\t about to set bit 0.\r\n" ); 
        xEventGroupSetBits( xEventGroup, mainFIRST_TASK_BIT ); 

        /* Delay for a short while before setting the other bit. */ 
        vTaskDelay( xDelay200ms ); 

        /* Print out a message to say event bit 1 is about to be set by the task, 
        then set event bit 1. */ 
        vPrintString( "Bit setting task -\t about to set bit 1.\r\n" ); 
        xEventGroupSetBits( xEventGroup, mainSECOND_TASK_BIT ); 
    } 
} 

Listing138はISRがイベントグループのbit2にセットする実装を示す。文字列の出力は先ほどと同じだが、コンソール出力は直接ISRから呼び出されるのではなく、xTimerPendFunctionCallFromISRを使うことによって、RTOSデーモンタスクのコンテキストで出力される。

前の例では、ISRはソフトウェア割り込みで周期的に引き起こされた。今回の例では、500msごとのに呼ばれる。

Listing138
static uint32_t ulEventBitSettingISR( void ) 
{ 
/* The string is not printed within the interrupt service routine, but is instead 
sent to the RTOS daemon task for printing.  It is therefore declared static to ensure 
the compiler does not allocate the string on the stack of the ISR, as the ISR's stack 
frame will not exist when the string is printed from the daemon task. */ 
static const char *pcString = "Bit setting ISR -\t about to set bit 2.\r\n"; 
BaseType_t xHigherPriorityTaskWoken = pdFALSE; 

    /* Print out a message to say bit 2 is about to be set.  Messages cannot be  
    printed from an ISR, so defer the actual output to the RTOS daemon task by  
    pending a function call to run in the context of the RTOS daemon task. */ 
    xTimerPendFunctionCallFromISR( vPrintStringFromDaemonTask,  
                                   ( void * ) pcString,  
                                   0,  
                                   &xHigherPriorityTaskWoken ); 

    /* Set bit 2 in the event group. */ 
    xEventGroupSetBitsFromISR( xEventGroup, mainISR_BIT, &xHigherPriorityTaskWoken ); 

    /* xTimerPendFunctionCallFromISR() and xEventGroupSetBitsFromISR() both write to  
    the timer command queue, and both used the same xHigherPriorityTaskWoken  
    variable.  If writing to the timer command queue resulted in the RTOS daemon task  
    leaving the Blocked state, and if the priority of the RTOS daemon task is higher  
    than the priority of the currently executing task (the task this interrupt  
    interrupted) then xHigherPriorityTaskWoken will have been set to pdTRUE. 
. 

    xHigherPriorityTaskWoken is used as the parameter to portYIELD_FROM_ISR().  If  
    xHigherPriorityTaskWoken equals pdTRUE, then calling portYIELD_FROM_ISR() will  
    request a context switch.  If xHigherPriorityTaskWoken is still pdFALSE, then  
    calling portYIELD_FROM_ISR() will have no effect. 

    The implementation of portYIELD_FROM_ISR() used by the Windows port includes a  
    return statement, which is why this function does not explicitly return a  
    value. */ 
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); 
} 

Listing139では、xEventGroupWaitBitsを呼ぶタスクの実装を示す。イベントグループにセットされたビットを出力している。

xEventGroupWaitBits xClearOnExitパラメータはpdTRUEなので、xEventGroupWaitBitsで設定したビットは、xEventGroupWaitBitsがリターンする前に自動的にクリアされる。

Listing139
static void vEventBitReadingTask( void *pvParameters ) 
{ 
EventBits_t xEventGroupValue; 
const EventBits_t xBitsToWaitFor = ( mainFIRST_TASK_BIT  |  
                                     mainSECOND_TASK_BIT |  
                                     mainISR_BIT ); 

    for( ;; ) 
    { 
        /* Block to wait for event bits to become set within the event group. */ 
        xEventGroupValue = xEventGroupWaitBits( /* The event group to read. */ 
                                                xEventGroup, 

                                                /* Bits to test. */ 
                                                xBitsToWaitFor, 

                                                /* Clear bits on exit if the 
                                                unblock condition is met. */ 
                                                pdTRUE, 

                                                /* Don't wait for all bits.  This  
                                                parameter is set to pdTRUE for the  
                                                second execution. */ 
                                                pdFALSE, 

                                                /* Don't time out. */ 
                                                portMAX_DELAY ); 

        /* Print a message for each bit that was set. */ 
        if( ( xEventGroupValue & mainFIRST_TASK_BIT ) != 0 ) 
        { 
            vPrintString( "Bit reading task -\t Event bit 0 was set\r\n" ); 
        } 

        if( ( xEventGroupValue & mainSECOND_TASK_BIT ) != 0 ) 
        { 
            vPrintString( "Bit reading task -\t Event bit 1 was set\r\n" ); 
        } 

        if( ( xEventGroupValue & mainISR_BIT ) != 0 ) 
        { 
            vPrintString( "Bit reading task -\t Event bit 2 was set\r\n" ); 
        } 
    } 
} 

main関数は、イベントグループ、タスクを生成してスケジューラーをスタートする。Listing140に実装を示す。
イベントグループをリードするタスクは、ライトするタスクより優先度が高い。unblock conditionを満たしたときには、readするタスクが、writeするタスクをpre-emptできることを保証している。

Listing140
int main( void ) 
{ 
    /* Before an event group can be used it must first be created. */ 
    xEventGroup = xEventGroupCreate(); 

    /* Create the task that sets event bits in the event group. */ 
    xTaskCreate( vEventBitSettingTask, "Bit Setter", 1000, NULL, 1, NULL ); 

    /* Create the task that waits for event bits to get set in the event group. */ 
    xTaskCreate( vEventBitReadingTask, "Bit Reader", 1000, NULL, 2, NULL ); 

    /* Create the task that is used to periodically generate a software interrupt. */ 
    xTaskCreate( vInterruptGenerator, "Int Gen", 1000, NULL, 3, NULL ); 

    /* Install the handler for the software interrupt.  The syntax necessary to do  
    this is dependent on the FreeRTOS port being used.  The syntax shown here can  
    only be used with the FreeRTOS Windows port, where such interrupts are only  
    simulated. */ 
    vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulEventBitSettingISR ); 

    /* Start the scheduler so the created tasks start executing. */ 
    vTaskStartScheduler(); 

    /* The following line should never be reached. */ 
    for( ;; ); 
    return 0; 
} 

実行した結果をFigure73に示す。xWaitForAllBitsがpdFALSE(OR条件)なので、ビットがどれか設定されるたびに、リードするタスクはBlocked状態を抜けて実行される。
image.png

xWaitForAllBitsをpdTRUEにした結果をFigure74に示す。3つのビットすべてがセットされたときに、リードするタスクがBlocked状態を抜けて実行されている。
image.png

8.4 イベントグループを使ったタスクの同期

アプリケーションの設計で、複数のタスクを同期して動かしたいことがある。たとえば、Task Aがイベントを受信して、いくつかの処理を3つのタスク B,C,Dに移譲したいとする。もし、Task AがTask B, C, Dが前の処理をすべて終えるまでは、つぎのイベントを受信できないければ、4つのタスクは同期が必要になる。各タスクの同期ポイントは、その処理を完了した後になり、続く処理は他のタスクが同じく完了するまで進めない。Task Aだけが、他の4つのタスクが同期ポイントに達した後に、次のイベントを受信できる。

もう少し具体的な例は、FreeRTOS+TCPのデモプロジェクトで見つけることができる。デモでは2つのタスクでTCPソケットを共有する。1つはソケットに送信して、もう1つは同じソケットで受信をする。一方のタスクがソケットに再度接続を試みていないことを確認するまで、各タスクは安全にソケットをクローズすることはできない。もしどちらかのタスクがソケットをクローズしたいのであれば、他のタスクにそれを伝える必要がある。その後、他のタスクがソケットを使い終えるのをまって、処理を続ける。このシナリオでは、送信するタスクがソケットをクローズする側であり、Listing140に疑似コードを示す。

Listing140のシナリオは2つのタスクの同期なので、規模は小さいが、さらに複雑になったシナリオにはしやすい。
もし、他のタスクがソケットがオープンなことに依存している処理をするのであれば、追加のタスクを同期に参加させればよい。

Listing141
void SocketTxTask( void *pvParameters ) 
{ 
xSocket_t xSocket; 
uint32_t ulTxCount = 0UL; 

    for( ;; ) 
    { 
      /* Create a new socket.  This task will send to this socket, and another task will receive  
      from this socket. */ 
      xSocket = FreeRTOS_socket( ... ); 

      /* Connect the socket. */ 
      FreeRTOS_connect( xSocket, ... ); 

      /* Use a queue to send the socket to the task that receives data. */ 
      xQueueSend( xSocketPassingQueue, &xSocket, portMAX_DELAY ); 

      /* Send 1000 messages to the socket before closing the socket. */ 
      for( ulTxCount = 0; ulTxCount < 1000; ulTxCount++ ) 
      { 
          if( FreeRTOS_send( xSocket, ... ) < 0 ) 
          { 
              /* Unexpected error - exit the loop, after which the socket will be closed. */ 
              break; 
          } 
      } 

      /* Let the Rx task know the Tx task wants to close the socket. */ 
      TxTaskWantsToCloseSocket(); 

      /* This is the Tx task’s synchronization point.  The Tx task waits here for the Rx task to  
      reach its synchronization point.  The Rx task will only reach its synchronization point  
      when it is no longer using the socket, and the socket can be closed safely. */  
      xEventGroupSync( ... ); 

      /* Neither task is using the socket.  Shut down the connection, then close the socket. */ 
      FreeRTOS_shutdown( xSocket, ... ); 
      WaitForSocketToDisconnect(); 
      FreeRTOS_closesocket( xSocket ); 
  } 
} 
/*-----------------------------------------------------------*/ 

void SocketRxTask( void *pvParameters ) 
{ 
xSocket_t xSocket; 

  for( ;; ) 
  { 
      /* Wait to receive a socket that was created and connected by the Tx task. */ 
      xQueueReceive( xSocketPassingQueue, &xSocket, portMAX_DELAY ); 

      /* Keep receiving from the socket until the Tx task wants to close the socket. */ 
      while( TxTaskWantsToCloseSocket() == pdFALSE ) 
      { 
          /* Receive then process data. */ 
          FreeRTOS_recv( xSocket, ... ); 
          ProcessReceivedData(); 
      } 

      /* This is the Rx task’s synchronization point - it only reaches here when it is no longer  
      using the socket, and it is therefore safe for the Tx task to close the socket. */ 
      xEventGroupSync( ... ); 
  } 
} 

イベントグループは同期ポイント生成のために使うことができる

  • 同期する各タスクは、1つのイベントビットにアサインする
  • 各タスクは、処理が終了したら自分のビットにセットする
  • ビットをセットしたら、他のすべての同期タスクがビットをセットしたことをイベントグループで待つ

しかし、xEventGroupSetBits, xEventGroupWaitBits API functionは、このシナリオでは使えない。もし使うと、同期ポイントに達したことを示すビットをセットして、他のタスクが同期ポイントにセットしたことをテストするため、2つの分かれた操作として実行される。なぜこれが問題かというと、Task A, B, Cが1つのイベントグループを使って同期する場合を考えてみればよい。

  • Task A, Task Bはすでに同期ポイントに達していため、それぞれのビットはイベントグループにセットされている。その後、Task Cのビットがセットされるのを待つ
  • Task Cが同期ポイントに達して、xEventGroupBitsを使ってイベントグループにビットを立てる。Task A, Task BはすぐにBloked状態を抜け出して、イベントのビットをクリアする
  • Task CはxEventGroupWaitBitsを使って、3つのイベントがセットされるのを待つが、すでにイベントビットはTask A, Bが同期ポイントを抜けてからクリアされているので、同期処理は失敗する

同期ポイント生成をイベントグループを使って成功させるには、イベントビットをセットして、各ビットをテストする処理が、割り込まれない単一の処理である必要がある。これはxEventGroupSyncを使って実現できる。

xEventGruopSync API Function

xEventGroupSyncは複数のタスクを互いに同期させることができる。この関数は、1つのタスクに1つまたは複数のイベントビットをイベントグループにセットさせ、同じイベントグループのイベントビットの組み合わせがセットされるのを待つことができる。処理は割り込まれない単一の処理として実行される。

xEventGroupSyncのuxBitsToWaitForパラメータは呼んだダスクのunblock conditionを表す。uxBitsToWaitForで設定された各ビットはxEventGroupSyncがリターンされたときにゼロクリアされる。xEventGroupSyncがreturnされるとは、unblock conditionが満たされたことになる。

Listing142
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, 
                             const EventBits_t uxBitsToSet, 
                             const EventBits_t uxBitsToWaitFor, 
                             TickType_t xTicksToWait ); 
パラメータ名 説明
xEventGroup xEventGroupCreateでリターンされたhandle
uxbitsToset イベントグループに設定するビットマスク。今の値とのORを取って、設定される
uxBitsToWaitFor イベントグループに設定するビットマスク。たとえば、bit0, 1, 2がsetされるのを待つのであれば、0x07(binary 111)を設定する
xTicksToWait unblock conditionが満たされるまでBlocked状態でまつ最大時間。xTicksToWaitが0または、unblock conditionが満たされていれば、xEventGroupSyncはすぐにリターンされる。tick periodで設定するためpdMS_TO_TICKSマクロを使ってmillsecondsで指定すると良い。xTicksToWaitをportMAX_DELAYに設定すると、無限待ちになる。これには、INCLUDE_vTaskSuspendを1にすることが必要。
戻り値 unblock conditionが満たされていれば、その時のイベントグループの値を返す。タイムアウトしたときには、タイムアウトしたときのイベントグループの値を返す。

Example23. 同期タスク

Example23ではxEventGroupSyncを3つのタスクインスタンスの同期に使う。タスクのパラメータで、各タスクがxEventGroupSyncでセットするビットを渡す。

タスクはxEventgroupSyncのcall前後でメッセージをprintする。各メッセージにはタイムスタンプがついている。これはシーケンスの観察に使われる。同じ時間に同期ポイントに達するを防ぐために、タスク遅延値にランダム値を使っている。

Listing143に実装を示す。

Listing143
static void vSyncingTask( void *pvParameters ) 
{ 
const TickType_t xMaxDelay = pdMS_TO_TICKS( 4000UL ); 
const TickType_t xMinDelay = pdMS_TO_TICKS( 200UL ); 
TickType_t xDelayTime; 
EventBits_t uxThisTasksSyncBit; 
const EventBits_t uxAllSyncBits = ( mainFIRST_TASK_BIT  |  
                                    mainSECOND_TASK_BIT |  
                                    mainTHIRD_TASK_BIT ); 

    /* Three instances of this task are created - each task uses a different event  
    bit in the synchronization.  The event bit to use is passed into each task  
    instance using the task parameter.  Store it in the uxThisTasksSyncBit  
    variable. */ 
    uxThisTasksSyncBit = ( EventBits_t ) pvParameters; 

    for( ;; ) 
    { 
        /* Simulate this task taking some time to perform an action by delaying for a  
        pseudo random time.  This prevents all three instances of this task reaching 
        the synchronization point at the same time, and so allows the example’s  
        behavior to be observed more easily. */ 
        xDelayTime = ( rand() % xMaxDelay ) + xMinDelay; 
        vTaskDelay( xDelayTime ); 

        /* Print out a message to show this task has reached its synchronization 
        point.  pcTaskGetTaskName() is an API function that returns the name assigned  
        to the task when the task was created. */ 
        vPrintTwoStrings( pcTaskGetTaskName( NULL ), "reached sync point" ); 

        /* Wait for all the tasks to have reached their respective synchronization  
        points. */ 
        xEventGroupSync( /* The event group used to synchronize. */ 
                         xEventGroup, 

                         /* The bit set by this task to indicate it has reached the  
                         synchronization point. */ 
                         uxThisTasksSyncBit, 

                         /* The bits to wait for, one bit for each task taking part  
                         in the synchronization. */ 
                         uxAllSyncBits, 

                         /* Wait indefinitely for all three tasks to reach the 
                         synchronization point. */ 
                         portMAX_DELAY ); 

        /* Print out a message to show this task has passed its synchronization  
        point.  As an indefinite delay was used the following line will only be  
        executed after all the tasks reached their respective synchronization  
        points. */ 
        vPrintTwoStrings( pcTaskGetTaskName( NULL ), "exited sync point" ); 
    } 
}  

mainでは、イベントグループ、3つのタスクを生成して、スケジューラーをスタートする。実装をListing144に示す。

Listing144
/* Definitions for the event bits in the event group. */ 
#define mainFIRST_TASK_BIT ( 1UL << 0UL ) /* Event bit 0, set by the first task. */ 
#define mainSECOND_TASK_BIT( 1UL << 1UL ) /* Event bit 1, set by the second task. */ 
#define mainTHIRD_TASK_BIT ( 1UL << 2UL ) /* Event bit 2, set by the third task. */ 

/* Declare the event group used to synchronize the three tasks. */ 
EventGroupHandle_t xEventGroup; 

int main( void ) 
{ 
    /* Before an event group can be used it must first be created. */ 
    xEventGroup = xEventGroupCreate(); 

    /* Create three instances of the task.  Each task is given a different name, 
    which is later printed out to give a visual indication of which task is 
    executing.  The event bit to use when the task reaches its synchronization point 
    is passed into the task using the task parameter. */ 
    xTaskCreate( vSyncingTask, "Task 1", 1000, mainFIRST_TASK_BIT, 1, NULL ); 
    xTaskCreate( vSyncingTask, "Task 2", 1000, mainSECOND_TASK_BIT, 1, NULL ); 
    xTaskCreate( vSyncingTask, "Task 3", 1000, mainTHIRD_TASK_BIT, 1, NULL ); 

    /* Start the scheduler so the created tasks start executing. */ 
    vTaskStartScheduler(); 

    /* As always, the following line should never be reached. */ 
    for( ;; ); 
    return 0; 
}  

Example23の実行結果をFigure75に示す。各タスクが異なる時間で同期ポイントに達しているが、各タスクは同じ時間で終了していることがわかる。

image.png

3
2
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
3
2