Contents
この記事でわかること
- FreeRTOS Queueの機能内容(何ができるか)
- Queueとグローバル変数の決定的な違い(状態共有 vs イベント共有)
- ESP32で動くQueue利用のLチカ実装例
- 主要なQueue関数(xQueueCreate / Send / Overwrite / Receive)の役割と引数
- Queueが溢れたときの扱い(捨てる/待つ/上書き)
Queueとは(タスク間通信の基本)
FreeRTOSの Queue(キュー) は、タスク間でデータを受け渡しするための仕組みです。
一言でいうと、
送信された値を、順番に“ためておける箱”
です。
Queueの重要ポイント(初心者が最初に押さえるべきこと)
- Queueは 値をコピーして保存します(変数の共有ではない)
- 作成時に決めた件数(Queue長)まで保持できます
- FIFO(先入れ先出し)で取り出します
イメージ:
[ TaskA ] –send–> [ Queueの箱 ] –receive–> [ TaskB ]
Queueとグローバル変数の違い
違いを一言でまとめるとこうです。
- Queue:イベント共有(履歴を保持できる)
- グローバル変数:状態共有(最後の1つしか残らない)
グローバル変数は「途中の指示」が消える
TaskAが短時間に何度も更新すると、
blinkInterval = 500
blinkInterval = 200
blinkInterval = 500
グローバル変数には 最後の500しか残りません。
「200に変えた」という途中の事実は消えます。
LED点滅くらいなら許容できることもありますが、以下だと致命的になりやすいです。
- ボタン押下イベント
- エラー通知
- コマンド処理
- ログ収集
- 通信送信キュー(MQTT/HTTPなど)
Queueは「送った順に溜められる」
Queueなら(長さが足りる限り)
[500][200][500]
のように 履歴として保持できます。
受信側のタスクが少し遅れても、溜まっている限り 順番に受け取れます。
【実装】ESP32で動かすQueue利用のLチカ例(最新値だけ欲しい構成)
ここでは「点滅周期(200ms/500ms)」をTaskAが更新し、TaskBが受け取って反映します。
今回の設計方針:
- 途中履歴はいらない
- 最新の設定値だけあればよい
→ そのため Queue長=1 + xQueueOverwrite()(最新値箱 / Mailbox) を使います。
サンプルコード(Arduino-ESP32で動作)
#include <Arduino.h>
QueueHandle_t blinkQueue;
const int LED_PIN = 16;
void TaskA(void *pvParameters) {
uint32_t interval = 500;
for (;;) {
interval = (interval == 500) ? 200 : 500;
// 最新値だけ保持したい → Queue長=1 + Overwrite
xQueueOverwrite(blinkQueue, &interval);
vTaskDelay(pdMS_TO_TICKS(2000)); // 2秒ごとに更新
}
}
void TaskB(void *pvParameters) {
uint32_t currentInterval = 500;
pinMode(LED_PIN, OUTPUT);
for (;;) {
// 0:Queueが空なら待たずに即戻る(ポーリング)
if (xQueueReceive(blinkQueue, ¤tInterval, 0) == pdPASS) {
// 受信できた場合は currentInterval が更新されている
}
digitalWrite(LED_PIN, HIGH);
vTaskDelay(pdMS_TO_TICKS(currentInterval));
digitalWrite(LED_PIN, LOW);
vTaskDelay(pdMS_TO_TICKS(currentInterval));
}
}
void setup() {
blinkQueue = xQueueCreate(1, sizeof(uint32_t)); // 最新値だけ保持する
if (!blinkQueue) {
// メモリ不足などで作成失敗
while (1) delay(1000);
}
xTaskCreate(TaskA, "TaskA", 2048, NULL, 1, NULL);
xTaskCreate(TaskB, "TaskB", 2048, NULL, 1, NULL);
}
void loop() {
vTaskDelay(portMAX_DELAY); // CPU占有を避ける
}
主要なQueue関数まとめ(役割と引数)
Queueの主要関数を整理します。
まずは Create / Send(or Overwrite) / Receive の3つを押さえればOKです。
Queueを作る:xQueueCreate
blinkQueue = xQueueCreate(
1, // uxQueueLength
sizeof(uint32_t) // uxItemSize
);
| 引数 | この例での値 | 意味 |
|---|---|---|
uxQueueLength | 1 | Queueに保持できる件数(最大いくつ溜めるか) |
uxItemSize | sizeof(uint32_t) | 1件あたりのデータサイズ(バイト) |
戻り値:QueueHandle_t(失敗時は NULL)
Queueへ送る:xQueueSend(履歴を溜めたいとき)
xQueueSend(
blinkQueue, // xQueue
&interval, // pvItemToQueue
0 // xTicksToWait
);
| 引数 | この例での値 | 意味 |
|---|---|---|
xQueue | blinkQueue | 送信先Queue |
pvItemToQueue | &interval | 送るデータのアドレス(値はQueue内にコピーされる) |
xTicksToWait | 0 | Queueが満杯なら待たずに失敗で戻る |
使い方:
xQueueSend(queue, &data, 0); // 満杯なら捨てる(ロスOK)
xQueueSend(queue, &data, portMAX_DELAY); // 空くまで待つ(ロス禁止)
Queueへ送る:xQueueOverwrite(最新値だけ欲しいとき)
xQueueOverwrite(
blinkQueue, // xQueue(Queue長=1で使うのが定番)
&interval // pvItemToQueue
);
| 引数 | この例での値 | 意味 |
|---|---|---|
xQueue | blinkQueue | 送信先Queue(Queue長=1の“最新値箱”向け) |
pvItemToQueue | &interval | 送るデータのアドレス(値はQueue内にコピーされる) |
Queue長=1とセットで使うと、常に最新値だけ保持できます(古い値は残りません)
Queueから受け取る:xQueueReceive
if (xQueueReceive(
blinkQueue, // xQueue
&interval, // pvBuffer
0 // xTicksToWait
) == pdPASS) {
// 受信できたら interval が更新される
//pdPASSはQueue APIが成功したかどうか(送信/受信できたか)」を示す戻り値
}
| 引数 | この例での値 | 意味 |
|---|---|---|
xQueue | blinkQueue | 受信元Queue |
pvBuffer | &interval | 受け取った値を書き込む先(ここにコピーされる) |
xTicksToWait | 0 | Queueが空なら待たずに即戻る |
使い方:
xQueueReceive(queue, &data, 0); // 空なら即戻る(ポーリング)
xQueueReceive(queue, &data, portMAX_DELAY); // 来るまで待つ(ブロッキング受信)
Queueを使うべき場面と設計指針(最新値/履歴)
Queue設計は、まずこの2択で迷いが減ります。
A. 最新値だけ欲しい(状態に近い)
例:温度の現在値、設定値、LED点滅周期など
- Queue長:1
- 送信:
xQueueOverwrite() - 受信:必要なタイミングで読む
→ 今の状態だけ欲しいならこの構成が最もシンプルです。
B. 履歴も処理したい(イベント)
例:ボタン押下、エラー通知、コマンド、ログなど
- Queue長:2以上
- 送信:
xQueueSend() - 受信:基本はブロッキング受信(
portMAX_DELAYなど)
→ 起きた順番に全部処理したいならこの構成が基本です。
Queueが溢れたときの選択肢(取りこぼし対策)
Queueが満杯のとき、FreeRTOSが勝手に良い感じに捨ててくれるわけではありません。
設計者が「どう扱うか」を決めます。
選択肢1:満杯なら捨てる(ロスOK)
if (xQueueSend(queue, &data, 0) != pdPASS) {
// 送れなかった(捨てた)ことをログに残すなど
}
- 送信側が止まらない
- ロスが許容できるデータ向け(センサーの頻繁な更新など)
選択肢2:空くまで待つ(ロス禁止)
xQueueSend(queue, &data, portMAX_DELAY);
- ロスしない
- 送信側が止まる(設計次第で全体の遅延になる)
選択肢3:最新値だけ残す(Queue長=1 + Overwrite)
xQueueOverwrite(queue, &data);
- 最新値を維持できる
- 履歴(途中経過)は捨てる、という設計になる
まとめ
- Queueは タスク間で値を安全に受け渡しする“箱”
- グローバル変数は 最後の値しか残らず、途中イベントが消えやすい
- 「最新値だけ欲しい」なら Queue長=1 + xQueueOverwrite
- 「履歴を処理したい」なら Queue長>1 + xQueueSend/Receive
- 溢れ対策は 捨てる/待つ/上書き を設計で選ぶ
次回はSemaphoreについて記載します。


コメント