Task Notification は、FreeRTOS に標準搭載されている
最も軽量なタスク間同期機構です。
本記事では、ESP32(ESP-IDF)環境を前提に、
- Task Notification の役割
- 内部構造(なぜ軽いのか)
- Semaphore / EventGroup との違い
- 実際に動く最小サンプル
- 主要APIの使い方
- 設計時の注意点
までを体系的に解説します。
Contents
この記事でわかること
- 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 Notification | 1 対 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:通知が来るまで待つ
- 第1引数:終了時にカウントをクリアするか
- 戻り値
- 成功:受信した通知カウント値
- 失敗: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設計において、
「まず検討すべき軽量同期手段」と言えます。
内部構造を理解することで、
より効率的なリアルタイム設計が可能になります。

コメント