FreeRTOS任务通知
xTaskNotifyGive与
xTaskNotifyGiveIndexed
在 FreeRTOS 中,xTaskNotifyGive 和 xTaskNotifyGiveIndexed 均属于任务通知(Task Notifications) 机制的 API,用于向目标任务发送 “通知”(类似轻量级信号量),但两者的核心区别在于是否支持多通知索引。任务通知是一种高效的任务间通信方式,直接操作任务控制块(TCB)中的通知值,无需额外内存分配,性能优于队列或信号量。
一、任务通知基础
任务通知的核心是每个任务拥有一个或多个 “通知值”(32 位无符号整数),发送方通过 API 修改目标任务的通知值,接收方通过等待 API 获取通知。通知值的修改方式包括:递增、置位、覆盖等。
xTaskNotifyGive 和 xTaskNotifyGiveIndexed 均采用 “递增通知值” 的方式(类似计数信号量的 “释放” 操作),即发送一次通知,目标任务的通知值加 1。
二、xTaskNotifyGive:默认索引的简单通知
xTaskNotifyGive 是简化版 API,仅操作任务的默认通知索引(索引 0),用于向目标任务发送一个 “递增型” 通知。
1. 函数原型
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
- 参数:
xTaskToNotify为目标任务的句柄(NULL表示当前任务)。 - 返回值:始终返回
pdPASS(因为任务通知直接操作 TCB,无内存分配失败风险)。
2. 功能说明
调用该函数时,目标任务的索引 0 通知值会自动加 1(通知值 = 通知值 + 1)。若目标任务正阻塞在等待该通知的 API(如 xTaskNotifyWait)上,会被唤醒。
三、xTaskNotifyGiveIndexed:指定索引的多事件通知
xTaskNotifyGiveIndexed 支持操作任务的多个通知索引(最多 32 个,由 configTASK_NOTIFICATION_ARRAY_ENTRIES 配置,默认 1),用于向目标任务的指定索引发送 “递增型” 通知。
当一个任务需要处理多个独立事件时(如同时响应按键、定时器、串口数据),可通过不同索引区分事件,避免使用多个信号量或队列。
1. 函数原型
BaseType_t xTaskNotifyGiveIndexed( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify);
- 参数:
xTaskToNotify:目标任务句柄(NULL表示当前任务);uxIndex:指定的通知索引(0 ≤ uxIndex <configTASK_NOTIFICATION_ARRAY_ENTRIES)。
- 返回值:始终返回
pdPASS。
2. 功能说明
调用该函数时,目标任务的指定索引(uxIndex)通知值会自动加 1。若目标任务正阻塞在等待该索引通知的 API(如 xTaskNotifyWaitIndexed)上,会被唤醒。
四、核心区别
| 特性 | xTaskNotifyGive |
xTaskNotifyGiveIndexed |
|---|---|---|
| 通知索引 | 固定使用索引 0 | 可指定索引(0 ~ 配置最大值 - 1) |
| 适用场景 | 单事件通知(任务只需处理一种事件) | 多事件通知(任务需区分多种独立事件) |
| 灵活性 | 简单,无需指定索引 | 灵活,支持按索引区分事件 |
五、使用场景
1. xTaskNotifyGive:单事件场景
当任务仅需响应一种事件时,用默认索引即可,简化代码。
例:按键事件通知
- 按键中断触发后,向 “按键处理任务” 发送通知:
// 按键中断服务程序 void EXTI0_IRQHandler(void) { // 向按键任务发送通知(索引0) xTaskNotifyGive(key_task_handle); portYIELD_FROM_ISR(pdTRUE); // 触发任务切换 } // 按键处理任务 void key_task(void *param) { while(1) { // 等待索引0的通知(通知值≥1时唤醒) xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); // 处理按键事件(如点灯) GPIO_ToggleBits(GPIOA, GPIO_Pin_5); } }
2. xTaskNotifyGiveIndexed:多事件场景
当任务需要区分多种事件(如按键、定时器、串口)时,用不同索引隔离事件。
例:一个任务处理 3 种事件
- 索引 0:按键事件;
- 索引 1:定时器事件;
- 索引 2:串口数据事件。
// 全局任务句柄
TaskHandle_t event_task_handle;
// 1. 按键中断:发送索引0通知
void EXTI0_IRQHandler(void) {
xTaskNotifyGiveIndexed(event_task_handle, 0); // 索引0:按键
portYIELD_FROM_ISR(pdTRUE);
}
// 2. 定时器中断:发送索引1通知
void TIM3_IRQHandler(void) {
xTaskNotifyGiveIndexed(event_task_handle, 1); // 索引1:定时器
portYIELD_FROM_ISR(pdTRUE);
}
// 3. 串口中断:发送索引2通知
void USART1_IRQHandler(void) {
xTaskNotifyGiveIndexed(event_task_handle, 2); // 索引2:串口
portYIELD_FROM_ISR(pdTRUE);
}
// 事件处理任务:区分3种事件
void event_task(void *param) {
uint32_t notify_value;
while(1) {
// 等待任意索引的通知(超时无限)
if (xTaskNotifyWaitIndexed(0, 0, 0xFFFFFFFF, ¬ify_value, portMAX_DELAY)) {
// 检查索引0(按键)
if (notify_value & (1 << 0)) {
USART1_SendString("处理按键事件\r\n");
}
// 检查索引1(定时器)
if (notify_value & (1 << 1)) {
USART1_SendString("处理定时器事件\r\n");
}
// 检查索引2(串口)
if (notify_value & (1 << 2)) {
USART1_SendString("处理串口事件\r\n");
}
}
}
}
六、接收通知的配套 API
发送通知后,接收任务需通过以下 API 等待通知:
- 对应
xTaskNotifyGive:xTaskNotifyWait(等待索引 0 的通知); - 对应
xTaskNotifyGiveIndexed:xTaskNotifyWaitIndexed(等待指定索引的通知)。
这两个等待 API 可配置 “清除通知值的方式”(如获取后重置为 0,或保留原值),灵活适配计数型(累计通知次数)或触发型(单次通知)场景。
总结
xTaskNotifyGive是单索引(索引 0)的简化通知 API,适合单一事件场景,代码简洁;xTaskNotifyGiveIndexed支持多索引,适合一个任务处理多种独立事件的场景,灵活性更高。
两者均通过 “递增通知值” 实现类似计数信号量的功能,且比信号量更高效(无额外内存分配),是资源受限场景的优选。
vTaskNotifyGiveFromISR 与vTaskNotifyGiveIndexedFromISR
vTaskNotifyGiveFromISR 和 vTaskNotifyGiveIndexedFromISR 是 FreeRTOS 中中断服务程序(ISR)专用的任务通知 API,用于在中断中向目标任务发送 “递增型” 通知(类似计数信号量的 “释放” 操作)。它们是任务级 API xTaskNotifyGive 和 xTaskNotifyGiveIndexed 的中断适配版本,核心区别在于支持中断环境下的安全调用和多通知索引。
一、中断中使用任务通知的必要性
中断服务程序(ISR)的执行时间必须尽可能短,且不能调用可能引起阻塞的函数(如 vTaskDelay、任务级队列 / 信号量 API)。任务通知机制通过直接操作目标任务的控制块(TCB),无需额外内存分配,比队列或信号量更高效,适合在 ISR 中快速传递事件。
vTaskNotifyGiveFromISR 和 vTaskNotifyGiveIndexedFromISR 专为 ISR 设计,确保在中断上下文安全地发送通知,同时支持触发任务调度(若高优先级任务被唤醒)。
二、vTaskNotifyGiveFromISR:中断中向默认索引发送通知
vTaskNotifyGiveFromISR 用于在 ISR 中向目标任务的默认通知索引(索引 0) 发送 “递增型” 通知(通知值 +1),功能等同于中断环境下的 xTaskNotifyGive。
1. 函数原型
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken );
-
参数:
xTaskToNotify:目标任务的句柄(NULL表示当前任务,但 ISR 中通常指定具体任务)。pxHigherPriorityTaskWoken:输出参数,用于标记 “是否有更高优先级任务被唤醒”。若通知导致更高优先级任务就绪,该变量会被设为pdTRUE,需在 ISR 末尾触发任务切换。
-
返回值:
void(中断中发送通知不会失败,无需返回值)。
2. 功能说明
调用该函数时,目标任务的索引 0 通知值会自动 +1。若目标任务正阻塞在等待索引 0 通知的 API(如 xTaskNotifyWait)上,会被立即唤醒。
pxHigherPriorityTaskWoken 是关键参数:若被唤醒的任务优先级高于当前运行的任务(中断返回后即将运行的任务),需通过 portYIELD_FROM_ISR 触发调度,确保高优先级任务优先执行。
三、vTaskNotifyGiveIndexedFromISR:中断中向指定索引发送通知
vTaskNotifyGiveIndexedFromISR 是 ISR 专用的多索引版本,支持向目标任务的指定通知索引(0 ~ configTASK_NOTIFICATION_ARRAY_ENTRIES-1)发送 “递增型” 通知,适合在 ISR 中区分多个独立事件。
1. 函数原型
void vTaskNotifyGiveIndexedFromISR( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, BaseType_t *pxHigherPriorityTaskWoken);
-
参数:
xTaskToNotify:目标任务句柄(同前)。uxIndex:指定的通知索引(需 ≤ 配置的最大索引值,默认配置为 1,即仅支持索引 0)。pxHigherPriorityTaskWoken:同前,标记是否需要任务切换。
-
返回值:
void。
2. 功能说明
调用该函数时,目标任务的指定索引(uxIndex)通知值会自动 +1。若目标任务正阻塞在等待该索引通知的 API(如 xTaskNotifyWaitIndexed)上,会被立即唤醒。
通过指定不同索引,可在一个 ISR 或多个 ISR 中向同一任务发送不同类型的通知,实现多事件区分(如按键、定时器、串口事件用不同索引)。
四、核心区别与适用场景
| 特性 | vTaskNotifyGiveFromISR |
vTaskNotifyGiveIndexedFromISR |
|---|---|---|
| 通知索引 | 固定使用索引 0 | 可指定索引(0 ~ 配置最大值 - 1) |
| 适用场景 | 单一事件通知(ISR 仅需向任务发送一种事件) | 多事件通知(ISR 需向任务发送多种独立事件,用索引区分) |
| 灵活性 | 简单,无需指定索引 | 灵活,支持按索引隔离事件 |
| 与任务级 API 对应关系 | 对应 xTaskNotifyGive |
对应 xTaskNotifyGiveIndexed |
五、使用示例
1. vTaskNotifyGiveFromISR:单一事件(按键中断通知)
// 按键处理任务句柄
TaskHandle_t key_task_handle;
// 按键中断服务程序(ISR)
void EXTI0_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 向按键任务的索引0发送通知(通知值+1)
vTaskNotifyGiveFromISR(key_task_handle, &xHigherPriorityTaskWoken);
// 若有更高优先级任务被唤醒,触发任务切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
// 清除中断标志
EXTI_ClearITPendingBit(EXTI_Line0);
}
// 按键处理任务(等待索引0通知)
void key_task(void *param) {
while(1) {
// 等待索引0的通知(通知值≥1时唤醒,获取后清零)
xTaskNotifyWait(0, ULONG_MAX, NULL, portMAX_DELAY);
// 处理按键事件(如点灯)
GPIO_ToggleBits(GPIOA, GPIO_Pin_5);
}
}
2. vTaskNotifyGiveIndexedFromISR:多事件(区分按键、定时器中断)
// 事件处理任务句柄
TaskHandle_t event_task_handle;
// 配置支持3个通知索引(需在FreeRTOSConfig.h中定义:#define configTASK_NOTIFICATION_ARRAY_ENTRIES 3)
// 1. 按键中断(索引0)
void EXTI0_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveIndexedFromISR(event_task_handle, 0, &xHigherPriorityTaskWoken); // 索引0:按键
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
EXTI_ClearITPendingBit(EXTI_Line0);
}
// 2. 定时器中断(索引1)
void TIM3_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveIndexedFromISR(event_task_handle, 1, &xHigherPriorityTaskWoken); // 索引1:定时器
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
// 事件处理任务(区分索引0和1的通知)
void event_task(void *param) {
uint32_t notify_value;
while(1) {
// 等待索引0或1的通知(获取后清零对应位)
if (xTaskNotifyWaitIndexed(0, 0, ULONG_MAX, ¬ify_value, portMAX_DELAY)) {
// 处理索引0(按键)
if (notify_value & (1 << 0)) {
USART1_SendString("按键事件触发\r\n");
}
// 处理索引1(定时器)
if (notify_value & (1 << 1)) {
USART1_SendString("定时器事件触发\r\n");
}
}
}
}
六、关键注意事项
- 中断优先级:ISR 的优先级必须 ≤
configMAX_SYSCALL_INTERRUPT_PRIORITY(FreeRTOS 配置项),否则调用任务通知 API 会导致系统异常。 - 快速执行:ISR 中应仅执行必要操作(如发送通知),避免复杂逻辑,确保中断响应时间。
- 任务切换触发:若
pxHigherPriorityTaskWoken被设为pdTRUE,必须在 ISR 末尾调用portYIELD_FROM_ISR(xHigherPriorityTaskWoken),否则高优先级任务可能无法及时运行。 - 索引配置:使用多索引(
vTaskNotifyGiveIndexedFromISR)时,需在FreeRTOSConfig.h中定义configTASK_NOTIFICATION_ARRAY_ENTRIES为所需的索引数量(默认 1,最大 32)。
总结
vTaskNotifyGiveFromISR 和 vTaskNotifyGiveIndexedFromISR 是中断环境下高效发送任务通知的核心 API:
- 前者适用于单一事件场景,仅操作默认索引(0);
- 后者适用于多事件场景,通过指定索引区分不同事件。
两者均通过 “递增通知值” 实现轻量级事件传递,比中断中使用队列 / 信号量更高效,是嵌入式实时系统中中断与任务通信的优选方案。
ulTaskNotifyTake与
ulTaskNotifyTakeIndexed
ulTaskNotifyTake 和 ulTaskNotifyTakeIndexed 是 FreeRTOS 中任务级的任务通知接收 API,用于从目标任务的通知值中 “获取” 通知(通常与 xTaskNotifyGive 等 “递增型” 发送 API 配合使用)。它们的核心功能是读取任务的通知值,并根据配置清零或递减通知值,适用于 “计数型” 事件场景(如统计事件发生次数)。两者的区别在于是否支持多通知索引。
一、核心功能与机制
任务通知的 “递增型” 发送 API(如 xTaskNotifyGive)会使目标任务的通知值加 1;而 ulTaskNotifyTake 和 ulTaskNotifyTakeIndexed 则用于 “获取” 这些通知:
- 若通知值 > 0,函数会返回当前通知值,并根据配置将通知值清零或减 1;
- 若通知值 = 0,函数会使任务阻塞等待(最多等待指定时间),直到有新的通知到来(通知值 > 0)或超时。
二、ulTaskNotifyTake:默认索引的通知获取
ulTaskNotifyTake 用于获取当前任务(调用者)默认通知索引(索引 0) 的通知值,是针对单一索引的简化 API。
1. 函数原型
uint32_t ulTaskNotifyTake( BaseType_t xClearOnExit, TickType_t xTicksToWait );
-
参数:
xClearOnExit:通知值的处理方式(pdTRUE或pdFALSE):pdTRUE:获取通知后,将通知值清零(适合 “一次性处理所有累计通知” 场景);pdFALSE:获取通知后,将通知值减 1(适合 “逐个处理通知” 场景,类似计数信号量的xSemaphoreTake)。
xTicksToWait:通知值为 0 时的阻塞等待时间(单位:tick,portMAX_DELAY表示无限等待)。
-
返回值:获取到的通知值(>0 表示成功获取;=0 表示超时)。
2. 工作流程
- 读取当前任务索引 0 的通知值;
- 若通知值 > 0:
- 若
xClearOnExit = pdTRUE:返回当前通知值,然后将通知值设为 0; - 若
xClearOnExit = pdFALSE:返回当前通知值,然后将通知值减 1;
- 若
- 若通知值 = 0:任务进入阻塞状态,等待
xTicksToWait时间,期间若有新通知(通知值被递增),则重复步骤 2;若超时,返回 0。
三、ulTaskNotifyTakeIndexed:指定索引的通知获取
ulTaskNotifyTakeIndexed 用于获取当前任务(调用者)指定通知索引的通知值,支持多索引场景(需配置 configTASK_NOTIFICATION_ARRAY_ENTRIES 启用多索引),适合一个任务处理多种独立事件。
1. 函数原型
uint32_t ulTaskNotifyTakeIndexed( UBaseType_t uxIndexToWaitOn, BaseType_t xClearOnExit, TickType_t xTicksToWait );
-
参数:
uxIndex:指定的通知索引(0 ≤ uxIndex <configTASK_NOTIFICATION_ARRAY_ENTRIES,默认配置为 1,即仅支持索引 0);xClearOnExit和xTicksToWait:同ulTaskNotifyTake。
-
返回值:指定索引的通知值(>0 表示成功;=0 表示超时)。
2. 工作流程
与 ulTaskNotifyTake 完全一致,仅操作对象从 “索引 0” 变为 “指定索引 uxIndex”。
四、核心区别与适用场景
| 特性 | ulTaskNotifyTake |
ulTaskNotifyTakeIndexed |
|---|---|---|
| 操作的索引 | 固定索引 0 | 可指定索引(0 ~ 配置最大值 - 1) |
| 适用场景 | 单一事件计数(如仅统计按键次数) | 多事件计数(如同时统计按键、定时器、串口事件次数) |
| 灵活性 | 简单,无需指定索引 | 灵活,支持按索引隔离不同事件 |
五、使用示例
1. ulTaskNotifyTake:单一事件计数(按键按下次数统计)
// 按键中断:向任务发送通知(索引0,通知值+1)
void EXTI0_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(xTaskGetCurrentTaskHandle(), &xHigherPriorityTaskWoken); // 通知当前任务
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
EXTI_ClearITPendingBit(EXTI_Line0);
}
// 按键处理任务:统计并处理按键次数
void key_task(void *param) {
uint32_t press_count;
while(1) {
// 等待通知,获取后清零(一次性处理所有累计按键)
press_count = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// 输出按键次数
char buf[30];
sprintf(buf, "按键被按下 %u 次\r\n", press_count);
USART1_SendString((uint8_t*)buf);
}
}
- 若按键被连续按下 3 次,
press_count会返回 3,之后通知值被清零。
2. ulTaskNotifyTakeIndexed:多事件计数(区分按键和定时器事件)
// 配置支持2个索引(FreeRTOSConfig.h):#define configTASK_NOTIFICATION_ARRAY_ENTRIES 2
// 1. 按键中断:向索引0发送通知(+1)
void EXTI0_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveIndexedFromISR(xTaskGetCurrentTaskHandle(), 0, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
EXTI_ClearITPendingBit(EXTI_Line0);
}
// 2. 定时器中断:向索引1发送通知(+1)
void TIM3_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveIndexedFromISR(xTaskGetCurrentTaskHandle(), 1, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
// 事件处理任务:分别统计按键和定时器事件次数
void event_task(void *param) {
uint32_t key_count, timer_count;
while(1) {
// 获取索引0的通知(按键,获取后清零)
key_count = ulTaskNotifyTakeIndexed(0, pdTRUE, portMAX_DELAY);
// 获取索引1的通知(定时器,获取后清零)
timer_count = ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);
// 输出统计结果
char buf[50];
sprintf(buf, "按键:%u次,定时器:%u次\r\n", key_count, timer_count);
USART1_SendString((uint8_t*)buf);
}
}
- 按键和定时器事件的通知值分别存储在索引 0 和 1,互不干扰,实现多事件独立计数。
六、关键注意事项
- 与发送 API 的配合:
ulTaskNotifyTake系列通常与 “递增型” 发送 API(xTaskNotifyGive、vTaskNotifyGiveFromISR等)配合,前者负责 “消费” 通知值,后者负责 “生产” 通知值。 xClearOnExit的选择:- 需一次性处理所有累计通知(如批量处理按键):用
pdTRUE(清零); - 需逐个处理通知(如每次通知处理一个事件):用
pdFALSE(减 1),类似计数信号量。
- 需一次性处理所有累计通知(如批量处理按键):用
- 阻塞行为:若通知值为 0,函数会使任务阻塞,释放 CPU 给其他任务,适合低功耗场景。
- 多索引配置:使用
ulTaskNotifyTakeIndexed时,需在FreeRTOSConfig.h中定义configTASK_NOTIFICATION_ARRAY_ENTRIES为所需索引数量(默认 1,最大 32)。
总结
ulTaskNotifyTake 和 ulTaskNotifyTakeIndexed 是任务通知机制中用于 “获取并消费” 通知值的核心 API:
- 前者适用于单一事件的计数场景,仅操作默认索引 0;
- 后者适用于多事件的独立计数场景,通过指定索引区分事件。
两者通过灵活的通知值处理方式(清零或减 1),可替代计数信号量实现高效的事件统计与处理,且比信号量更轻量(无需额外内存分配)。
xTaskNotify 和 xTaskNotifyIndexed
xTaskNotify 和 xTaskNotifyIndexed 是 FreeRTOS 中功能最灵活的任务级任务通知 API,支持通过多种方式修改目标任务的通知值(不仅限于 “递增”),可替代信号量、事件标志组甚至轻量级队列的功能。两者的核心区别在于是否支持多通知索引(用于区分不同事件)。
一、核心特性:灵活的通知值修改方式
与 xTaskNotifyGive(仅支持 “通知值 + 1”)不同,xTaskNotify 和 xTaskNotifyIndexed 通过 eAction 参数支持5 种通知值修改方式,覆盖从 “事件标志” 到 “数据传递” 的多种场景。
二、xTaskNotify:默认索引的灵活通知
xTaskNotify 用于向目标任务的默认通知索引(索引 0) 发送通知,通过 eAction 参数指定通知值的修改逻辑,功能覆盖从简单事件到数据传递的多种需求。
1. 函数原型
BaseType_t xTaskNotify(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction
);
-
参数:
xTaskToNotify:目标任务句柄(NULL表示当前任务);ulValue:操作通知值的 “输入数据”(具体含义由eAction决定);eAction:通知值的修改方式(核心参数,共 5 种,见下表)。
-
返回值:
pdPASS:通知发送成功;errQUEUE_FULL:仅当eAction = eSetValueWithoutOverwrite且通知值已存在时返回(表示未覆盖原有值)。
2. eAction 详解(通知值修改方式)
eAction 选项 |
功能说明 | 适用场景 | |
|---|---|---|---|
eNoAction |
不修改通知值,仅唤醒阻塞在等待通知的任务(若有)。 | 单纯的 “触发” 事件(无需传递数据,类似二进制信号量)。 | |
eSetBits |
用 ulValue 与通知值做 “按位或”(` 通知值 |
= ulValue`)。 | 事件标志组(多个事件用不同位表示,如 bit0 表示按键,bit1 表示定时器)。 |
eIncrement |
通知值 +1(与 xTaskNotifyGive 功能完全一致)。 |
计数型事件(如统计触发次数,替代计数信号量)。 | |
eOverwrite |
用 ulValue 直接覆盖通知值(通知值 = ulValue)。 |
传递 “最新数据”(旧数据可丢弃,如传感器实时值)。 | |
eSetValueWithoutOverwrite |
若通知值为 0,则用 ulValue 覆盖;否则不修改(返回 errQUEUE_FULL)。 |
传递 “一次性数据”(确保数据不被覆盖,如命令消息)。 |
三、xTaskNotifyIndexed:指定索引的灵活通知
xTaskNotifyIndexed 是 xTaskNotify 的多索引版本,支持向目标任务的指定通知索引(0 ~ configTASK_NOTIFICATION_ARRAY_ENTRIES-1)发送通知,其他功能与 xTaskNotify 完全一致。
1. 函数原型
BaseType_t xTaskNotifyIndexed(
TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction
);
- 新增参数:
uxIndex:指定的通知索引(需 ≤ 配置的最大索引值,默认 1,即仅支持索引 0)。 - 其他参数和返回值:与
xTaskNotify一致。
四、核心区别与适用场景
| 特性 | xTaskNotify |
xTaskNotifyIndexed |
|---|---|---|
| 操作的索引 | 固定索引 0 | 可指定索引(0 ~ 配置最大值 - 1) |
| 功能灵活性 | 支持 5 种通知方式,覆盖多种通信场景 | 与 xTaskNotify 相同,但支持多索引隔离事件 |
| 典型场景 | 单一事件的复杂通信(如事件标志、数据传递) | 多事件的独立通信(如按键、定时器、串口用不同索引传递数据) |
五、使用示例
1. xTaskNotify + eSetBits:实现事件标志组
用不同位表示不同事件(bit0 = 按键,bit1 = 定时器),任务等待任意事件触发:
// 目标任务句柄
TaskHandle_t flag_task_handle;
// 1. 按键中断:设置bit0
void EXTI0_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 向flag_task的索引0发送通知,设置bit0(ulValue=0x01)
xTaskNotifyFromISR(flag_task_handle, 0x01, eSetBits, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
EXTI_ClearITPendingBit(EXTI_Line0);
}
// 2. 定时器中断:设置bit1
void TIM3_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 向flag_task的索引0发送通知,设置bit1(ulValue=0x02)
xTaskNotifyFromISR(flag_task_handle, 0x02, eSetBits, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
// 事件处理任务:等待bit0或bit1被设置
void flag_task(void *param) {
uint32_t notify_value;
while(1) {
// 等待索引0的通知,获取后清除已处理的位(bit0和bit1)
xTaskNotifyWait(0, 0x03, ¬ify_value, portMAX_DELAY);
// 检查bit0(按键事件)
if (notify_value & 0x01) {
USART1_SendString("按键事件触发\r\n");
}
// 检查bit1(定时器事件)
if (notify_value & 0x02) {
USART1_SendString("定时器事件触发\r\n");
}
}
}
2. xTaskNotifyIndexed + eOverwrite:多索引传递实时数据
用不同索引传递不同类型的实时数据(索引 0 = 温度,索引 1 = 湿度):
// 配置支持2个索引(FreeRTOSConfig.h):#define configTASK_NOTIFICATION_ARRAY_ENTRIES 2
// 温度采集任务:向索引0写入最新温度(覆盖旧值)
void temp_task(void *param) {
uint32_t temp = 0;
while(1) {
temp++; // 模拟温度递增
// 向数据处理任务的索引0发送温度(覆盖旧值)
xTaskNotifyIndexed(data_task_handle, 0, temp, eOverwrite);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 湿度采集任务:向索引1写入最新湿度(覆盖旧值)
void humi_task(void *param) {
uint32_t humi = 50;
while(1) {
humi++; // 模拟湿度递增
// 向数据处理任务的索引1发送湿度(覆盖旧值)
xTaskNotifyIndexed(data_task_handle, 1, humi, eOverwrite);
vTaskDelay(pdMS_TO_TICKS(200));
}
}
// 数据处理任务:读取索引0和1的实时数据
void data_task(void *param) {
uint32_t temp, humi;
while(1) {
// 读取索引0的温度(不清除,下次可再读)
xTaskNotifyWaitIndexed(0, 0, 0, &temp, portMAX_DELAY);
// 读取索引1的湿度(不清除,下次可再读)
xTaskNotifyWaitIndexed(1, 0, 0, &humi, portMAX_DELAY);
// 输出数据
char buf[50];
sprintf(buf, "温度:%u℃,湿度:%u%%\r\n", temp, humi);
USART1_SendString((uint8_t*)buf);
}
}
六、关键注意事项
- 与接收 API 配合:需使用
xTaskNotifyWait(索引 0)或xTaskNotifyWaitIndexed(指定索引)接收通知,这两个 API 可配置 “清除通知值的方式”(如清除特定位、清零等)。 - 中断中使用:若需在 ISR 中发送通知,需使用对应的中断版本
xTaskNotifyFromISR或xTaskNotifyIndexedFromISR。 - 替代传统组件:
eSetBits可替代事件标志组(xEventGroupSetBits);eIncrement可替代计数信号量(xSemaphoreGive);eOverwrite可替代轻量级队列(传递 32 位以内的数据)。
- 多索引配置:使用
xTaskNotifyIndexed时,需在FreeRTOSConfig.h中定义configTASK_NOTIFICATION_ARRAY_ENTRIES为所需索引数量(默认 1,最大 32)。
总结
xTaskNotify 和 xTaskNotifyIndexed 是 FreeRTOS 任务通知机制中最灵活的 API,通过 eAction 参数支持 5 种通知值修改方式,可覆盖从事件标志、计数到数据传递的多种场景:
xTaskNotify适用于单一索引的复杂通信;xTaskNotifyIndexed适用于多索引的独立事件 / 数据传递。
它们比传统的信号量、队列更轻量(无需额外内存分配),是嵌入式系统中任务间通信的优选方案。
xTaskNotifyAndQuery 和 xTaskNotifyAndQueryIndexed
xTaskNotifyAndQuery 和 xTaskNotifyAndQueryIndexed 是 FreeRTOS 中任务级任务通知 API,在发送通知(修改目标任务的通知值)的同时,能查询并返回修改前的通知值。这一特性使其在需要 “获取通知发送前的状态” 场景中非常实用(如统计累计通知次数、检查未处理的事件标志等)。两者的核心区别在于是否支持多通知索引。
一、核心功能:发送通知 + 查询旧值
与 xTaskNotify(仅修改通知值)相比,xTaskNotifyAndQuery 系列的独特之处在于:
- 不仅按指定方式修改目标任务的通知值(支持
xTaskNotify的所有eAction操作); - 同时将修改前的通知值存储到指定变量中,供调用者获取历史状态。
二、xTaskNotifyAndQuery:默认索引的 “通知 + 查询”
xTaskNotifyAndQuery 用于向目标任务的默认通知索引(索引 0) 发送通知,并返回修改前的通知值,适用于单一索引场景。
1. 函数原型
BaseType_t xTaskNotifyAndQuery(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotificationValue
);
-
参数:
xTaskToNotify:目标任务句柄(NULL表示当前任务);ulValue:用于修改通知值的输入数据(含义由eAction决定);eAction:通知值的修改方式(与xTaskNotify完全一致,共 5 种,见下表);pulPreviousNotificationValue:输出参数,用于存储修改前的通知值(必须为有效指针)。
-
返回值:
pdPASS:通知发送成功(无论是否修改了通知值,旧值均会被返回);errQUEUE_FULL:仅当eAction = eSetValueWithoutOverwrite且通知值非 0 时返回(表示未覆盖旧值,但旧值仍会被返回)。
2. eAction 回顾(与 xTaskNotify 一致)
eAction 选项 |
功能说明(修改通知值的方式) | |
|---|---|---|
eNoAction |
不修改通知值,仅唤醒阻塞的任务,pulPreviousNotificationValue 返回当前值。 |
|
eSetBits |
通知值 | = ulValue(按位或)。 |
eIncrement |
通知值 += 1(递增)。 | |
eOverwrite |
通知值 = ulValue(覆盖)。 | |
eSetValueWithoutOverwrite |
若通知值为 0,则设为 ulValue;否则不修改。 |
三、xTaskNotifyAndQueryIndexed:指定索引的 “通知 + 查询”
xTaskNotifyAndQueryIndexed 是多索引版本,用于向目标任务的指定通知索引(0 ~ configTASK_NOTIFICATION_ARRAY_ENTRIES-1)发送通知,并返回修改前的通知值,其他功能与 xTaskNotifyAndQuery 完全一致。
1. 函数原型
BaseType_t xTaskNotifyAndQueryIndexed(
TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotificationValue
);
- 新增参数:uxIndexToNotify:指定的通知索引(需 ≤ 配置的最大索引值,默认 1,即仅支持索引 0)。
- 其他参数和返回值:与
xTaskNotifyAndQuery一致。
四、核心区别与适用场景
| 特性 | xTaskNotifyAndQuery |
xTaskNotifyAndQueryIndexed |
|---|---|---|
| 操作的索引 | 固定索引 0 | 可指定索引(0 ~ 配置最大值 - 1) |
| 核心功能 | 发送通知 + 返回索引 0 的旧值 | 发送通知 + 返回指定索引的旧值 |
| 适用场景 | 需获取单一事件通知的历史状态 | 需获取多事件通知的独立历史状态 |
五、使用场景:为何需要 “查询旧值”?
xTaskNotifyAndQuery 系列的核心价值在于获取通知修改前的状态,典型场景包括:
- 统计累计通知次数:已知旧值,结合新操作(如递增)可计算总次数;
- 处理未完成的事件:若旧值包含未处理的标志位(如
eSetBits操作),可在发送新通知前优先处理; - 避免数据覆盖:使用
eSetValueWithoutOverwrite时,通过旧值判断是否有未处理的数据,避免丢失。
六、使用示例
1. xTaskNotifyAndQuery + eIncrement:统计累计通知次数
// 目标任务句柄
TaskHandle_t count_task_handle;
// 通知发送任务:每次发送通知时,获取旧值并计算累计次数
void sender_task(void *param) {
uint32_t ulPrevCount;
while(1) {
// 向count_task的索引0发送通知(递增),同时获取旧值
xTaskNotifyAndQuery(count_task_handle, 0, eIncrement, &ulPrevCount);
// 输出累计次数(旧值+1,因为本次递增了1)
char buf[30];
sprintf(buf, "累计通知次数:%u\r\n", ulPrevCount + 1);
USART1_SendString((uint8_t*)buf);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 接收任务:等待通知并处理
void count_task(void *param) {
while(1) {
// 等待通知(获取后清零)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
USART1_SendString("收到一次通知\r\n");
}
}
- 发送任务通过
ulPrevCount获取递增前的通知值,加 1 后即为累计次数(无需在接收任务中维护计数)。
2. xTaskNotifyAndQueryIndexed + eSetBits:处理未完成的事件标志
用索引 0 存储按键事件标志,发送新标志前先检查旧标志是否已处理:
// 配置支持1个索引(默认)
#define KEY_BIT 0x01 // 按键事件标志位
// 按键中断服务程序(任务级模拟)
void key_simulator_task(void *param) {
uint32_t ulPrevBits;
while(1) {
// 模拟按键触发:向索引0设置KEY_BIT,同时获取旧标志
xTaskNotifyAndQueryIndexed(flag_task_handle, 0, KEY_BIT, eSetBits, &ulPrevBits);
// 检查旧标志:若KEY_BIT仍为1,说明前一次事件未处理
if (ulPrevBits & KEY_BIT) {
USART1_SendString("警告:前一次按键事件未处理!\r\n");
}
vTaskDelay(pdMS_TO_TICKS(500)); // 模拟按键间隔
}
}
// 事件处理任务:处理按键标志
void flag_task(void *param) {
uint32_t ulNotifyValue;
while(1) {
// 等待KEY_BIT被设置,获取后清除该位
xTaskNotifyWaitIndexed(0, 0, KEY_BIT, &ulNotifyValue, portMAX_DELAY);
USART1_SendString("处理按键事件\r\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟处理耗时(可能导致事件堆积)
}
}
- 发送方通过
ulPrevBits检测到未处理的旧标志,可及时报警(避免事件丢失)。
七、关键注意事项
pulPreviousNotificationValue必须有效:该参数不能为NULL,否则会导致内存访问错误(函数会强制写入旧值)。- 与接收 API 的配合:接收通知仍需使用
xTaskNotifyWait或xTaskNotifyWaitIndexed,xTaskNotifyAndQuery仅负责发送和查询旧值。 - 中断中使用:若需在 ISR 中实现 “通知 + 查询旧值”,需使用中断版本
xTaskNotifyAndQueryFromISR或xTaskNotifyAndQueryIndexedFromISR。 - 多索引配置:使用
xTaskNotifyAndQueryIndexed时,需在FreeRTOSConfig.h中定义configTASK_NOTIFICATION_ARRAY_ENTRIES为所需索引数量。
总结
xTaskNotifyAndQuery 和 xTaskNotifyAndQueryIndexed 是功能增强的任务通知 API,核心价值在于发送通知的同时获取修改前的通知值,适用于需要跟踪通知历史状态的场景(如统计累计次数、检查未处理事件):
xTaskNotifyAndQuery操作默认索引 0,适合单一事件;xTaskNotifyAndQueryIndexed支持指定索引,适合多事件独立跟踪。
它们继承了任务通知的轻量特性(无需额外内存),同时提供了更灵活的状态查询能力,是复杂任务通信场景的优选。
xTaskNotifyAndQueryFromISR 和 xTaskNotifyAndQueryIndexedFromISR
xTaskNotifyAndQueryFromISR 和 xTaskNotifyAndQueryIndexedFromISR 是 FreeRTOS 中中断服务程序(ISR)专用的任务通知 API,用于在中断环境下向目标任务发送通知(修改通知值),同时查询并返回修改前的通知值。它们是任务级 API xTaskNotifyAndQuery 和 xTaskNotifyAndQueryIndexed 的中断适配版本,核心特点是支持中断安全调用,并能在发送通知时获取历史状态。
一、核心功能:中断中 “发送通知 + 查询旧值”
在中断服务程序(ISR)中,往往需要快速处理事件并将结果传递给任务,同时可能需要了解 “发送前的通知状态”(如累计中断次数、未处理的事件标志等)。这两个 API 正是为满足此需求设计:
- 按指定方式修改目标任务的通知值(支持
eAction所有操作); - 同步返回修改前的通知值(存储到指定变量);
- 支持通过
pxHigherPriorityTaskWoken标记是否需要触发任务切换(确保高优先级任务及时运行)。
二、xTaskNotifyAndQueryFromISR:中断中默认索引的 “通知 + 查询”
xTaskNotifyAndQueryFromISR 用于在 ISR 中向目标任务的默认通知索引(索引 0) 发送通知,并返回修改前的通知值,适用于单一索引场景。
1. 函数原型
BaseType_t xTaskNotifyAndQueryFromISR(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotificationValue,
BaseType_t *pxHigherPriorityTaskWoken
);
-
参数:
xTaskToNotify:目标任务句柄(NULL表示当前任务,但 ISR 中通常指定具体任务);ulValue:用于修改通知值的输入数据(含义由eAction决定);eAction:通知值的修改方式(与任务级 API 一致,共 5 种,见下表);pulPreviousNotificationValue:输出参数,必须为有效指针,用于存储修改前的通知值;pxHigherPriorityTaskWoken:输出参数,标记 “是否有更高优先级任务被唤醒”(若为pdTRUE,需在 ISR 末尾触发调度)。
-
返回值:
pdPASS:通知发送成功(无论是否修改值,旧值均会返回);errQUEUE_FULL:仅当eAction = eSetValueWithoutOverwrite且通知值非 0 时返回(未覆盖旧值,但旧值仍会返回)。
2. eAction 回顾(与任务级一致)
eAction 选项 |
功能说明(修改通知值的方式) | |
|---|---|---|
eNoAction |
不修改通知值,仅唤醒阻塞任务,pulPreviousNotificationValue 返回当前值。 |
|
eSetBits |
通知值 | = ulValue(按位或)。 |
eIncrement |
通知值 += 1(递增)。 | |
eOverwrite |
通知值 = ulValue(覆盖)。 | |
eSetValueWithoutOverwrite |
若通知值为 0,则设为 ulValue;否则不修改。 |
三、xTaskNotifyAndQueryIndexedFromISR:中断中指定索引的 “通知 + 查询”
xTaskNotifyAndQueryIndexedFromISR 是 ISR 专用的多索引版本,用于向目标任务的指定通知索引(0 ~ configTASK_NOTIFICATION_ARRAY_ENTRIES-1)发送通知,并返回修改前的通知值,其他功能与前者一致。
1. 函数原型
BaseType_t xTaskNotifyAndQueryIndexedFromISR(
TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotificationValue,
BaseType_t *pxHigherPriorityTaskWoken
);
- 新增参数:
uxIndex:指定的通知索引(需 ≤ 配置的最大索引值,默认 1,即仅支持索引 0)。 - 其他参数和返回值:与
xTaskNotifyAndQueryFromISR一致。
四、核心区别与适用场景
| 特性 | xTaskNotifyAndQueryFromISR |
xTaskNotifyAndQueryIndexedFromISR |
|---|---|---|
| 操作的索引 | 固定索引 0 | 可指定索引(0 ~ 配置最大值 - 1) |
| 核心功能 | 中断中发送通知 + 返回索引 0 的旧值 | 中断中发送通知 + 返回指定索引的旧值 |
| 适用场景 | 单一中断事件的状态跟踪(如定时器次数) | 多中断事件的独立状态跟踪(如按键、ADC、串口) |
五、使用场景:为何在 ISR 中需要 “查询旧值”?
中断中查询旧值的核心需求是实时跟踪事件状态,典型场景包括:
- 统计中断触发次数:通过旧值(递增前的计数)计算总次数(无需在任务中维护计数);
- 检测未处理的事件:若旧值包含未清除的标志位(如
eSetBits),说明任务未及时处理,可记录警告; - 避免数据覆盖:使用
eSetValueWithoutOverwrite时,通过旧值判断是否有未处理数据,防止丢失。
六、使用示例
1. xTaskNotifyAndQueryFromISR + eIncrement:统计定时器中断次数
// 目标任务句柄
TaskHandle_t timer_task_handle;
// 定时器中断服务程序:每1ms触发一次,统计总次数
void TIM3_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint32_t ulPrevCount; // 存储递增前的通知值
// 向timer_task的索引0发送通知(递增),同时获取旧值
xTaskNotifyAndQueryFromISR(
timer_task_handle,
0, // eIncrement无需ulValue
eIncrement,
&ulPrevCount,
&xHigherPriorityTaskWoken
);
// 总次数 = 旧值 + 1(本次递增)
// (可选:在ISR中轻量处理,如超过阈值时置位警告标志)
if (ulPrevCount + 1 > 1000) {
USART1_SendStringFromISR((uint8_t*)"已触发1000次中断\r\n");
}
// 若需切换任务,触发调度
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
// 定时器处理任务:等待通知并输出信息
void timer_task(void *param) {
while(1) {
// 等待通知(获取后清零)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
USART1_SendString((uint8_t*)"处理一次定时器事件\r\n");
}
}
- ISR 中通过
ulPrevCount实时统计总中断次数,无需任务参与计数。
2. xTaskNotifyAndQueryIndexedFromISR + eSetBits:多中断源标志跟踪
用索引 0 和 1 分别跟踪按键和 ADC 中断的未处理标志:
// 配置支持2个索引(FreeRTOSConfig.h):#define configTASK_NOTIFICATION_ARRAY_ENTRIES 2
#define KEY_BIT 0x01 // 索引0:按键标志
#define ADC_BIT 0x01 // 索引1:ADC标志(独立于按键)
// 1. 按键中断:向索引0设置KEY_BIT,查询旧标志
void EXTI0_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint32_t ulPrevBits;
xTaskNotifyAndQueryIndexedFromISR(
event_task_handle,
0, // 索引0:按键
KEY_BIT,
eSetBits,
&ulPrevBits,
&xHigherPriorityTaskWoken
);
// 若旧标志中KEY_BIT未清除,说明任务未处理前一次按键
if (ulPrevBits & KEY_BIT) {
USART1_SendStringFromISR((uint8_t*)"按键事件堆积!\r\n");
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
EXTI_ClearITPendingBit(EXTI_Line0);
}
// 2. ADC中断:向索引1设置ADC_BIT,查询旧标志
void ADC1_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint32_t ulPrevBits;
xTaskNotifyAndQueryIndexedFromISR(
event_task_handle,
1, // 索引1:ADC
ADC_BIT,
eSetBits,
&ulPrevBits,
&xHigherPriorityTaskWoken
);
// 若旧标志中ADC_BIT未清除,说明任务未处理前一次ADC数据
if (ulPrevBits & ADC_BIT) {
USART1_SendStringFromISR((uint8_t*)"ADC事件堆积!\r\n");
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
}
// 事件处理任务:处理两个索引的标志
void event_task(void *param) {
uint32_t ulKeyBits, ulAdcBits;
while(1) {
// 处理索引0(按键)
xTaskNotifyWaitIndexed(0, 0, KEY_BIT, &ulKeyBits, portMAX_DELAY);
// 处理索引1(ADC)
xTaskNotifyWaitIndexed(1, 0, ADC_BIT, &ulAdcBits, portMAX_DELAY);
}
}
- 两个中断源通过不同索引独立跟踪未处理事件,避免相互干扰。
七、关键注意事项
- 中断优先级限制:ISR 优先级必须 ≤
configMAX_SYSCALL_INTERRUPT_PRIORITY(FreeRTOS 配置项),否则调用 API 会导致系统异常。 pulPreviousNotificationValue必须有效:该参数不能为NULL,否则会导致内存访问错误(函数强制写入旧值)。- 任务切换触发:若
pxHigherPriorityTaskWoken被设为pdTRUE,必须在 ISR 末尾调用portYIELD_FROM_ISR(xHigherPriorityTaskWoken),确保高优先级任务及时运行。 - ISR 轻量化:中断中应仅执行必要操作(如发送通知、查询旧值),避免复杂逻辑(如字符串格式化),可通过队列将复杂处理交给任务。
- 多索引配置:使用
xTaskNotifyAndQueryIndexedFromISR时,需在FreeRTOSConfig.h中定义configTASK_NOTIFICATION_ARRAY_ENTRIES为所需索引数量(默认 1,最大 32)。
总结
xTaskNotifyAndQueryFromISR 和 xTaskNotifyAndQueryIndexedFromISR 是中断环境下功能强大的任务通知 API,核心价值在于发送通知的同时获取修改前的通知值,适用于需要实时跟踪中断事件状态的场景:
- 前者操作默认索引 0,适合单一中断事件;
- 后者支持指定索引,适合多中断事件的独立跟踪。
它们兼顾了中断处理的高效性(无额外内存分配)和状态跟踪的灵活性,是嵌入式实时系统中中断与任务通信的高级方案。
xTaskNotifyFromISR 和 xTaskNotifyIndexedFromISR
xTaskNotifyFromISR 和 xTaskNotifyIndexedFromISR 是 FreeRTOS 中中断服务程序(ISR)专用的核心任务通知 API,用于在中断环境下向目标任务发送灵活的通知(支持多种通知值修改方式),同时确保中断安全(不阻塞、快速执行)。它们是任务级 API xTaskNotify 和 xTaskNotifyIndexed 的中断适配版本,核心区别在于是否支持多通知索引(用于区分不同中断事件)。
一、核心定位:ISR 中的灵活通知
中断服务程序(ISR)的核心约束是 “不能阻塞、必须快速执行”,因此普通任务级 API(如 xTaskNotify)无法在 ISR 中直接使用。这两个 API 专为 ISR 设计:
- 无阻塞操作:直接修改目标任务的控制块(TCB)中的通知值,无需等待;
- 支持任务切换触发:通过
pxHigherPriorityTaskWoken标记是否唤醒高优先级任务,确保中断返回后高优先级任务及时执行; - 继承任务级通知的灵活性:支持 5 种通知值修改方式(如事件标志、数据覆盖、递增等),可替代 ISR 中的信号量、轻量级队列。
二、xTaskNotifyFromISR:ISR 中默认索引的灵活通知
xTaskNotifyFromISR 用于在 ISR 中向目标任务的默认通知索引(索引 0) 发送通知,支持 5 种通知值修改方式,适合单一中断事件场景(如仅处理定时器中断、单一按键中断)。
1. 函数原型
BaseType_t xTaskNotifyFromISR(
TaskHandle_t xTaskToNotify, // 目标任务句柄(NULL 表示当前任务,ISR 中通常指定具体任务)
uint32_t ulValue, // 通知值(含义由 eAction 决定)
eNotifyAction eAction, // 通知值修改方式(核心参数)
BaseType_t *pxHigherPriorityTaskWoken // 输出:是否唤醒高优先级任务
);
-
关键参数解析:
eAction:定义通知值的修改逻辑,是任务通知灵活性的核心,共 5 种选项(见下表);pxHigherPriorityTaskWoken:必须传入有效指针(不可为NULL)。若通知导致目标任务(或其他高优先级任务)从阻塞变为就绪,该变量会被设为pdTRUE,需在 ISR 末尾调用portYIELD_FROM_ISR触发任务切换,否则高优先级任务会延迟执行。
-
返回值:
pdPASS:通知发送成功(无论是否修改通知值);errQUEUE_FULL:仅当eAction = eSetValueWithoutOverwrite且通知值非 0 时返回(表示未覆盖旧值,避免数据丢失)。
2. eAction 详解(5 种通知值修改方式)
eAction 选项 |
功能说明 | 适用场景(ISR 中) | |
|---|---|---|---|
eNoAction |
不修改通知值,仅唤醒阻塞在等待该通知的任务(若有)。 | 单纯 “触发任务执行”(无需传递数据),如按键中断触发任务点灯。 | |
eSetBits |
用 ulValue 与通知值做 “按位或”(` 通知值 |
= ulValue`)。 | 中断事件标志(多个子事件用不同位表示),如 UART 中断中 “接收完成”(bit0)和 “发送完成”(bit1)。 |
eIncrement |
通知值 +1(与 vTaskNotifyGiveFromISR 功能完全一致)。 |
统计中断触发次数(如定时器中断计数),替代计数信号量。 | |
eOverwrite |
用 ulValue 直接覆盖通知值(通知值 = ulValue)。 |
传递中断中的实时数据(旧数据可丢弃),如 ADC 中断传递单次采样值。 | |
eSetValueWithoutOverwrite |
若通知值为 0,则用 ulValue 覆盖;否则不修改(返回 errQUEUE_FULL)。 |
传递 “不可丢失的一次性数据”(如中断中的命令帧),避免旧数据被新数据覆盖。 |
三、xTaskNotifyIndexedFromISR:ISR 中指定索引的灵活通知
xTaskNotifyIndexedFromISR 是 xTaskNotifyFromISR 的多索引版本,支持在 ISR 中向目标任务的指定通知索引(0 ~ configTASK_NOTIFICATION_ARRAY_ENTRIES-1)发送通知,其他功能与前者完全一致。
多索引的核心价值是隔离多个独立中断事件—— 例如,一个任务需要处理按键(索引 0)、定时器(索引 1)、ADC(索引 2)三个中断事件,通过不同索引区分,无需额外创建多个信号量或队列。
1. 函数原型
BaseType_t xTaskNotifyIndexedFromISR(
TaskHandle_t xTaskToNotify, // 目标任务句柄
UBaseType_t uxIndexToNotify, // 新增:指定通知索引(0 ≤ uxIndexToNotify< 配置值)
uint32_t ulValue, // 通知值
eNotifyAction eAction, // 通知值修改方式
BaseType_t *pxHigherPriorityTaskWoken // 任务切换标志
);
- 新增参数:uxIndexToNotify—— 指定操作的通知索引,需在
FreeRTOSConfig.h中通过configTASK_NOTIFICATION_ARRAY_ENTRIES配置最大索引数量(默认 1,即仅支持索引 0;最大可设为 32)。 - 其他参数 / 返回值:与
xTaskNotifyFromISR完全一致。
四、核心区别与适用场景
| 特性 | xTaskNotifyFromISR |
xTaskNotifyIndexedFromISR |
|---|---|---|
| 操作的索引 | 固定使用索引 0 | 可指定索引(0 ~ 配置最大值 - 1) |
| 灵活性 | 适合单一中断事件(如仅处理定时器中断) | 适合多中断事件(如按键、定时器、ADC 中断共用一个任务) |
| 事件隔离 | 无隔离,多个中断事件需通过 eSetBits 位区分 |
索引天然隔离事件,无需位操作,逻辑更清晰 |
| 配置依赖 | 无需额外配置(默认支持索引 0) | 需配置 configTASK_NOTIFICATION_ARRAY_ENTRIES 启用多索引 |
五、典型使用示例
1. xTaskNotifyFromISR + eOverwrite:ADC 中断传递实时采样值
ISR 中读取 ADC 采样值,通过通知覆盖旧值,任务端获取最新值:
// 目标任务句柄
TaskHandle_t adc_task_handle;
// ADC 中断服务程序(ISR)
void ADC1_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint16_t adc_value = ADC_GetConversionValue(ADC1); // 读取采样值(16位)
// 向 adc_task 的索引 0 发送通知,覆盖旧值(传递 16 位采样值,存入 32 位 ulValue)
xTaskNotifyFromISR(
adc_task_handle,
(uint32_t)adc_value, // 32 位通知值可容纳 16 位 ADC 数据
eOverwrite,
&xHigherPriorityTaskWoken
);
// 若唤醒高优先级任务,触发调度
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
ADC_ClearITPendingBit(ADC1, ADC_IT_EOC); // 清除中断标志
}
// ADC 处理任务(获取最新采样值)
void adc_task(void *param) {
uint32_t notify_value;
while(1) {
// 等待索引 0 的通知,获取后不清除(下次可再读最新值)
xTaskNotifyWait(0, 0, ¬ify_value, portMAX_DELAY);
uint16_t adc_result = (uint16_t)notify_value; // 还原 16 位采样值
// 输出结果
char buf[30];
sprintf(buf, "ADC 采样值:%d\r\n", adc_result);
USART1_SendString((uint8_t*)buf);
}
}
2. xTaskNotifyIndexedFromISR + eSetBits:多中断事件隔离
用索引 0 处理按键中断、索引 1 处理定时器中断,任务端通过不同索引获取事件:
// 配置支持 2 个索引(FreeRTOSConfig.h)
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 2
// 目标任务句柄
TaskHandle_t event_task_handle;
// 1. 按键中断(索引 0,设置 bit0)
void EXTI0_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyIndexedFromISR(
event_task_handle,
0, // 索引 0:按键事件
0x01, // bit0 表示按键按下
eSetBits,
&xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
EXTI_ClearITPendingBit(EXTI_Line0);
}
// 2. 定时器中断(索引 1,设置 bit0)
void TIM3_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyIndexedFromISR(
event_task_handle,
1, // 索引 1:定时器事件
0x01, // bit0 表示定时器超时
eSetBits,
&xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
// 事件处理任务(区分索引 0 和 1 的事件)
void event_task(void *param) {
uint32_t key_notify, timer_notify;
while(1) {
// 处理索引 0(按键事件):等待 bit0 被设置,获取后清除该位
xTaskNotifyWaitIndexed(0, 0, 0x01, &key_notify, portMAX_DELAY);
USART1_SendString((uint8_t*)"按键事件触发\r\n");
// 处理索引 1(定时器事件):等待 bit0 被设置,获取后清除该位
xTaskNotifyWaitIndexed(1, 0, 0x01, &timer_notify, portMAX_DELAY);
USART1_SendString((uint8_t*)"定时器事件触发\r\n");
}
}
六、关键注意事项
-
中断优先级限制:ISR 的优先级必须 ≤
configMAX_SYSCALL_INTERRUPT_PRIORITY(在FreeRTOSConfig.h中定义,默认值通常为 5)。若优先级过高,调用 FreeRTOS API 会导致系统异常(因为高优先级中断会打断 FreeRTOS 内核的临界区操作)。 -
pxHigherPriorityTaskWoken必须处理:- 该参数不能为
NULL,需初始化为pdFALSE; - 若函数返回后其值为
pdTRUE,必须在 ISR 末尾调用portYIELD_FROM_ISR(xHigherPriorityTaskWoken),否则被唤醒的高优先级任务会延迟到下一次调度时执行,影响实时性。
- 该参数不能为
-
ISR 轻量化:中断中仅执行 “读取数据 + 发送通知” 的核心操作,避免复杂逻辑(如字符串格式化、浮点运算)—— 复杂处理应交给任务端执行,防止中断占用 CPU 时间过长,影响其他中断响应。
-
多索引配置:使用
xTaskNotifyIndexedFromISR时,需在FreeRTOSConfig.h中显式配置索引数量,例如:#define configTASK_NOTIFICATION_ARRAY_ENTRIES 3 // 支持 3 个索引(0、1、2) -
通知值的位数限制:通知值为 32 位无符号整数,若需传递超过 32 位的数据(如结构体),需通过指针传递(但需确保数据在 ISR 和任务间的访问安全,如使用临界区或双缓冲区)。
总结
xTaskNotifyFromISR 和 xTaskNotifyIndexedFromISR 是 FreeRTOS 中断与任务通信的核心灵活工具:
- 前者适用于单一中断事件,无需额外配置,简洁高效;
- 后者适用于多中断事件,通过索引隔离事件,逻辑清晰,避免位操作的复杂性。
两者均支持 5 种通知值修改方式,可替代 ISR 中的信号量、事件标志组甚至轻量级队列,兼顾了高效性(无额外内存分配)和灵活性,是嵌入式实时系统中中断处理的优选方案。
xTaskNotifyWait 和 xTaskNotifyWaitIndexed
xTaskNotifyWait 和 xTaskNotifyWaitIndexed 是 FreeRTOS 中任务级的任务通知接收 API,用于任务主动等待通知的到来,并在收到通知时获取通知值(或其状态),同时支持灵活配置通知值的清除方式。两者的核心区别在于是否支持多通知索引(用于区分不同事件),是任务接收通知的 “核心入口”。
一、核心功能:等待通知 + 获取并清除通知值
任务通知的发送 API(如 xTaskNotify、xTaskNotifyFromISR 等)负责修改目标任务的通知值,而 xTaskNotifyWait 和 xTaskNotifyWaitIndexed 则负责:
- 若通知值满足条件(如非 0、特定位被设置),立即返回并获取通知值;
- 若不满足条件,任务进入阻塞状态,等待指定时间(或无限等待),直到通知到来或超时;
- 根据配置清除通知值的部分或全部(如清除特定标志位、清零等),避免重复处理。
二、xTaskNotifyWait:默认索引的通知等待
xTaskNotifyWait 用于等待当前任务(调用者)默认通知索引(索引 0) 的通知,适用于单一事件场景(如仅等待按键、定时器等一种事件)。
1. 函数原型
BaseType_t xTaskNotifyWait(
uint32_t ulBitsToClearOnEntry, // 进入函数时需要清除的通知值位(掩码)
uint32_t ulBitsToClearOnExit, // 退出函数时需要清除的通知值位(掩码)
uint32_t *pulNotificationValue, // 输出:存储收到的通知值(可为NULL)
TickType_t xTicksToWait // 阻塞等待时间(单位:tick,portMAX_DELAY表示无限等待)
);
2. 参数详解
| 参数 | 作用说明 |
|---|---|
ulBitsToClearOnEntry |
进入函数时,用该掩码清除通知值(通知值 &= ~ulBitsToClearOnEntry)。例:0x01 表示进入时清除 bit0。 |
ulBitsToClearOnExit |
退出函数时(无论是否收到通知),用该掩码清除通知值(通知值 &= ~ulBitsToClearOnExit)。例:0x03 表示退出时清除 bit0 和 bit1。 |
pulNotificationValue |
输出参数:存储收到的通知值(修改前的值)。若无需获取值,可传 NULL。 |
xTicksToWait |
通知值不满足条件时的阻塞时间(0 表示不阻塞,立即返回;portMAX_DELAY 表示无限等待)。 |
3. 工作流程
- 进入阶段:用
ulBitsToClearOnEntry清除通知值的指定位(通常用于初始化或清除历史残留值); - 检查通知值:若当前通知值 ≠ 0,直接进入步骤 4;若 = 0,任务进入阻塞状态,等待
xTicksToWait时间(期间若有新通知使通知值≠0,会被唤醒); - 超时判断:若阻塞超时(通知值仍 = 0),返回
pdFALSE; - 获取通知值:将当前通知值存入
pulNotificationValue(若不为 NULL); - 退出阶段:用
ulBitsToClearOnExit清除通知值的指定位; - 返回结果:返回
pdTRUE(成功收到通知)。
三、xTaskNotifyWaitIndexed:指定索引的通知等待
xTaskNotifyWaitIndexed 是多索引版本,用于等待当前任务(调用者)指定通知索引(0 ~ configTASK_NOTIFICATION_ARRAY_ENTRIES-1)的通知,适用于多事件场景(如同时等待按键、定时器、串口等多种事件,用索引区分)。
1. 函数原型
BaseType_t xTaskNotifyWaitIndexed(
UBaseType_t uxIndexToWaitOn, // 新增:指定的通知索引
uint32_t ulBitsToClearOnEntry, // 进入时清除的位(同前)
uint32_t ulBitsToClearOnExit, // 退出时清除的位(同前)
uint32_t *pulNotificationValue, // 输出:通知值(同前)
TickType_t xTicksToWait // 阻塞时间(同前)
);
2. 核心差异
仅新增参数 uxIndex,用于指定操作的通知索引(需 ≤ 配置的最大索引值,默认 1,即仅支持索引 0)。其他参数、工作流程与 xTaskNotifyWait 完全一致,仅操作对象从 “索引 0” 变为 “指定索引 uxIndex”。
四、核心区别与适用场景
| 特性 | xTaskNotifyWait |
xTaskNotifyWaitIndexed |
|---|---|---|
| 操作的索引 | 固定索引 0 | 可指定索引(0 ~ 配置最大值 - 1) |
| 适用场景 | 单一事件等待(如仅等待按键中断) | 多事件独立等待(如同时等待按键、定时器、ADC) |
| 事件区分方式 | 需通过 ulValue 的位(eSetBits)区分 |
直接通过索引区分,无需位操作,逻辑更清晰 |
| 配置依赖 | 无需额外配置(默认支持索引 0) | 需配置 configTASK_NOTIFICATION_ARRAY_ENTRIES 启用多索引 |
五、典型使用示例
1. xTaskNotifyWait + eSetBits:等待单一事件标志(按键或定时器)
用索引 0 的不同位表示按键(bit0)和定时器(bit1)事件,任务等待任意事件并清除对应位:
// 发送方:中断中设置标志位(bit0=按键,bit1=定时器)
// 按键中断:xTaskNotifyFromISR(task_handle, 0x01, eSetBits, ...)
// 定时器中断:xTaskNotifyFromISR(task_handle, 0x02, eSetBits, ...)
// 接收任务:等待索引0的事件标志
void event_task(void *param) {
uint32_t notify_value;
while(1) {
// 等待通知:
// - 进入时不清除任何位(ulBitsToClearOnEntry=0)
// - 退出时清除已处理的位(bit0和bit1,ulBitsToClearOnExit=0x03)
// - 阻塞无限等待(portMAX_DELAY)
if (xTaskNotifyWait(0, 0x03, ¬ify_value, portMAX_DELAY)) {
// 检查bit0(按键事件)
if (notify_value & 0x01) {
USART1_SendString((uint8_t*)"处理按键事件\r\n");
}
// 检查bit1(定时器事件)
if (notify_value & 0x02) {
USART1_SendString((uint8_t*)"处理定时器事件\r\n");
}
}
}
}
2. xTaskNotifyWaitIndexed:多索引独立等待(按键、ADC、串口)
用索引 0 = 按键、索引 1=ADC、索引 2 = 串口,任务分别等待并处理:
// 配置支持3个索引(FreeRTOSConfig.h):#define configTASK_NOTIFICATION_ARRAY_ENTRIES 3
// 接收任务:分别等待3个索引的通知
void multi_event_task(void *param) {
uint32_t key_val, adc_val, uart_val;
while(1) {
// 等待索引0(按键):退出时清除所有位(0xFFFFFFFF)
xTaskNotifyWaitIndexed(0, 0, 0xFFFFFFFF, &key_val, portMAX_DELAY);
USART1_SendString((uint8_t*)"处理按键事件\r\n");
// 等待索引1(ADC):退出时清除所有位
xTaskNotifyWaitIndexed(1, 0, 0xFFFFFFFF, &adc_val, portMAX_DELAY);
USART1_SendString((uint8_t*)"处理ADC数据\r\n");
// 等待索引2(串口):退出时清除所有位
xTaskNotifyWaitIndexed(2, 0, 0xFFFFFFFF, &uart_val, portMAX_DELAY);
USART1_SendString((uint8_t*)"处理串口命令\r\n");
}
}
- 每个索引的通知独立处理,无需通过位操作区分,逻辑更简单。
六、关键注意事项
-
清除掩码的灵活使用:
ulBitsToClearOnEntry:通常用于清除历史残留值(如任务启动时清除索引 0 的初始值);ulBitsToClearOnExit:核心用于清除已处理的事件(如处理 bit0 后清除 bit0,避免重复处理)。- 若需保留通知值(如多次读取最新数据),可将清除掩码设为 0(
ulBitsToClearOnExit=0)。
-
阻塞时间与实时性:
- 无限等待(
portMAX_DELAY)适用于必须处理的事件(如系统关键中断); - 有限等待(如
pdMS_TO_TICKS(100))适用于允许超时的场景(如非关键传感器数据)。
- 无限等待(
-
与发送 API 的配合:
- 若发送方用
eSetBits(按位或),接收方需用ulBitsToClearOnExit清除对应位; - 若发送方用
eOverwrite(覆盖值),接收方可设ulBitsToClearOnExit=0保留值,供后续读取; - 若发送方用
eIncrement(递增),接收方可设ulBitsToClearOnExit=0xFFFFFFFF(清零),一次性处理所有计数。
- 若发送方用
-
多索引配置:使用
xTaskNotifyWaitIndexed时,需在FreeRTOSConfig.h中定义索引数量,例如:#define configTASK_NOTIFICATION_ARRAY_ENTRIES 4 // 支持4个索引 -
返回值判断:返回
pdTRUE表示成功收到通知(无论阻塞与否);返回pdFALSE仅表示超时(未收到通知)。
总结
xTaskNotifyWait 和 xTaskNotifyWaitIndexed 是任务接收通知的核心 API,负责等待通知、获取值并清除已处理状态:
xTaskNotifyWait适用于单一事件,通过位操作区分子事件,简洁高效;xTaskNotifyWaitIndexed适用于多事件,通过索引天然隔离事件,逻辑清晰,无需位操作。
两者通过灵活的清除掩码和阻塞机制,可适配从简单事件触发到复杂数据传递的多种场景,是任务通知机制中 “消费端” 的核心实现。
xTaskNotifyStateClear, xTaskNotifyStateClearIndexed
xTaskNotifyStateClear 和 xTaskNotifyStateClearIndexed 是 FreeRTOS 中任务级的任务通知辅助 API,用于主动清除目标任务的通知值(将通知值重置为 0)。它们的核心作用是手动重置通知状态,避免残留的通知值干扰后续处理,区别在于是否支持多通知索引(用于指定需要清除的索引)。
一、核心功能:清除通知值(重置为 0)
任务通知的通知值会被发送 API(如 xTaskNotify、xTaskNotifyFromISR 等)修改(如递增、置位、覆盖等),而 xTaskNotifyStateClear 和 xTaskNotifyStateClearIndexed 用于在不需要等待通知的场景下,主动将指定任务的通知值清零。
例如:
- 任务启动时,清除初始状态下的残留通知值(避免误处理);
- 处理完一批通知后,手动重置通知值,确保下一次处理从 “干净状态” 开始;
- 放弃处理某类通知时,清除已积累的通知值(如暂停功能时)。
二、xTaskNotifyStateClear:清除默认索引的通知值
xTaskNotifyStateClear 用于清除目标任务默认通知索引(索引 0) 的通知值(重置为 0),适用于单一索引场景。
1. 函数原型
BaseType_t xTaskNotifyStateClear( TaskHandle_t xTask );
- 参数:
xTask为目标任务的句柄(NULL表示清除当前任务的通知值)。 - 返回值:始终返回
pdPASS(清除操作不会失败,因为仅需将通知值设为 0,无需内存分配或阻塞)。
2. 功能说明
调用该函数后,目标任务的索引 0 通知值会被直接设置为 0,无论其之前的值是多少。该操作是 “即时生效” 的,不影响任务的运行状态(即使任务正阻塞在等待通知的 API 上,清除后若通知值变为 0,任务会继续阻塞,直到新的通知到来)。
三、xTaskNotifyStateClearIndexed:清除指定索引的通知值
xTaskNotifyStateClearIndexed 是多索引版本,用于清除目标任务指定通知索引(0 ~ configTASK_NOTIFICATION_ARRAY_ENTRIES-1)的通知值(重置为 0),适用于多索引场景(需配置多索引支持)。
1. 函数原型
BaseType_t xTaskNotifyStateClearIndexed( TaskHandle_t xTask,
UBaseType_t uxIndexToClear );
- 参数:
xTask:目标任务句柄(NULL表示当前任务);uxIndexToClear:指定需要清除的通知索引(0 ≤ uxIndexToClear <configTASK_NOTIFICATION_ARRAY_ENTRIES)。
- 返回值:始终返回
pdPASS。
2. 功能说明
调用该函数后,目标任务的指定索引通知值会被直接设置为 0,其他索引的通知值不受影响。与默认索引版本一样,操作即时生效,不阻塞任务。
四、核心区别与适用场景
| 特性 | xTaskNotifyStateClear |
xTaskNotifyStateClearIndexed |
|---|---|---|
| 操作的索引 | 固定索引 0 | 可指定索引(0 ~ 配置最大值 - 1) |
| 适用场景 | 单一事件场景,清除默认索引的残留值 | 多事件场景,清除指定索引的残留值(不影响其他索引) |
| 功能影响 | 仅影响索引 0 的通知值 | 仅影响指定索引的通知值,其他索引保持不变 |
| 配置依赖 | 无需额外配置(默认支持索引 0) | 需配置 configTASK_NOTIFICATION_ARRAY_ENTRIES 启用多索引 |
五、使用场景示例
1. xTaskNotifyStateClear:任务启动时清除初始通知值
避免任务启动前的残留通知值(如系统复位后未清零的内存)导致误处理:
// 任务函数:启动时清除索引0的通知值
void sensor_task(void *param) {
// 清除初始状态下的通知值(避免残留值干扰)
xTaskNotifyStateClear(NULL); // NULL表示清除当前任务的索引0通知值
USART1_SendString((uint8_t*)"传感器任务启动,已清除初始通知值\r\n");
uint32_t notify_value;
while(1) {
// 等待新的通知(确保从0开始)
xTaskNotifyWait(0, 0xFFFFFFFF, ¬ify_value, portMAX_DELAY);
USART1_SendString((uint8_t*)"收到传感器数据通知\r\n");
}
}
2. xTaskNotifyStateClearIndexed:多索引场景下单独清除某类通知
在包含按键(索引 0)、ADC(索引 1)、串口(索引 2)的多事件系统中,暂停 ADC 功能时清除其积累的通知值:
// 配置支持3个索引(FreeRTOSConfig.h):#define configTASK_NOTIFICATION_ARRAY_ENTRIES 3
// 控制任务:暂停ADC功能时清除索引1的通知值
void control_task(void *param) {
while(1) {
// 模拟接收到“暂停ADC”命令
if (is_pause_adc_command_received()) {
USART1_SendString((uint8_t*)"暂停ADC功能,清除积累的通知值\r\n");
// 清除ADC对应的索引1通知值(当前任务)
xTaskNotifyStateClearIndexed(NULL, 1);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// ADC处理任务:仅处理清除后的新通知
void adc_task(void *param) {
uint32_t notify_value;
while(1) {
// 等待索引1的通知(清除操作后,需新通知才能唤醒)
xTaskNotifyWaitIndexed(1, 0, 0xFFFFFFFF, ¬ify_value, portMAX_DELAY);
USART1_SendString((uint8_t*)"处理新的ADC通知\r\n");
}
}
- 清除索引 1 后,之前积累的通知值被清零,任务需等待新的 ADC 通知才能继续处理,实现 “暂停后忽略历史数据” 的效果。
六、关键注意事项
-
任务级 API:这两个函数是任务级 API,不能在中断服务程序(ISR)中使用。若需在 ISR 中清除通知值,需通过任务间接实现(如 ISR 发送通知给任务,由任务调用清除函数)。
-
清除的是 “值” 而非 “阻塞状态”:若任务正阻塞在
xTaskNotifyWait或xTaskNotifyWaitIndexed上,调用清除函数会将通知值设为 0,但任务会继续阻塞(直到新的通知到来或超时)。 -
与其他 API 的配合:
- 清除操作不会影响发送 API(如
xTaskNotify仍可正常修改通知值); - 通常在任务初始化、功能暂停 / 恢复等场景下使用,避免在正常处理流程中频繁清除(可能导致通知丢失)。
- 清除操作不会影响发送 API(如
-
多索引配置:使用
xTaskNotifyStateClearIndexed时,需确保configTASK_NOTIFICATION_ARRAY_ENTRIES配置值 ≥ 所需索引 + 1(如索引 1 需配置至少 2)。
总结
xTaskNotifyStateClear 和 xTaskNotifyStateClearIndexed 是用于主动清除任务通知值的辅助 API:
- 前者清除默认索引(0)的通知值,适用于单一事件场景;
- 后者清除指定索引的通知值,适用于多事件场景,可单独重置某类通知状态。
它们的核心价值是手动重置通知状态,避免残留值干扰,确保任务从 “干净状态” 开始处理新通知,是任务通知机制中状态管理的重要补充。
ulTaskNotifyValueClear, ulTaskNotifyValueClearIndexed
ulTaskNotifyValueClear 和 ulTaskNotifyValueClearIndexed 是 FreeRTOS 中任务级的任务通知 API,用于按位清除目标任务的通知值(即清除指定的位,保留其他位),而非将整个通知值清零。它们的核心作用是精细化管理通知值中的标志位(如事件标志),区别在于是否支持多通知索引(用于指定需要操作的索引)。
一、核心功能:按位清除通知值(保留未清除位)
与 xTaskNotifyStateClear(整体清零通知值)不同,ulTaskNotifyValueClear 系列的核心是按位清除:仅将通知值中与 ulBitsToClear 掩码匹配的位设为 0,其他位保持不变。
例如,若通知值为 0x05(二进制 00000101),且 ulBitsToClear = 0x03(二进制 00000011),则清除后的值为 0x04(二进制 00000100)—— 仅清除了低 2 位,保留了第 3 位。
二、ulTaskNotifyValueClear:按位清除默认索引的通知值
ulTaskNotifyValueClear 用于按位清除目标任务默认通知索引(索引 0) 的通知值,适用于单一索引场景(如用索引 0 的不同位表示多个子事件)。
1. 函数原型
uint32_t ulTaskNotifyValueClear( TaskHandle_t xTask,
uint32_t ulBitsToClear );
-
参数:
xTask:目标任务句柄(NULL表示当前任务);ulBitsToClear:需要清除的位掩码(仅该掩码中的 “1” 对应的位会被清除)。
-
返回值:清除操作之前的通知值(可用于判断哪些位被清除,或处理未清除的位)。
2. 功能说明
调用该函数后,目标任务的索引 0 通知值会执行 通知值 &= ~ulBitsToClear 操作(按位清除),仅清除 ulBitsToClear 中为 “1” 的位,其他位保持不变。
例如:
- 原通知值 =
0x0F(00001111),ulBitsToClear = 0x03(00000011); - 操作后通知值 =
0x0C(00001100); - 返回值 =
0x0F(操作前的值)。
三、ulTaskNotifyValueClearIndexed:按位清除指定索引的通知值
ulTaskNotifyValueClearIndexed 是多索引版本,用于按位清除目标任务指定通知索引(0 ~ configTASK_NOTIFICATION_ARRAY_ENTRIES-1)的通知值,适用于多索引场景(需配置多索引支持)。
1. 函数原型
uint32_t ulTaskNotifyValueClearIndexed( TaskHandle_t xTask,
UBaseType_t uxIndexToClear,
uint32_t ulBitsToClear );
- 新增参数:
uxIndexToClear:指定需要按位清除的通知索引(0 ≤ uxIndexToClear<configTASK_NOTIFICATION_ARRAY_ENTRIES)。 - 其他参数和返回值:与
ulTaskNotifyValueClear一致。
2. 功能说明
与默认索引版本逻辑完全一致,仅操作对象从 “索引 0” 变为 “指定索引 uxIndex”,其他索引的通知值不受影响。
四、核心区别与适用场景
| 特性 | ulTaskNotifyValueClear |
ulTaskNotifyValueClearIndexed |
|---|---|---|
| 操作的索引 | 固定索引 0 | 可指定索引(0 ~ 配置最大值 - 1) |
| 清除方式 | 按位清除(仅清除 ulBitsToClear 中的位) |
按位清除(同左) |
与 xTaskNotifyStateClear 的区别 |
保留未清除的位(精细化操作) | 保留未清除的位(同左) |
| 适用场景 | 单一索引的事件标志管理(如清除已处理的子事件位) | 多索引的事件标志管理(单独清除某索引的子事件位) |
五、使用场景示例
1. ulTaskNotifyValueClear:单一索引中清除已处理的事件标志
用索引 0 的 bit0(按键)、bit1(定时器)、bit2(ADC)表示子事件,处理完按键后仅清除 bit0,保留其他未处理的位:
// 发送方:中断中设置对应位(如按键中断设bit0,定时器中断设bit1)
// 按键中断:xTaskNotifyFromISR(task_handle, 0x01, eSetBits, ...)
// 定时器中断:xTaskNotifyFromISR(task_handle, 0x02, eSetBits, ...)
// 接收任务:处理bit0后仅清除bit0,保留bit1等未处理位
void event_task(void *param) {
uint32_t prev_value;
while(1) {
// 等待索引0的任意事件(不清除位,避免丢失未处理事件)
xTaskNotifyWait(0, 0, &prev_value, portMAX_DELAY);
// 处理bit0(按键事件)
if (prev_value & 0x01) {
USART1_SendString((uint8_t*)"处理按键事件\r\n");
// 仅清除bit0,保留bit1、bit2等未处理位
ulTaskNotifyValueClear(NULL, 0x01);
}
// 处理bit1(定时器事件)(假设后续处理)
if (prev_value & 0x02) {
USART1_SendString((uint8_t*)"处理定时器事件\r\n");
ulTaskNotifyValueClear(NULL, 0x02); // 仅清除bit1
}
}
}
- 若同时收到按键(bit0)和定时器(bit1)事件,处理完按键后仅清除 bit0,bit1 仍保留,确保后续能处理定时器事件。
2. ulTaskNotifyValueClearIndexed:多索引中单独清除某索引的指定位
索引 0(按键)用 bit0/bit1 表示短按 / 长按,索引 1(ADC)用 bit0 表示超阈值,分别清除已处理的位:
// 配置支持2个索引(FreeRTOSConfig.h):#define configTASK_NOTIFICATION_ARRAY_ENTRIES 2
// 按键处理:清除索引0的bit0(短按)
void handle_key_short(void) {
// 清除索引0的bit0,保留bit1(长按)
ulTaskNotifyValueClearIndexed(NULL, 0, 0x01);
}
// ADC处理:清除索引1的bit0(超阈值)
void handle_adc_overflow(void) {
// 清除索引1的bit0
ulTaskNotifyValueClearIndexed(NULL, 1, 0x01);
}
// 主任务:分别处理两个索引的事件
void main_task(void *param) {
uint32_t key_val, adc_val;
while(1) {
// 等待索引0的按键事件
xTaskNotifyWaitIndexed(0, 0, 0, &key_val, portMAX_DELAY);
if (key_val & 0x01) handle_key_short(); // 处理短按并清除bit0
if (key_val & 0x02) handle_key_long(); // 处理长按(后续清除bit2)
// 等待索引1的ADC事件
xTaskNotifyWaitIndexed(1, 0, 0, &adc_val, portMAX_DELAY);
if (adc_val & 0x01) handle_adc_overflow(); // 处理超阈值并清除bit0
}
}
- 两个索引的位清除操作相互独立,避免干扰。
六、关键注意事项
-
按位清除逻辑:核心是
通知值 &= ~ulBitsToClear,仅清除掩码中为 “1” 的位,其他位保持不变 —— 这是与xTaskNotifyStateClear(整体清零)的核心区别。 -
返回值的用途:返回值是清除操作之前的通知值,可用于:
- 验证哪些位被清除(如
prev_value & ulBitsToClear可获取实际清除的位); - 处理未被清除的位(如保留的位可能对应未处理的事件)。
- 验证哪些位被清除(如
-
任务级 API:仅能在任务中调用,不能在中断服务程序(ISR)中使用。若需在 ISR 中按位清除,需通过任务间接实现(如 ISR 发送通知给任务,由任务调用清除函数)。
-
与接收 API 的配合:通常与
xTaskNotifyWait(或xTaskNotifyWaitIndexed)配合使用 —— 接收 API 获取通知值后,用ulTaskNotifyValueClear系列清除已处理的位,保留未处理的位供后续处理。 -
多索引配置:使用
ulTaskNotifyValueClearIndexed时,需确保configTASK_NOTIFICATION_ARRAY_ENTRIES配置值 ≥ 所需索引 + 1。
总结
ulTaskNotifyValueClear 和 ulTaskNotifyValueClearIndexed 是用于精细化按位清除通知值的 API,核心价值在于保留未处理的位,仅清除已处理的位,适用于事件标志组等需要区分多个子事件的场景:
- 前者操作默认索引 0,适合单一索引的子事件管理;
- 后者操作指定索引,适合多索引的独立子事件管理。
它们补充了整体清零 API 的不足,提供了更灵活的通知值状态管理能力。
更多推荐
所有评论(0)