任务通知
任务通知
FreeRTOS 任务通知(Task Notification)是一种轻量级的任务间通信机制。
它可以在很多场景下替代:
- 二值信号量
- 计数信号量
- 消息队列
- 部分事件标志组
相比这些独立的内核对象,任务通知直接存储在任务控制块 TCB 中,不需要额外创建通信对象,因此速度更快、内存开销更小。
其本质可以概括为:
任务通知 = 写入目标任务 TCB 中的 32 位通知值,并唤醒目标任务。
配置
在 FreeRTOSConfig.h 中开启任务通知:
#define configUSE_TASK_NOTIFICATIONS 1通常默认已经开启。
每个任务都有一个通知值:
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;其中:
ulNotifiedValue就是任务通知的核心数据。
因此,一个任务天然就拥有一个 32 位通知值。
任务通知的两种主要用法
任务通知最常见的使用方式可以分为两类:
计数/信号量模式
把通知值当作一个计数器使用:
xTaskNotifyGive()
ulTaskNotifyTake()适合:
- 二值信号量
- 计数信号量
- ISR 通知任务
- “有事件发生了”这种简单同步
其本质就是:
发送通知
↓
通知值 +1
↓
任务等待通知
↓
取走通知值数值/事件模式
直接向任务发送一个 32 位数值:
xTaskNotify()
xTaskNotifyWait()适合:
- 传递简单数据
- 按键值
- 状态值
- 多事件位
- 状态机
例如:
通知值:
0000 0000 0000 0000 0000 0000 0000 0001
↑
KEY0
0000 0000 0000 0000 0000 0000 0000 0010
↑
KEY1因此也可以把 32 位通知值当作一个事件位图使用。
核心 API
发送通知
任务中:
xTaskNotifyGive()用于将目标任务的通知值加 1。
xTaskNotify()用于向目标任务写入一个通知值,并根据 eNotifyAction 决定具体操作。
常见操作:
eIncrement
eSetValueWithOverwrite
eSetValueWithoutOverwrite
eSetBits
eNoAction其中最常用的是:
eIncrement和:
eSetValueWithOverwrite
eSetBitsISR 中发送通知
中断中使用:
vTaskNotifyGiveFromISR()或者:
xTaskNotifyFromISR()典型流程:
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(
xTaskHandle,
&xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);本质就是:
ISR
↓
修改目标任务通知值
↓
唤醒目标任务
↓
必要时触发任务切换等待通知
ulTaskNotifyTake()
uint32_t ulTaskNotifyTake(
BaseType_t xClearCountOnExit,
TickType_t xTicksToWait
);它主要用于计数型通知。
例如:
uint32_t count;
count = ulTaskNotifyTake(
pdTRUE,
portMAX_DELAY
);其中:
pdTRUE表示获取通知后将通知值清零。
pdFALSE表示只减 1,不清零。
因此:
xTaskNotifyGive()
↓
通知值 +1
↓
ulTaskNotifyTake()
↓
通知值 -1 / 清零这就是它为什么可以替代计数信号量。
xTaskNotifyWait()
BaseType_t xTaskNotifyWait(
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait
);它主要用于数值和事件模式。
例如:
uint32_t notifyValue;
if(xTaskNotifyWait(
0,
UINT32_MAX,
¬ifyValue,
portMAX_DELAY) == pdTRUE)
{
// 处理 notifyValue
}这里:
ulBitsToClearOnEntry表示进入等待前清除哪些位。
ulBitsToClearOnExit表示退出等待后清除哪些位。
pulNotificationValue用于获取收到的通知值。
两组 API 的区别
可以直接记成:
| 需求 | 发送 | 接收 |
|---|---|---|
| 计数/信号量 | xTaskNotifyGive() | ulTaskNotifyTake() |
| 数值/事件 | xTaskNotify() | xTaskNotifyWait() |
| ISR 计数通知 | vTaskNotifyGiveFromISR() | ulTaskNotifyTake() |
| ISR 数值通知 | xTaskNotifyFromISR() | xTaskNotifyWait() |
核心区别:
ulTaskNotifyTake()
↓
把通知值当“计数器”
xTaskNotifyWait()
↓
把通知值当“数据/位图”使用流程
计数通知
1. 创建任务
↓
2. 获取目标任务句柄
↓
3. 发送通知
xTaskNotifyGive()
↓
4. 目标任务等待
ulTaskNotifyTake()
↓
5. 处理事件数值通知
1. 创建任务
↓
2. 获取目标任务句柄
↓
3. 发送数据
xTaskNotify()
↓
4. 目标任务等待
xTaskNotifyWait()
↓
5. 获取通知值
↓
6. 根据通知值处理事件示例:任务发送按键值
发送任务:
void KeyTask(void *pvParameters)
{
uint8_t key;
for(;;)
{
key = KEY_Scan();
if(key != 0)
{
xTaskNotify(
DataTaskHandle,
key,
eSetValueWithOverwrite
);
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}接收任务:
void DataTask(void *pvParameters)
{
uint32_t notifyValue;
for(;;)
{
if(xTaskNotifyWait(
0,
UINT32_MAX,
¬ifyValue,
portMAX_DELAY) == pdTRUE)
{
switch(notifyValue)
{
case KEY0_PRES:
// 处理 KEY0
break;
case KEY1_PRES:
// 处理 KEY1
break;
case WKUP_PRES:
// 处理 WKUP
break;
}
}
}
}这里的本质就是:
KeyTask
│
│ xTaskNotify(key)
↓
DataTask 的通知值
│
│ xTaskNotifyWait()
↓
根据 key 判断事件示例:ISR 通知任务
例如串口接收完成后,在 ISR 中通知数据处理任务:
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(
DataProcessTaskHandle,
&xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);数据处理任务:
void DataProcessTask(void *pvParameters)
{
for(;;)
{
ulTaskNotifyTake(
pdTRUE,
portMAX_DELAY
);
// 收到通知,处理串口数据
ProcessData();
}
}整个过程:
串口 ISR
│
│ vTaskNotifyGiveFromISR()
↓
通知 DataProcessTask
│
↓
DataProcessTask 被唤醒
│
│ ulTaskNotifyTake()
↓
处理数据这也是任务通知非常典型的使用场景:
ISR 只负责发通知,任务负责真正处理数据。
任务通知与其他通信机制
任务通知最大的特点是:
普通通信机制:
任务
↓
Queue / Semaphore / EventGroup
↓
任务而任务通知:
任务
↓
直接修改目标任务 TCB
↓
目标任务因此不需要单独创建:
QueueHandle_t
SemaphoreHandle_t
EventGroupHandle_t通信对象。
这使得任务通知具有:
- 更少的 RAM 开销
- 更少的操作步骤
- 更高的执行效率
- 特别适合 ISR → Task 通知
但任务通知也有一个重要限制:
一个任务只有一个通知值,因此不适合替代所有通信场景。
例如需要缓存多条独立消息时,仍然应该使用消息队列。
总结
任务通知可以记住下面这张关系:
任务通知
│
┌────────┴────────┐
│ │
计数模式 数值/事件模式
│ │
▼ ▼
xTaskNotifyGive() xTaskNotify()
│ │
▼ ▼
ulTaskNotifyTake() xTaskNotifyWait()
│ │
▼ ▼
信号量/计数器 数据/事件位图最核心的一句话:
任务通知就是利用目标任务 TCB 中的 32 位通知值,实现任务之间的快速同步和简单数据传递。

