14-任务通知.md 12 KB


tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第14章 任务通知" author: "尚硅谷研究院" date: 2026-07-17

created: 2026-07-17

任务通知

用生活理解:任务通知就像同事直接走到你工位前告诉你一件事,不需要通过公司广播(队列)、公告栏(信号量)或旗语系统(事件标志组)。直接、高效,但因为是一对一,没法同时通知所有人。

概念介绍

基本概念

任务通知(Task Notification)是 FreeRTOS V8.2.0 引入的轻量级任务间通信机制。每个 TCB 中内置了一个 32 位通知值8 位通知状态,无需创建额外的内核对象。

  • 无需中间对象:发送任务直接向目标任务发通知
  • 更快:比队列/信号量省去了创建和操作中间对象的时间
  • 一对一:每个任务只有一个通知值,不能一对多广播

工作原理

任务通知支持 3 种使用模式:

模式 发送函数 接收函数 行为
信号量模式 xTaskNotifyGive() ulTaskNotifyTake() 通知值递增/递减,模拟二值/计数信号量
消息邮箱模式 xTaskNotify(handle, value, eSetValueWithOverwrite) xTaskNotifyWait() 直接覆盖通知值,模拟消息邮箱
事件标志组模式 xTaskNotify(handle, bits, eSetBits) xTaskNotifyWait() 按位 OR,模拟事件标志组

核心API

函数 参数 功能简述
xTaskNotifyGive() xTaskToNotify 发送通知(信号量模式),通知值 +1
ulTaskNotifyTake() xClearCountOnExit, xTicksToWait 接收通知(信号量模式),返回通知值
xTaskNotify() xTaskToNotify, ulValue, eAction 发送通知(带值),eAction 控制写/OR/覆写行为
xTaskNotifyWait() ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait 接收通知(带值),可获取通知值和清除指定位
xTaskNotifyFromISR() 同上+ISR 版本 中断中发送通知
vTaskNotifyGiveFromISR() xTaskToNotify, pxHigherPriorityTaskWoken 中断中发送(信号量模式)

实验

以下实验代码均提取自配套 Keil 项目的 freertos_demo.c,仅展示核心任务函数。

实验1:任务通知模拟信号量 (P16)

需求:KEY1 → xTaskNotifyGive(task2_handle) 通知 task2,task2 通过 ulTaskNotifyTake() 阻塞等待通知。

代码来源:03_代码/16_任务通知模拟信号量/Core/Src/freertos_demo.c

/* task1:KEY1按下,向task2发送任务通知(信号量模式:通知值+1) */
void task1(void *pvParameters)
{
    uint8_t key = 0;
    BaseType_t res = 0;
    while (1)
    {
        key = Key_Detect();
        if (key == KEY1_PRESS)
        {
            /* 向task2发送通知,通知值计数+1 */
            res = xTaskNotifyGive(task2_handle);
            if (res == pdPASS)
            {
                printf("task1向task2发送任务通知成功!\r\n");
            }
        }
        vTaskDelay(500);
    }
}

/* task2:阻塞等待任务通知 */
void task2(void *pvParameters)
{
    uint32_t notify_value = 0;
    while (1)
    {
        printf("task2等待...\r\n");

        /* pdFALSE = 收到通知后通知值减1(适合计数信号量)
           pdTRUE  = 收到通知后通知值清零(适合二值信号量) */
        notify_value = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);

        printf("Task2收到通知值=%d\r\n", notify_value);
        vTaskDelay(5000);   /* 模拟长时间工作,期间task1的通知累积 */
    }
}

实验2:任务通知模拟消息邮箱 (P17)

需求:KEY1/KEY2 → xTaskNotify(task2_handle, key, eSetValueWithOverwrite) 覆写通知值,task2 接收后打印。

代码来源:03_代码/17_任务通知模拟消息队列/Core/Src/freertos_demo.c

/* task1:扫描按键,通过任务通知将键值发送给task2 */
void task1(void *pvParameters)
{
    uint8_t key = 0;
    BaseType_t res = 0;
    while (1)
    {
        key = Key_Detect();
        if (key == KEY1_PRESS || key == KEY2_PRESS)
        {
            /* 直接覆写task2的通知值(消息邮箱模式) */
            res = xTaskNotify(
                task2_handle,
                key,                   // 要发送的值(按键编号)
                eSetValueWithOverwrite // 无条件覆写通知值
            );
            if (res == pdPASS)
            {
                printf("task1向task2发送通知值[%d]成功...\r\n", key);
            }
        }
        vTaskDelay(500);
    }
}

/* task2:接收通知值并打印 */
void task2(void *pvParameters)
{
    uint32_t notify_value = 0;
    BaseType_t res = 0;
    while (1)
    {
        printf("task2等待...\r\n");

        /* 入口不清零(0x00000000),出口全清零(0xffffffff) */
        res = xTaskNotifyWait(
            0x00000000,    // 进入时不清除任何位
            0xffffffff,    // 退出时清除所有位(通知值归零)
            &notify_value, // 保存接收到的通知值
            portMAX_DELAY);

        if (res == pdTRUE)
        {
            printf("Task2收到通知值=%d\r\n", notify_value);
        }
    }
}

