事件标志组
事件标志组
为什么需要事件标志组
信号量通常适合表示一个事件是否发生,例如:
数据到达 → 释放信号量 → 任务处理但实际应用中,一个任务可能需要同时等待多个事件:
网络连接成功
传感器初始化完成
SD 卡初始化完成
│
▼
所有条件满足
│
▼
任务开始运行如果使用多个信号量,需要分别等待和管理。
FreeRTOS 提供了事件标志组(Event Group),可以使用一个变量中的不同 bit 表示不同事件,从而实现:
- 等待任意一个事件;
- 等待多个事件全部发生;
- 多个任务之间同步。
因此:
事件标志组本质上是利用一个整数的不同 bit 表示多个事件。
事件标志组结构
事件标志组本质上是一个位图:
Bit31 Bit24 Bit23 Bit0
│ │ │ │
└────── 控制位/保留位 ─────┘ └────── 用户事件位 ──────────┘FreeRTOS 会保留高 8 位作为内部控制用途,因此常见的 32-bit 配置下:
32 bit
├── 高 8 bit:FreeRTOS 内部使用
└── 低 24 bit:用户事件位因此常见情况下可以表示 24 个独立事件。
16-bit 配置下:
16 bit
├── 高 8 bit:FreeRTOS 内部使用
└── 低 8 bit:用户事件位所以通常只有 8 个用户事件位。
例如:
#define EVENT_1 (1 << 0)
#define EVENT_2 (1 << 1)
#define EVENT_3 (1 << 2)对应:
Bit2 Bit1 Bit0
│ │ │
│ │ └── EVENT_1
│ └─────── EVENT_2
└──────────── EVENT_3多个事件可以组合:
#define ALL_EVENTS (EVENT_1 | EVENT_2 | EVENT_3)使用前的配置
使用事件标志组之前,需要在 FreeRTOSConfig.h 中开启:
#define configUSE_EVENT_GROUPS 1不同 FreeRTOS 版本的配置项可能略有区别,以实际版本的 FreeRTOSConfig.h 为准。
创建事件组
使用:
EventGroupHandle_t xEventGroupCreate(void);例如:
EventGroupHandle_t xEventGroup;
xEventGroup = xEventGroupCreate();
if (xEventGroup == NULL)
{
// 创建失败
}创建成功后返回:
EventGroupHandle_t即事件组句柄。
设置事件位
任务中使用:
EventBits_t xEventGroupSetBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet
);例如:
xEventGroupSetBits(xEventGroup, EVENT_1);表示:
将
EVENT_1对应的 bit 设置为 1。
也可以一次设置多个事件:
xEventGroupSetBits(
xEventGroup,
EVENT_1 | EVENT_2
);如果在中断中设置事件位,则使用:
xEventGroupSetBitsFromISR(
xEventGroup,
EVENT_1,
&xHigherPriorityTaskWoken
);典型写法:
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xEventGroupSetBitsFromISR(
xEventGroup,
EVENT_1,
&xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);等待事件位
这是事件标志组最核心的 API:
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait
);最重要的是后面三个参数。
xClearOnExit
表示满足等待条件并退出后,是否清除等待的事件位:
pdTRUE → 清除
pdFALSE → 不清除例如:
xEventGroupWaitBits(
xEventGroup,
EVENT_1,
pdTRUE,
pdFALSE,
portMAX_DELAY
);表示等待 EVENT_1,满足条件后自动清除 EVENT_1。
xWaitForAllBits
表示等待任意事件还是所有事件:
pdFALSE → 任意一个事件发生即可
pdTRUE → 所有指定事件都发生才继续例如:
EVENT_1 | EVENT_2 | EVENT_3如果:
xWaitForAllBits = pdFALSE那么:
EVENT_1 发生
↓
立即满足条件如果:
xWaitForAllBits = pdTRUE那么必须:
EVENT_1
+
EVENT_2
+
EVENT_3
↓
全部发生
↓
满足条件清除事件位
任务中:
EventBits_t xEventGroupClearBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear
);例如:
xEventGroupClearBits(xEventGroup, EVENT_1);表示清除 EVENT_1。
中断中使用:
xEventGroupClearBitsFromISR(
xEventGroup,
EVENT_1
);不过实际开发中,如果只是等待事件后自动清除,通常直接使用 xEventGroupWaitBits() 的 xClearOnExit = pdTRUE 即可。
读取事件位
任务中:
EventBits_t xEventGroupGetBits(
EventGroupHandle_t xEventGroup
);用于获取当前事件组的值,并且不会清除事件位。
中断中:
EventBits_t xEventGroupGetBitsFromISR(
EventGroupHandle_t xEventGroup
);删除事件组
void vEventGroupDelete(
EventGroupHandle_t xEventGroup
);用于删除事件组并释放相关资源。
任务同步
事件标志组还可以用于多个任务之间的同步。
使用:
EventBits_t xEventGroupSync(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait
);它可以同时完成:
设置自己的事件位
+
等待其他任务的事件位例如三个任务分别完成初始化:
Task1 → 设置 BIT0
Task2 → 设置 BIT1
Task3 → 设置 BIT2Task1 等待:
BIT0 | BIT1 | BIT2当三个任务全部完成后,所有等待条件的任务才能继续执行。
配置和使用流程
实际使用事件标志组,可以记成下面几个步骤。
第一步:开启配置
在 FreeRTOSConfig.h 中:
#define configUSE_EVENT_GROUPS 1第二步:包含头文件
#include "FreeRTOS.h"
#include "event_groups.h"第三步:定义事件位
#define EVENT_1 (1 << 0)
#define EVENT_2 (1 << 1)
#define EVENT_3 (1 << 2)
#define ALL_EVENTS (EVENT_1 | EVENT_2 | EVENT_3)第四步:创建事件组
EventGroupHandle_t xEventGroup;
xEventGroup = xEventGroupCreate();
if (xEventGroup == NULL)
{
// 创建失败
}第五步:设置事件
任务中:
xEventGroupSetBits(xEventGroup, EVENT_1);中断中:
xEventGroupSetBitsFromISR(
xEventGroup,
EVENT_1,
&xHigherPriorityTaskWoken
);第六步:等待事件
例如等待所有事件:
xEventGroupWaitBits(
xEventGroup,
ALL_EVENTS,
pdTRUE,
pdTRUE,
portMAX_DELAY
);因此可以直接记成:
配置
↓
包含头文件
↓
定义事件位
↓
创建事件组
↓
设置事件
↓
等待事件典型例子:等待三个任务全部就绪
假设系统有三个初始化任务:
Task1 → 初始化传感器
Task2 → 初始化网络
Task3 → 初始化存储设备只有三个任务全部初始化完成后,主任务才能继续运行。
定义事件位:
#define TASK1_READY_BIT (1 << 0)
#define TASK2_READY_BIT (1 << 1)
#define TASK3_READY_BIT (1 << 2)
#define ALL_TASK_READY \
(TASK1_READY_BIT | TASK2_READY_BIT | TASK3_READY_BIT)创建事件组:
EventGroupHandle_t xEventGroup;
xEventGroup = xEventGroupCreate();Task1 完成初始化:
xEventGroupSetBits(
xEventGroup,
TASK1_READY_BIT
);Task2:
xEventGroupSetBits(
xEventGroup,
TASK2_READY_BIT
);Task3:
xEventGroupSetBits(
xEventGroup,
TASK3_READY_BIT
);主任务等待三个事件:
xEventGroupWaitBits(
xEventGroup,
ALL_TASK_READY,
pdTRUE,
pdTRUE,
portMAX_DELAY
);这里:
ALL_TASK_READY
↓
等待 BIT0 + BIT1 + BIT2
↓
xWaitForAllBits = pdTRUE
↓
三个事件全部发生
↓
任务继续执行如果只希望任意一个事件发生就继续,则:
xEventGroupWaitBits(
xEventGroup,
ALL_TASK_READY,
pdTRUE,
pdFALSE,
portMAX_DELAY
);此时:
BIT0 ─┐
BIT1 ─┼── 任意一个发生 → 继续
BIT2 ─┘核心 API
实际开发中最常用的就是下面几个:
| API | 作用 |
|---|---|
xEventGroupCreate() | 创建事件组 |
xEventGroupSetBits() | 设置事件位 |
xEventGroupSetBitsFromISR() | ISR 中设置事件位 |
xEventGroupWaitBits() | 等待事件位 |
xEventGroupClearBits() | 清除事件位 |
xEventGroupGetBits() | 获取当前事件位 |
xEventGroupSync() | 多任务同步 |
vEventGroupDelete() | 删除事件组 |
其中最核心的是:
设置事件
↓
xEventGroupSetBits()
↓
等待事件
↓
xEventGroupWaitBits()总结
事件标志组本质上就是:
用一个整数的不同 bit 表示不同事件。
它相比二值信号量最大的优势是:
一个信号量
↓
通常表示一个同步事件
一个事件组
↓
多个 bit
↓
可以表示多个事件
↓
可以等待任意事件
↓
也可以等待所有事件因此,遇到“一个任务需要同时等待多个条件”的场景,就应该考虑事件标志组。
最需要记住的只有三个参数:
xEventGroupWaitBits(
xEventGroup,
uxBitsToWaitFor,
xClearOnExit,
xWaitForAllBits,
xTicksToWait
);其中:
xClearOnExit
pdTRUE → 满足条件后清除
pdFALSE → 保留
xWaitForAllBits
pdTRUE → 等待全部事件
pdFALSE → 等待任意事件最终把事件标志组理解成一句话即可:
事件标志组就是一个“多事件状态集合”,通过不同 bit 表示不同事件,通过
xEventGroupWaitBits()等待一个或多个事件。

