news 2026/4/30 15:37:03

FreeRTOS 事件组(Event Group)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FreeRTOS 事件组(Event Group)

一、事件组是什么?

事件组是 FreeRTOS 提供的一种任务间同步机制,允许一个或多个任务等待一组事件(位标志)的任意组合发生。与队列、信号量相比,事件组的特点:

  • 多事件组合:一个事件组包含多个位(通常 24 位可用,取决于配置),每个位代表一个独立的事件。
  • 灵活的条件:任务可以等待“任意位”或“所有位”被设置。
  • 自动清除:可以选择在唤醒时自动清除等待的位。
  • 低开销:基于位操作,效率高。

事件组主要用于同步多个事件源,例如:

  • 等待外设初始化完成、数据准备好、超时等多个条件同时满足。
  • 多个任务各自完成某个阶段后,再一起进入下一阶段(类似“栅栏”)。

二、核心 API 函数

1. 创建事件组

EventGroupHandle_t xEventGroup=xEventGroupCreate();
  • 动态分配内存,返回句柄。失败返回 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,则在满足条件返回时自动清除这些位(原子操作)。
  • xWaitForAllBits
    • pdTRUE:等待uxBitsToWaitFor所有位都被置 1。
    • pdFALSE:等待任意一个位被置 1。
  • xTicksToWait:阻塞超时时间(tick)。
  • 返回值:事件组在退出时的值(如果超时返回部分位组合)。

4. 其他常用 API

  • xEventGroupClearBits()/xEventGroupClearBitsFromISR():手动清除位。
  • xEventGroupGetBits()/xEventGroupGetBitsFromISR():读取当前事件组值。
  • xEventGroupSync():同步任务(同时设置位并等待位,用于多任务同步点)。

三、代码场景分析

EventGroupHandle_t xEventGroup;#defineBIT_0(1<<0)#defineBIT_1(1<<1)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 都被设置uxBits=xEventGroupWaitBits(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_1

