ESP32 × FreeRTOS:タスク設計の基本と実践

FreeRTOS

この記事でわかること

  • 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
)
引数この例での値意味
pxTaskCodeblink_task実行するタスク関数(タスクの中身)
pcName"blink_task"タスク名(デバッグ用の名前)
usStackDepth2048タスク用スタックサイズ
pvParametersNULLタスクに渡す引数(今回はなし)
uxPriority1優先度(数値が大きいほど優先される)
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を以下の順で解説していく予定です。

  1. タスク:Task(本記事)
  2. Queue(タスク間通信の基本)
  3. Semaphore / Mutex(排他制御)
  4. EventGroup(状態通知)

次の記事では Queue を取り上げ、実例付きで解説します。


まとめ

  • タスクはFreeRTOS設計の最小単位
  • ESP32はデュアルコアだが、タスクはそれ以上に存在できる
  • 実行できないタスクは待機し、CPUを奪わない
  • タスクだけでは完結せず、必ずタスク間連携が必要

コメント

タイトルとURLをコピーしました