FreeRTOS 事件组(Event Group)
一、事件组是什么事件组是 FreeRTOS 提供的一种任务间同步机制允许一个或多个任务等待一组事件位标志的任意组合发生。与队列、信号量相比事件组的特点多事件组合一个事件组包含多个位通常 24 位可用取决于配置每个位代表一个独立的事件。灵活的条件任务可以等待“任意位”或“所有位”被设置。自动清除可以选择在唤醒时自动清除等待的位。低开销基于位操作效率高。事件组主要用于同步多个事件源例如等待外设初始化完成、数据准备好、超时等多个条件同时满足。多个任务各自完成某个阶段后再一起进入下一阶段类似“栅栏”。二、核心 API 函数1. 创建事件组EventGroupHandle_t xEventGroupxEventGroupCreate();动态分配内存返回句柄。失败返回 NULL。静态版本xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer )2. 设置位触发事件EventBits_txEventGroupSetBits(EventGroupHandle_t xEventGroup,constEventBits_t uxBitsToSet);将uxBitsToSet中为 1 的位在事件组中置 1。返回值是设置后的事件组值可用于调试。中断安全版本xEventGroupSetBitsFromISR()需要将操作延迟到守护任务执行。3. 等待位阻塞等待事件EventBits_txEventGroupWaitBits(constEventGroupHandle_t xEventGroup,constEventBits_t uxBitsToWaitFor,constBaseType_t xClearOnExit,constBaseType_t xWaitForAllBits,TickType_t xTicksToWait);参数说明uxBitsToWaitFor需要关注的位掩码。xClearOnExit如果为pdTRUE则在满足条件返回时自动清除这些位原子操作。xWaitForAllBitspdTRUE等待uxBitsToWaitFor中所有位都被置 1。pdFALSE等待任意一个位被置 1。xTicksToWait阻塞超时时间tick。返回值事件组在退出时的值如果超时返回部分位组合。4. 其他常用 APIxEventGroupClearBits()/xEventGroupClearBitsFromISR()手动清除位。xEventGroupGetBits()/xEventGroupGetBitsFromISR()读取当前事件组值。xEventGroupSync()同步任务同时设置位并等待位用于多任务同步点。三、代码场景分析EventGroupHandle_t xEventGroup;#defineBIT_0(10)#defineBIT_1(11)voidvTaskEvent1(void*pvParameters){while(1){vTaskDelay(1000/portTICK_PERIOD_MS);xEventGroupSetBits(xEventGroup,BIT_0);// 每秒设置 BIT_0}}voidvTaskEvent2(void*pvParameters){EventBits_t uxBits;while(1){// 等待 BIT_0 和 BIT_1 都被设置uxBitsxEventGroupWaitBits(xEventGroup,BIT_0|BIT_1,pdTRUE,// 退出时自动清除这些位pdTRUE,// 需要所有位portMAX_DELAY);if((uxBits(BIT_0|BIT_1))(BIT_0|BIT_1)){printf(Both events received!\r\n);}}}功能预期任务 1 每 1 秒钟设置 BIT_0但从来没有设置 BIT_1。任务 2 等待BIT_0 和 BIT_1 同时被设置才打印消息。由于 BIT_1 从未被设置任务 2 将永远阻塞不会打印任何内容。问题与改进缺少 BIT_1 的触发源实际应用中通常由另一个任务或中断设置 BIT_1。自动清除标志xClearOnExit pdTRUE意味着当两个位都被设置后任务 2 被唤醒系统会原子地清除 BIT_0 和 BIT_1。这样后续再次调用xEventGroupWaitBits时会重新等待。无限等待portMAX_DELAY会导致任务永久阻塞除非事件发生。改进示例添加第二个任务设置 BIT_1voidvTaskEvent3(void*pvParameters){while(1){vTaskDelay(1500/portTICK_PERIOD_MS);xEventGroupSetBits(xEventGroup,BIT_1);// 每 1.5 秒设置 BIT_1}}现在 BIT_0 每 1 秒触发一次BIT_1 每 1.5 秒触发一次。任务 2 会在大约3 秒后两个位的最小公倍数周期被唤醒因为需要同时设置两个位且自动清除后会重新等待。四、事件组的典型应用模式模式 1等待任意一个事件类似“或”逻辑EventBits_t bitsxEventGroupWaitBits(xEventGroup,BIT_0|BIT_1,pdTRUE,// 清除已发生的位pdFALSE,// 任意位即可portMAX_DELAY);if(bitsBIT_0){/* 处理事件0 */}if(bitsBIT_1){/* 处理事件1 */}模式 2等待所有事件类似“与”逻辑即示例xEventGroupWaitBits(xEventGroup,BIT_0|BIT_1,pdTRUE,pdTRUE,portMAX_DELAY);模式 3同步多个任务使用xEventGroupSync// 每个任务到达同步点时调用EventBits_t resultxEventGroupSync(xEventGroup,MY_TASK_BIT,// 本任务设置的位ALL_TASKS_BITS_MASK,// 需要等待的所有位portMAX_DELAY);当所有任务都设置了各自的位后函数返回可以用于实现“栅栏”同步。模式 4设置多个位示例// 一次性设置两个位唤醒等待的任务xEventGroupSetBits(xEventGroup,BIT_0|BIT_1);五、与队列、信号量的对比特性事件组队列二进制信号量数据传递只传递位无数据负载可传递任意数据无数据等待条件多种位组合与/或单个消息单个信号自动清除支持原子操作消息取出即删除获取即清除适用场景多事件组合、复杂同步数据流、消息传递简单同步、中断通知内存开销低一个整数变量中等缓冲区低六、注意事项位数限制configUSE_16_BIT_TICKS会影响事件组可使用的位数。默认 32 位配置下通常最多 24 个位可用高 8 位保留给内核使用。中断中使用xEventGroupSetBitsFromISR()不会立即设置位而是将操作发送到 RTOS 守护任务需要configUSE_TASK_NOTIFICATIONS和configUSE_TIMERS开启。因此它不适用于高频率中断。自动清除的原子性当任务被唤醒并设置xClearOnExit pdTRUE时清位和检查条件是在一个临界区内完成的不会丢失事件。超时处理检查返回值若超时可能只获得了部分位需要根据实际逻辑处理。不要长期持有调度器锁在临界区内调用事件组 API 是安全的但避免在关调度期间长时间等待。七、调试建议打印事件组值printf(Event group value: 0x%08x\n, xEventGroupGetBits(xEventGroup));使用 FreeRTOS 跟踪宏configUSE_TRACE_FACILITY查看任务阻塞状态。确保所有任务访问事件组时使用正确的句柄。总结事件组是 FreeRTOS 中处理多事件组合同步的强大工具通过位掩码表示多个独立事件。支持“与”/“或”等待条件。可自动清除事件位简化编程。适用于任务栅栏、多条件触发、状态机等场景。代码示例正确地使用了事件组的基本流程但缺少 BIT_1 的触发源导致任务永远阻塞。实际使用时请确保所有等待的位都有对应的设置者。附录完整代码示例/* 事件组*//* FreeRTOS三方库 */#includeFreeRTOSConfig.h#includeFreeRTOS.h#includetask.h// vTaskDelay等#includequeue.h// QueueHandle_tEventGroupHandle_t xEventGroup;#defineBIT_0(10)#defineBIT_1(11)voidvTaskEvent1(void*pvParameters){while(1){vTaskDelay(1000/portTICK_PERIOD_MS);xEventGroupSetBits(xEventGroup,BIT_0);// 每秒设置 BIT_0}}voidvTaskEvent2(void*pvParameters){EventBits_t uxBits;while(1){// 等待 BIT_0 和 BIT_1 都被设置uxBitsxEventGroupWaitBits(xEventGroup,BIT_0|BIT_1,pdTRUE,// 退出时自动清除这些位pdTRUE,// 需要所有位portMAX_DELAY);if((uxBits(BIT_0|BIT_1))(BIT_0|BIT_1)){printf(Both events received!\r\n);}}}voidvTaskEvent3(void*pvParameters){while(1){vTaskDelay(1500/portTICK_PERIOD_MS);xEventGroupSetBits(xEventGroup,BIT_1);// 每 1.5 秒设置 BIT_1// 可以一次性设置两个位唤醒等待的任务//xEventGroupSetBits(xEventGroup, BIT_0 | BIT_1);}}/** * brief 主应用函数事件组 * * note None * param None * return None * warning 此函数不会返回内部包含无限循环 * remark 通过函数指针从main()调用支持灵活的启动架构 */voidCurrent_Program6(void){LED_Init();// 初始化LED设备UART1_Config();// UART1串口初始化externEventGroupHandle_t xEventGroup;voidvTaskEvent1(void*pvParameters);voidvTaskEvent2(void*pvParameters);voidvTaskEvent3(void*pvParameters);// 创建事件组xEventGroupxEventGroupCreate();// 创建任务xTaskCreate(vTaskEvent1,vTaskEvent1,128,NULL,1,NULL);xTaskCreate(vTaskEvent2,vTaskEvent2,128,NULL,1,NULL);xTaskCreate(vTaskEvent3,vTaskEvent3,128,NULL,1,NULL);// 启动调度器永远不会返回vTaskStartScheduler();// 理论上程序不会执行到这里但为了安全可以加一个死循环while(1){}}演示效果