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, &notify_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 等待通知:

  • 对应 xTaskNotifyGivexTaskNotifyWait(等待索引 0 的通知);
  • 对应 xTaskNotifyGiveIndexedxTaskNotifyWaitIndexed(等待指定索引的通知)。

这两个等待 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, &notify_value, portMAX_DELAY)) {
            // 处理索引0(按键)
            if (notify_value & (1 << 0)) {
                USART1_SendString("按键事件触发\r\n");
            }
            // 处理索引1(定时器)
            if (notify_value & (1 << 1)) {
                USART1_SendString("定时器事件触发\r\n");
            }
        }
    }
}

六、关键注意事项

  1. 中断优先级:ISR 的优先级必须 ≤ configMAX_SYSCALL_INTERRUPT_PRIORITY(FreeRTOS 配置项),否则调用任务通知 API 会导致系统异常。
  2. 快速执行:ISR 中应仅执行必要操作(如发送通知),避免复杂逻辑,确保中断响应时间。
  3. 任务切换触发:若 pxHigherPriorityTaskWoken 被设为 pdTRUE,必须在 ISR 末尾调用 portYIELD_FROM_ISR(xHigherPriorityTaskWoken),否则高优先级任务可能无法及时运行。
  4. 索引配置:使用多索引(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. 工作流程
  1. 读取当前任务索引 0 的通知值;
  2. 若通知值 > 0:
    • 若 xClearOnExit = pdTRUE:返回当前通知值,然后将通知值设为 0;
    • 若 xClearOnExit = pdFALSE:返回当前通知值,然后将通知值减 1;
  3. 若通知值 = 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,互不干扰,实现多事件独立计数。

六、关键注意事项

  1. 与发送 API 的配合ulTaskNotifyTake 系列通常与 “递增型” 发送 API(xTaskNotifyGivevTaskNotifyGiveFromISR 等)配合,前者负责 “消费” 通知值,后者负责 “生产” 通知值。
  2. xClearOnExit 的选择
    • 需一次性处理所有累计通知(如批量处理按键):用 pdTRUE(清零);
    • 需逐个处理通知(如每次通知处理一个事件):用 pdFALSE(减 1),类似计数信号量。
  3. 阻塞行为:若通知值为 0,函数会使任务阻塞,释放 CPU 给其他任务,适合低功耗场景。
  4. 多索引配置:使用 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, &notify_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);
    }
}

六、关键注意事项

  1. 与接收 API 配合:需使用 xTaskNotifyWait(索引 0)或 xTaskNotifyWaitIndexed(指定索引)接收通知,这两个 API 可配置 “清除通知值的方式”(如清除特定位、清零等)。
  2. 中断中使用:若需在 ISR 中发送通知,需使用对应的中断版本 xTaskNotifyFromISR 或 xTaskNotifyIndexedFromISR
  3. 替代传统组件
    • eSetBits 可替代事件标志组(xEventGroupSetBits);
    • eIncrement 可替代计数信号量(xSemaphoreGive);
    • eOverwrite 可替代轻量级队列(传递 32 位以内的数据)。
  4. 多索引配置:使用 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 系列的核心价值在于获取通知修改前的状态,典型场景包括:

  1. 统计累计通知次数:已知旧值,结合新操作(如递增)可计算总次数;
  2. 处理未完成的事件:若旧值包含未处理的标志位(如 eSetBits 操作),可在发送新通知前优先处理;
  3. 避免数据覆盖:使用 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 检测到未处理的旧标志,可及时报警(避免事件丢失)。

七、关键注意事项

  1. pulPreviousNotificationValue 必须有效:该参数不能为 NULL,否则会导致内存访问错误(函数会强制写入旧值)。
  2. 与接收 API 的配合:接收通知仍需使用 xTaskNotifyWait 或 xTaskNotifyWaitIndexedxTaskNotifyAndQuery 仅负责发送和查询旧值。
  3. 中断中使用:若需在 ISR 中实现 “通知 + 查询旧值”,需使用中断版本 xTaskNotifyAndQueryFromISR 或 xTaskNotifyAndQueryIndexedFromISR
  4. 多索引配置:使用 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 中需要 “查询旧值”?