voidvTaskEvent3(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 bits=xEventGroupWaitBits(xEventGroup,BIT_0|BIT_1,pdTRUE,// 清除已发生的位pdFALSE,// 任意位即可portMAX_DELAY);if(bits&BIT_0){/* 处理事件0 */}if(bits&BIT_1){/* 处理事件1 */}

模式 2:等待所有事件(类似“与”逻辑,即示例)

xEventGroupWaitBits(xEventGroup,BIT_0|BIT_1,pdTRUE,pdTRUE,portMAX_DELAY);

模式 3:同步多个任务(使用xEventGroupSync

// 每个任务到达同步点时调用EventBits_t result=xEventGroupSync(xEventGroup,MY_TASK_BIT,// 本任务设置的位ALL_TASKS_BITS_MASK,// 需要等待的所有位portMAX_DELAY);

当所有任务都设置了各自的位后,函数返回,可以用于实现“栅栏”同步。


模式 4:设置多个位示例

// 一次性设置两个位,唤醒等待的任务xEventGroupSetBits(xEventGroup,BIT_0|BIT_1);

五、与队列、信号量的对比

特性事件组队列二进制信号量
数据传递只传递位(无数据负载)可传递任意数据无数据
等待条件多种位组合(与/或)单个消息单个信号
自动清除支持(原子操作)消息取出即删除获取即清除
适用场景多事件组合、复杂同步数据流、消息传递简单同步、中断通知
内存开销低(一个整数变量)中等(缓冲区)

六、注意事项

  1. 位数限制configUSE_16_BIT_TICKS会影响事件组可使用的位数。默认 32 位配置下,通常最多 24 个位可用(高 8 位保留给内核使用)。
  2. 中断中使用xEventGroupSetBitsFromISR()不会立即设置位,而是将操作发送到 RTOS 守护任务(需要configUSE_TASK_NOTIFICATIONSconfigUSE_TIMERS开启)。因此它不适用于高频率中断。
  3. 自动清除的原子性:当任务被唤醒并设置xClearOnExit = pdTRUE时,清位和检查条件是在一个临界区内完成的,不会丢失事件。
  4. 超时处理:检查返回值,若超时可能只获得了部分位,需要根据实际逻辑处理。
  5. 不要长期持有调度器锁:在临界区内调用事件组 API 是安全的,但避免在关调度期间长时间等待。

七、调试建议

  • 打印事件组值:printf("Event group value: 0x%08x\n", xEventGroupGetBits(xEventGroup));
  • 使用 FreeRTOS 跟踪宏(configUSE_TRACE_FACILITY)查看任务阻塞状态。
  • 确保所有任务访问事件组时使用正确的句柄。

总结

事件组是 FreeRTOS 中处理多事件组合同步的强大工具:

  • 通过位掩码表示多个独立事件。
  • 支持“与”/“或”等待条件。
  • 可自动清除事件位,简化编程。
  • 适用于任务栅栏、多条件触发、状态机等场景。

代码示例正确地使用了事件组的基本流程,但缺少 BIT_1 的触发源,导致任务永远阻塞。实际使用时请确保所有等待的位都有对应的设置者。

附录

完整代码示例:

/* ========================================================事件组========================================================*//* FreeRTOS三方库 */#include"FreeRTOSConfig.h"#include"FreeRTOS.h"#include"task.h"// vTaskDelay等#include"queue.h"// QueueHandle_tEventGroupHandle_t xEventGroup;#defineBIT_0(1<<0)#defineBIT_1(1<<1)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 都被设置uxBits=xEventGroupWaitBits(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);// 创建事件组xEventGroup=xEventGroupCreate();// 创建任务xTaskCreate(vTaskEvent1,"vTaskEvent1",128,NULL,1,NULL);xTaskCreate(vTaskEvent2,"vTaskEvent2",128,NULL,1,NULL);xTaskCreate(vTaskEvent3,"vTaskEvent3",128,NULL,1,NULL);// 启动调度器(永远不会返回)vTaskStartScheduler();// 理论上程序不会执行到这里,但为了安全,可以加一个死循环while(1){}}

演示效果

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/30 15:33:20

WPS-Zotero插件:3步实现跨平台文献管理无缝对接

WPS-Zotero插件&#xff1a;3步实现跨平台文献管理无缝对接 【免费下载链接】WPS-Zotero An add-on for WPS Writer to integrate with Zotero. 项目地址: https://gitcode.com/gh_mirrors/wp/WPS-Zotero 还在为学术论文的文献引用而烦恼吗&#xff1f;WPS-Zotero插件为…

作者头像 李华
网站建设 2026/4/30 15:29:28

维修工程师必备:手把手教你用AD-HP530ICE仿真器读取ADI BF533 DSP程序(附驱动加载避坑指南)

维修工程师实战&#xff1a;ADI BF533 DSP程序读取全流程与深度避坑指南 当一块搭载ADI Blackfin处理器的工控板因DSP芯片损坏需要更换时&#xff0c;许多维修工程师都会遇到这样的困境&#xff1a;新换的空白芯片无法使设备恢复正常工作。这背后往往是因为原DSP中存储的关键程…

作者头像 李华
网站建设 2026/4/30 15:22:46

终极窗口分辨率调整指南:如何用SRWE打破屏幕限制

终极窗口分辨率调整指南&#xff1a;如何用SRWE打破屏幕限制 【免费下载链接】SRWE Simple Runtime Window Editor 项目地址: https://gitcode.com/gh_mirrors/sr/SRWE 你是否厌倦了应用程序死板的分辨率限制&#xff1f;是否曾为无法调整某些软件窗口大小而烦恼&#x…

作者头像 李华
网站建设 2026/4/30 15:22:46

二维码智能修复指南:QRazyBox如何让损坏的二维码重获新生

二维码智能修复指南&#xff1a;QRazyBox如何让损坏的二维码重获新生 【免费下载链接】qrazybox QR Code Analysis and Recovery Toolkit 项目地址: https://gitcode.com/gh_mirrors/qr/qrazybox 想象一下&#xff0c;你正面临一个令人沮丧的场景&#xff1a;一份重要的…

作者头像 李华