FreeRTOSで複数のタスクを動かしていると、「1つのイベントが発生するまで待つ」だけではなく、複数の条件がそろうまで待ちたい場面が出てきます。
たとえばIoT機器の起動時に、次の3つの処理が必要だとします。
- Wi-Fiへの接続
- NTPによる時刻同期
- センサーの初期化
それぞれ別のタスクで処理していて、3つすべてが完了してからアプリケーションを開始したい場合、どうすればよいでしょうか。
Wi-Fi接続完了 ─────┐
│
時刻同期完了 ──────┼→ すべて完了 → アプリ開始
│
センサー初期化完了 ─┘
このような複数のイベントをまとめて管理し、条件がそろうまでタスクを待たせるために使えるのが、FreeRTOSのEvent Groups(イベントグループ)です。
Event Groupsでは、1つの整数値の各ビットをイベントの状態として利用します。
この記事では、Event Groupsの基本から、イベントビット、AND待ち・OR待ち、xEventGroupSetBits()、xEventGroupWaitBits()、ISRからの利用、Task NotificationやSemaphoreとの違いまで順番に解説します。
Event Groupsとは?
Event Groupsは、FreeRTOSで複数のイベント状態をビットで管理するための仕組みです。
たとえば、3つのイベントを次のように割り当てます。
bit 0:Wi-Fi接続完了 bit 1:時刻同期完了 bit 2:センサー初期化完了
イベントが発生したら、対応するビットを1にします。
初期状態 00000000 Wi-Fi接続完了 00000001 時刻同期も完了 00000011 センサー初期化も完了 00000111
このビットの集合を利用して、「どの処理が完了しているのか」を管理できます。
イベントをビットで表す
イベントビットは、通常ビットシフトを使って定義します。
#define WIFI_BIT (1UL << 0)
#define TIME_BIT (1UL << 1)
#define SENSOR_BIT (1UL << 2)
それぞれの値は次のようになります。
| イベント | ビット | 16進数 |
|---|---|---|
| Wi-Fi | bit 0 | 0x01 |
| 時刻同期 | bit 1 | 0x02 |
| センサー | bit 2 | 0x04 |
複数のイベントが発生すると、それぞれのビットが同時に1になります。
ここでは以前学習したビット演算がそのまま利用されています。
ビット演算については「ビット演算とは?AND・OR・XOR・NOT・シフト演算を初心者向けに解説」も参照してください。
Event Groupを作成する
Event Groupを使用するには、まずEvent Groupのハンドルを用意します。
EventGroupHandle_t eventGroup;
そしてxEventGroupCreate()で作成します。
eventGroup = xEventGroupCreate();
if (eventGroup == NULL) {
Serial.println("Event Group create failed");
}
作成に失敗した場合はNULLが返るため、実際のプログラムでは戻り値を確認しておくと安全です。
イベントが発生したらビットをセットする
イベントが発生したら、xEventGroupSetBits()で対応するビットを1にします。
たとえばWi-Fiへの接続が完了した場合は次のようにします。
xEventGroupSetBits(
eventGroup,
WIFI_BIT
);
時刻同期が完了した場合は、
xEventGroupSetBits(
eventGroup,
TIME_BIT
);
センサー初期化が完了したら、
xEventGroupSetBits(
eventGroup,
SENSOR_BIT
);
とします。
複数のビットを一度にセットできる
ビットORを使えば、複数のビットをまとめて指定することもできます。
xEventGroupSetBits(
eventGroup,
WIFI_BIT | TIME_BIT
);
この場合、Wi-Fiと時刻同期に対応する2つのビットがセットされます。
WIFI_BIT = 00000001
TIME_BIT = 00000010
OR
↓
00000011
xEventGroupWaitBits()でイベントを待つ
Event Groupsの重要な機能が、xEventGroupWaitBits()です。
このAPIを使うと、指定したイベントビットがセットされるまでタスクを待たせることができます。
EventBits_t bits;
bits = xEventGroupWaitBits(
eventGroup,
WIFI_BIT,
pdFALSE,
pdTRUE,
portMAX_DELAY
);
通知条件が成立していない間、タスクはBlocked状態で待つことができます。
xEventGroupWaitBits()の引数を理解しよう
xEventGroupWaitBits()には複数の引数があります。
xEventGroupWaitBits(
eventGroup,
bitsToWaitFor,
clearOnExit,
waitForAllBits,
ticksToWait
);
| 引数 | 意味 |
|---|---|
eventGroup | 対象のEvent Group |
bitsToWaitFor | 待ちたいイベントビット |
clearOnExit | 条件成立後に指定ビットをクリアするか |
waitForAllBits | すべてのビットを待つか、どれか1つを待つか |
ticksToWait | 最大待ち時間 |
特に重要なのがwaitForAllBitsです。
AND待ちとは?
複数のイベントがすべて発生するまで待ちたい場合はAND待ちを使います。
xEventGroupWaitBits()のwaitForAllBitsをpdTRUEにします。
EventBits_t bits;
bits = xEventGroupWaitBits(
eventGroup,
WIFI_BIT |
TIME_BIT |
SENSOR_BIT,
pdFALSE,
pdTRUE,
portMAX_DELAY
);
この場合、次の3つがすべてセットされるまで待ちます。
Wi-Fi = 1 時刻同期 = 1 センサー = 1 すべて1 ↓ 待ち解除
つまり、
00000001 → まだ待つ 00000011 → まだ待つ 00000111 → 条件成立
となります。
OR待ちとは?
指定したイベントのどれか1つでも発生したら処理を進めたい場合はOR待ちを使います。
waitForAllBitsをpdFALSEにします。
EventBits_t bits;
bits = xEventGroupWaitBits(
eventGroup,
WIFI_BIT |
TIME_BIT |
SENSOR_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY
);
Wi-Fi、時刻同期、センサー初期化のうち、どれか1つでもビットがセットされれば待ち状態が解除されます。
00000000 ↓ 待つ 00000010 ↓ TIME_BITが1 ↓ 待ち解除
AND待ちとOR待ちの違い
| 待ち方 | 条件 | 例 |
|---|---|---|
| AND待ち | 指定したすべてのビットが1 | Wi-Fi接続+時刻同期+センサー初期化をすべて待つ |
| OR待ち | 指定したビットのどれかが1 | ボタン・タイマー・通信のどれかを待つ |
Event Groupsの大きな特徴は、この複数条件の待ち合わせを簡単に記述できることです。
イベント発生後にビットをクリアする
xEventGroupWaitBits()の第3引数clearOnExitをpdTRUEにすると、条件成立後に待っていたビットをクリアできます。
xEventGroupWaitBits(
eventGroup,
WIFI_BIT,
pdTRUE,
pdTRUE,
portMAX_DELAY
);
たとえば、
00000001 ↓ 条件成立 ↓ タスクを起こす ↓ 00000000
という動作になります。
一方、pdFALSEならビットはそのまま残ります。
「現在の状態」を表したいのか、「一度発生したイベント」を表したいのかによって使い分けます。
ビットを明示的にクリアする
イベントビットはxEventGroupClearBits()を使って明示的に0へ戻すこともできます。
xEventGroupClearBits(
eventGroup,
WIFI_BIT
);
たとえばWi-Fiが切断されたときにWi-Fi接続状態のビットをクリアする、といった使い方ができます。
Wi-Fi接続 WIFI_BIT = 1 Wi-Fi切断 WIFI_BIT = 0
このようにEvent Groupsは、一瞬のイベントだけでなくシステムの状態を表すフラグとしても利用できます。
現在のイベントビットを確認する
現在どのビットがセットされているかを確認したい場合は、xEventGroupGetBits()を利用できます。
EventBits_t bits;
bits = xEventGroupGetBits(
eventGroup
);
if (bits & WIFI_BIT) {
Serial.println(
"Wi-Fi connected"
);
}
ここでもAND演算を使って特定のビットを確認しています。
複数のタスクでEvent Groupsを使ってみよう
3つの初期化タスクと、すべての完了を待つアプリケーションタスクを考えてみます。
#define WIFI_BIT (1UL << 0)
#define TIME_BIT (1UL << 1)
#define SENSOR_BIT (1UL << 2)
EventGroupHandle_t eventGroup;
void wifiTask(void *pvParameters)
{
// Wi-Fi接続処理
xEventGroupSetBits(
eventGroup,
WIFI_BIT
);
vTaskDelete(NULL);
}
void timeTask(void *pvParameters)
{
// NTP時刻同期処理
xEventGroupSetBits(
eventGroup,
TIME_BIT
);
vTaskDelete(NULL);
}
void sensorTask(void *pvParameters)
{
// センサー初期化処理
xEventGroupSetBits(
eventGroup,
SENSOR_BIT
);
vTaskDelete(NULL);
}
void appTask(void *pvParameters)
{
EventBits_t bits;
bits = xEventGroupWaitBits(
eventGroup,
WIFI_BIT |
TIME_BIT |
SENSOR_BIT,
pdFALSE,
pdTRUE,
portMAX_DELAY
);
Serial.println(
"All initialization completed!"
);
while (1) {
// メイン処理
vTaskDelay(
pdMS_TO_TICKS(1000)
);
}
}
Wi-Fi、時刻同期、センサー初期化の順番は問いません。
3つすべてが完了して対応するビットがセットされた時点で、appTaskの待ち状態が解除されます。
Event Groupsは処理の順番を固定しない
Event Groupsの便利なところは、それぞれの処理が完了する順番を固定する必要がないことです。
パターンA Wi-Fi ↓ 時刻同期 ↓ センサー パターンB センサー ↓ Wi-Fi ↓ 時刻同期 どちらでも すべてのビットが1 ↓ アプリ開始
複数の非同期処理を並行して行い、その完了を待ち合わせるようなシステムで便利です。
ISRからEvent Groupのビットをセットする
割り込みからイベントを通知したい場合は、ISR用のAPIを使用します。
BaseType_t higherPriorityTaskWoken =
pdFALSE;
xEventGroupSetBitsFromISR(
eventGroup,
BUTTON_BIT,
&higherPriorityTaskWoken
);
if (higherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
ISRから通常のxEventGroupSetBits()を直接呼ぶのではなく、xEventGroupSetBitsFromISR()を使用します。
ただし、Event GroupsのISR用APIにはFreeRTOS内部の処理上の特徴があります。ISRからの単純で軽量な1対1通知が目的なら、Task Notificationの方が適している場合もあります。
Task Notificationについては「Task Notificationとは?FreeRTOSの軽量なタスク通知を初心者向けに解説」も参照してください。
Event GroupsとTask Notificationの違い
Task Notificationでも通知値をビットフラグとして利用できるため、Event Groupsと似ているように見えます。
大きな違いは、Task Notificationが特定のタスクへ直接通知する仕組みなのに対し、Event Groupsは複数のイベント状態を共有して複数タスクから利用できる同期オブジェクトだという点です。
| Event Groups | Task Notification | |
|---|---|---|
| 主な用途 | 複数イベントの状態管理・待ち合わせ | 特定タスクへの軽量な通知 |
| イベント表現 | 複数ビット | 通知値・ビット・カウンタ |
| AND/OR待ち | 対応 | 用途によって実装可能だが性格が異なる |
| 複数タスクから利用 | 可能 | 通知先は特定タスク |
| 独立オブジェクト | 必要 | 不要 |
Event GroupsとSemaphoreの違い
Semaphoreは、イベント通知やタスク間の同期に利用できます。
Binary Semaphoreであれば、基本的には「イベントが発生したか」という1つの状態を扱います。
Binary Semaphore イベント ↓ Give ↓ Task ↓ Take Event Groups Wi-Fi ─────┐ Timer ─────┼→ Event Bits → Task Sensor ────┘
複数のイベント状態をまとめて管理したい場合は、Event Groupsが分かりやすいことがあります。
Semaphoreについては「Semaphore(セマフォ)とは?FreeRTOSのタスク同期を初心者向けに解説」も参照してください。
Event GroupsとQueueの違い
Event Groupsは状態やイベントの有無を表す仕組みであり、Queueのようにデータを順番に蓄積して渡すものではありません。
Wi-Fi接続した?
時刻同期した?
センサー初期化した?
↓
Event Groups
温度 = 25.3℃
湿度 = 48.2%
時刻 = 12:30:05
↓
Queue
「何が起きたか・どの状態か」を管理するならEvent Groups、「データそのものを渡す」ならQueueという考え方が分かりやすいでしょう。
Event Groups・Task Notification・Semaphore・Queueを比較
| 仕組み | 主な用途 | 向いている例 |
|---|---|---|
| Event Groups | 複数イベントの状態管理・待ち合わせ | Wi-Fi+時刻同期+センサー初期化 |
| Task Notification | 特定タスクへの軽量な直接通知 | ISRから処理タスクを起こす |
| Semaphore | イベント通知・同期 | 処理完了を別タスクへ通知 |
| Queue | データの受け渡し | センサーデータを別タスクへ送る |
| Mutex | 排他制御 | I2Cなどの共有リソースを保護する |
KUMITATE-C3でEvent Groupsを使うなら?
KUMITATE-C3でも、複数のイベントを待ち合わせる教材を作れます。
たとえば次の3つをイベントとしてみます。
- SW1が押された
- 起動から5秒経過した
- Wi-Fiへの接続が完了した
それぞれにビットを割り当てます。
#define BUTTON_BIT (1UL << 0)
#define TIMER_BIT (1UL << 1)
#define WIFI_BIT (1UL << 2)
そして3つすべてが成立したらLEDを点灯させます。
SW1押下 ─────────┐
│
5秒経過 ─────────┼→ Event Group
│ ↓
Wi-Fi接続完了 ───┘ 3条件成立
↓
LED点灯
KUMITATE-C3のサンプルコード
概念を理解するための簡単な例です。ボタン、タイマー、Wi-Fi接続に相当する3つのイベントがそろったらLEDを点灯します。
#include <Arduino.h>
#define BUTTON_BIT (1UL << 0)
#define TIMER_BIT (1UL << 1)
#define WIFI_BIT (1UL << 2)
const int LED_PIN = 0;
const int SW_PIN = 21;
EventGroupHandle_t eventGroup;
void buttonTask(void *pvParameters)
{
while (1) {
if (digitalRead(SW_PIN) == LOW) {
xEventGroupSetBits(
eventGroup,
BUTTON_BIT
);
vTaskDelay(
pdMS_TO_TICKS(300)
);
}
vTaskDelay(
pdMS_TO_TICKS(10)
);
}
}
void timerTask(void *pvParameters)
{
vTaskDelay(
pdMS_TO_TICKS(5000)
);
xEventGroupSetBits(
eventGroup,
TIMER_BIT
);
vTaskDelete(NULL);
}
void wifiTask(void *pvParameters)
{
// 実際にはここでWi-Fi接続処理を行う
vTaskDelay(
pdMS_TO_TICKS(3000)
);
xEventGroupSetBits(
eventGroup,
WIFI_BIT
);
vTaskDelete(NULL);
}
void ledTask(void *pvParameters)
{
xEventGroupWaitBits(
eventGroup,
BUTTON_BIT |
TIMER_BIT |
WIFI_BIT,
pdFALSE,
pdTRUE,
portMAX_DELAY
);
digitalWrite(
LED_PIN,
HIGH
);
Serial.println(
"All events completed!"
);
vTaskDelete(NULL);
}
void setup()
{
Serial.begin(115200);
pinMode(
LED_PIN,
OUTPUT
);
pinMode(
SW_PIN,
INPUT_PULLUP
);
eventGroup =
xEventGroupCreate();
if (eventGroup == NULL) {
Serial.println(
"Event Group create failed"
);
return;
}
xTaskCreate(
buttonTask,
"Button",
2048,
NULL,
1,
NULL
);
xTaskCreate(
timerTask,
"Timer",
2048,
NULL,
1,
NULL
);
xTaskCreate(
wifiTask,
"WiFi",
2048,
NULL,
1,
NULL
);
xTaskCreate(
ledTask,
"LED",
2048,
NULL,
2,
NULL
);
}
void loop()
{
vTaskDelay(
pdMS_TO_TICKS(1000)
);
}
この例では、実際のWi-Fi接続処理の代わりに3秒の待ち時間を入れています。
SW1押下、5秒経過、Wi-Fi処理完了の3つがそろうまでLED TaskはBlocked状態です。
最後のビットがセットされるとAND条件が成立し、LED Taskが実行可能になります。
Event Groupsのメリット
- 複数のイベントを1つのオブジェクトで管理できる
- イベントの状態をビットで分かりやすく表現できる
- 複数条件のAND待ちが簡単にできる
- 複数条件のOR待ちもできる
- 条件が成立するまでタスクをBlocked状態にできる
- 複数のタスクからイベントビットをセットできる
- イベントの完了順序に依存しない待ち合わせができる
- システムの状態管理にも利用できる
Event Groupsの注意点
Event Groupsは便利ですが、用途を理解して使うことが重要です。
- Queueのようにデータそのものを保存する仕組みではない
- 同じイベントが何回発生したかを数える用途には向かない
- ビットをセットしたままにするのかクリアするのかを設計する必要がある
- 同じビットを複数の意味で使わない
- イベント数には利用可能なビット数による上限がある
- ISRから使用する場合はISR用APIを使用する
特に重要なのが、Event Groupsはイベントの回数を保存するQueueではないという点です。
BUTTON_BIT 1回押した ↓ 1 さらに1回押した ↓ 1 さらに1回押した ↓ 1
同じビットを何度セットしても、状態は1のままです。
「ボタンが5回押された」のように回数そのものが重要なら、Task Notificationのカウンタ、Counting Semaphore、Queueなど別の方法を検討します。
Event Groupsで使えるビット数に注意
Event Groupsの内部値はEventBits_tですが、そのすべてのビットをユーザーイベントとして利用できるとは限りません。
FreeRTOSでは一部の上位ビットが内部制御用に予約されています。また、利用可能なビット数はFreeRTOSのtick型設定によって変わります。
そのため「32bit型だから32個のイベントを必ず使える」と考えず、使用しているFreeRTOSの設定と公式ドキュメントを確認してください。
Event Groupsを使うべき場面
Event Groupsが特に向いているのは、次のような処理です。
- 複数の初期化処理が完了するまで待つ
- Wi-Fi接続状態など複数のシステム状態を管理する
- 複数イベントのどれかが発生するまで待つ
- 複数イベントがすべて成立するまで待つ
- 複数タスクから1つの状態集合を更新する
どのFreeRTOS機能を使えばいい?
データを順番に渡したい
↓
Queue
イベントを通知・同期したい
↓
Semaphore
共有リソースを守りたい
↓
Mutex
特定タスクへ軽量に通知したい
↓
Task Notification
複数イベントをまとめて
AND / OR条件で待ちたい
↓
Event Groups
実際のシステムでは、これらを組み合わせて使用することも珍しくありません。
たとえば、Event Groupsで「Wi-Fi接続済み」という状態を管理し、Queueで送信データを受け渡し、MutexでI2Cバスを保護するといった設計ができます。
まとめ
Event Groupsは、FreeRTOSで複数のイベント状態をビットで管理し、タスクの待ち合わせを行うための仕組みです。
- 1つのEvent Groupで複数のイベント状態を管理できる
- 各イベントを1つのビットとして表現する
xEventGroupCreate()でEvent Groupを作成するxEventGroupSetBits()でイベントビットをセットするxEventGroupClearBits()でイベントビットをクリアできるxEventGroupGetBits()で現在の状態を取得できるxEventGroupWaitBits()でイベントを待てる- AND待ちでは指定したすべてのイベントを待つ
- OR待ちでは指定したイベントのどれか1つを待つ
- 条件成立後にビットを自動クリアすることもできる
- 複数の非同期処理の完了待ちに向いている
- イベント発生回数や任意データを保存する仕組みではない
- ISRから使用する場合はISR用APIを使う
- Task Notification、Semaphore、Queue、Mutexとはそれぞれ目的が異なる
Event Groupsを理解するうえで特に重要なのは、「複数の状態をビットで表現し、それらをANDまたはORの条件で待てる」という点です。
たとえば「Wi-Fi接続完了」「時刻同期完了」「センサー初期化完了」のように、複数の非同期処理がすべて完了してから次へ進みたい場合、Event Groupsを使うと処理の関係を分かりやすく表現できます。
Queue・Semaphore・Mutex・Task NotificationにEvent Groupsまで理解できると、FreeRTOSで「タスクを作る」だけでなく「複数のタスクをどう連携させるか」という設計がかなり見えるようになります。

