一、核心适用场景(结合实际开发需求)
场景 1:一个任务等待 "多个外设 / 传感器采集完成"(与逻辑 / 或逻辑)
- 需求举例 :你的项目中除了 ADC 采集(如电压传感器),还添加了温湿度传感器(I2C)、光照传感器(SPI),任务需要:
- (与逻辑)所有传感器都采集完成后,才统一处理数据(避免数据不完整);
- (或逻辑)任意一个传感器采集完成后,就及时处理该传感器数据(保证实时性)。
- 事件的作用 :
- 给每个传感器分配一个 "事件位"(如 ADC 采集完成 = bit0,温湿度采集 = bit1,光照采集 = bit2);
- 每个传感器采集完成(中断 / 任务中)后,置位对应的事件位;
- 处理任务调用
xEventGroupWaitBits(),等待 "bit0&bit1&bit2"(与)或 "bit0|bit1|bit2"(或),满足条件后唤醒执行。
- 为什么不用信号量 :若用 3 个信号量,任务需要连续调用 3 次
xSemaphoreTake()(与逻辑),代码繁琐且无法灵活支持 "或逻辑";事件标志组可一次性等待多个位,灵活配置触发条件。
场景 2:多个中断 / 任务 "通知同一任务处理不同事件"
- 需求举例 :你的 STM32 项目中,有 3 个中断需要通知同一个任务:
- 串口接收数据中断(bit0):通知任务解析串口指令;
- ADC 转换完成中断(bit1):通知任务读取 ADC 数据;
- 按键中断(bit2):通知任务响应按键操作。
- 事件的作用 :
- 每个中断服务函数中,通过
xEventGroupSetBitsFromISR()置位对应的事件位; - 处理任务等待这些事件位,触发后根据 "哪几位被置位" 执行对应逻辑(解析指令 / 读 ADC / 响应按键)。
- 每个中断服务函数中,通过
- 为什么不用消息队列:消息队列需定义消息结构体,区分不同事件类型,代码冗余;事件标志组通过 "位" 直接区分事件,操作更简洁,中断中使用也更高效。
场景 3:任务等待 "外设初始化完成" 的多个条件
- 需求举例 :系统启动时,任务需要等待多个外设初始化完成后才开始工作:
- 串口初始化完成(bit0);
- SPI(OLED 屏)初始化完成(bit1);
- 定时器(PWM)初始化完成(bit2)。
- 事件的作用 :
- 每个外设初始化函数执行完后,置位对应的事件位;
- 业务任务等待 "bit0&bit1&bit2" 都置位(所有外设就绪),才开始执行后续逻辑(如 OLED 显示、PWM 输出)。
- 为什么不用全局变量:全局变量需要手动判断多个条件,且存在线程安全问题(需加临界区保护);事件标志组自带原子操作,无需额外处理同步,且支持阻塞等待。
场景 4:"多任务触发同一事件" 的汇总通知
- 需求举例:3 个任务分别处理不同的传感器数据,当任意一个任务检测到 "数据异常" 时,通知报警任务触发蜂鸣器报警;或所有任务都检测 "数据正常" 时,通知 LED 任务点亮正常指示灯。
- 事件的作用 :
- 数据异常:每个任务检测到异常后置位 "bit0"(异常位),报警任务等待 bit0 置位;
- 所有正常:每个任务处理正常后置位自己的事件位(bit1/bit2/bit3),LED 任务等待 "bit1&bit2&bit3" 置位。
- 优势:事件标志组支持 "置位后保持"(默认),即使多个任务多次置位同一 bit,也不会丢失事件,报警任务只会被唤醒一次处理。
二、事件标志组的 "不适用场景"(避免用错)
- 单个事件同步:如 "中断通知任务处理数据",用二进制信号量更简单(事件标志组略显冗余);
- 共享资源保护:如串口、Flash 等共享资源,需用互斥量(事件标志组不具备 "独占访问" 机制);
- 数据传输:需要传递具体数据(如传感器数值、串口指令),用消息队列(事件标志组仅传递 "事件是否发生",不传递数据)。
三、关键使用要点(结合你的开发场景)
- 事件位配置:FreeRTOS 事件标志组支持最多 24 个事件位(32 位系统),每个位对应一个事件,建议用宏定义明确(如
#define EVENT_ADC_DONE (1<<0)); - 中断中使用:中断服务函数中需用
xEventGroupSetBitsFromISR()(中断安全版本),不可用普通版本; - 阻塞等待:任务等待事件时,可设置阻塞时间(如
portMAX_DELAY无限等待),超时后可处理 "等待失败" 逻辑(如重试初始化); - 清零方式:等待事件后可选择 "自动清零触发位"(
pdTRUE)或 "手动清零"(pdFALSE),自动清零更常用(避免重复处理同一事件)。