实用指南:FreeRTOS 事件组详解
1. 事件组基本概念
1.1 与队列、信号量的区别
事件组与队列、信号量有两个主要区别:
唤醒机制不同:
队列、信号量:事件发生时,只会唤醒一个任务
事件组:事件发生时,会唤醒所有符合条件的任务,具有"广播"作用
事件处理方式不同:
队列、信号量:消耗型资源(队列数据被读走就消失;信号量被获取后减少)
事件组:被唤醒的任务可以选择是否清除事件
2. 事件组操作流程
2.1 基本使用流程
创建事件组
任务C、D等待事件
任务A、B产生事件
2.2 事件等待选项
等待类型:可以等待某一位、某些位中的任意一个(或关系),或等待多位同时成立(与关系)
清除选项:可选择在获取事件后清除或保留事件

3. 事件组API详解
3.1 创建事件组
动态创建:
c
EventGroupHandle_t xEventGroupCreate(void);
内部自动分配事件组结构体内存
返回句柄,非NULL表示成功
静态创建:
c
EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer);
需要预先分配StaticEventGroup_t结构体
避免动态内存分配,适合内存受限场景
3.2 删除事件组
c
void vEventGroupDelete(EventGroupHandle_t xEventGroup);
删除动态创建的事件组,回收内存
参数:要删除的事件组句柄
3.3 设置事件
在任务中设置:
c
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet);
设置事件组中的特定位
可以同时设置多个位(如0x15表示设置bit4、bit2、bit0)
在ISR中设置:
c
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken);
通过向FreeRTOS后台任务发送队列数据来间接设置事件组
避免在ISR中直接执行可能唤醒多个任务的操作
3.4 等待事件
c
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait);
参数详解:
| 参数 | 说明 |
|---|---|
| xEventGroup | 要等待的事件组句柄 |
| uxBitsToWaitFor | 要等待的位(位掩码) |
| xWaitForAllBits | 测试方式:pdTRUE-所有位都为1(与关系),pdFALSE-任意位为1(或关系) |
| xClearOnExit | 退出时是否清除事件:pdTRUE-清除,pdFALSE-保留 |
| xTicksToWait | 阻塞超时时间:0-立即返回,portMAX_DELAY-无限等待,具体tick数 |
返回值:
事件发生时:返回"非阻塞条件成立"时的事件值
超时退出:返回超时时刻的事件值
4. 事件等待示例分析
4.1 "与"关系等待(所有位必须为1)
c
// 示例1:等待bit0和bit2同时为1
EventBits_t uxBits = xEventGroupWaitBits(xEventGroup,
0x05, // bit0和bit2
pdTRUE, // 退出时清除
pdTRUE, // 等待所有位
portMAX_DELAY);
// 事件组值变化过程:
// 初始值: 0100 (只有bit2为1) → 任务阻塞
// 设置bit0: 0101 → 满足条件,任务唤醒
4.2 "或"关系等待(任意位为1即可)
c
// 示例2:等待bit1或bit2任意一个为1
EventBits_t uxBits = xEventGroupWaitBits(xEventGroup,
0x06, // bit1和bit2
pdTRUE, // 退出时清除
pdFALSE, // 等待任意位
100); // 等待100个tick
// 事件组值变化过程:
// 初始值: 0100 (bit2为1) → 立即满足条件,任务不阻塞
5. 实际代码示例
5.1 基本事件组使用
c
// 定义事件位
#define TASK_READY_BIT (1 << 0)
#define DATA_READY_BIT (1 << 1)
#define ERROR_BIT (1 << 2)
EventGroupHandle_t xEventGroup;
// 任务A:设置事件
void vTaskA(void *pvParameters)
{
while(1)
{
// 执行某些操作...
xEventGroupSetBits(xEventGroup, TASK_READY_BIT);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 任务B:等待事件
void vTaskB(void *pvParameters)
{
const EventBits_t xBitsToWaitFor = TASK_READY_BIT | DATA_READY_BIT;
while(1)
{
// 等待任意一个事件发生(或关系)
EventBits_t xEvent = xEventGroupWaitBits(
xEventGroup, // 事件组句柄
xBitsToWaitFor, // 等待的位
pdTRUE, // 退出时清除事件
pdFALSE, // 或关系:任意位为1即可
portMAX_DELAY); // 无限等待
if((xEvent & TASK_READY_BIT) != 0)
{
// 处理TASK_READY事件
}
if((xEvent & DATA_READY_BIT) != 0)
{
// 处理DATA_READY事件
}
}
}
5.2 复杂事件条件处理
c
// 等待多个条件同时满足(与关系)
void vComplexTask(void *pvParameters)
{
// 等待系统就绪且数据有效
const EventBits_t xAllRequiredBits = TASK_READY_BIT | DATA_READY_BIT;
while(1)
{
EventBits_t xEvent = xEventGroupWaitBits(
xEventGroup,
xAllRequiredBits,
pdTRUE, // 清除事件
pdTRUE, // 与关系:所有位必须为1
pdMS_TO_TICKS(5000)); // 最多等待5秒
if((xEvent & xAllRequiredBits) == xAllRequiredBits)
{
// 所有条件满足,执行操作
}
else
{
// 超时,处理超时逻辑
}
}
}
6. 重要注意事项
6.1 原子操作优势
使用xClearOnExit参数为pdTRUE时,事件组的测试和清零在xEventGroupWaitBits()函数内部原子完成,避免了任务切换导致的事件状态不一致问题。
6.2 ISR中的特殊处理
在ISR中使用xEventGroupSetBitsFromISR()时需要注意:
实际设置操作由后台任务执行,存在延迟
可能唤醒多个任务,带来不确定性
需要检查
pxHigherPriorityTaskWoken参数,必要时进行任务切换
6.3 事件位管理
合理规划事件位分配,避免冲突
使用宏定义提高代码可读性
注意事件位的清除时机,避免误清除
浙公网安备 33010602011771号