10 事件组
事件组笔记
1. 什么是事件组
事件组(Event Group)是 FreeRTOS 提供的一种事件同步机制。
它的本质是:
- 用一个变量中的多个二进制位(bit)
- 分别表示多个事件是否发生
也就是说,一个 bit 对应一个事件状态:
- bit = 1:表示该事件已经发生
- bit = 0:表示该事件还没有发生
你可以把事件组理解成一个“多位事件标志集合”。
例如:
bit0 -> 按键按下
bit1 -> 串口接收到数据
bit2 -> 传感器采样完成
bit3 -> 电机运行到位如果某时刻事件组的值为:
0000 0101就表示:
- bit0 = 1,对应事件已发生
- bit2 = 1,对应事件已发生
- bit1、bit3 没发生
2. 为什么需要事件组
在实际开发中,经常会遇到下面这些需求:
- 一个任务要等待多个事件中的任意一个发生
- 一个任务要等待多个事件全部发生
- 系统中需要统一管理多个状态标志
如果只用二值信号量或任务通知,处理多个事件会比较麻烦。
而事件组正好适合这种“多个条件同步”的场景。
所以,事件组的核心价值是:
- 用 bit 位统一管理多个事件
- 支持按位等待
- 支持等待任意一个事件
- 支持等待多个事件全部满足
3. 事件组适合的场景
事件组通常适用于以下场景。
3.1 等待多个事件中的任意一个
例如任务需要等待:
- 按键中断到来
- 串口收到数据
- 定时器超时
只要其中任意一个发生,任务就可以继续执行。
3.2 等待多个事件全部发生
例如系统启动时,需要等待:
- 传感器初始化完成
- 通信模块初始化完成
- 存储模块初始化完成
只有全部完成后,系统主任务才能进入正常工作状态。
3.3 管理系统状态
例如一个设备的状态可以用多个 bit 表示:
- 已上电
- 已联网
- 正在运行
- 出现故障
这时候事件组也可以作为状态标志组来使用。
4. 事件组和其他机制的区别
4.1 事件组和队列的区别
队列(Queue)主要用于传递数据。
事件组主要用于表示事件状态。
例如:
- 队列:可以发送整数、结构体、消息内容
- 事件组:只能表示某件事是否发生
所以:
- 队列传数据
- 事件组传状态
4.2 事件组和信号量的区别
二值信号量通常表示一个单独的事件或资源。
事件组则可以同时管理多个事件。
可以简单理解为:
- 二值信号量:一个开关
- 事件组:一排开关
4.3 事件组和互斥量的区别
互斥量(Mutex)是用来保护共享资源的,强调“互斥访问”。
事件组是用来做任务同步的,强调“事件是否发生”。
所以:
- 互斥量解决资源竞争
- 事件组解决事件同步
事件组不能代替互斥量。
5. 事件组的核心概念
事件组内部由多个 bit 位组成。
每一位都表示一个独立事件。
任务可以对这些位执行以下操作:
- 设置某些位
- 清除某些位
- 读取某些位
- 等待某些位满足条件
因此,事件组本质上就是一个“多位事件标志寄存器”。
6. 常用数据类型
在 FreeRTOS 中,事件组相关常用类型有下面这些。
6.1 EventGroupHandle_t
EventGroupHandle_t作用:
- 事件组句柄
- 用于表示一个事件组对象
你可以把它理解成“事件组的引用”。
6.2 EventBits_t
EventBits_t作用:
- 用于保存事件位的值
- 本质上是一个整数类型,用每一位表示一个事件
比如:
EventBits_t bits;它就可以用来接收当前事件组的状态。
6.3 StaticEventGroup_t
StaticEventGroup_t作用:
- 用于静态创建事件组时,保存事件组控制块内存
如果使用静态创建,就需要用户自己提供这个结构体变量。
7. 事件位的定义方式
通常会使用宏定义事件位,方便阅读和维护。
#define EVENT_BIT_0 (1 << 0)
#define EVENT_BIT_1 (1 << 1)
#define EVENT_BIT_2 (1 << 2)
#define EVENT_BIT_3 (1 << 3)例如:
#define WIFI_OK (1 << 0)
#define SENSOR_OK (1 << 1)
#define UART_OK (1 << 2)
#define MOTOR_OK (1 << 3)这样代码会更清楚:
xEventGroupSetBits(xEventGroup, WIFI_OK);比直接写 0x01 更容易理解。
8. 事件组常用 API 总览表
| API 名称 | 作用 | 是否可在中断中使用 |
|---|---|---|
xEventGroupCreate() | 动态创建事件组 | 否 |
xEventGroupCreateStatic() | 静态创建事件组 | 否 |
xEventGroupSetBits() | 设置指定事件位 | 否 |
xEventGroupSetBitsFromISR() | 在中断中设置事件位 | 是 |
xEventGroupClearBits() | 清除指定事件位 | 否 |
xEventGroupGetBits() | 读取当前事件位 | 否 |
xEventGroupWaitBits() | 等待指定事件位满足条件 | 否 |
xEventGroupSync() | 任务间同步,类似集合点 | 否 |
vEventGroupDelete() | 删除事件组 | 否 |
xEventGroupGetBitsFromISR() | 在中断中读取事件位 | 是 |
9. 创建事件组
9.1 动态创建事件组
函数原型:
EventGroupHandle_t xEventGroupCreate(void);作用:
- 创建一个事件组
- 成功时返回事件组句柄
- 失败时返回
NULL
示例:
EventGroupHandle_t xEventGroup;
xEventGroup = xEventGroupCreate();
if (xEventGroup == NULL)
{
// 创建失败
}特点:
- 使用堆内存
- 简单方便
- 前提是系统开启了动态内存分配
9.2 静态创建事件组
函数原型:
EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer);作用:
- 静态创建事件组
- 用户自己提供内存空间
- 不依赖堆内存
示例:
StaticEventGroup_t xEventGroupBuffer;
EventGroupHandle_t xEventGroup;
xEventGroup = xEventGroupCreateStatic(&xEventGroupBuffer);特点:
- 不使用动态内存
- 更适合对内存管理要求严格的嵌入式系统
10. 设置事件位
10.1 任务中设置事件位
函数原型:
EventBits_t xEventGroupSetBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet
);作用:
- 将指定的 bit 位置 1
示例:
xEventGroupSetBits(xEventGroup, EVENT_BIT_0);表示将第 0 位置 1。
再比如:
xEventGroupSetBits(xEventGroup, EVENT_BIT_0 | EVENT_BIT_2);表示同时设置第 0 位和第 2 位。
返回值:
- 返回设置后的事件组值
10.2 中断中设置事件位
函数原型:
BaseType_t xEventGroupSetBitsFromISR(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken
);作用:
- 在中断服务函数中设置事件位
参数说明:
xEventGroup:事件组句柄uxBitsToSet:要置 1 的位pxHigherPriorityTaskWoken:如果唤醒了高优先级任务,则会被置为pdTRUE
示例:
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xEventGroupSetBitsFromISR(
xEventGroup,
EVENT_BIT_1,
&xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);注意:
- ISR 中必须使用
FromISR版本 - 不能在中断中直接调用普通的
xEventGroupSetBits()
11. 清除事件位
函数原型:
EventBits_t xEventGroupClearBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear
);作用:
- 将指定的 bit 位清零
示例:
xEventGroupClearBits(xEventGroup, EVENT_BIT_0);表示清除第 0 位。
再比如:
xEventGroupClearBits(xEventGroup, EVENT_BIT_1 | EVENT_BIT_2);表示同时清除第 1 位和第 2 位。
返回值:
- 返回清除前的事件组值
12. 读取事件位
12.1 任务中读取事件位
函数原型:
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup);作用:
- 获取当前事件组中的所有 bit 状态
示例:
EventBits_t bits;
bits = xEventGroupGetBits(xEventGroup);
if (bits & EVENT_BIT_0)
{
// 第 0 位已经置位
}12.2 中断中读取事件位
函数原型:
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup);作用:
- 在中断中获取事件位状态
注意:
- 在 ISR 中读取时,应该使用这个版本
13. 等待事件位
这是事件组最核心的 API。
函数原型:
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait
);13.1 参数详解
1)xEventGroup
要等待的事件组句柄。
2)uxBitsToWaitFor
指定要等待哪些 bit。
例如:
EVENT_BIT_0 | EVENT_BIT_1表示要等待第 0 位和第 1 位。
3)xClearOnExit
等待成功返回后,是否自动清除这些事件位。
pdTRUE:成功后自动清零pdFALSE:成功后不清零
4)xWaitForAllBits
等待条件。
pdTRUE:等待所有指定 bit 都为 1pdFALSE:只要任意一个指定 bit 为 1 就返回
5)xTicksToWait
最大阻塞时间。
0:不阻塞,立即返回portMAX_DELAY:一直等待- 其他值:等待指定 tick 数
13.2 返回值
返回当前事件组的事件位值。
可以通过返回值判断到底是哪些事件满足了条件。
13.3 两种典型用法
等待任意一个事件发生
xEventGroupWaitBits(
xEventGroup,
EVENT_BIT_0 | EVENT_BIT_1,
pdFALSE,
pdFALSE,
portMAX_DELAY
);含义:
- 等待 bit0 或 bit1
- 只要任意一个置位就返回
- 返回后不清除
等待所有事件都发生
xEventGroupWaitBits(
xEventGroup,
EVENT_BIT_0 | EVENT_BIT_1,
pdFALSE,
pdTRUE,
portMAX_DELAY
);含义:
- 等待 bit0 和 bit1
- 两个位都置位才返回
- 返回后不清除
14. 同步函数 xEventGroupSync
函数原型:
EventBits_t xEventGroupSync(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait
);作用:
- 用于多个任务之间做“同步点”控制
- 当前任务先设置自己的事件位
- 然后等待其他任务也设置各自的事件位
- 类似于“集合点”或“屏障同步”
14.1 典型理解
假设有三个任务:
- 任务 A 完成后设置 bit0
- 任务 B 完成后设置 bit1
- 任务 C 完成后设置 bit2
所有任务都调用 xEventGroupSync():
- 每个任务都先设置自己的 bit
- 然后等待
bit0 | bit1 | bit2全部到齐 - 全部到齐后,三个任务一起继续往下执行
这非常适合多任务分阶段同步。
14.2 示例
#define TASK1_BIT (1 << 0)
#define TASK2_BIT (1 << 1)
#define TASK3_BIT (1 << 2)
#define ALL_SYNC_BITS (TASK1_BIT | TASK2_BIT | TASK3_BIT)
xEventGroupSync(
xEventGroup,
TASK1_BIT,
ALL_SYNC_BITS,
portMAX_DELAY
);如果当前是任务 1 调用,那么它表示:
- 我已经完成当前阶段,先置位
TASK1_BIT - 然后等待三个任务的位都置位
- 等大家都到齐再继续
15. 删除事件组
函数原型:
void vEventGroupDelete(EventGroupHandle_t xEventGroup);作用:
- 删除一个事件组
注意:
- 如果是动态创建的,会释放相应内存
- 如果是静态创建的,不需要释放用户提供的静态内存,但事件组对象会失效
示例:
vEventGroupDelete(xEventGroup);16. 事件组 API 详细表格总结
16.1 创建与删除类 API
| API | 原型 | 功能 | 返回值 | 说明 |
|---|---|---|---|---|
xEventGroupCreate | EventGroupHandle_t xEventGroupCreate(void); | 动态创建事件组 | 成功返回句柄,失败返回 NULL | 需要动态内存 |
xEventGroupCreateStatic | EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer); | 静态创建事件组 | 返回事件组句柄 | 用户提供内存 |
vEventGroupDelete | void vEventGroupDelete(EventGroupHandle_t xEventGroup); | 删除事件组 | 无 | 删除后句柄失效 |
16.2 设置、清除、读取类 API
| API | 原型 | 功能 | 返回值 | 是否可在 ISR 中使用 |
|---|---|---|---|---|
xEventGroupSetBits | EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet); | 设置指定事件位 | 返回设置后的事件位值 | 否 |
xEventGroupSetBitsFromISR | BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken); | ISR 中设置事件位 | pdPASS 等状态值 | 是 |
xEventGroupClearBits | EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear); | 清除指定事件位 | 返回清除前的值 | 否 |
xEventGroupGetBits | EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup); | 获取当前事件位 | 当前事件位值 | 否 |
xEventGroupGetBitsFromISR | EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup); | ISR 中获取事件位 | 当前事件位值 | 是 |
16.3 等待与同步类 API
| API | 原型 | 功能 | 返回值 | 说明 |
|---|---|---|---|---|
xEventGroupWaitBits | EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait); | 等待指定事件位满足条件 | 返回事件位值 | 既可等待任意一个,也可等待全部 |
xEventGroupSync | EventBits_t xEventGroupSync(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait); | 多任务同步到同一阶段 | 返回事件位值 | 适合“集合点同步” |
17. 事件组使用流程
一个典型的事件组使用流程如下:
17.1 创建事件组
EventGroupHandle_t xEventGroup;
xEventGroup = xEventGroupCreate();17.2 定义事件位
#define WIFI_OK (1 << 0)
#define SENSOR_OK (1 << 1)
#define UART_OK (1 << 2)17.3 某个任务完成工作后设置事件位
xEventGroupSetBits(xEventGroup, WIFI_OK);17.4 其他任务等待事件位
xEventGroupWaitBits(
xEventGroup,
WIFI_OK | SENSOR_OK,
pdFALSE,
pdTRUE,
portMAX_DELAY
);表示等待 WIFI_OK 和 SENSOR_OK 都完成。
17.5 需要时清除事件位
xEventGroupClearBits(xEventGroup, WIFI_OK);18. 完整示例:等待多个模块初始化完成
下面给一个完整例子。
场景:
- 任务 1 初始化传感器,完成后设置
SENSOR_OK - 任务 2 初始化串口,完成后设置
UART_OK - 主任务等待两个模块都完成初始化后再继续
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#define SENSOR_OK (1 << 0)
#define UART_OK (1 << 1)
EventGroupHandle_t xEventGroup;
void SensorTask(void *pvParameters)
{
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟初始化耗时
xEventGroupSetBits(xEventGroup, SENSOR_OK);
while (1)
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void UartTask(void *pvParameters)
{
vTaskDelay(pdMS_TO_TICKS(1500)); // 模拟初始化耗时
xEventGroupSetBits(xEventGroup, UART_OK);
while (1)
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void MainTask(void *pvParameters)
{
EventBits_t uxBits;
uxBits = xEventGroupWaitBits(
xEventGroup,
SENSOR_OK | UART_OK,
pdFALSE,
pdTRUE,
portMAX_DELAY);
if ((uxBits & (SENSOR_OK | UART_OK)) == (SENSOR_OK | UART_OK))
{
// 两个模块都初始化完成
}
while (1)
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void)
{
xEventGroup = xEventGroupCreate();
xTaskCreate(SensorTask, "SensorTask", 256, NULL, 2, NULL);
xTaskCreate(UartTask, "UartTask", 256, NULL, 2, NULL);
xTaskCreate(MainTask, "MainTask", 256, NULL, 3, NULL);
vTaskStartScheduler();
while (1);
}