今回も 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
機能:キューを作成する
第1引数:項目を何個分のキューを用意するか、その数を指定する。
第2引数:項目のバイト数。
戻り値:キューがつくられるとキューへのハンドルが返る。つくられない場合には0が返る。
機能:キューに項目を送信する
第1引数:キューへのハンドル
今回はタスクをつくる際の引数にキューハンドルを渡しておき、タスクの引数で受け取っている。
第2引数:キューに置く項目へのポインタ。
第3引数:キューがいっぱいの場合の待ち時間。0を指定するとすぐに戻ってくる。
戻り値:項目が正常に送られたかどうか。
機能:キューから項目を受信する
第1引数:キューへのハンドル
今回はタスクをつくる際の引数にキューハンドルを渡しておき、タスクの引数で受け取っている。
第2引数:キューに置く項目へのポインタ。
第3引数:呼び出し時にキューが空になった場合の待ち時間。0を指定するとすぐに戻ってくる。
戻り値:項目がキューから正常に受信されたかどうか。
機能:キューに保存されている項目数を取得する
第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 に実行結果が表示されます。
いかがでしたか?
うまく動かすことができたでしょうか。