事件组
事件组是 FreeRTOS 提供的一种同步机制,用于实现任务之间或任务与中断之间的事件标志位通信。它本质上是一个变量,通常为 32 位,每一位代表一个独立的事件,任务可以通过等待某些位被设置来实现对多个事件的同步控制。事件组常用于任务间协作、外设同步等场景,具有效率高、实现简单的优点。
备注
事件组总位数取决于配置项
configUSE_16_BIT_TICKS
:当
configUSE_16_BIT_TICKS
为 1 时,事件组为 16 位。当
configUSE_16_BIT_TICKS
为 0 时,事件组为 32 位。
其中高 8 位为系统保留位,不可用于用户事件标志,因此可用位数为 8 或 24 位。
本文档仅涵盖队列管理中部分常用 API,更多内容请参考 FreeRTOS 官方文档。
在 ESP-IDF 中调用事件组相关 API 时,需要在文件中包含以下头文件:
#include "freertos/event_groups.h"
事件组创建 API
xEventGroupCreate()
用于创建一个事件组,成功时返回事件组的句柄,供后续设置、清除或等待事件位使用。该函数通常在系统初始化阶段调用,创建失败时返回 NULL
。
API 原型
EventGroupHandle_t xEventGroupCreate( void );
返回值 |
说明 |
---|---|
事件组句柄 |
事件组创建成功。 |
|
事件组创建失败,没有足够的 FreeRTOS 堆来创建事件组。 |
示例 1:创建事件组
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
// 定义句柄,用于接收返回值
EventGroupHandle_t xHandle = NULL;
void app_main(void)
{
// 创建事件组
xHandle = xEventGroupCreate();
if (xHandle != NULL) {
printf("Create SUCCESS\n");
}
else {
printf("Create FALSE\n");
}
}
以上代码执行结果如下:
Create SUCCESS
该示例展示了如何使用 xEventGroupCreate()
创建事件组:定义句柄用于接收事件组创建后的返回值,并根据返回值判断是否创建成功。
事件组删除 API
vEventGroupDelete()
用于删除一个事件组,释放其占用的内存资源。调用该函数后,事件组句柄将失效,不能再用于事件操作。
备注
该 API 通常在事件组不再使用时调用,以避免内存泄漏。
API 原型
void vEventGroupDelete( EventGroupHandle_t xEventGroup );
传入参数 |
参数功能 |
参数说明 |
---|---|---|
|
需要删除的事件组的句柄 |
例如由 |
删除事件组后,事件组句柄本身的值不会自动变成 NULL
或其他特殊值,它仍然保持原来的数值。但该句柄已失效,不能再用于任何事件组操作,否则会导致未定义行为。
因此,调用 vEventGroupDelete()
后,建议手动将句柄置为 NULL
,防止误用。
设置事件标志位 API
xEventGroupSetBits()
用于设置事件组中的一个或多个标志位,表示相应事件已发生。设置成功后,所有等待这些事件位的任务会根据等待条件被唤醒。
API 原型
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
传入参数 |
参数功能 |
参数说明 |
---|---|---|
|
需要设置的事件组的句柄 |
例如由 |
|
指定要设置的事件位 |
表示哪些位将被置为 1,可通过按位或 |
返回值 |
说明 |
---|---|
返回设置之后事件组中所有事件位的状态,而非仅本次设置的事件位。 |
类型为 |
示例 2:设置事件标志位
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
// 定义标志位
#define BIT0 (1<<0)
#define BIT2 (1<<2)
// 定义句柄,用于接收返回值
EventGroupHandle_t xHandle = NULL;
EventBits_t uxBits; // 用于接收标志位返回值
void app_main(void)
{
int cnt = 0;
// 创建信号量
xHandle = xEventGroupCreate();
if (xHandle != NULL) {
printf("Create SUCCESS\n");
}
else {
printf("Create FALSE\n");
}
while (1) {
if (cnt == 0) {
printf("Set BIT0\n");
uxBits = xEventGroupSetBits(xHandle, BIT0);
}
else {
printf("Set BIT0 and BIT2\n");
uxBits = xEventGroupSetBits(xHandle, BIT0|BIT2);
}
if ((uxBits & BIT0) != 0)
{
printf("BIT0 set SUCCESS\n");
xEventGroupClearBits(xHandle, BIT0);
}
else {
printf("BIT0 set FALSE\n");
}
if ((uxBits & BIT2) != 0)
{
printf("BIT2 set SUCCESS\n");
xEventGroupClearBits(xHandle, BIT2);
}
else {
printf("BIT2 set FALSE\n");
}
cnt++;
vTaskDelay(10);
}
}
以上代码执行结果如下:
Create SUCCESS
Set BIT0
BIT0 set SUCCESS
BIT2 set FALSE
Set BIT0 and BIT2
BIT0 set SUCCESS
BIT2 set SUCCESS
该示例演示了如何使用 xEventGroupSetBits()
设置事件标志位,并通过返回值判断哪些位已成功置位。同时结合 xEventGroupClearBits()
实现手动清除已设置的事件位。
程序通过循环依次设置 BIT0
,以及同时设置 BIT0
和 BIT2
,展示了如何单独或组合设置多个事件位,并根据返回值逐位判断设置是否成功。
该示例展示了事件位的设置、判断与清除的完整流程,有助于理解事件组的基本用法和位操作机制。
备注
任务可通过对返回值与期望位进行按位与
&
运算,判断哪些位被置位。使用按位与
&
判断位状态时,不能用== 1
进行判断,因为事件位的位置可能不在最低位,应使用!= 0
判断是否被置位。同一个事件位被重复设置不会改变其状态,也不会额外触发事件。
返回值
uxBits
表示调用xEventGroupSetBits()
后事件组中被置位的所有位,包括本次和之前已置位的内容,若不及时清除事件位,后续判断中这些位将一直保持置位状态,可能导致逻辑误判。
等待事件标志位 API
xEventGroupWaitBits()
用于阻塞当前任务,等待指定的事件位被设置。可配置等待所有位或任意一位,并支持超时和是否自动清除事件位的选项。常用于任务同步或等待外部事件完成。
API 原型
EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
传入参数 |
参数功能 |
参数说明 |
---|---|---|
|
需要等待的事件组的句柄 |
例如由 |
|
等待的事件位 |
任务希望等待的位,可以是一个或多个位的组合。 |
|
是否在退出前清除事件位 |
若设置为 |
|
等待所有位或任一位 |
若设置为 |
|
最长等待时间 |
当事件标志位未满足要求时,调用者阻塞等待的最大时间,以滴答周期(tick)为单位定义;为 0 时立即返回。 |
备注
可使用按位或运算符
|
组合多个事件位,使任务可同时等待多个事件中的任意或全部发生。当
xTicksToWait
设置为portMAX_DELAY
时,表示任务无限期等待,直到事件条件满足。
返回值 |
说明 |
---|---|
满足等待条件时事件组的全部位状态。 |
类型为 |
备注
返回值是事件组当前的状态位图。
即使将自动清除位
xClearOnExit
设置为pdTRUE
,返回值仍包含触发前的完整事件位信息。
示例 3:等待事件标志位
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
// 定义标志位
#define BIT0 (1<<0)
#define BIT2 (1<<2)
// 定义句柄,用于接收返回值
EventGroupHandle_t xHandle = NULL;
EventBits_t uxBits; // 用于接收标志位返回值
static void vSetBitTask (void *pvParameters)
{
int cnt = 0;
while (1)
{
// 模拟一段任务
vTaskDelay(pdTICKS_TO_MS(10));
if (cnt == 0) {
// 设置 BIT0
printf("Set BIT0\n");
xEventGroupSetBits(xHandle, BIT0); // 此处不需要用到该 API 返回值,所以不需要接收
}
else if (cnt == 1) {
// 设置 BIT0 和 BIT2
printf("Set BIT0 and BIT2\n");
xEventGroupSetBits(xHandle, BIT0|BIT2);
}
else if (cnt == 3) {
// 设置 BIT2
printf("Set BIT2\n");
xEventGroupSetBits(xHandle, BIT2);
}
cnt++;
}
}
void app_main(void)
{
// 创建信号量
xHandle = xEventGroupCreate();
if (xHandle != NULL) {
printf("Create SUCCESS\n");
xTaskCreate(vSetBitTask, "Set Bits", 2048, NULL, 1, NULL);
}
else {
printf("Create FALSE\n");
}
// 设置一个等待位
uxBits = xEventGroupWaitBits(xHandle, BIT0, pdTRUE, pdFALSE, portMAX_DELAY);
if ((uxBits & BIT0) != 0)
{
printf("BIT0 set SUCCESS\n");
}
else {
printf("BIT0 set FALSE\n");
}
if ((uxBits & BIT2) != 0)
{
printf("BIT2 set SUCCESS\n");
}
else {
printf("BIT2 set FALSE\n\n");
}
// 设置两个个等待位,等待所有指定位都被设置
uxBits = xEventGroupWaitBits(xHandle, (BIT0 | BIT2), pdTRUE, pdTRUE, portMAX_DELAY);
if ((uxBits & ( BIT0 | BIT2)) != 0)
{
printf("BIT0 and BIT2 set SUCCESS\n\n");
}
else {
printf("BIT0 and BIT2 set FALSE\n");
}
// 设置两个个等待位,任一位满足即可
uxBits = xEventGroupWaitBits(xHandle, (BIT0 | BIT2), pdTRUE, pdFALSE, portMAX_DELAY);
if ((uxBits & BIT0) != 0)
{
printf("BIT0 set SUCCESS\n");
}
else {
printf("BIT0 set FALSE\n");
}
if ((uxBits & BIT2) != 0)
{
printf("BIT2 set SUCCESS\n\n");
}
else {
printf("BIT2 set FALSE\n");
}
}
以上代码执行结果如下:
Create SUCCESS
Set BIT0
BIT0 set SUCCESS
BIT2 set FALSE
Set BIT0 and BIT2
BIT0 and BIT2 set SUCCESS
Set BIT2
BIT0 set FALSE
BIT2 set SUCCESS
该示例演示了如何使用 xEventGroupWaitBits()
等待一个或多个事件标志位的设置,并结合 xEventGroupSetBits()
实时模拟事件的发生,从而帮助理解任务同步的基本原理。
程序中创建了一个事件组,并启动任务 vSetBitTask()
,该任务以一定延时依次设置事件组中的指定标志位,用于模拟不同事件的触发。
主任务通过 xEventGroupWaitBits()
实现三种典型等待场景:
等待单个位
BIT0
:执行结果显示任务唤醒时BIT0
成功置位,BIT2
未设置。等待
BIT0
和BIT2
:执行结果显示任务唤醒时BIT0
和BIT2
都成功置位。等待
BIT0
和BIT2
:执行结果显示任务唤醒时BIT0
未设置,BIT2
成功置位。
该示例清晰展现了事件组的等待机制,包括事件位的组合使用、等待条件配置、返回值判断及自动清除等关键操作,有助于理解任务间基于事件标志的同步逻辑。
清除事件标志位 API
xEventGroupClearBits()
用于手动清除事件组中的一个或多个事件位,即将指定的位从 1 置为 0。该函数适用于任务主动清除事件标志,以控制后续事件判断的逻辑,常用于避免误判或重复响应。
API 原型
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear );
传入参数 |
参数功能 |
参数说明 |
---|---|---|
|
需要清除的事件组的句柄 |
例如由 |
|
指定要清除的事件位 |
表示哪些位将被置为 0,可通过按位或 |
返回值 |
说明 |
---|---|
调用该 API 前,事件组中所有位的状态。 |
类型为 |
xEventGroupWaitBits()
和 xEventGroupClearBits()
都可用于清除事件标志位,但用途和清除方式不同。
xEventGroupWaitBits()
可通过设置参数xClearOnExit
,在任务成功等待到事件后自动清除满足条件的事件位,适用于边等待边清除的场景。
xEventGroupClearBits()
用于主动在任意位置手动清除指定的事件位,无需等待过程,适用于需要精确控制事件状态的情形。前者依赖任务等待,清除范围受限于等待的事件位,后者则更灵活,适合在任务或中断中显式调用以维护事件位状态。
有关该 API 的使用示例可参考示例 2,其中通过 xEventGroupClearBits()
手动清除已设置的事件位,展示了事件状态管理的基本用法。
获取事件标志位 API
xEventGroupGetBits()
用于获取指定事件组当前的事件位状态,仅用于读取,不会修改事件组内容。常用于任务或中断中检查事件组的当前状态,以便根据已设置的事件位进行相应处理。
API 原型
EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup );
传入参数 |
参数功能 |
参数说明 |
---|---|---|
|
需要清除的事件组的句柄 |
例如由 |
返回值 |
说明 |
---|---|
调用时事件组中所有事件位的状态。 |
类型为 |
示例 4:获取事件标志位
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
// 定义标志位
#define BIT0 (1<<0)
#define BIT2 (1<<2)
// 定义句柄,用于接收返回值
EventGroupHandle_t xHandle = NULL;
EventBits_t uxSetBits;
EventBits_t uxGetBits;
void app_main(void)
{
// 创建信号量
xHandle = xEventGroupCreate();
if (xHandle != NULL) {
printf("Create SUCCESS\n");
printf("Set BIT0\n");
uxSetBits = xEventGroupSetBits(xHandle, BIT0);
}
else {
printf("Create FALSE\n");
}
uxGetBits = xEventGroupGetBits(xHandle);
if (uxSetBits == uxGetBits) {
printf("BIT0 Set SUCCESS\n");
}
}
以上代码执行结果如下:
Create SUCCESS
Set BIT0
BIT0 Set SUCCESS
该示例演示了如何使用 xEventGroupGetBits()
获取事件组当前的标志位状态,并与 xEventGroupSetBits()
设置后的返回值进行比较,从而验证事件位是否成功置位。
理论上,xEventGroupGetBits()
、xEventGroupSetBits()
、xEventGroupWaitBits()
和 xEventGroupClearBits()
都可以获取事件组中已设置的事件标志位状态,但它们的设计目的和使用场景存在明显差异。如下表所示:
API |
是否修改事件组 |
主要用途 |
使用场景 |
---|---|---|---|
|
不修改 |
只读查询 |
状态判断、条件查询 |
|
设置指定位为 1 |
通知事件发生 |
任务中触发事件 |
|
可选是否清除标志位 |
等待事件同步 |
任务阻塞等待某事件 |
|
清除指定位为 0 |
手动清除事件位 |
状态重置、事件清空 |
因此,当仅需获取事件位状态而不影响事件组时,应优先使用 xEventGroupGetBits()
,而其他 API 返回值主要用于判断相关操作后的状态结果。