09-事件标志组与任务通知.md 6.6 KB


tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第15~16章 + 配套代码15/16/17/18" author: "尚硅谷研究院" date: 2026-07-12 url: ""

created: 2026-07-12

事件标志组与任务通知

用生活理解

  • 事件标志组:就像你妈让你出门前检查"带钥匙了吗?带手机了吗?带钱包了吗?"——每个事件是一个 checkbox,全部勾上了才能出门
  • 任务通知:就像同事直接在你桌上贴便条——不用经过前台、不用信箱,直接通知你。效率最高

事件标志组

核心概念

事件标志组位结构示意图

事件标志组是一个整数(24位或8位),每个 bit 代表一个独立事件:

位宽计算:实际可用位数 = tick 类型的位数 - 8 位预留。由 configUSE_16_BIT_TICKS 决定——置 1 → 8 位事件标志(16-8=8),置 0 → 24 位(32-8=24)。事件标志组占用 tick 类型的高 8 位。

Bit:  23 ... 2  1  0
     +---------+--+--+
     | 其他事件 |B |A | ← A事件=bit0, B事件=bit1
     +---------+--+--+

关键特点(对比信号量): | 特性 | 事件标志组 | 信号量 | |------|-----------|--------| | 等待条件 | 可等多个位同时置1 | 只能等一个"资源" | | 位操作 | 每个位独立,可分别设置 | 计数器整体加减 | | 灵活性 | 高(任意组合) | 低(单一条件) |

示例:事件标志组(项目15)

情景

  • task1:按 KEY1 → 置位 bit0(事件A发生),按 KEY2 → 置位 bit1(事件B发生)
  • task2:等待 bit0 和 bit1 同时置位,然后处理

核心代码

#include "event_groups.h"

EventGroupHandle_t event_group;

#define EVENTBIT_0  (1 << 0)  // 事件A = 第0位
#define EVENTBIT_1  (1 << 1)  // 事件B = 第1位

void freertos_start(void)
{
    event_group = xEventGroupCreate();  // 创建事件标志组
    xTaskCreate(/* task1 */);
    xTaskCreate(/* task2 */);
    vTaskStartScheduler();
}

void task1(void *pvParameters)
{
    uint8_t key = 0;
    while (1)
    {
        key = Key_Detect();
        if (key == KEY1_PRESS)
        {
            xEventGroupSetBits(event_group, EVENTBIT_0);  // "我带了钥匙!"
        }
        else if (key == KEY2_PRESS)
        {
            xEventGroupSetBits(event_group, EVENTBIT_1);  // "我带了手机!"
        }
        vTaskDelay(500);
    }
}

void task2(void *pvParameters)
{
    EventBits_t bits;
    while (1)
    {
        bits = xEventGroupWaitBits(
            event_group,              // 事件标志组句柄
            EVENTBIT_0 | EVENTBIT_1,  // 等bit0和bit1同时成立
            pdTRUE,                   // 等到之后自动清除这两个位
            pdTRUE,                   // pdTRUE = 所有位都置1才算
            portMAX_DELAY);          // 死等
        printf("钥匙和手机都带齐了,可以出门了!\r\n");
    }
}

xEventGroupWaitBits 第4个参数

  • pdTRUE(AND模式):所有指定的位都置1才唤醒
  • pdFALSE(OR模式):任意一个位置1就唤醒

任务通知

为什么任务通知更快?

传统方式(信号量):                    任务通知方式:
发送方 → 信号量对象 → 接收方             发送方 → 接收方
        中间多了一层                       直达!

任务通知不需要创建中间对象。每个任务内部都有 32 位通知值,其他任务可以直接修改它。

信箱类比:每个任务的 TCB 中有一个"邮箱",其他任务直接把信放进去——不需要队列、信号量等中间对象。发送方不能阻塞(只能覆盖或放弃),只有接收方可以阻塞。

发送非阻塞:发送方总是非阻塞的——要么覆盖旧值,要么放弃。这跟队列不同——队列满了发送方可以等,通知满了就只能覆盖或丢弃。

三种典型用法

项目16/17/18 分别演示了三种用法:

用法1:模拟二值信号量(项目16)

// 发送方(Task1)
xTaskNotifyGive(task2_handle);  // 直接给 task2 发通知

// 接收方(Task2)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// pdTRUE = 收到通知后把通知值清零(就像二值信号量)

用法2:模拟消息邮箱(项目17)

// 发送方(Task1)
uint8_t key = KEY1_PRESS;
xTaskNotify(task2_handle, key, eSetValueWithOverwrite);
// 第3个参数 eSetValueWithOverwrite = "直接覆盖旧值"

// 接收方(Task2)
uint32_t notify_val;
xTaskNotifyWait(0, 0xFFFFFFFF, &notify_val, portMAX_DELAY);
switch (notify_val) {
    case KEY1_PRESS: LED_Toggle(LED1_Pin); break;
    case KEY2_PRESS: LED_Toggle(LED2_Pin); break;
}

用法3:模拟事件标志组(项目18)

// 发送方
xTaskNotify(task2_handle, EVENTBIT_0, eSetBits);  // 设置某一位
xTaskNotify(task2_handle, EVENTBIT_1, eSetBits);

// 接收方
uint32_t val;
xTaskNotifyWait(0, 0xFFFFFFFF, &val, portMAX_DELAY);
if (val & EVENTBIT_0) { /* 事件A发生了 */ }
if (val & EVENTBIT_1) { /* 事件B发生了 */ }

任务通知 API 速查

函数 用途 对应传统机制
xTaskNotifyGive() / ulTaskNotifyTake() 发/收通知(无数据) 二值信号量
xTaskNotify(handle, val, eAction) / xTaskNotifyWait() 发/收通知(带数据) 消息邮箱/队列
xTaskNotify(handle, bits, eSetBits) + xTaskNotifyWait() 设置位 + 等待 事件标志组

eAction 参数选项: | 选项 | 含义 | |------|------| | eSetValueWithOverwrite | 直接覆盖通知值(像邮箱) | | eSetBits | 按位或操作(像事件组) | | eIncrement | 通知值+1(像计数信号量) | | eNoAction | 只发通知不改值 |

注意事项

  • ⚠️ 任务通知不能在 ISR 中接收(没有 FromISR 版本的接收函数)——因为 ISR 没有自己的 TCB
  • ✅ 发送可以在 ISR 中:xTaskNotifyFromISR() / vTaskNotifyGiveFromISR()
  • 通知默认启用,禁用时设置 configUSE_TASK_NOTIFICATIONS = 0

v10.4 新增通知数组:旧版本每个任务只有一个 32 位通知值。新版本支持通知数组configTASK_NOTIFICATION_ARRAY_ENTRIES),可接收来自多个不同任务的独立通知。发送时通过 xTaskNotifyIndexed() 指定数组索引。

配套代码

  • code/15_事件标志组/
  • code/16_任务通知模拟信号量/
  • code/17_任务通知模拟消息队列/
  • code/18_任务通知模拟事件标志组/

后续章节:[[10-软件定时器与Tickless低功耗]]