事件组 Event Group
用一组位标志实现"等待多个条件"的同步:AND/OR 组合等待、多任务同步点,一个任务醒来处理多路事件。
用一组位标志实现"等待多个条件"的同步:AND/OR 组合等待、多任务同步点,一个任务醒来处理多路事件。
EventBits_t 通常为 24 位可用(配置 configUSE_16_BIT_TICKS 会变 8 位),高 8 位被内核保留。用宏定义清晰命名,如 #define BIT_WIFI (1<<0),避免魔法数字。xEventGroupSetBitsFromISR,且它通过定时器服务任务延迟执行(返回 pdPASS 表示成功挂到 daemon)。uxBitsToWaitFor(关心哪些位)、xWaitForAllBits(pdTRUE=AND 全满足 / pdFALSE=OR 任一满足)、xClearOnExit(返回前是否清除这些位)、xTicksToWait(超时)。返回等待结束时的位值。xEventGroupWaitBits 的参数。pdTRUE 时,条件满足、任务被唤醒后自动清除所关心的位(相当于"消费"了事件),适合边沿触发语义;pdFALSE 时保留位,需手动 xEventGroupClearBits,适合表示持久状态(如"WiFi 一直在线")。多任务同时等待时用 pdTRUE 要小心竞争。xEventGroupSync 实现"多个任务互相等待、到齐了一起放行"的屏障(barrier)。每个任务置自己的位并等待所有指定位都置位,最后到达的那个任务触发全体同时解除阻塞并自动清位。用于多任务分阶段协同,如"三个初始化任务都完成后再进入主循环"。xEventGroupSetBitsFromISR 会把置位请求发给定时器服务任务(Timer Daemon,见第 7 章)异步执行。因此需 configUSE_TIMERS=1,且置位并非在中断里立刻生效,而是在 daemon 得到运行时。这是事件组与信号量在 ISR 行为上的重要差异。设想一个联网任务:必须等到 WiFi 已连接 且 时间已同步 且 配置已加载 三件事全部就绪,才能开始上报。用三个信号量要写复杂的嵌套等待逻辑,用事件组一行 AND 等待即可:
#include "FreeRTOS.h" #include "event_groups.h" #define BIT_WIFI (1UL << 0) #define BIT_TIME (1UL << 1) #define BIT_CFG (1UL << 2) #define ALL_READY (BIT_WIFI | BIT_TIME | BIT_CFG) EventGroupHandle_t g_sysEvents; void ReportTask(void *arg) { for (;;) { /* 等三位全部置位(AND),超时 5s;退出时清除这些位 */ EventBits_t bits = xEventGroupWaitBits( g_sysEvents, ALL_READY, /* 关心的位 */ pdTRUE, /* xClearOnExit:清除 */ pdTRUE, /* xWaitForAllBits:AND */ pdMS_TO_TICKS(5000)); if ((bits & ALL_READY) == ALL_READY) { start_reporting(); /* 三者齐备,开始上报 */ } else { printf("not ready in 5s, got 0x%lx\r\n", bits); } } } /* 其他任务在各自完成时置位 */ void on_wifi_up(void) { xEventGroupSetBits(g_sysEvents, BIT_WIFI); } void on_time_ok(void) { xEventGroupSetBits(g_sysEvents, BIT_TIME); } void on_cfg_ok(void) { xEventGroupSetBits(g_sysEvents, BIT_CFG); }
#define EVT_BTN (1UL << 0) #define EVT_ALARM (1UL << 1) #define EVT_TIMER (1UL << 2) #define ANY_EVT (EVT_BTN | EVT_ALARM | EVT_TIMER) void UiTask(void *arg) { for (;;) { EventBits_t b = xEventGroupWaitBits( g_sysEvents, ANY_EVT, pdTRUE, /* 清除 */ pdFALSE, /* OR:任一即可 */ portMAX_DELAY); if (b & EVT_BTN) on_button(); if (b & EVT_ALARM) on_alarm(); if (b & EVT_TIMER) on_timer(); } }
xEventGroupWaitBits 返回的是"等待结束那一刻的位值"。超时返回时位可能并未满足条件,因此务必用 (bits & MASK) == MASK(AND 场景)或 bits & MASK(OR 场景)显式判断,不要只凭"函数返回了"就认为条件成立。
#define SYNC_A (1UL << 0) #define SYNC_B (1UL << 1) #define SYNC_C (1UL << 2) #define SYNC_ALL (SYNC_A | SYNC_B | SYNC_C) /* 三个初始化任务,各自跑完在同步点集合,全到齐再放行 */ void InitTaskA(void *arg) { init_hardware(); /* 置自己的位 SYNC_A,等待所有位到齐 */ xEventGroupSync(g_sysEvents, SYNC_A, SYNC_ALL, portMAX_DELAY); run_phase2(); /* 三任务同时抵达此处 */ vTaskDelete(NULL); } /* InitTaskB 用 SYNC_B,InitTaskC 用 SYNC_C,逻辑相同 */
| 需求 | 推荐 | 说明 |
|---|---|---|
| 等待"多个条件全就绪"(AND) | 事件组 | 信号量无法组合 |
| 等待"任一事件发生"(OR) | 事件组 / 队列集 | 事件组更简洁 |
| 多任务集合屏障 | 事件组 xEventGroupSync | 唯一直接支持 |
| 传递数据内容 | 队列 | 事件组只有位,不带数据 |
| 单一事件通知 + 计数 | 信号量 / 任务通知 | 更轻量 |
| 单接收者的位标志 | 任务通知 eSetBits | 省一个内核对象 |
事件位只有"置位/清零"两种状态,不能像队列那样承载消息内容,也不像计数信号量那样累计次数——同一个位置位两次和一次效果相同。若需要"发生了几次"或"具体是什么数据",必须搭配队列使用:事件组通知"有事发生",队列传递"具体内容"。
事件组是一组二进制事件位(EventBits_t,通常 24 位可用),每位代表一个独立事件。核心能力是组合等待:xEventGroupWaitBits 用 xWaitForAllBits 选择 AND(全满足)或 OR(任一满足),这是信号量做不到的。xClearOnExit 决定唤醒后是否自动清位(边沿 vs 状态语义)。xEventGroupSetBits 置位并唤醒满足条件的等待者;ISR 里用 xEventGroupSetBitsFromISR,它通过定时器 Daemon 延迟执行(需 configUSE_TIMERS=1)。xEventGroupSync 实现多任务集合屏障,全体到齐才一起放行。事件组不带数据也不计数,需传内容时配合队列。选型:多条件 AND/OR 用事件组,多任务屏障用 Sync,传数据用队列,单接收者位标志用任务通知 eSetBits。务必用 (bits & MASK)==MASK 校验返回值。