Contents
この記事でわかること
- FreeRTOSにおける「タスク」とは何か
- ESP32(デュアルコア)でタスクがどう実行されるか
- FreeRTOSスケジューラの基本動作
- delay系処理とRTOS的待機の違い
- 実務・IoTで破綻しないタスク設計の考え方
- タスク間通信(Queue / Semaphore / EventGroup)の位置づけ
なぜ FreeRTOS が必要なのか?
シンプルな組み込みプログラムは、1本の処理ループで全体を制御する構造になりがちです。
しかし ESP32 を使った実システムでは、
- センサー読み取り
- 通信処理(Wi-Fi / MQTT)
- ログ保存
- LED・UI制御
といった 複数の処理を同時に扱う必要 があります。
これらを1本の処理で順番に実行すると、
- 処理待ちで他が止まる
- タイミングが崩れる
- コードが複雑化する
といった問題が発生します。
この問題を解決するために使われるのが FreeRTOS です。
FreeRTOS における「タスク」とは?
FreeRTOSの タスク(Task) とは、
CPU上で独立して実行される処理単位
です。
タスクは「関数を並列に動かす仕組み」であり、FreeRTOSのスケジューラによって実行タイミングが管理されます。
タスクをESP32のコアで考えてみる(例え)
ESP32は デュアルコアCPU を搭載しています。ここで、以下のようなタスクがあるとします。
- TaskA:センサー読み取り
- TaskB:通信処理
- TaskC:LED制御
ESP32にはコアが2つしかないため、
- Core0 が TaskA を実行中
- Core1 が TaskB を実行中
の間、TaskCは同時には実行できず「待機状態」 になります。
しかし、TaskAやTaskBが vTaskDelay() や I/O待ちなどで Blocked(待機) に入ると、
スケジューラはすぐに TaskC を Ready にして実行させます。
すべてのタスクが「同時に動いているように見える」のは、 スケジューラが状況に応じてタスクを切り替えているためです。
FreeRTOSのスケジューラとタスク状態
FreeRTOSでは、タスクは次の状態を行き来します。
Ready <-> Running <-> Blocked
- Ready:実行可能だがCPU待ち
- Running:CPUを使用中
- Blocked:待機中(delay / Queue / Semaphore など)
ポイント
- 実行できるタスクがあればCPUは遊ばない
- 待機中のタスクはCPUを消費しない
- コア数より多いタスクが存在できる
これにより、
ESP32では複雑な並列処理が可能になります。
Taskの作成方法
xTaskCreateという関数でタスクを作成する。
xTaskCreate(
blink_task, // pxTaskCode
"blink_task", // pcName
2048, // usStackDepth
NULL, // pvParameters
1, // uxPriority
&handle // pxCreatedTask
)
| 引数 | この例での値 | 意味 |
|---|---|---|
pxTaskCode | blink_task | 実行するタスク関数(タスクの中身) |
pcName | "blink_task" | タスク名(デバッグ用の名前) |
usStackDepth | 2048 | タスク用スタックサイズ |
pvParameters | NULL | タスクに渡す引数(今回はなし) |
uxPriority | 1 | 優先度(数値が大きいほど優先される) |
pxCreatedTask | &handle | 作成したタスクのハンドルを受け取る変数(後で操作したい場合に使う) |
5章の実装例ではsetup内で2つのタスクを作成しています。
実装例:FreeRTOSタスクによるLチカ
以下は FreeRTOSのみを使った最小構成のタスク例 です。
処理内容
・LEDを1秒間隔で点滅(タスクA)
・LED状態を0.2秒間隔でシリアル出力(タスクB)
LEDはGPIOの16ピンを使用しています。
回路はLEDをつけているだけなので簡単ですが一応載せておきます。
(ESP32が雑ですみません…、いつか作り直します。)

以下コードです。
#include <Arduino.h>
bool led_flag = false;
void LedTask(void *pvParameters) {
//GPIOの16ピンをOUTPUTに設定
pinMode(16, OUTPUT);
while (1) {
//LEDをON
digitalWrite(16, HIGH);
led_flag = true;
//1秒待機
vTaskDelay(1000 / portTICK_PERIOD_MS);
//LEDをOFF
digitalWrite(16, LOW);
led_flag = false;
//1秒待機
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void SerialTask(void *pvParameters) {
while (1) {
if(led_flag) {
Serial.println("LED is ON");
} else {
Serial.println("LED is OFF");
}
//0.2秒待機
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
//2つのタスクを開始
xTaskCreate(LedTask, "LedTask", 2048, NULL, 1, NULL);
xTaskCreate(SerialTask, "SerialTask", 2048, NULL, 1, NULL);
}
void loop() {vTaskDelay(portMAX_DELAY);}
▼ 処理の流れ
タスクA(LED)
LED ON → 1秒休憩 → LED OFF → 1秒休憩
(以後ループ)
タスクB(シリアル)
LEDの状態を出力 → 0.2秒休憩
(以後ループ)
実際に動かすと、LEDの点滅とターミナルにLEDの状態が表示されることが確認できます。

この例で重要な点
以下のポイントを押さえると、FreeRTOSタスクの動きが理解しやすくなります。
vTaskDelay()により CPU を他タスクに譲る- タスクは
while(1)の無限ループで継続動作する - この例では
loop()では処理せず、タスク側で処理する
delay系処理とRTOS的待機の違い
RTOS的な待機(vTaskDelay)
- スケジューラに制御を返す
- 他タスクが実行可能
- CPUを無駄にしない
RTOSを無視した待機(Arduino の delay())
- CPUを占有する
- 他タスクが動けない
- リアルタイム性が崩れる
FreeRTOS環境では 「待つ=CPUを譲る」 が基本です。
タスク設計の基本指針
- タスクは増やしすぎない(目安:2〜5個)
- 優先度は基本「1」から始める
- 処理の役割ごとにタスクを分ける
- タスクは「仕事の単位」
タスクだけでは解決できない問題
タスクを分けただけでは、次の問題が残ります。
- タスクAのデータをタスクBに渡したい(タスク間のデータ受け渡し)
- 複数タスクで同じI2CやUARTを使いたい(タスク間の共有資源保護)
- 複数条件が揃うまで処理を待ちたい(タスク間のイベント通知/状態待ち)
これらはすべて 「タスク間の問題」 です。
FreeRTOSが提供する「タスク間」の仕組み
FreeRTOSには、タスク間で安全に連携するための機能があります。
- Queue
→ タスク間でデータやイベントを渡す - Semaphore / Mutex
→ タスク間で共有資源(UART / I2C / SPI など)を守る - EventGroup
→ タスク間で状態や条件の成立を通知する
これらはすべて
「タスク同士がどう協調するか」を解決する仕組み です。
今後の記事構成と次に読むべき内容
本ブログでは、FreeRTOSを以下の順で解説していく予定です。
- タスク:Task(本記事)
- Queue(タスク間通信の基本)
- Semaphore / Mutex(排他制御)
- EventGroup(状態通知)
次の記事では Queue を取り上げ、実例付きで解説します。
まとめ
- タスクはFreeRTOS設計の最小単位
- ESP32はデュアルコアだが、タスクはそれ以上に存在できる
- 実行できないタスクは待機し、CPUを奪わない
- タスクだけでは完結せず、必ずタスク間連携が必要


コメント