FreeRTOS 事件标志组详解:原理、用法与实战技巧
在嵌入式系统中,任务之间经常需要同步和通信。FreeRTOS 提供了多种机制来实现这些需求,例如队列、信号量、消息缓冲区等。而当你需要一个任务等待多个“事件条件”同时满足或任一满足时,事件标志组(Event Group) 就是最优解。
本文将深入讲解 FreeRTOS 的事件标志组原理、用法、应用场景,并配合“大白话”解释,帮助你快速学会并灵活使用。
一、什么是事件标志组(Event Group)?
事件标志组是一个由多个二进制位(bit)组成的集合,每一位可以被设置(set)或清除(clear),用来表示一个事件是否发生。
- 每一个 bit 是一个“标志”;
- 多个任务可以等待一个或多个标志;
- 标志可以由其他任务或中断服务程序(ISR)设置。
大白话解释:
想象你在一个工地上,负责启动挖掘机的总开关,但前提是:
- 工人 A 必须戴好安全帽(事件 A);
- 工人 B 必须穿好反光衣(事件 B);
- 工人 C 必须签到上班(事件 C)。
你可以等待这三个条件全部满足(AND)或任意一个满足(OR)再开始工作。
这就是事件标志组的作用:多个“事件”,你可以选择等“一个”或“全都”。
二、事件标志组的原理
事件标志组本质上是一个 32 位无符号整数,每一位代表一个事件标志(bit 0 ~ 31)。FreeRTOS 提供 API 支持对这些位进行设置、清除、等待等操作。
一个 Event Group 可以被多个任务共享。任务可以阻塞地等待某些位被置位(Set),满足条件后解除阻塞。
三、事件标志组的 API 用法
1. 创建事件组
EventGroupHandle_t xEventGroupCreate(void);
返回一个事件组句柄。
EventGroupHandle_t xEventGroup;
xEventGroup = xEventGroupCreate();
2. 设置事件标志位(Set Bits)
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToSet);
- 将某些位设置为 1,表示事件已发生。
- 其他任务如果在等待这些位,就可能被唤醒。
xEventGroupSetBits(xEventGroup, BIT0 | BIT2); // 设置第0和第2位
3. 清除事件标志位(Clear Bits)
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToClear);
- 手动清除某些位(设为0),可用于重置状态。
4. 等待事件标志位(Wait Bits)
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup,EventBits_t uxBitsToWaitFor,BaseType_t xClearOnExit,BaseType_t xWaitForAllBits,TickType_t xTicksToWait
);
参数说明:
参数 | 说明 |
---|---|
uxBitsToWaitFor | 要等待的标志位(如 `BIT0 |
xClearOnExit | 是否在退出等待后自动清除这些位 |
xWaitForAllBits | pdTRUE 表示等待所有位都为1,pdFALSE 表示等任一位即可 |
xTicksToWait | 最长等待时间(tick数),0为不等待 |
示例:
// 等待 BIT0 和 BIT1 同时被设置,超时时间为 1000 tick
xEventGroupWaitBits(xEventGroup, BIT0 | BIT1, pdTRUE, pdTRUE, 1000);
5. 在中断中设置事件(From ISR)
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,EventBits_t uxBitsToSet,BaseType_t *pxHigherPriorityTaskWoken
);
- 可在中断中使用,安全地设置事件位;
pxHigherPriorityTaskWoken
用于中断后切换任务。
四、事件标志组的大白话图解
标志位 | 含义 |
---|---|
BIT0 = 0x01 | 任务 A 完成 |
BIT1 = 0x02 | 任务 B 完成 |
BIT2 = 0x04 | 任务 C 完成 |
如果主任务要等 A、B、C 全部完成:
xEventGroupWaitBits(xEventGroup, BIT0 | BIT1 | BIT2, pdTRUE, pdTRUE, portMAX_DELAY);
如果只要等任意一个完成:
xEventGroupWaitBits(xEventGroup, BIT0 | BIT1 | BIT2, pdTRUE, pdFALSE, portMAX_DELAY);
五、实战案例:任务同步应用
场景:多个传感器数据采集任务,主任务需要在全部采集完成后处理数据。
步骤:
- 创建事件组;
- 每个采集任务完成后设置对应事件位;
- 主任务等待所有位置位后继续执行。
示例代码:
#define SENSOR1_DONE_BIT (1 << 0)
#define SENSOR2_DONE_BIT (1 << 1)EventGroupHandle_t xSyncEvent;void vSensor1Task(void *pvParameters) {// 模拟采集vTaskDelay(pdMS_TO_TICKS(100));xEventGroupSetBits(xSyncEvent, SENSOR1_DONE_BIT);vTaskDelete(NULL);
}void vSensor2Task(void *pvParameters) {vTaskDelay(pdMS_TO_TICKS(200));xEventGroupSetBits(xSyncEvent, SENSOR2_DONE_BIT);vTaskDelete(NULL);
}void vMainTask(void *pvParameters) {xSyncEvent = xEventGroupCreate();xTaskCreate(vSensor1Task, "Sensor1", 128, NULL, 1, NULL);xTaskCreate(vSensor2Task, "Sensor2", 128, NULL, 1, NULL);// 等待两个任务完成xEventGroupWaitBits(xSyncEvent,SENSOR1_DONE_BIT | SENSOR2_DONE_BIT,pdTRUE,pdTRUE,portMAX_DELAY);printf("所有传感器数据采集完成,开始处理\n");vEventGroupDelete(xSyncEvent);vTaskDelete(NULL);
}
六、注意事项与最佳实践
问题 | 原因 | 建议 |
---|---|---|
不会唤醒任务 | 等待条件不满足或等待超时 | 检查标志位设置是否正确 |
ISR 中使用不当 | 使用普通 API 而非 FromISR 版本 | 中断中需使用 xEventGroupSetBitsFromISR() |
标志位“粘连” | 未启用 xClearOnExit | 需要在任务中手动清除或设置自动清除 |