事件组(Event Group)是一种特殊的任务间通信机制,用于一个任务等待多个事件中的任意一个或全部发生。相比用多个信号量逐个等待,事件组用一个整数的每个 bit 表示一个事件,非常节省内存。

基本概念

  • 事件标志:一个布尔值(0 或 1),表示某个事件是否发生
  • 事件组:多个事件标志的集合,用一个 EventBits_t(通常是 uint32_t)的各个 bit 位表示
bit31 bit30 ... bit3 bit2 bit1 bit0
  0     0   ...  0    0    0    0
  │     │        │    │    │    │
  │     │        │    │    │    └── 事件0:初始化完成
  │     │        │    │    └─────── 事件1:数据就绪
  │     │        │    └──────────── 事件2:ISR 触发
  │     │        └───────────────── 事件3...
  │     └────────────────────────── 事件30...
  └──────────────────────────────── 事件31...

每个 bit:0 = 事件未发生,1 = 事件已发生。

创建事件组

// 动态创建
EventGroupHandle_t xEventGroupCreate(void);

// 静态创建
EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer);

设置事件位

// 任务中使用
EventBits_t xEventGroupSetBits(
    EventGroupHandle_t xEventGroup,
    const EventBits_t uxBitsToSet  // 要设置的 bit(可以 OR 多个)
);

// ISR 中使用(带 FromISR 后缀)
BaseType_t xEventGroupSetBitsFromISR(
    EventGroupHandle_t xEventGroup,
    const EventBits_t uxBitsToSet,
    BaseType_t *pxHigherPriorityTaskWoken
);

设置 bit 就是把对应位置 1,表示事件发生了:

#define EVENT_INIT_DONE  (1UL << 0)   // bit0
#define EVENT_DATA_READY (1UL << 1)   // bit1
#define EVENT_ISR_TRIG   (1UL << 2)   // bit2

// 设置单个 bit
xEventGroupSetBits(xEventGroup, EVENT_INIT_DONE);

// 同时设置多个 bit(OR 组合)
xEventGroupSetBits(xEventGroup, EVENT_DATA_READY | EVENT_ISR_TRIG);

等待事件位

EventBits_t xEventGroupWaitBits(
    EventGroupHandle_t xEventGroup,
    const EventBits_t uxBitsToWaitFor,  // 等待哪些 bit
    const BaseType_t xClearOnExit,      // pdTRUE:满足条件后自动清除这些bit
    const BaseType_t xWaitForAllBits,   // pdTRUE:所有指定bit都为1才唤醒;pdFALSE:任意一个为1就唤醒
    TickType_t xTicksToWait             // 超时时间
);

返回值:唤醒时(或超时时)事件组的值。需要手动检查返回值中是否包含期望的 bit。

参数详解

xClearOnExit

  • pdTRUE:函数退出(拿到满足条件的事件后)自动清除 uxBitsToWaitFor 中指定的 bit,适合”消费事件”的场景
  • pdFALSE:不自动清除,bit 保持 1,适合”广播事件”的场景(多个任务都要看到这个事件)

xWaitForAllBits

  • pdTRUE:等待所有指定的 bit 都为 1(逻辑 AND)
  • pdFALSE:等待任意一个指定的 bit 为 1(逻辑 OR)

使用示例

EventBits_t uxBits;
uxBits = xEventGroupWaitBits(
    xEventGroup,
    EVENT_INIT_DONE | EVENT_DATA_READY | EVENT_ISR_TRIG,  // 等这三个事件
    pdTRUE,    // 满足后自动清除
    pdTRUE,    // 三个都满足才唤醒
    portMAX_DELAY  // 一直等
);

// 检查返回值,区分是正常唤醒还是超时
if((uxBits & (EVENT_INIT_DONE | EVENT_DATA_READY | EVENT_ISR_TRIG))
        == (EVENT_INIT_DONE | EVENT_DATA_READY | EVENT_ISR_TRIG))
{
    // 三个事件都发生了,正常处理
}
else
{
    // 超时返回,条件未全部满足
}

