ESP32 RTOSのキューを使ってみる (Arduino)

ESP32 RTOSのキューを使ってみる (Arduino)

今回も Arduino のフレームワークで RTOS を使います。
今回はキューを使ってみます。

この記事は JTAG でデバッグすることを前提にして書いています。
環境構築については こちら をご覧になってください。

投稿時の開発環境を記しておきます。

PC:
Windows10 OS

開発ボード :
ESP32-DevKitCーVE
(Soc : ESP32-D0WD-V3)

デバッガー(H/W):
FT2232D

デバッガー (S/W) :
Visual Studio Code + PlatformIO + Arduino Framework

FreeRTOS

こちら が本家ですが、ESP32用としては このへん を見るのが良さそうです。

プロジェクトをつくる

VSCodeで、もし使っていたプロジェクトを開いていたら、File – Close Folder して閉じておきます。

その後にVSCodeからPlatformIOをOpenします。

以下の内容でプロジェクトを新規に作成します。

Name : ESP32A-RTOS-Queue
Board : Espressif ESP32 Dev Module
Framework : Arduino Framework

Name : ESP32A の “A” は Framework (Arduino Framework)の頭文字を示しています。

(後から見てわかるように、Arduinoを使うことを明示しています)

platformio.ini に以下の4行を追加して、 Ctrl + s で保存しておきます。

COM[8]の8の部分はデバイスマネージャーのポート(COMとLPT)で Silicon Labs CP210x から始まるCOMの番号を記述します。

upload_port はプログラムをアップロードする時に使うCOMポートを指定します。
monitor_port は printf()出力をモニターするCOMポートを指定します。

debug_tool = minimodule
upload_port = COM[8]
monitor_port = COM[8]
monitor_speed = 115200

キューとは

タスク間でデータを送受信するための手段のひとつです。
先に入れたものから先に出ていくその構造は FiFo (First in First out : ファイフォー)と呼ばれています。

今回使うキューに関連するAPI

xQueueCreate()

機能:キューを作成する
第1引数:項目を何個分のキューを用意するか、その数を指定する。
第2引数:項目のバイト数。
戻り値:キューがつくられるとキューへのハンドルが返る。つくられない場合には0が返る。

xQueueSend()

機能:キューに項目を送信する
第1引数:キューへのハンドル
 今回はタスクをつくる際の引数にキューハンドルを渡しておき、タスクの引数で受け取っている。
第2引数:キューに置く項目へのポインタ。
第3引数:キューがいっぱいの場合の待ち時間。0を指定するとすぐに戻ってくる。
戻り値:項目が正常に送られたかどうか。

xQueueReceive()

機能:キューから項目を受信する
第1引数:キューへのハンドル
 今回はタスクをつくる際の引数にキューハンドルを渡しておき、タスクの引数で受け取っている。
第2引数:キューに置く項目へのポインタ。
第3引数:呼び出し時にキューが空になった場合の待ち時間。0を指定するとすぐに戻ってくる。
戻り値:項目がキューから正常に受信されたかどうか。

uxQueueMessagesWaiting()

機能:キューに保存されている項目数を取得する
第1引数:キューへのハンドル
戻り値:キューに保存されている項目の数。

コーディングする

main.cppを以下のようにコーディングします。

#include <Arduino.h>

TaskHandle_t taskHandle[3];
QueueHandle_t xQueue;

typedef struct
{
 int id;
 int data[10];
} QMsg_t;

void TaskQueueSend1(void *args) {

  QueueHandle_t queue = (QueueHandle_t)args;

  QMsg_t msg1;

  msg1.id = 1;
  msg1.data[0] = 0;
  msg1.data[1] = 1;

  while (true) {

    xQueueSend(queue, (void*)&msg1, 0);
    printf("TaskQueueSend 1\r\n");
    vTaskDelay(100);
    msg1.data[0] += 1;
    msg1.data[1] += 2;
  }
}

void TaskQueueSend2(void *args) {

  QueueHandle_t queue = (QueueHandle_t)args;

  QMsg_t msg2;

  msg2.id = 2;
  msg2.data[0] = 2;
  msg2.data[1] = 3;

  while (true) {

    xQueueSend(queue, (void*)&msg2, 0);
    printf("TaskQueueSend 2\r\n");
    vTaskDelay(100);
    msg2.data[0] += 4;
    msg2.data[1] += 8;
  }
}

void TaskQueueRecv(void *args) {

  QueueHandle_t queue = (QueueHandle_t)args;
  QMsg_t msg;
  UBaseType_t itemNums;
  BaseType_t ret;

  while (true) {
    itemNums = uxQueueMessagesWaiting(queue);
    printf("Items = %d\r\n", itemNums);
    if (itemNums) {
      ret = xQueueReceive(queue, &msg, 0);
      if (ret == pdTRUE) {
        printf("TaskRecv, id = %d data[0] = %d data[1] = %d \r\n", msg.id, msg.data[0], msg.data[1]);
      }
      else {
        printf("TaskRecv, not received.\r\n");
      }
    }
    vTaskDelay(50);
  }
}

void setup() {

  xQueue = xQueueCreate(10, sizeof(QMsg_t));
  xTaskCreatePinnedToCore(TaskQueueSend1, "TaskQueueSend1", 4096, xQueue, 10, &taskHandle[1], APP_CPU_NUM);
  xTaskCreatePinnedToCore(TaskQueueSend2, "TaskQueueSend2", 4096, xQueue, 10, &taskHandle[1], APP_CPU_NUM);
  xTaskCreatePinnedToCore(TaskQueueRecv, "TaskQueueRecv", 4096, xQueue, 11, &taskHandle[0], APP_CPU_NUM);
}

void loop() {
}

プログラムの概要

xTaskCreatePinnedToCore()の4番目の引数をタスクの引数として受け取ることができます。
今回はキューのハンドルを渡してみました。これは良く使われる手法だと思います。
それぞれのタスクでキューを受け取ることで、おなじキューを使うことができます。

キューの受信用タスク:TaskQueueRecv()の優先順位を少し高くしてみました。
気持ちの問題です (^_^)

uxQueueMessagesWaiting()でキューの中に溜まっている項目の数がわかるようなので使ってみました。
設計が悪いとキューに溜まってしまうので、これを実装して確認される方が安心ですね。(特にデバッグ時)
受信側のディレイ:vTaskDelay()の時間を送信側より短くして、取りこぼしのないようにしてみました。

同じデータ構造なら送信タスクを2つにしても送受信できることが確認できました。

受信側の if(itemNums){}の部分をはずしたり、vTaskDelay()の引数を調整すると挙動がかわります。
いろいろ試してみるとお勉強になります。

ソースコード

ソースコードを github esp32a-RTOS-Queue におきましたので参考になさってください。

ビルドして実行する

ビルドしてエラーがないことを確認して、メニューから Run – Start Debugging (またはF5キー)すると少ししてから

loopTaskWDTEnabled = false; の行で停止します。

もう一度 F5キーを押すとプログラムを実行します。

その後アクティビティバーのPlatformIOのアイコンを選択してツリー表示させて Monitor を選択します。

するとTERMINAL に実行結果が表示されます。

いかがでしたか?

うまく動かすことができたでしょうか。

Queueカテゴリの最新記事