一、事件组概念
事件组是FreeRTOS提供的多任务同步、多事件标志等待内核对象,本质是一组按位管理的事件标志位(bit),由一个无符号整数存储标志:
1.每一个bit代表一件独立事件(bit0=事件0、bit1=事件1......);
2.任务可置位事件、等待一组事件、清除事件位;
3.支持两种等待逻辑:等待所有指定位全部置1(与逻辑)、等待任意一个指定位置1(或逻辑);
4.优势:单个事件组可替代多个二值信号量,简化多事件同步场景。
限制:
默认事件组存储类型:EventBits_t(16位/32位,由configUSE_16_BIT_TICKS决定,一般32位,最多32个独立事件);
中断中只能调用置位事件API,不能阻塞等待事件;
等待事件时可选择自动清除匹配到的事件位或保留标志位。
二、数据结构
1.事件组句柄结构体EventGroup_t

uxEventBits:核心存储,全局共享事件状态;
xTasksWaitingForBits:阻塞链表,所有等待事件未满足的任务挂在此链表,内核统一唤醒。
三、API与底层行为
1.创建事件组xEventGroupCreate()
1.从堆分配EventGroup_t内存;
2.uxEventBits初始化为0(所有事件清零);
3.初始化阻塞任务链表;
4.返回句柄,失败返回`NULL`。
2.置位事件xEventGroupSetBits()中断版本xEventGroupSetBitsFromISR()
完整执行流程:
1.进入临界区保护(防止多任务并发修改标志);
2.按入参uxBitsToSet对uxEventBits做按位或,对应bit置1
uxEventBits |= uxBitsToSet;
3.遍历阻塞链表xTasksWaitingForBits,逐个检查每个等待任务的等待条件:
任务保存了两个参数:等待掩码uxBitsWait、等待模式(全部匹配/任意匹配);
模式1(pdTRUE,等待所有位):(uxEventBits & uxBitsWait) == uxBitsWait`
模式2(pdFALSE,等待任意位):(uxEventBits & uxBitsWait) != 0
4.对条件满足的任务:
从阻塞链表移除,加入就绪链表;
若任务设置了「自动清除标志位」,匹配完成后清除对应bit;
5.退出临界区,触发任务调度(若有高优先级任务就绪则切换上下文)。
关键点:一次置位多个bit时,会一次性唤醒所有满足条件的等待任务,支持一对多同步。
3.等待事件xEventGroupWaitBits()
函数原型:

内部执行机制:
1.临界区读取当前事件标志uxEventBits;
2.判断当前标志是否满足等待条件:
满足:直接返回当前事件值,若xClearOnExit=pdTRUE则清除匹配 bit;不阻塞;
不满足:进入阻塞流程;
3.阻塞流程:
将当前任务、等待掩码、等待模式存入任务TCB;
任务从就绪链表移出,加入事件组阻塞链表;
设置阻塞超时时间,退出临界区;
触发任务调度,CPU切换至其他就绪任务;
4.唤醒/超时返回:
被置位API唤醒:条件满足,按参数决定是否清除bit,返回事件标志;
超时自动唤醒:返回当前事件标志(未满足条件)。
自动清除标志的两种场景差异:
1.xClearOnExit = pdTRUE:任务拿到事件后自动清位,独占式事件(典型:任务接收单次触发信号);
2.xClearOnExit = pdFALSE:标志永久保留,多任务可同时读取同一个事件位,共享式事件(典型:状态标记,多个任务读取同一设备就绪标志)。
4.手动清除事件xEventGroupClearBits()
直接对uxEventBits做按位与取反,清除指定bit:
uxEventBits &= (~uxBitsToClear);
仅修改标志,不会唤醒任何任务,仅主动清零状态。
5.获取当前事件xEventGroupGetBits()
临界区内读取uxEventBits,无阻塞,仅查询状态。
四、事件组同步任务实验

配置:




五、阻塞与唤醒流程
场景:任务A等待bit0+bit1全部置位;任务B置bit0;任务C置bit1
1.系统启动,事件组bits=0;
1.任务A调用xEventGroupWaitBits(bits0|bits1, auto_clear=1, wait_all=1, portMAX_DELAY);
当前bits不满足,任务A进入事件组阻塞链表,CPU切走;
3.任务B执行xEventGroupSetBits(bit0):
bits变为0b01;遍历阻塞链表,检查任务A条件:缺少bit1,不唤醒;函数返回;
4.任务C执行xEventGroupSetBits(bit1):
bits变为0b11;遍历阻塞链表,任务A条件全部匹配;
将A移出阻塞链表、放入就绪链表;auto\_clear开启,自动清零bit0、bit1;
5.调度器切换到任务A,xEventGroupWaitBits返回匹配的事件掩码,任务A继续执行。
六、两种等待模式对比
设当前事件:bits = 0b1010(bit1、bit3置1),等待掩码wait = 0b1001(bit0、bit3)
1.等待任意(xWaitForAllBits=pdFALSE)
0b1010 & 0b1001 = 0b1000 != 0 → 条件成立,立即唤醒;
只要掩码中任意一个 bit 存在即可。
2.等待全部(xWaitForAllBits=pdTRUE)
0b1010 & 0b1001 = 0b1000 != 0b1001 → 条件不成立,继续阻塞;
掩码内所有 bit 必须同时置 1 才满足。
七、中断中使用限制
仅允许xEventGroupSetBitsFromISR,禁止xEventGroupWaitBits:
1.中断上下文不能阻塞,无任务调度机制,阻塞会直接死机;
2.中断中只能修改事件标志,通过置位被动唤醒阻塞任务;
3.中断置位底层:使用中断保护、尾部回调xHigherPriorityTaskWoken,需在ISR末尾手动portYIELD_FROM_ISR(xHigherPriorityTaskWoken)触发调度。
八、事件组与信号量的区别
1.二值信号量:仅单一同步源,只能标记"有/无",无法区分多种事件;
2.计数信号量:资源计数,用于资源占用释放;
3.事件组:多bit状态标记,同时等待多个事件组合,支持或/与逻辑,适合多条件同步(多传感器就绪、多按键触发、多外设中断汇总)。
使用场景:
多中断汇总:UART、ADC、DMA 中断统一置对应 bit,单个任务等待所有中断标志;
多条件启动:必须收到传感器数据和按键触发两个事件才执行业务;
多任务共享状态:全局设备就绪标志,多个读取任务同时查询。
九、工作机制总结
1.存储层:32位bit数组统一管理所有事件状态;
2.修改层:任务/中断可置位、清零、查询标志;修改后遍历阻塞任务链表校验等待条件;
3.阻塞层:条件不满足的任务挂载专属阻塞链表,释放CPU;
4.唤醒层:置位事件时批量校验,匹配条件任务转入就绪态,触发调度;
5.逻辑层:内置等待全部/等待任意两种位匹配算法,支持自动清除事件标记,适配多事件同步场景。