ESP32のFreeRTOS入門 その8 イベントグループ

概要

前回はタイマーでした。今回は複数のミューテックスを一つにまとめたイベントグループです。

イベントグループとは?

ビット単位でミューテックスを管理しており、最大24個まで同時にミューテックスを利用できます。I2Cや無線系通信などで利用されています。

特徴としては、複数のミューテックスが取得できる場合のみに処理を行うなどが可能になります。普通のミューテックスは1個単位で取得するので、複数のミューテックスを利用した場合に、順番によってはロックしてしまう恐れがあります。

ただし、イベントグループの利用例をみたところ、複数のミューテックスを1つの変数で管理する用途で使われていることが多かったです。

スケッチ例

#include <M5StickC.h>

EventGroupHandle_t event_group = NULL;

#define EVENT_M5_BUTTON_A (1<<0)
#define EVENT_M5_BUTTON_B (1<<1)

void task1(void *pvParameters) {
  while (1) {
    uint32_t eBits = xEventGroupWaitBits(
                       event_group,                           // イベントグループを指定
                       EVENT_M5_BUTTON_A | EVENT_M5_BUTTON_B, // 一つ以上のイベントビットを指定
                       pdTRUE,                                // 呼び出し後にイベントビットをクリアするか
                       pdTRUE,                                // 指定したイベントビットがすべて揃うまで待つか
                       portMAX_DELAY                          // 待ち時間
                     );

    // イベント発生
    Serial.printf("Event Task : %0X\n", eBits);
    delay(1);
  }
}

void setup() {
  M5.begin();

  // イベントグループの初期化
  event_group = xEventGroupCreate();
  xEventGroupClearBits(event_group, 0xFFFFFF);

  // イベント用のタスク作成
  xTaskCreateUniversal(
    task1,
    "task1",
    8192,
    NULL,
    1,
    NULL,
    APP_CPU_NUM
  );
}

void loop() {
  M5.update();

  // ボタンを押すとイベントビットのON、離すとOFF
  if (M5.BtnA.wasPressed()) {
    xEventGroupSetBits(event_group, EVENT_M5_BUTTON_A);
    Serial.println("EVENT_M5_BUTTON_A ON");
  }
  if (M5.BtnA.wasReleased()) {
    xEventGroupClearBits(event_group, EVENT_M5_BUTTON_A);
    Serial.println("EVENT_M5_BUTTON_A OFF");
  }
  if (M5.BtnB.wasPressed()) {
    xEventGroupSetBits(event_group, EVENT_M5_BUTTON_B);
    Serial.println("EVENT_M5_BUTTON_B ON");
  }
  if (M5.BtnB.wasReleased()) {
    xEventGroupClearBits(event_group, EVENT_M5_BUTTON_B);
    Serial.println("EVENT_M5_BUTTON_B OFF");
  }

  delay(1);
}

上記がM5StickCの2つのボタンを利用したスケッチ例です。ボタンが押されている場合にはミューテックスを取得し、離されると解放しています。

両方のボタンが押された場合にのみ、タスクが実行されます。

#define EVENT_M5_BUTTON_A (1<<0)
#define EVENT_M5_BUTTON_B (1<<1)

ビットでイベントを管理するので、上記のような宣言が必要になります。1<<0はビットシフトしないので1、1<<1は1ビットシフトするので2になります。フラグを増やす場合には1<<2にすることで4の条件を追加することができます。もちろん、1, 2, 4, 8などと直値で指定しても構いませんが、この書式の方が直感的にわかると思います。

    uint32_t eBits = xEventGroupWaitBits(
                       event_group,                           // イベントグループを指定
                       EVENT_M5_BUTTON_A | EVENT_M5_BUTTON_B, // 一つ以上のイベントビットを指定
                       pdTRUE,                                // 呼び出し後にイベントビットをクリアするか
                       pdTRUE,                                // 指定したイベントビットがすべて揃うまで待つか
                       portMAX_DELAY                          // 待ち時間
                     );

上記が、イベントグループの取得待ちの関数です。ミューテックスなどとの違いは複数のビットを指定して待つことができます。