实验3:任务通知模拟事件标志组 (P18)

需求:KEY1 → 通知值 bit0 置 1,KEY2 → 通知值 bit1 置 1,task2 累加检查。

代码来源:03_代码/18_任务通知模拟事件标志组/Core/Src/freertos_demo.c

#define EVENTBIT_0  (1 << 0)
#define EVENTBIT_1  (1 << 1)

/* task1:KEY1 -> 置bit0,KEY2 -> 置bit1 */
void task1(void *pvParameters)
{
    uint8_t key = 0;
    BaseType_t res = 0;
    while (1)
    {
        key = Key_Detect();
        if (key == KEY1_PRESS)
        {
            /* 使用eSetBits模式,按位OR将bit0置1 */
            res = xTaskNotify(task2_handle, EVENTBIT_0, eSetBits);
            if (res == pdPASS) printf("KEY1按下将bit0置1..\r\n");
        }
        else if (key == KEY2_PRESS)
        {
            res = xTaskNotify(task2_handle, EVENTBIT_1, eSetBits);
            if (res == pdPASS) printf("KEY2按下将bit1置1..\r\n");
        }
        vTaskDelay(500);
    }
}

/* task2:累加收到的位,两个位都收到后打印信号 */
void task2(void *pvParameters)
{
    uint32_t notify_value = 0;
    uint32_t expect_value = 0;   /* 累加累计收到的位 */
    BaseType_t res = 0;
    while (1)
    {
        printf("task2等待...\r\n");

        res = xTaskNotifyWait(
            0x00000000,    // 进入时不清除
            0xffffffff,    // 退出时清除所有位
            &notify_value,
            portMAX_DELAY);

        if (notify_value & EVENTBIT_0)
        {
            printf("接收到bit0=1\r\n");
            expect_value |= EVENTBIT_0;
        }
        if (notify_value & EVENTBIT_1)
        {
            printf("接收到bit1=1\r\n");
            expect_value |= EVENTBIT_1;
        }

        /* 两个位都已收到则输出 */
        if (expect_value == (EVENTBIT_0 | EVENTBIT_1))
        {
            printf("两个事件均已收到 expect_value=%d\r\n", expect_value);
        }
        vTaskDelay(500);
    }
}

关键配置

/* FreeRTOSConfig.h */
#define configUSE_TASK_NOTIFICATIONS             1   /* 使能任务通知(默认已使能) */
#define configTASK_NOTIFICATION_ARRAY_ENTRIES    1   /* 通知数组大小,v10.4起支持多通知 */

核心函数速查表

函数 参数 返回值 功能简述
BaseType_t xTaskNotifyGive(xTaskToNotify) 目标任务句柄 pdPASS/pdFAIL 发送通知(信号量模式,通知值+1)
uint32_t ulTaskNotifyTake(xClearCountOnExit, xTicksToWait) 退出时清零?, 超时 通知值 接收通知(信号量模式,阻塞)
BaseType_t xTaskNotify(xTaskToNotify, ulValue, eAction) 目标任务, 值, 动作(覆写/OR/增量等) pdPASS/pdFAIL 发送通知(带值)
BaseType_t xTaskNotifyWait(ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait) 入口清除, 出口清除, 值指针, 超时 pdPASS/pdFAIL 接收通知(阻塞等待)
BaseType_t xTaskNotifyFromISR(xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken) 同 xTaskNotify+唤醒标记 pdPASS/pdFAIL ISR中发送通知
void vTaskNotifyGiveFromISR(xTaskToNotify, pxHigherPriorityTaskWoken) 目标任务, 唤醒标记 ISR中信号量模式发送
BaseType_t xTaskNotifyAndQuery(xTaskToNotify, ulValue, eAction, pulPreviousValue) 同 xTaskNotify+原值 pdPASS/pdFAIL 发送并获取原通知值

eAction 枚举值

  • eNoAction:仅发送通知(不修改值)
  • eSetBits:与现有值按位 OR
  • eIncrement:通知值 +1
  • eSetValueWithOverwrite:无条件覆写
  • eSetValueWithoutOverwrite:仅当接收方未读取时覆写(否则返回 pdFAIL)

常见问题与避坑

  1. 任务通知只能一对一:一个任务只有一个通知值。不能像事件标志组那样多个任务等待同一个事件。
  2. ISR 不可接收通知:没有 xTaskNotifyWaitFromISR() 函数。接收任务通知只能在任务上下文中进行。
  3. 通知值溢出不提示xTaskNotifyGive() 使 32 位通知值递增,溢出后归零且无错误提示。
  4. 信号量模式的 xClearCountOnExit
    • pdTRUE:读取后清零(适合二值信号量)
    • pdFALSE:读取后减 1(适合计数信号量),ulTaskNotifyTake() 返回减 1 前的值
  5. 消息邮箱模式的数据丢失:使用 eSetValueWithOverwrite 时,如果发送者在接收者读取前多次发送,只有最后一次的值会被保留。
  6. 性能优势的来源:任务通知不经过队列内核对象,直接在 TCB 中操作,省去了队列的入队/出队开销和等待列表管理。