中断中查询旧值的核心需求是实时跟踪事件状态,典型场景包括:

  1. 统计中断触发次数:通过旧值(递增前的计数)计算总次数(无需在任务中维护计数);
  2. 检测未处理的事件:若旧值包含未清除的标志位(如 eSetBits),说明任务未及时处理,可记录警告;
  3. 避免数据覆盖:使用 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);
    }
}
  • 两个中断源通过不同索引独立跟踪未处理事件,避免相互干扰。

七、关键注意事项

  1. 中断优先级限制:ISR 优先级必须 ≤ configMAX_SYSCALL_INTERRUPT_PRIORITY(FreeRTOS 配置项),否则调用 API 会导致系统异常。
  2. pulPreviousNotificationValue 必须有效:该参数不能为 NULL,否则会导致内存访问错误(函数强制写入旧值)。
  3. 任务切换触发:若 pxHigherPriorityTaskWoken 被设为 pdTRUE,必须在 ISR 末尾调用 portYIELD_FROM_ISR(xHigherPriorityTaskWoken),确保高优先级任务及时运行。
  4. ISR 轻量化:中断中应仅执行必要操作(如发送通知、查询旧值),避免复杂逻辑(如字符串格式化),可通过队列将复杂处理交给任务。
  5. 多索引配置:使用 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 设计:

  1. 无阻塞操作:直接修改目标任务的控制块(TCB)中的通知值,无需等待;
  2. 支持任务切换触发:通过 pxHigherPriorityTaskWoken 标记是否唤醒高优先级任务,确保中断返回后高优先级任务及时执行;
  3. 继承任务级通知的灵活性:支持 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, &notify_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");
    }
}

六、关键注意事项

  1. 中断优先级限制:ISR 的优先级必须 ≤ configMAX_SYSCALL_INTERRUPT_PRIORITY(在 FreeRTOSConfig.h 中定义,默认值通常为 5)。若优先级过高,调用 FreeRTOS API 会导致系统异常(因为高优先级中断会打断 FreeRTOS 内核的临界区操作)。

  2. pxHigherPriorityTaskWoken 必须处理

    • 该参数不能为 NULL,需初始化为 pdFALSE
    • 若函数返回后其值为 pdTRUE,必须在 ISR 末尾调用 portYIELD_FROM_ISR(xHigherPriorityTaskWoken),否则被唤醒的高优先级任务会延迟到下一次调度时执行,影响实时性。
  3. ISR 轻量化:中断中仅执行 “读取数据 + 发送通知” 的核心操作,避免复杂逻辑(如字符串格式化、浮点运算)—— 复杂处理应交给任务端执行,防止中断占用 CPU 时间过长,影响其他中断响应。

  4. 多索引配置:使用 xTaskNotifyIndexedFromISR 时,需在 FreeRTOSConfig.h 中显式配置索引数量,例如:

    #define configTASK_NOTIFICATION_ARRAY_ENTRIES 3  // 支持 3 个索引(0、1、2)
    
  5. 通知值的位数限制:通知值为 32 位无符号整数,若需传递超过 32 位的数据(如结构体),需通过指针传递(但需确保数据在 ISR 和任务间的访问安全,如使用临界区或双缓冲区)。

总结

xTaskNotifyFromISR 和 xTaskNotifyIndexedFromISR 是 FreeRTOS 中断与任务通信的核心灵活工具

  • 前者适用于单一中断事件,无需额外配置,简洁高效;
  • 后者适用于多中断事件,通过索引隔离事件,逻辑清晰,避免位操作的复杂性。