bool型の引数が2つあり、最初は呼び出し後にビットをクリアするかの設定です。pdTRURにすると、呼び出し後にイベントグループのビットがクリアされます。pdFALSEにすると条件が一致している間は実行される関数となります。

2個目はすべてのビットが揃うのを待つかのフラグです。pdTRURだとすべて揃うかタイムアウトまで待ちます。pdFALSEにすると1つでもビットが立っていれば戻ってきます。

タイムアウトの時間は他のと同じです。0にすると即時に終了して、戻り値のビットによってどのビットが立っているのかがわかります。

用途別使い方

条件が揃うまで待って一度だけ実行する

    uint32_t eBits = xEventGroupWaitBits(
                       event_group,                           // イベントグループを指定
                       EVENT_M5_BUTTON_A | EVENT_M5_BUTTON_B, // 一つ以上のイベントビットを指定
                       pdTRUE,                                // 呼び出し後にイベントビットをクリアするか
                       pdTRUE,                                // 指定したイベントビットがすべて揃うまで待つか
                       portMAX_DELAY                          // 待ち時間
                     );

サンプルスケッチの使い方で、portMAX_DELAYなので条件が揃うまで待ちます。実行後にフラグがリセットされるので、再度フラグがセットされるまで呼び出されません。

条件が揃っているときのみ連続実行

    uint32_t eBits = xEventGroupWaitBits(
                       event_group,                           // イベントグループを指定
                       EVENT_M5_BUTTON_A | EVENT_M5_BUTTON_B, // 一つ以上のイベントビットを指定
                       pdFALSE,                               // 呼び出し後にイベントビットをクリアするか
                       pdTRUE,                                // 指定したイベントビットがすべて揃うまで待つか
                       portMAX_DELAY                          // 待ち時間
                     );

イベントクリアをpdFALSEにすると、フラグがリセットされないので、条件が揃っている間にイベントグループ用のタスクが連続実行されるようになります。

どちらかといえば、許可のフラグで問題がある場合にはフラグを落として、タスクの実行を止めるような制御になるのでしょうか?

どれかが発生したら実行する

    uint32_t eBits = xEventGroupWaitBits(
                       event_group,                           // イベントグループを指定
                       EVENT_M5_BUTTON_A | EVENT_M5_BUTTON_B, // 一つ以上のイベントビットを指定
                       pdTRUE,                                // 呼び出し後にイベントビットをクリアするか
                       pdFALSE,                               // 指定したイベントビットがすべて揃うまで待つか
                       portMAX_DELAY                          // 待ち時間
                     );

これは通知でも良い気がしますが、どのフラグでの実行かがわかる利点があります。

ノンブロックで状態を取得する

    uint32_t eBits = xEventGroupWaitBits(
                       event_group,                           // イベントグループを指定
                       EVENT_M5_BUTTON_A | EVENT_M5_BUTTON_B, // 一つ以上のイベントビットを指定
                       pdFALSE,                               // 呼び出し後にイベントビットをクリアするか
                       pdTRUE,                                // 指定したイベントビットがすべて揃うまで待つか
                       0                                      // 待ち時間
                     );

待ち時間を0にすると、ビットが揃うまで待つフラグの意味がなくなり、現状の状態を返すような使い方になります。

複雑な使い方

xEventGroupSync()関数を使うと、xEventGroupWaitBits()関数のように条件が揃うまで待ち、さらに特定のフラグをあげる動作をすることができます。

しかしながら、実際に有効に利用している例や、使ったほうがよさそうな利用例が思いつきません。

資料

まとめ

いろいろパラメーターを変えて試すと、楽しい動きをする機能です。ただし、ちょっと有効な使い方が思いつかず、複数のミューテックスを1つの変数で管理する用途以外ではあまり使わないかもしれません。

使ったほうがよい状況はおそらくあるのですが、シンプルな他の機能を使ったほうがよさそうです。

コメントする

メールアドレスが公開されることはありません。

管理者承認後にページに追加されます。公開されたくない相談はその旨本文に記載するかTwitterなどでDM投げてください。またスパム対策として、日本語が含まれない投稿は無視されますのでご注意ください。