FreeRTOS 事件标志组

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 投递的事件组操作可能失败
相关推荐
编码追梦人1 天前
基于 ESP32-S3 的便携式多参数环境监护仪设计:从硬件选型到 FreeRTOS 多任务落地
数据库·单片机·嵌入式硬件·mongodb·esp32·freertos·低功耗设计
天吾cc5 天前
FreeRTOS 队列集
freertos
硅基手札5 天前
操作系统设计思想系列 06 · FreeRTOS 深度 — 极简可移植范本
架构·操作系统·freertos·实时操作系统·嵌入式开发·设计思想
天吾cc10 天前
FreeRTOS 列表和列表项
freertos
qq_4017004111 天前
RTOS址与值 void*任务传参的坑 走void*万能转的天坑
freertos
天吾cc11 天前
FreeRTOS任务创建和删除
freertos
天吾cc11 天前
FreeRTOS中断管理
freertos
天吾cc11 天前
FreeRTOS任务挂起和恢复
freertos
中文英文-我选中文15 天前
8.2 学习探索
freertos