2025最新超详细FreeRTOS入门教程:第八章 FreeRTOS任务通知

摘要

在前几章中,我们依次学习了 队列、信号量、互斥量、事件组 等任务间通信与同步机制。虽然这些工具功能强大,但在某些轻量级场景下,它们可能显得“过于复杂”,比如:

  • 任务之间只需要传递一个整数值
  • ISR 只需要快速通知任务执行一次操作
  • 需要更快、更轻量的同步方式

此时,使用 任务通知(Task Notification) 是最佳选择。
任务通知是 FreeRTOS 提供的最轻量级任务通信机制,它不需要额外的内存开销,比队列和信号量更高效。

2025最新超详细FreeRTOS入门教程


一、任务通知的基本概念

1. 定义

  • 每个任务都有一个 32 位通知值,由 FreeRTOS 内核维护
  • 任务可以等待通知、发送通知
  • 通知既可以当作 二值信号量 使用,也可以当作 计数信号量/事件标志/消息传递 使用

2. 特点

  • 每个任务最多有一个通知值(可扩展为多个通知数组,FreeRTOS V10.4+ 引入 TaskNotifyTakeIndexed
  • 不需要额外的句柄对象(不像队列、信号量需要额外 RAM)
  • 速度最快,适合 ISR 与任务之间的通信
通知
通知值
中断
任务1
任务2
任务3

二、任务通知 API

1. 发送任务通知

BaseType_t xTaskNotify(
   TaskHandle_t xTaskToNotify,
   uint32_t ulValue,
   eNotifyAction eAction
);
  • eAction 取值:
    • eSetBits:对通知值执行按位或
    • eIncrement:通知值自增
    • eSetValueWithOverwrite:覆盖写入
    • eSetValueWithoutOverwrite:不覆盖已有通知

2. 等待任务通知

BaseType_t xTaskNotifyWait(
   uint32_t ulBitsToClearOnEntry,
   uint32_t ulBitsToClearOnExit,
   uint32_t *pulNotificationValue,
   TickType_t xTicksToWait
);
  • ulBitsToClearOnEntry:进入等待前清除的位
  • ulBitsToClearOnExit:退出等待后清除的位
  • pulNotificationValue:输出通知值
  • xTicksToWait:阻塞等待时间

3. 类似信号量的 API

uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit,
                          TickType_t xTicksToWait);
  • 用于实现 二值/计数信号量
  • xClearCountOnExit = pdTRUE:退出时清零
  • 返回值:成功接收到的次数

4. ISR 中发送任务通知

BaseType_t xTaskNotifyFromISR(
   TaskHandle_t xTaskToNotify,
   uint32_t ulValue,
   eNotifyAction eAction,
   BaseType_t *pxHigherPriorityTaskWoken
);

三、任务通知使用示例

示例1:ISR 通知任务

#include "FreeRTOS.h"
#include "task.h"

TaskHandle_t xHandleTask;

void vTaskHandler(void *pvParameters)
{
    uint32_t ulNotifiedValue;
    for(;;)
    {
        if(xTaskNotifyWait(0x00, 0xFFFFFFFF, &ulNotifiedValue, portMAX_DELAY) == pdTRUE)
        {
            printf("任务被中断唤醒, 值=%lu\n", ulNotifiedValue);
        }
    }
}

void EXTI0_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xTaskNotifyFromISR(xHandleTask, 1, eIncrement, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    xTaskCreate(vTaskHandler, "Handler", 128, NULL, 2, &xHandleTask);

    vTaskStartScheduler();
}

运行结果:

  • 每次中断触发,任务收到通知并打印计数

示例2:任务间通信(传递整数)

TaskHandle_t xTaskSender, xTaskReceiver;

void vTaskSender(void *pvParameters)
{
    uint32_t count = 0;
    for(;;)
    {
        count++;
        xTaskNotify(xTaskReceiver, count, eSetValueWithOverwrite);
        vTaskDelay(1000);
    }
}

void vTaskReceiver(void *pvParameters)
{
    uint32_t ulValue;
    for(;;)
    {
        if(xTaskNotifyWait(0, 0, &ulValue, portMAX_DELAY) == pdTRUE)
        {
            printf("接收值=%lu\n", ulValue);
        }
    }
}

四、任务通知的工作机制

调用 xTaskNotifyWait
ISR/任务发出通知
任务被唤醒并读取值
清除通知位
未通知
等待
已通知
处理

五、任务通知与队列/信号量对比

特性 任务通知 队列 信号量
速度 ✅ 最快 ❌ 较慢 中等
内存占用 ✅ 无额外对象 ❌ 队列结构体 ❌ 信号量对象
传递数据 ✅ 支持整数/位图 ✅ 任意数据
ISR 使用
多任务等待 ❌ 仅单任务

六、常见应用场景

  1. 任务与中断同步
    • ISR 中快速通知任务处理事件
  2. 任务间轻量通信
    • 不需要完整队列机制,只需传递整数
  3. 任务事件触发
    • 使用按位通知,代替事件组的轻量版本

七、调试与监控

  • 使用 uxTaskGetStackHighWaterMark() 检查任务是否正常运行
  • 使用 vTaskGetInfo() 获取任务通知状态
  • 注意避免丢失通知(覆盖写模式可能导致数据丢失)

八、常见问题与解决方法

问题 可能原因 解决方法
通知丢失 使用覆盖写模式 改用 eIncrement 或事件组
ISR 报错 使用了错误 API 改用 xTaskNotifyFromISR
多任务等待通知失败 每个任务只能有一个通知值 使用事件组/队列替代
通知无法清零 xTaskNotifyWait 参数设置错误 检查清除位参数

九、经验总结

📌 开发建议

  1. 若只是 ISR 与任务间的 单一事件触发,优先使用任务通知
  2. 若需要多个任务等待,使用事件组或队列
  3. 若需要传递复杂数据结构,使用队列
  4. 在高实时性场景,任务通知是最快的通信方式

十、总结

通过本章学习,你已经掌握:

  • 任务通知的概念与机制
  • 发送与接收任务通知的 API
  • 在 ISR 与任务之间使用任务通知
  • 与队列/信号量的对比和适用场景

任务通知是 FreeRTOS 中最轻量的同步与通信工具,在性能要求高的嵌入式系统中非常有价值。


🔗 FreeRTOS专栏👉 下一章:2025最新超详细FreeRTOS入门教程:第九章 FreeRTOS软件定时器 ——将学习如何在任务之外调度定时操作。


Logo

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

更多推荐