FreeRTOS 事件标志组:用一组标志表示多个事件
1. 先把事件组想成一张"状态表"
事件标志组是一组二进制标志位。每一位由应用自行定义含义:
| 标志位 | 示例含义 | 置为 1 |
|---|---|---|
| bit 0 | 收到传感器数据 | 事件已发生 |
| bit 1 | 网络连接完成 | 事件已发生 |
| bit 2 | 收到控制命令 | 事件已发生 |
把它想成一张"状态表":某个事件发生,负责该事件的任务就把对应位设为 1;其他任务可以等待一位,或等待多个位满足条件。
事件组只记录事件是否发生,不保存事件的具体数据。需要传递数据时使用队列;需要"计数几个资源/事件"时,考虑计数信号量。
2. 事件位如何组合?
用按位或 | 把多个标志组合成掩码。例如:
#define EVENT_SENSOR_READY ((EventBits_t)(1UL << 0))
#define EVENT_NET_READY ((EventBits_t)(1UL << 1))
#define EVENT_COMMAND ((EventBits_t)(1UL << 2))
#define EVENT_SENSOR_AND_NET (EVENT_SENSOR_READY | EVENT_NET_READY)
bit 0 和 bit 1 同时为 1 时,EVENT_SENSOR_AND_NET 对应的两位都满足。
EventBits_t 的宽度与 TickType_t 有关。配套 V10.4.6 源码中,configUSE_16_BIT_TICKS == 0 时通常有 24 个可用事件位;为 1 时有 8 个可用位。部分位留给内核控制信息,所以不要把整型变量的所有位都当成应用事件位,也不要直接写死位数而不核对配置。
3. 等待一位、任意一位或全部位
xEventGroupWaitBits() 的两个参数决定等待条件:
| 参数 | 传 pdFALSE |
传 pdTRUE |
|---|---|---|
xWaitForAllBits |
等待掩码中任意一位为 1(逻辑或) | 等待掩码中所有位都为 1(逻辑与) |
xClearOnExit |
条件满足后保留相关位 | 条件满足后清除等待掩码中当时为 1 的位 |
只等一个事件时,掩码中放一个位即可;等多个事件时,先用 | 组合。
EventBits_t bits;
EventBits_t waitMask = EVENT_SENSOR_AND_NET;
bits = xEventGroupWaitBits(eventGroup,
waitMask,
pdTRUE, /* 条件满足后清除等待位 */
pdTRUE, /* 等待所有位都置 1 */
pdMS_TO_TICKS(1000));
if ((bits & waitMask) == waitMask) {
/* 两个事件都发生了;返回值是自动清位前的状态 */
} else {
/* 超时,仍有至少一位没有发生 */
}
等待时间的单位是 Tick。pdMS_TO_TICKS(1000) 把 1000 毫秒换算为 Tick;具体精度受系统节拍周期影响。传 0 表示不等待、立即检查;传 portMAX_DELAY 常用于无限等待,具体语义受配置影响。
返回值不是简单的成功/失败代码。 它是等待条件满足或超时时事件组的位状态。判断是否成功,要检查返回值中的目标位:等待全部位时用 (bits & waitMask) == waitMask;等待任意位时检查 (bits & waitMask) != 0。等待超时时,pdTRUE 不会替你清除未满足的等待位。
4. 创建、设置和清除
创建事件组
#include "FreeRTOS.h"
#include "event_groups.h"
EventGroupHandle_t eventGroup = xEventGroupCreate();
if (eventGroup == NULL) {
/* 创建失败:通常检查 FreeRTOS 堆空间 */
}
xEventGroupCreate() 使用动态内存;若项目采用静态分配,可使用 xEventGroupCreateStatic(),并提供 StaticEventGroup_t 控制块。先创建成功,再把句柄交给其他任务使用。
事件发生时设置位
xEventGroupSetBits(eventGroup, EVENT_SENSOR_READY);
/* 同时设置多个位:EVENT_SENSOR_READY | EVENT_NET_READY */
xEventGroupSetBits() 把指定的位设为 1,不会清除其他位。任务等待的条件因此满足时,内核会解除相应任务的阻塞。多个等待任务的条件都满足时,可能有多个任务被唤醒,因此事件组常被称为具有"广播"效果;具体哪些位保留或清除,取决于各任务的等待条件与 xClearOnExit 设置。
同一位反复设置仍只是 1,不会累计发生次数。若每次事件都必须计数,使用队列或计数信号量更合适。
清除事件位
EventBits_t oldBits = xEventGroupClearBits(eventGroup,
EVENT_SENSOR_READY);
指定的位被清为 0,其他位不变;返回的是清除前的事件位状态。可以显式清除,也可以在等待时选择 xClearOnExit = pdTRUE 自动清除。
5. 多任务同步:xEventGroupSync()
有时多个任务各自完成一部分工作后,需要等大家都到齐再继续。比如任务 A 做完饭、任务 B 做好菜,双方都到齐后再开始下一步。这类"每个任务报告已到达,并等待所有人"的场景适合 xEventGroupSync()。
#define TASK_A_DONE ((EventBits_t)(1UL << 0))
#define TASK_B_DONE ((EventBits_t)(1UL << 1))
#define ALL_TASKS_DONE (TASK_A_DONE | TASK_B_DONE)
/* 任务 A */
EventBits_t bits = xEventGroupSync(eventGroup,
TASK_A_DONE, /* 报告 A 已到达 */
ALL_TASKS_DONE, /* 等待 A 和 B 都到达 */
pdMS_TO_TICKS(5000));
if ((bits & ALL_TASKS_DONE) == ALL_TASKS_DONE) {
/* 同步成功,双方都到达 */
}
任务 B 使用同样的等待掩码,但把 uxBitsToSet 换成 TASK_B_DONE。函数会原子地设置自己的位,再等待掩码中的所有位;成功到达同步点时,等待掩码会自动清除,供下一轮同步使用。它只能在任务中调用,不能在中断中调用。参与同步的任务都应正确报告自己的位,否则其他任务可能一直等待到超时。
6. 中断中操作事件组
普通 xEventGroupSetBits()、xEventGroupClearBits()、xEventGroupWaitBits() 和 xEventGroupSync() 都用于任务上下文。ISR 若要设置或清除事件位,应使用对应的 FromISR 接口。
void Device_IRQHandler(void)
{
BaseType_t higherPriorityTaskWoken = pdFALSE;
/* 先处理或清除硬件中断状态 */
if (xEventGroupSetBitsFromISR(eventGroup,
EVENT_SENSOR_READY,
&higherPriorityTaskWoken) == pdPASS) {
portYIELD_FROM_ISR(higherPriorityTaskWoken);
}
}
在配套 V10.4.6 实现中,ISR 设置/清除事件位会把操作投递给软件定时器服务任务执行,并非在中断里立即完成。因此需启用软件定时器服务(configUSE_TIMERS == 1),保证定时器任务运行,并合理设置定时器命令队列长度;队列满时投递可能失败。ISR 还必须遵守当前移植层允许调用 FreeRTOS API 的中断优先级限制。
xEventGroupClearBitsFromISR() 与设置版本类似,但没有 higherPriorityTaskWoken 参数;成功投递返回 pdPASS,队列满等投递失败时返回 pdFAIL。
7. 本章函数与宏汇总
| 函数 / 宏 | 中文含义 | 使用方法与要点 |
|---|---|---|
xEventGroupCreate() |
动态创建事件标志组 | EventGroupHandle_t group = xEventGroupCreate();;失败返回 NULL |
xEventGroupCreateStatic() |
静态创建事件标志组 | xEventGroupCreateStatic(&buffer);需提供 StaticEventGroup_t,并启用静态分配 |
xEventGroupSetBits() |
设置一个或多个事件位 | `xEventGroupSetBits(group, BIT_A |
xEventGroupSetBitsFromISR() |
中断中请求设置事件位 | xEventGroupSetBitsFromISR(group, bits, &woken);检查 pdPASS,依赖定时器服务队列 |
xEventGroupClearBits() |
清除一个或多个事件位 | xEventGroupClearBits(group, bits);任务中调用,返回清除前的位值 |
xEventGroupClearBitsFromISR() |
中断中请求清除事件位 | xEventGroupClearBitsFromISR(group, bits);返回投递结果,依赖定时器服务队列 |
xEventGroupWaitBits() |
等待指定事件位 | xEventGroupWaitBits(group, bits, clear, waitAll, ticks);返回事件位快照,须自行判断条件是否满足 |
xEventGroupSync() |
设置本任务到达位并等待所有同步位 | xEventGroupSync(group, bitsToSet, bitsToWaitFor, ticks);任务中调用,成功时自动清除等待掩码 |
pdTRUE / pdFALSE |
真 / 假配置值 | 在 xClearOnExit、xWaitForAllBits 参数中分别控制清位和等待任意/全部位 |
pdMS_TO_TICKS(ms) |
毫秒换算为系统 Tick | pdMS_TO_TICKS(1000);等待精度受系统节拍影响 |
portMAX_DELAY |
最大等待 Tick 值 | 常用于无限等待;具体语义以项目配置为准 |
portYIELD_FROM_ISR(woken) |
请求 ISR 退出时进行调度 | 配合 ISR API 返回的唤醒标志使用;宏名称以移植层为准 |
configUSE_16_BIT_TICKS |
Tick 类型宽度配置 | 在配套 V10.4.6 中影响 EventBits_t 宽度和可用事件位数;32 位 Tick 对应 24 位可用事件标志,16 位对应 8 位 |
configUSE_TIMERS |
软件定时器功能开关 | 事件组的 FromISR 操作通过定时器服务任务延后执行,需要启用 |
configTIMER_QUEUE_LENGTH |
软件定时器命令队列长度 | 队列太短或已满时,ISR 投递的事件组操作可能失败 |