[ESP32]タスク間通信でキューを使用する方法

ESP32

キューを使用してタスク間通信する方法

キューとは

電車などに人が並んでいる状態を想像してみてください。先に並んだ人が先に乗ることができます。後から並んだ人は先に並んだ人が乗るまで待ちます。

キューあるいは待ち行列は、コンピュータにおける基本的なデータ構造の一つ。データを先入れ先出しのリスト構造で保持するものである。キューからデータを取り出すときには、先に入れられたデータから順に取り出される。キューにデータを入れることをエンキュー、取り出すことをデキューという。

https://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A5%E3%83%BC_(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF)

このキューを使用すると、別タスクにデータを送ることや、処理開始タイミングを指示することに使うことができます。

サンプルコード(コピペで動作)

使用API:xQueueCreate() / xQueueSend() / xQueueReceive()

TaskHandle_t thp[2];

#define MAX_QUE_NUM      1
#define MAX_QUE_SIZE     sizeof(ulong)
QueueHandle_t queue;

ulong count = 0;

// the setup function runs once when you press reset or power the board
void setup() {
  
  // initialize serial communication at 115200 bits per second:
  Serial.begin(115200);
  
  queue = xQueueCreate(MAX_QUE_NUM, MAX_QUE_SIZE);

  // Now set up two tasks to run independently.
  xTaskCreatePinnedToCore( TaskA, "TaskA", 2048, NULL, 2, &thp[0], APP_CPU_NUM);
  xTaskCreatePinnedToCore( TaskB, "TaskB", 2048, NULL, 2, &thp[1], APP_CPU_NUM);
}

void loop()
{
  // Empty. Things are done in Tasks.
}

/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/
void TaskA(void *pvParameters)  // This is a task.
{
  Serial.println("TaskA Start");
  while(1) {
    count++;
    xQueueSend(queue, &count, ( TickType_t ) 0);
    delay(1000);
  }
  // ここにはこないが明示的にタスク終了を記載しておく。
  vTaskDelete(NULL);
}

void TaskB(void *pvParameters)  // This is a task.
{
  ulong buf;
  Serial.println("TaskB Start");

  while(1) {
    if(xQueueReceive(queue, &buf, 0)) {
      Serial.printf("count : %ld\n", buf);
    }
    delay(100);
  }
  // ここにはこないが明示的にタスク終了を記載しておく。
  vTaskDelete(NULL);
}

まず、18行目,19行目でTaskAとTaskBを用意しています。

15行目でキューを作成しています。

xQueueCreate(引数)

uxQueueLengthキューの数。
uxItemSize各キューのサイズ(全て同じサイズになる)。
戻り値:成功時は キューのハンドル、失敗時は 0。

34行目でTaskAは1秒おきにカウンタを1増加して、35行目でTaskBにカウンタの値をキューで通知しています。

xQueueSend(引数)

xQueueキューのハンドル。
pvItemToQueueキューに配置される項目へのポインター。
xTicksToWait キューがすでにいっぱいである場合に、キュー上のスペースが利用可能になるまでタスクがブロックする最大時間。(時間はティックで指定)
0 に設定され、キューがいっぱいの場合、呼び出しはすぐに戻る。
戻り値:アイテムが正常に投稿された場​​合は pdTRUE、それ以外の場合は errQUEUE_FULL。

48行目でTaskBはキューで通知された内容を表示しています。

xQueueReceive(引数)

xQueue キューのハンドル。
pvBuffer受信したアイテムがコピーされるバッファーへのポインター。
xTicksToWait 呼び出し時にキューが空だった場合に、タスクがアイテムの受信を待機してブロックする最大時間。(時間はティックで指定)
0 に設定され、キューが空の場合、呼び出しはすぐに戻る。
戻り値:正常に受信できた場合は pdTRUE、それ以外の場合は pdFALSE。

pvBuffer のポインタ領域には実際にデータがコピーされるため、呼び出し側で受信サイズ分[uxItemSize]の領域を確保しておく必要があります。

今回のサンプルでは、uxQueueLength を1に設定していますが、複数保持できるようにしておき、受信側の処理を待たずに複数送信する使い方をよくします。その際、受信処理が遅れてキューが一杯にならないようにする設計や、一杯になった場合の対策処置が必要になります。

確認方法

実行すると、以下のようにシリアルポートに出力されます。

1秒毎に count の数値が増加していれば成功です。

その他API

サンプルコードでは使用していませんが、便利そうなAPIを載せておきます。

  • xQueuePeek()
    • キューから項目を削除せずに、キューから項目を受け取ります。
  • uxQueueMessagesWaiting()
    • キューに保存されているメッセージの数を返します。
  • vQueueDelete()
    • キューを削除します。キューに配置されたアイテムの保存に割り当てられたすべてのメモリを解放します。
  • xQueueReset()
    • キューをリセットして元の空の状態に戻します。

まとめ

キューを使えるようになれば、構想の幅が広がりますね。

コメント

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