多任务同步:xEventGroupSync

一个常见的场景是:多个任务都到达同步点后,才能一起继续执行(类似”会合”/rendezvous)。

问题:SetBits 和 WaitBits 是两步操作,有竞态

假设 Task A、B、C 三个任务同步:

1. Task A 和 B 已经到达同步点,调用 WaitBits 等待 C 的 bit
2. Task C 到达:
   第一步:SetBits(C 的 bit)  → A 和 B 立刻被唤醒,并自动清除了所有 bit!
   第二步:WaitBits(A|B|C)   → 但 bit 已经被 A/B 的 ClearOnExit 清掉了
   → Task C 永远等不到三个 bit 都为 1!

解决:xEventGroupSync 原子操作

xEventGroupSync() 把”设置自己的 bit”和”等待其他 bit”合并为一个不可分割的原子操作

EventBits_t xEventGroupSync(
    EventGroupHandle_t xEventGroup,
    const EventBits_t uxBitsToSet,      // 设置自己的同步 bit
    const EventBits_t uxBitsToWaitFor,  // 等待哪些 bit
    TickType_t xTicksToWait
);
// 每个任务到达同步点时调用:
xEventGroupSync(
    xEventGroup,
    MY_BIT,              // 设置自己的 bit
    ALL_BITS,            // 等待所有人都到齐
    portMAX_DELAY
);
// 到这里说明所有任务都到达同步点了

执行过程:

  1. 设置自己的 bit
  2. 立刻登记等待条件(这两步之间不会被其他任务打断)
  3. 阻塞等待,直到所有等待的 bit 都为 1
  4. 如果配置了 xClearOnExit,退出时清除 bit(Sync 内部会自动处理清除逻辑)

三任务同步的正确写法

三个任务都调用 xEventGroupSync(),分别设置自己的 bit,等待 BIT_A | BIT_B | BIT_C。任何一个任务先到就设置自己的 bit 并阻塞等待;最后一个任务设置自己的 bit 时,所有 bit 凑齐,三个任务同时被唤醒。

清除事件位

// 手动清除指定 bit
EventBits_t xEventGroupClearBits(
    EventGroupHandle_t xEventGroup,
    const EventBits_t uxBitsToClear
);

// ISR 版本
BaseType_t xEventGroupClearBitsFromISR(
    EventGroupHandle_t xEventGroup,
    const EventBits_t uxBitsToClear
);

通常不需要手动清除

配合 xClearOnExit = pdTRUE 使用时,WaitBits 成功返回后会自动清除对应的 bit,不需要手动 Clear。

查询事件位

// 查询当前事件组的值(不改变任何 bit,不阻塞)
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup);

// ISR 版本
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup);

事件组 vs 信号量 vs 队列

特性事件组信号量队列
等待多个条件✅ 一个调用等多个 bit❌ 只能等一个信号量❌ 只能等一个队列
广播给多个任务✅ 可多个任务等同一组 bit❌ 信号量被一个任务拿走就没了❌ 数据被一个任务取走就没了
传递数据❌ 只有 0/1 标志❌ 只有有/无✅ 可以传任意数据
ISR 中使用✅ SetBitsFromISR✅ GiveFromISR✅ SendFromISR
典型场景多条件同步、状态广播资源互斥、事件通知数据传递

典型使用场景

场景1:系统初始化同步

// 各个初始化任务分别完成后设置 bit
// 主任务等待所有初始化完成才启动业务逻辑

场景2:多条件触发

// 等待"网络连接上" AND "MQTT 连上" AND "配置加载完" 才开始工作

场景3:任务会合

// 多个任务都完成各自的阶段后,同时进入下一阶段

下一步:08-任务通知

相关:04-中断处理06-资源管理返回目录