14-任务通知.md 8.8 KB


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

created: 2026-07-17

任务通知

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

Concept

Basic concept

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

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

Working principle

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

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

Core API

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

Experiment

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

Requirements: KEY1 → xTaskNotifyGive(task2),task2 通过 ulTaskNotifyTake() 接收。

File: P16/16_任务通知模拟信号量/Core/Src/freertos_demo.c

/* task1:KEY1按下,发送任务通知(信号量模式) */
void Task1(void *pvParameters)
{
    uint8_t key = 0;

    while (1) {
        key = Key_Detect();
        if (key == KEY1_PRESS) {
            printf("任务通知模拟二值信号量释放\r\n");
            /* 向task2发送任务通知,通知值+1 */
            xTaskNotifyGive(task2_handler);
        }
        vTaskDelay(10);
    }
}

/* task2:接收任务通知(信号量模式) */
void Task2(void *pvParameters)
{
    uint32_t rev = 0;

    while (1) {
        /* 等待任务通知,pdTRUE表示退出时清零通知值 */
        rev = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        if (rev != 0) {
            printf("接收任务通知成功,模拟获取二值信号量\r\n");
        }
    }
}

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

Requirements: KEY1/KEY2 → xTaskNotify(task2, key, eSetValueWithOverwrite),task2 通过 xTaskNotifyWait() 接收并处理。

File: P17/17_任务通知模拟消息邮箱/Core/Src/freertos_demo.c

/* task1:扫描按键,通过任务通知发送键值 */
void Task1(void *pvParameters)
{
    uint8_t key = 0;

    while (1) {
        key = Key_Detect();
        if ((key != 0) && (task2_handler != NULL)) {
            printf("任务通知模拟消息邮箱发送,发送的键值为:%d\r\n", key);
            /* 直接覆写task2的通知值(消息邮箱模式) */
            xTaskNotify(task2_handler, key, eSetValueWithOverwrite);
        }
        vTaskDelay(10);
    }
}

/* task2:接收通知值,根据键值做不同动作 */
void Task2(void *pvParameters)
{
    uint32_t notify_val = 0;

    while (1) {
        /* 等待任务通知,参数:入口不清零,出口全清零 */
        xTaskNotifyWait(0, 0xFFFFFFFF, &notify_val, portMAX_DELAY);

        switch (notify_val) {
            case KEY1_PRESS: {
                printf("接收到的通知值为:%d\r\n", notify_val);
                LED_Toggle(LED1_Pin);
                break;
            }
            case KEY2_PRESS: {
                printf("接收到的通知值为:%d\r\n", notify_val);
                LED_Toggle(LED2_Pin);
                break;
            }
            default:
                break;
        }
    }
}

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

Requirements: KEY1 → 置 EVENTBIT_0,KEY2 → 置 EVENTBIT_1,task2 等待两个位都置位。

File: P18/18_任务通知模拟事件标志组/Core/Src/freertos_demo.c

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

/* task1:KEY1 -> 设置EVENTBIT_0,KEY2 -> 设置EVENTBIT_1 */
void Task1(void *pvParameters)
{
    uint8_t key = 0;

    while (1) {
        key = Key_Detect();
        if (key == KEY1_PRESS) {
            printf("将bit0位置1\r\n");
            /* 使用eSetBits模式,按位OR设置通知值 */
            xTaskNotify(task2_handler, EVENTBIT_0, eSetBits);
        } else if (key == KEY2_PRESS) {
            printf("将bit1位置1\r\n");
            xTaskNotify(task2_handler, EVENTBIT_1, eSetBits);
        }
        vTaskDelay(10);
    }
}

/* task2:等待bit0和bit1都置位 */
void Task2(void *pvParameters)
{
    uint32_t notify_val = 0, event_bit = 0;

    while (1) {
        /* 等待任务通知 */
        xTaskNotifyWait(0, 0xFFFFFFFF, &notify_val, portMAX_DELAY);

        if (notify_val & EVENTBIT_0) {
            event_bit |= EVENTBIT_0;
        }
        if (notify_val & EVENTBIT_1) {
            event_bit |= EVENTBIT_1;
        }

        if (event_bit == (EVENTBIT_0 | EVENTBIT_1)) {
            printf("任务通知模拟事件标志组接收成功\r\n");
            event_bit = 0;                      /* 手动清除 */
        }
    }
}

Key Config

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

Core API Reference Table

Function Parameters Return Description
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)

Common Issues & Pitfalls

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