tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第15~16章 + 配套代码15/16/17/18" author: "尚硅谷研究院" date: 2026-07-12 url: ""
用生活理解:
- 事件标志组:就像你妈让你出门前检查"带钥匙了吗?带手机了吗?带钱包了吗?"——每个事件是一个 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 | 只能等一个"资源" | | 位操作 | 每个位独立,可分别设置 | 计数器整体加减 | | 灵活性 | 高(任意组合) | 低(单一条件) |
#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 分别演示了三种用法:
// 发送方(Task1)
xTaskNotifyGive(task2_handle); // 直接给 task2 发通知
// 接收方(Task2)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// pdTRUE = 收到通知后把通知值清零(就像二值信号量)
// 发送方(Task1)
uint8_t key = KEY1_PRESS;
xTaskNotify(task2_handle, key, eSetValueWithOverwrite);
// 第3个参数 eSetValueWithOverwrite = "直接覆盖旧值"
// 接收方(Task2)
uint32_t notify_val;
xTaskNotifyWait(0, 0xFFFFFFFF, ¬ify_val, portMAX_DELAY);
switch (notify_val) {
case KEY1_PRESS: LED_Toggle(LED1_Pin); break;
case KEY2_PRESS: LED_Toggle(LED2_Pin); break;
}
// 发送方
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发生了 */ }
| 函数 | 用途 | 对应传统机制 |
|---|---|---|
xTaskNotifyGive() / ulTaskNotifyTake() |
发/收通知(无数据) | 二值信号量 |
xTaskNotify(handle, val, eAction) / xTaskNotifyWait() |
发/收通知(带数据) | 消息邮箱/队列 |
xTaskNotify(handle, bits, eSetBits) + xTaskNotifyWait() |
设置位 + 等待 | 事件标志组 |
eAction 参数选项:
| 选项 | 含义 |
|------|------|
| eSetValueWithOverwrite | 直接覆盖通知值(像邮箱) |
| eSetBits | 按位或操作(像事件组) |
| eIncrement | 通知值+1(像计数信号量) |
| eNoAction | 只发通知不改值 |
xTaskNotifyFromISR() / vTaskNotifyGiveFromISR()configUSE_TASK_NOTIFICATIONS = 0v10.4 新增通知数组:旧版本每个任务只有一个 32 位通知值。新版本支持通知数组(
configTASK_NOTIFICATION_ARRAY_ENTRIES),可接收来自多个不同任务的独立通知。发送时通过xTaskNotifyIndexed()指定数组索引。
code/15_事件标志组/code/16_任务通知模拟信号量/code/17_任务通知模拟消息队列/code/18_任务通知模拟事件标志组/后续章节:[[10-软件定时器与Tickless低功耗]]