两者均支持 5 种通知值修改方式,可替代 ISR 中的信号量、事件标志组甚至轻量级队列,兼顾了高效性(无额外内存分配)和灵活性,是嵌入式实时系统中中断处理的优选方案。

xTaskNotifyWait 和 xTaskNotifyWaitIndexed

xTaskNotifyWait 和 xTaskNotifyWaitIndexed 是 FreeRTOS 中任务级的任务通知接收 API,用于任务主动等待通知的到来,并在收到通知时获取通知值(或其状态),同时支持灵活配置通知值的清除方式。两者的核心区别在于是否支持多通知索引(用于区分不同事件),是任务接收通知的 “核心入口”。

一、核心功能:等待通知 + 获取并清除通知值

任务通知的发送 API(如 xTaskNotifyxTaskNotifyFromISR 等)负责修改目标任务的通知值,而 xTaskNotifyWait 和 xTaskNotifyWaitIndexed 则负责:

  1. 若通知值满足条件(如非 0、特定位被设置),立即返回并获取通知值;
  2. 若不满足条件,任务进入阻塞状态,等待指定时间(或无限等待),直到通知到来或超时;
  3. 根据配置清除通知值的部分或全部(如清除特定标志位、清零等),避免重复处理。

二、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. 工作流程
  1. 进入阶段:用 ulBitsToClearOnEntry 清除通知值的指定位(通常用于初始化或清除历史残留值);
  2. 检查通知值:若当前通知值 ≠ 0,直接进入步骤 4;若 = 0,任务进入阻塞状态,等待 xTicksToWait 时间(期间若有新通知使通知值≠0,会被唤醒);
  3. 超时判断:若阻塞超时(通知值仍 = 0),返回 pdFALSE
  4. 获取通知值:将当前通知值存入 pulNotificationValue(若不为 NULL);
  5. 退出阶段:用 ulBitsToClearOnExit 清除通知值的指定位;
  6. 返回结果:返回 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, &notify_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");
    }
}
  • 每个索引的通知独立处理,无需通过位操作区分,逻辑更简单。

六、关键注意事项

  1. 清除掩码的灵活使用

    • ulBitsToClearOnEntry:通常用于清除历史残留值(如任务启动时清除索引 0 的初始值);
    • ulBitsToClearOnExit:核心用于清除已处理的事件(如处理 bit0 后清除 bit0,避免重复处理)。
    • 若需保留通知值(如多次读取最新数据),可将清除掩码设为 0(ulBitsToClearOnExit=0)。
  2. 阻塞时间与实时性

    • 无限等待(portMAX_DELAY)适用于必须处理的事件(如系统关键中断);
    • 有限等待(如 pdMS_TO_TICKS(100))适用于允许超时的场景(如非关键传感器数据)。
  3. 与发送 API 的配合

    • 若发送方用 eSetBits(按位或),接收方需用 ulBitsToClearOnExit 清除对应位;
    • 若发送方用 eOverwrite(覆盖值),接收方可设 ulBitsToClearOnExit=0 保留值,供后续读取;
    • 若发送方用 eIncrement(递增),接收方可设 ulBitsToClearOnExit=0xFFFFFFFF(清零),一次性处理所有计数。
  4. 多索引配置:使用 xTaskNotifyWaitIndexed 时,需在 FreeRTOSConfig.h 中定义索引数量,例如:

    #define configTASK_NOTIFICATION_ARRAY_ENTRIES 4  // 支持4个索引
    
  5. 返回值判断:返回 pdTRUE 表示成功收到通知(无论阻塞与否);返回 pdFALSE 仅表示超时(未收到通知)。

总结

xTaskNotifyWait 和 xTaskNotifyWaitIndexed 是任务接收通知的核心 API,负责等待通知、获取值并清除已处理状态:

  • xTaskNotifyWait 适用于单一事件,通过位操作区分子事件,简洁高效;
  • xTaskNotifyWaitIndexed 适用于多事件,通过索引天然隔离事件,逻辑清晰,无需位操作。

