ESP32 × FreeRTOS:Task Notificationとは?内部構造から理解する最軽量タスク間同期(ESP32 / ESP-IDF)

FreeRTOS

Task Notification は、FreeRTOS に標準搭載されている
最も軽量なタスク間同期機構です。

本記事では、ESP32(ESP-IDF)環境を前提に、

  • Task Notification の役割
  • 内部構造(なぜ軽いのか)
  • Semaphore / EventGroup との違い
  • 実際に動く最小サンプル
  • 主要APIの使い方
  • 設計時の注意点

までを体系的に解説します。


この記事でわかること

  • FreeRTOS における Task Notification の位置づけ
  • なぜ「最軽量」と言われるのか(TCB内部構造)
  • ISRから高速にタスクを起こす方法
  • 実装パターンと落とし穴

こんな方におすすめ

  • ESP32 × FreeRTOS でタスク設計をしている
  • ISRからタスクを安全かつ高速に起こしたい
  • Semaphoreより軽量な仕組みを探している
  • FreeRTOSの内部動作まで理解したい

Task Notification とは

FreeRTOS の Task Notification(タスク通知) は、1 対 1 のタスク同期をシンプルに実現するための最軽量機構です。本質的には「あるタスクにイベントを送信し、待っているタスクを起床させる」ための仕組みになります。

主な特徴:

  • 1対1の通知機構
  • 待機中はCPUを消費しない(ブロック状態)
  • 各タスクは 32bit の通知値を保持
  • カウント/ビット/値として利用可能

単純な「合図」用途において、最も効率的な手段です。


Task Notification の内部構造

なぜ「最軽量」と言われるのか?

Task Notification が軽量と言われる理由は、
通知は Task Control Block(TCB)に内蔵されているから。
Semaphore や Queue は、生成時にヒープ上へ制御構造体を確保します。
一方、Task Notification は違います。

TCBに組み込まれている通知領域


各タスクのTCBにはあらかじめ以下が存在します。

  • 32bit の通知値
  • 通知状態フラグ

追加オブジェクトは不要です。

TCBのイメージ

TCB
├─ スタック情報
├─ 優先度
├─ 状態
├─ 通知値(32bit)
└─ 通知状態

つまり、
Notification = TCB内部の整数更新のみ
これが高速かつ軽量である本質です。

Queueとの動作差

Queueの場合:

  • キュー構造体操作
  • データコピー
  • バッファ管理

Notificationの場合:

  • TCB内の値更新
  • 状態変更
  • Readyリスト移動

コピー処理がないため、ISRとの相性が非常に良いです。


Semaphore / EventGroup との違い

手段特徴典型用途
Task Notification1 対 1 の起床・合図に最適(最軽量)ISR → タスク起床、フラグ待ち
Semaphore共有資源 / 排他制御共有リソースのアクセス制御
EventGroup複数フラグの AND/OR 待ち複数イベントの組合せ待ち

単純な「合図」ならNotificationが最適です。


最小サンプル:通知でタスクを起こす(ESP32)

やりたいこと

  • SenderTask:一定間隔で通知を送信
  • ReceiverTask:通知を待ち、来たら処理する

実装例(ESP-IDF)

include<cstdio>
include "freertos/FreeRTOS.h"
include "freertos/task.h"

static TaskHandle_t receiverHandle = NULL;

static void SenderTask(void *pv)
{
for (;;) {
xTaskNotifyGive(receiverHandle);
vTaskDelay(pdMS_TO_TICKS(500)); // 500msごと
}
}

static void ReceiverTask(void *pv)
{
for (;;) {
uint32_t count = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
printf("[Receiver] notified! count=%u\n", (unsigned)count);
}
}

extern "C" void app_main(void)
{
xTaskCreate(ReceiverTask, "ReceiverTask", 2048, NULL, 5, &receiverHandle);
xTaskCreate(SenderTask, "SenderTask", 2048, NULL, 5, NULL);
}

動作ポイント

  • xTaskNotifyGive() が通知(カウント +1)を送信
  • ulTaskNotifyTake() で通知を待つ(CPU 負荷なし)
  • 待機中に通知が来ると ReceiverTask が起動

主要 API

xTaskNotifyGive(通知する)

BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
  • 役割:指定したタスクに通知(カウント +1)を送る
  • 引数
    • 第1引数:通知先のタスクハンドル
  • 戻り値
    • pdPASS:通知成功
    • errQUEUE_FULL:通知失敗(通知が既に最大値)

ulTaskNotifyTake(通知待ち)

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
  • 役割:タスク通知を待ち受ける(ブロック可能)
  • 引数
    • 第1引数:終了時にカウントをクリアするか
      • pdTRUE:0 にクリア
      • pdFALSE:デクリメントのみ
    • 第2引数:待ち時間(Tick)
      • 0:待たない
      • portMAX_DELAY:通知が来るまで待つ
  • 戻り値
    • 成功:受信した通知カウント値
    • 失敗:0(タイムアウト)

xTaskNotify / xTaskNotifyWait(値/ビット付き通知)

BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
                        uint32_t ulValue,
                        eNotifyAction eAction );
  • 役割:値やビットを指定して通知する
  • 引数
    • 第1引数:通知先タスクハンドル
    • 第2引数:通知値
    • 第3引数:通知動作
      • eSetBits:ビットOR
      • eIncrement:インクリメント
      • eSetValueWithOverwrite:上書き
      • eSetValueWithoutOverwrite:未通知時のみ上書き
  • 戻り値
    • pdPASS:成功
    • pdFAIL:失敗

vTaskNotifyGiveFromISR()(ISRから通知する)

ISR からタスク通知を送る場合は、専用 API を使います。

void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
                             BaseType_t *pxHigherPriorityTaskWoken );
  • 役割:割り込み(ISR)から指定タスクへ通知(カウント +1)を送る
  • 引数
    • 第1引数:通知先タスクハンドル
    • 第2引数:優先度の高いタスクが起床したかを受け取るポインタ
      • pdTRUE が入った場合はコンテキストスイッチ要求が必要
  • 戻り値
    • なし

設計のコツと注意点

注意 1:1 対 1 が基本

Task Notification は 1つのタスクを対象とする仕組みです。複数タスクへの通知や共有が必要な場合は別の同期手段(Queue / EventGroup)を検討してください。

注意 2:用途に応じた API 選択

  • 「単純な起床合図」なら xTaskNotifyGive() / ulTaskNotifyTake()
  • 「値・フラグ付き制御」なら xTaskNotify() / xTaskNotifyWait()
  • 「ISR からの通知」には FromISR 系を使用

どのタスク間通知を使うべきか

  • 1対1の高速起床 → Task Notification
  • データ転送 → StreamBuffer
  • 複数イベント待ち → EventGroup
  • 排他制御 → Mutex

用途に応じた使い分けが重要です。


まとめ

Task Notification は、

  • TCB内蔵の通知機構
  • 追加メモリ不要
  • ISRとの相性が非常に良い
  • 1対1同期に最適

FreeRTOS設計において、
「まず検討すべき軽量同期手段」と言えます。

内部構造を理解することで、
より効率的なリアルタイム設計が可能になります。

コメント

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