ESP32でキューを使う

古くなっている可能性があるので、最新情報はM5StickC非公式日本語リファレンスで確認してください。

キューとは

処理が必要なデータの集まりで、主に別タスクにデータ処理を依頼する場合に利用します。通常はキューの最後に追加するFIFOですが、先頭に割り込んで追加することもできます。

キューの種類

  • 通常のキュー
  • メールボックス(最後のアイテムのみ保持)

1つのメッセージしか保存しないメールボックスと呼ばれるキューがあるようです。最後の情報を表示するなどの場合はこれを使うのかな?

通常は複数のメッセージが保存できるキューを使います。FreeRTOSのキューはメッセージを送信すると、送信したデータをコピーしてキューに保存しますので送信元でそのデータを書き換えたりしても安全に利用することができます。

サンプルスケッチ

// キューの大きさ
#define QUEUE_LENGTH 4

// 通常のキュー
QueueHandle_t xQueue;

// Mailbox用のキュー
QueueHandle_t xQueueMailbox;

void setup() {
  // キューアイテム
  uint8_t data = 1;

  // デバッグ出力準備
  Serial.begin(115200);
  Serial.println("Queue Test");

  // キュー作成
  xQueue = xQueueCreate( QUEUE_LENGTH, sizeof( uint8_t ) );

  // メールボックス用途は長さ1のみ
  xQueueMailbox = xQueueCreate( 1, sizeof( uint8_t ) );

  // 最後に追加(1)
  xQueueSend(xQueue, &data, 0);
  data++;

  // 先頭に追加(2)
  xQueueSendToFront(xQueue, &data, 0);
  data++;

  // 最後に追加(3)
  xQueueSendToBack(xQueue, &data, 0);
  data++;

  // 上書き送信(4)
  xQueueOverwrite(xQueueMailbox, &data);
  data++;

  // 上書き送信(5)
  xQueueOverwrite(xQueueMailbox, &data);
  data++;

  // キューの数確認
  Serial.printf( "uxQueueMessagesWaiting = %d\n", uxQueueMessagesWaiting(xQueue) );

  // キューの追加可能数確認
  Serial.printf( "uxQueueSpacesAvailable = %d\n", uxQueueSpacesAvailable(xQueue) );

  // キューの状態取得
  Serial.printf( "xQueueIsQueueEmptyFromISR = %d\n", xQueueIsQueueEmptyFromISR(xQueue) );
  Serial.printf( "xQueueIsQueueFullFromISR = %d\n", xQueueIsQueueFullFromISR(xQueue) );
  Serial.printf( "uxQueueMessagesWaitingFromISR = %d\n", uxQueueMessagesWaitingFromISR(xQueue) );

  // Peek受信(何度受信しても同じ値)
  Serial.println( "Peek Test" );
  xQueuePeek( xQueue, &data, 0 );
  Serial.println( data );
  xQueuePeek( xQueue, &data, 0 );
  Serial.println( data );

  // 通常受信(213の順で受信し、受信成功した場合のみアイテムが更新される)
  int ret;
  Serial.println( "Receive Test" );
  ret = xQueueReceive( xQueue, &data, 0 );
  Serial.printf( "ret = %d, data = %d\n", ret, data );
  ret = xQueueReceive( xQueue, &data, 0 );
  Serial.printf( "ret = %d, data = %d\n", ret, data );
  ret = xQueueReceive( xQueue, &data, 0 );
  Serial.printf( "ret = %d, data = %d\n", ret, data );
  ret = xQueueReceive( xQueue, &data, 0 );
  Serial.printf( "ret = %d, data = %d\n", ret, data );
  ret = xQueueReceive( xQueue, &data, 0 );
  Serial.printf( "ret = %d, data = %d\n", ret, data );

  // Mailbox受信(最後に送信したデータのみ受信)
  Serial.println( "Mailbox Test" );
  xQueuePeek( xQueueMailbox, &data, 0 );
  Serial.println( data );

  // キュークリア
  Serial.println( "xQueueReset Test" );
  xQueueReset(xQueue);
  xQueueReset(xQueueMailbox);

  // キューセット
  Serial.println( "QueueSet Test" );
  QueueSetHandle_t xQueueSet;
  xQueueSet = xQueueCreateSet( QUEUE_LENGTH + 1 ); // Setするキューの合計
  xQueueAddToSet( xQueue, xQueueSet );
  xQueueAddToSet( xQueueMailbox, xQueueSet );

  // アイテム追加
  data = 100;
  xQueueSend(xQueue, &data, 0);
  data++;
  xQueueSend(xQueue, &data, 0);
  data++;
  xQueueSend(xQueueMailbox, &data, 0);

  // 取得してみる
  while(1){
    // 取得可能なキューを取得する
    QueueHandle_t queue = xQueueSelectFromSet( xQueueSet, 0 );
    if( queue == NULL ){
      // キューがなくなったので終了
      break;
    }

    // キューの種類を調べる
    if( queue == xQueue ){
      Serial.print( "xQueue " );
    } else if( queue == xQueueMailbox ) {
      Serial.print( "xQueueMailbox " );
    } else {
      Serial.print( "? " );
    }
    
    // 受信
    ret = xQueueReceive( queue, &data, 0 );
    Serial.printf( "ret = %d, data = %d\n", ret, data );
  }

  // キュー削除
  vQueueDelete(xQueue);
  vQueueDelete(xQueueMailbox);
}

void loop() {
}

実行結果

Queue Test
uxQueueMessagesWaiting = 3
uxQueueSpacesAvailable = 1
xQueueIsQueueEmptyFromISR = 0
xQueueIsQueueFullFromISR = 0
uxQueueMessagesWaitingFromISR = 3
Peek Test
2
2
Receive Test
ret = 1, data = 2
ret = 1, data = 1
ret = 1, data = 3
ret = 0, data = 3
ret = 0, data = 3
Mailbox Test
5
xQueueReset Test
QueueSet Test
xQueue ret = 1, data = 100
xQueue ret = 1, data = 101
xQueueMailbox ret = 1, data = 102

解説

いろいろごちゃごちゃ処理を書いてありますが、xQueueCreate()でキューを作成して、xQueueSend()で送信して、xQueueReceive()で受信できます。キューの先頭に割り込ませたい場合にはxQueueSendToFront()で送信します。

割り込みの内部から呼び出した場合にはFromISRが最後についている関数群があるのでそちらを利用します。

QueueSetは複数のタスクからキューにアクセスするときの機能なので、Arduinoでのユースケースは思いつかないです。

まとめ

おそらくは受信は通知で、送信はキューを使ってメッセージ処理をするのが王道のはずです。

あと標準のFreeRTOSと、ESP32で使えるようにしたESP-IDF用FreeRTOSと、それをArduinoで使えるようにした Arduino用FreeRTOSがあり、3つとも使える機能がちょっと違うのでわかりにくいです。

ドキュメントはESP-IDF用FreeRTOSが一番無難なんですが、Arduino版で使えない関数などがあるので、Arduino IDEで実際にコンパイルしてリファレンスは書いています。概要は日本語で公開されているFreeRTOSのオフィシャルが一番充実していますがArduinoでは使えない関数とか、ESP-IDFで拡張した機能とかがわからないです。

コメントする

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

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)