两者通过灵活的清除掩码和阻塞机制,可适配从简单事件触发到复杂数据传递的多种场景,是任务通知机制中 “消费端” 的核心实现。

xTaskNotifyStateClear, xTaskNotifyStateClearIndexed

xTaskNotifyStateClear 和 xTaskNotifyStateClearIndexed 是 FreeRTOS 中任务级的任务通知辅助 API,用于主动清除目标任务的通知值(将通知值重置为 0)。它们的核心作用是手动重置通知状态,避免残留的通知值干扰后续处理,区别在于是否支持多通知索引(用于指定需要清除的索引)。

一、核心功能:清除通知值(重置为 0)

任务通知的通知值会被发送 API(如 xTaskNotifyxTaskNotifyFromISR 等)修改(如递增、置位、覆盖等),而 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, &notify_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, &notify_value, portMAX_DELAY);
        USART1_SendString((uint8_t*)"处理新的ADC通知\r\n");
    }
}
  • 清除索引 1 后,之前积累的通知值被清零,任务需等待新的 ADC 通知才能继续处理,实现 “暂停后忽略历史数据” 的效果。

六、关键注意事项

  1. 任务级 API:这两个函数是任务级 API,不能在中断服务程序(ISR)中使用。若需在 ISR 中清除通知值,需通过任务间接实现(如 ISR 发送通知给任务,由任务调用清除函数)。

  2. 清除的是 “值” 而非 “阻塞状态”:若任务正阻塞在 xTaskNotifyWait 或 xTaskNotifyWaitIndexed 上,调用清除函数会将通知值设为 0,但任务会继续阻塞(直到新的通知到来或超时)。

  3. 与其他 API 的配合

    • 清除操作不会影响发送 API(如 xTaskNotify 仍可正常修改通知值);
    • 通常在任务初始化、功能暂停 / 恢复等场景下使用,避免在正常处理流程中频繁清除(可能导致通知丢失)。
  4. 多索引配置:使用 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” 的位,其他位保持不变。

例如:

  • 原通知值 = 0x0F00001111),ulBitsToClear = 0x0300000011);
  • 操作后通知值 = 0x0C00001100);
  • 返回值 = 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
    }
}
  • 两个索引的位清除操作相互独立,避免干扰。

六、关键注意事项

  1. 按位清除逻辑:核心是 通知值 &= ~ulBitsToClear,仅清除掩码中为 “1” 的位,其他位保持不变 —— 这是与 xTaskNotifyStateClear(整体清零)的核心区别。

  2. 返回值的用途:返回值是清除操作之前的通知值,可用于:

    • 验证哪些位被清除(如 prev_value & ulBitsToClear 可获取实际清除的位);
    • 处理未被清除的位(如保留的位可能对应未处理的事件)。
  3. 任务级 API:仅能在任务中调用,不能在中断服务程序(ISR)中使用。若需在 ISR 中按位清除,需通过任务间接实现(如 ISR 发送通知给任务,由任务调用清除函数)。

  4. 与接收 API 的配合:通常与 xTaskNotifyWait(或 xTaskNotifyWaitIndexed)配合使用 —— 接收 API 获取通知值后,用 ulTaskNotifyValueClear 系列清除已处理的位,保留未处理的位供后续处理。

  5. 多索引配置:使用 ulTaskNotifyValueClearIndexed 时,需确保 configTASK_NOTIFICATION_ARRAY_ENTRIES 配置值 ≥ 所需索引 + 1。

总结

ulTaskNotifyValueClear 和 ulTaskNotifyValueClearIndexed 是用于精细化按位清除通知值的 API,核心价值在于保留未处理的位,仅清除已处理的位,适用于事件标志组等需要区分多个子事件的场景:

  • 前者操作默认索引 0,适合单一索引的子事件管理;
  • 后者操作指定索引,适合多索引的独立子事件管理。

它们补充了整体清零 API 的不足,提供了更灵活的通知值状态管理能力。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