ESP32×FreeRTOS:EventGroupとは?AND/OR待ちとxEventGroupWaitBitsの使い方

FreeRTOS

ESP32(ESP-IDF)でFreeRTOSを使うと、EventGroupで“複数条件待ち”を実装する場面がすぐ出ます。
例えば「Wi-Fi接続完了」+「センサー初期化完了」の両方が揃ったら送信開始、のようなケースです。

この“待ち合わせ”を シンプルに書ける仕組みが、FreeRTOSの EventGroup(イベントグループ)です。EventGroupは、複数のフラグ(ビット)をまとめて管理し、AND待ち/OR待ちができます。


この記事でわかること

  • EventGroup(イベントグループ)で何ができるか
  • 「複数条件待ち(AND/OR)」をどう書くか
  • 最小サンプル(ESP32 ESP-IDF で動く)
  • 主要API(Create / Set / Wait / Clear)の役割と引数の意味

こんな方におすすめ

  • Wi-FiやMQTTやセンサーなど、複数の準備完了を待ってから処理したい
  • グローバル変数のフラグ監視(ポーリング)が増えて、見通しが悪い
  • EventGroupの xEventGroupWaitBits() の引数がよく分からない

EventGroupとは(何ができる?)

EventGroupは、複数のビット(フラグ)を1つのグループとして扱い、

  • 条件が成立したらビットを立てる(SET)
  • 必要なビットが揃うまで待つ(WAIT:AND/OR)

を実現する仕組みです。


EventGroupを使うメリット

メリット1:複数条件待ちがスッキリ書ける

条件が増えるほど「フラグ変数が散らかる」「チェックが増える」問題が出ます。EventGroupはビットで条件を集約でき、待ち条件が明確になります。

メリット2:待機中はCPUを使わない(ポーリング不要)

定期チェック(ポーリング)ではなく、条件が揃うまでタスクをブロックできます。


【最小サンプル】2条件が揃うまで待つ(ESP32 + ESP-IDF)

やりたいこと

  • WifiTask:Wi-Fi準備完了を想定してビットを立てる
  • SensorTask:センサー準備完了を想定してビットを立てる
  • MainTask:2つのビットが揃うまで待つ(AND待ち)→ 揃ったら処理開始
    ※この例は「起動シーケンス」として一度だけ待つ例です。周期処理で毎回待つ場合は、後述の「イベントとして使う(xClearOnExit=pdTRUE)」を使います。
#include <cstdio>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"

static EventGroupHandle_t eg;

/* 「どのビットが何を意味するか」は設計で決める */
#define WIFI_READY_BIT    (1U << 0)
#define SENSOR_READY_BIT  (1U << 1)

static void WifiTask(void *pvParameters)
{
    vTaskDelay(pdMS_TO_TICKS(3000));               // 3秒後にWi-Fi接続できた想定
    xEventGroupSetBits(eg, WIFI_READY_BIT);        // Wi-Fi準備完了を通知
    printf("[Wi-Fi] Connected!\n");
    for (;;) vTaskDelay(pdMS_TO_TICKS(1000));
}

static void SensorTask(void *pvParameters)
{
    vTaskDelay(pdMS_TO_TICKS(1000));               // 1秒後にセンサー準備できた想定
    xEventGroupSetBits(eg, SENSOR_READY_BIT);      // センサー準備完了を通知
    printf("[Sensor] Ready!\n");
    for (;;) vTaskDelay(pdMS_TO_TICKS(1000));
}

static void MainTask(void *pvParameters)
{
        /* AND待ち:両方揃うまでブロック */
        xEventGroupWaitBits(
            eg,
            WIFI_READY_BIT | SENSOR_READY_BIT,  // 待つビット
            pdFALSE,                             // 成立してもビットを消さない(状態として保持)
            pdTRUE,                              // 全ビット待ち(AND)
            portMAX_DELAY                        // 無期限待ち
        );

        printf("[Main] Wi-Fi & Sensor OK -> Start!\n");
    for (;;) vTaskDelay(pdMS_TO_TICKS(1000));
}

extern "C" void app_main(void)
{
    eg = xEventGroupCreate();
    if (eg == NULL) {
        printf("xEventGroupCreate failed\n");
        return;
    }

    xTaskCreate(WifiTask,   "WifiTask",   2048, NULL, 5, NULL);
    xTaskCreate(SensorTask, "SensorTask", 2048, NULL, 5, NULL);
    xTaskCreate(MainTask,   "MainTask",   2048, NULL, 5, NULL);
}

この形にすると「Wi-Fiもセンサーも揃ったら開始」が、xEventGroupWaitBits() 1発で書けます。

ログからもセンサーとWi-FiのタスクでEventGroupのビットがセットされたあと、メインタスクが動いていることが確認できます。


主要API(役割・引数・戻り値を最短で理解)

ここでは、EventGroupで最初に覚えるべきAPIだけに絞って整理します。

xEventGroupCreate(EventGroupを作る)

  • 役割:EventGroup を作成して、ハンドルを返す
  • 引数
    • なし
  • 戻り値
    • 成功EventGroupHandle_t(作成したEventGroupのハンドル)
    • 失敗NULL(メモリ不足など)

xEventGroupSetBits(ビットを立てる)

  • 役割:指定したビットを 1(セット) にする(状態成立/イベント発生を通知)
  • 引数
    • 第1引数:対象の EventGroup ハンドル(EventGroupHandle_t
    • 第2引数:セットしたいビット(EventBits_t
      • 例:WIFI_READY_BIT
      • 複数指定:WIFI_READY_BIT | SENSOR_READY_BIT
  • 戻り値
    • EventBits_tSet後の EventGroup のビット状態(実装上の戻り値)

xEventGroupWaitBits(待つ:AND/OR、クリア有無、タイムアウト)

  • 役割:指定したビット条件が成立するまで待つ(タスクをブロック)
  • 引数
    • 第1引数:対象の EventGroup ハンドル(EventGroupHandle_t
    • 第2引数:待ちたいビット(EventBits_t
      • 例:WIFI_READY_BIT | SENSOR_READY_BIT
    • 第3引数xClearOnExit(条件成立後にビットを消すか)
      • pdFALSE:消さない(状態フラグとして保持したい)
      • pdTRUE:消す(1回きりのイベントとして扱いたい)
    • 第4引数xWaitForAllBits(AND待ち / OR待ち)
      • pdTRUE:AND待ち(全部揃うまで待つ
      • pdFALSE:OR待ち(どれか1つで起床
    • 第5引数:待ち時間(Tick)
      • 0:待たない(条件未成立ならすぐ戻る)
      • portMAX_DELAY:ずっと待つ(ブロック)
      • pdMS_TO_TICKS(100):最大100ms待つ、など
  • 戻り値
    • EventBits_t:起床時点のビット状態(戻り値に「どのビットが立っていたか」が入る)
      • OR待ちのときは if (ret & WIFI_READY_BIT) のように判定できる
      • タイムアウト時は、待っていたビットが揃っていない状態で返る(0とは限らない)

上記の実装例を下記に修正しOR待ち(xWaitForAllBits=pdFALSE)にすると、最初に成立したビットでMainTaskが起床します。
今回はSensorが先にreadyになるため、[Main] Sensor ready のみが出力されます。
ただし WifiTask 自体の [Wi-Fi] Connected! は遅れて出ます。
Wi-Fi側の [Main] Wi-Fi ready も出したい場合は、もう一度 xEventGroupWaitBits() を呼ぶ(ループする)必要があります。

// OR待ち:どちらかが揃えば起床
//ret には「起床した瞬間に立っていたビット」が入ります。
EventBits_t ret = xEventGroupWaitBits(
    eg,
    WIFI_READY_BIT | SENSOR_READY_BIT,
    pdFALSE,   // 条件成立後もビット保持
    pdFALSE,   // OR待ち
    portMAX_DELAY
);


if (ret & WIFI_READY_BIT)   printf("[Main] Wi-Fi ready\n");
if (ret & SENSOR_READY_BIT) printf("[Main] Sensor ready\n");

xEventGroupClearBits(ビットを下げる)

  • 役割:指定したビットを 0(クリア) に戻す(状態リセット/次サイクル準備)
  • 引数
    • 第1引数:対象の EventGroup ハンドル(EventGroupHandle_t
    • 第2引数:クリアしたいビット(EventBits_t
      • 例:WIFI_READY_BIT
      • 複数指定:WIFI_READY_BIT | SENSOR_READY_BIT
  • 戻り値
    • EventBits_tClear前のビット状態(実装上の戻り値)

(補足)FromISR系(割り込みから使う場合)

xEventGroupSetBitsFromISR(ISRからビットを立てる)

  • 役割:割り込み(ISR)から安全にビットをセットする
  • 引数
    • 第1引数:対象の EventGroup ハンドル
    • 第2引数:セットしたいビット
    • 第3引数pxHigherPriorityTaskWoken(高優先度タスクが起きたか返る)
  • 戻り値
    • BaseType_t:成功/失敗(実装依存)
  • 注意
    • ISR内で重い処理はしない。ビットを立ててタスクに任せるのが基本。

AND待ち・OR待ちの使い分け(設計のコツ)

EventGroupの設計で迷いやすいのが、「ビットを状態として扱うか/イベントとして扱うか」です。

  • 状態(readyフラグ)として使う:xClearOnExit = pdFALSE(保持)
    • 例:Wi-Fi接続済み、初期化完了など
  • イベント(1回きりの合図)として使う:xClearOnExit = pdTRUE(自動で消す)
    • 例:ボタン押下、1回だけの開始合図など

まとめ

  • EventGroupは、ビット(フラグ)を束ねて複数条件の待ち合わせ(AND/OR)ができる
  • ポーリングせずに、条件が揃うまでタスクをブロックできる
  • 最初は Create / SetBits / WaitBits / ClearBits の4つを押さえると実装が進む

次回はMessageBufferについて記載します。

コメント

タイトルとURLをコピーしました