在嵌入式实时操作系统中,任务间的同步与通信是核心问题。FreeRTOS提供了多种机制,如信号量、队列、事件组等。其中,事件组(Event Groups) 是一种非常灵活且高效的同步方式,特别适用于一个任务需要等待多个事件发生的场景。本文将用通俗易懂的语言,带你快速掌握FreeRTOS事件组的使用方法。
1. 什么是事件组?
想象一下,你有一个任务需要等待“温度传感器数据就绪”且 “串口发送完成”两个条件同时满足才能继续执行。如果用信号量,你可能需要多个信号量并小心处理,而事件组可以直接解决这类“多条件等待”的问题。
事件组本质上是一个32位的二进制数,每一位(bit)代表一个独立的事件。任务可以:
- 设置某一位或某几位(表示事件发生)
- 等待某一位或某几位被设置(可以等待任意一位为1,或所有指定位都为1)
- 清除某一位(可选)
事件组允许任务阻塞等待,并支持超时机制,是任务间同步的强大工具。
2. 常用API函数
FreeRTOS提供了以下常用事件组API:
| xEventGroupCreate() | 创建一个事件组 |
| xEventGroupSetBits() | 设置事件组中的指定位(任务级) |
| xEventGroupSetBitsFromISR() | 在中断中设置事件位 |
| xEventGroupWaitBits() | 等待事件组中的指定位被设置 |
| xEventGroupClearBits() | 清除事件组中的指定位 |
| vEventGroupDelete() | 删除事件组 |
本文重点介绍最核心的三个:创建、设置、等待。
3. 示例场景
硬件平台:STM32F103C8T6,使用标准外设库(StdPeriph_Lib) 开发环境:Keil MDK + FreeRTOS V10.x 功能描述:
- 任务 Task1:每1秒设置事件组的BIT0,模拟“传感器数据就绪”事件。
- 任务 Task2:阻塞等待BIT0被设置,一旦等到,翻转LED(PC13),并打印信息。
- 同时任务 Task1 还设置BIT1,但任务 Task2 等待的是BIT0与BIT1同时为1(演示多事件等待)。
为了演示完整,我们将使用xEventGroupWaitBits的xWaitForAllBits参数为pdTRUE,表示必须所有指定位都为1才返回。
4. 完整代码示例
4.1 FreeRTOS配置(FreeRTOSConfig.h关键项)
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( SystemCoreClock )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES ( 5 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 16 )
#define configUSE_TRACE_FACILITY 0
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 8
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_MALLOC_FAILED_HOOK 1
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configGENERATE_RUN_TIME_STATS 0
4.2 主程序(main.c)
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
/* 事件组句柄 */
EventGroupHandle_t xEventGroup;
/* 事件位定义 */
#define EVENT_DATA_READY ( 1 << 0 ) // BIT0
#define EVENT_UART_SENT ( 1 << 1 ) // BIT1
#define EVENT_ALL_BITS ( EVENT_DATA_READY | EVENT_UART_SENT )
/* 任务句柄 */
TaskHandle_t xTask1Handle = NULL;
TaskHandle_t xTask2Handle = NULL;
/* 函数声明 */
static void GPIO_Config(void);
static void Task1_Producer(void *pvParameters);
static void Task2_Consumer(void *pvParameters);
/* 主函数 */
int main(void)
{
/* 配置系统时钟(标准库默认) */
SystemInit();
/* 初始化GPIO(LED) */
GPIO_Config();
/* 创建事件组 */
xEventGroup = xEventGroupCreate();
if (xEventGroup == NULL) {
/* 创建失败,死循环 */
while (1);
}
/* 创建任务 */
xTaskCreate(Task1_Producer, "Producer", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
xTaskCreate(Task2_Consumer, "Consumer", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到这里,说明堆内存不足导致调度器启动失败 */
while (1);
}
/* GPIO初始化:LED连接在PC13 */
static void GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* 默认关闭LED */
GPIO_SetBits(GPIOC, GPIO_Pin_13);
}
/* 任务1:生产者,周期性设置事件位 */
static void Task1_Producer(void *pvParameters)
{
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xPeriod = pdMS_TO_TICKS(1000);
for (;;) {
/* 设置BIT0(数据就绪) */
xEventGroupSetBits(xEventGroup, EVENT_DATA_READY);
/* 模拟一些延迟后设置BIT1(发送完成) */
vTaskDelay(pdMS_TO_TICKS(500));
xEventGroupSetBits(xEventGroup, EVENT_UART_SENT);
/* 等待下一个周期 */
vTaskDelayUntil(&xLastWakeTime, xPeriod);
}
}
/* 任务2:消费者,等待所有事件位都被设置 */
static void Task2_Consumer(void *pvParameters)
{
EventBits_t uxBits;
const TickType_t xTimeout = pdMS_TO_TICKS(2000); // 超时2秒
for (;;) {
/* 等待BIT0和BIT1都被设置(AND条件) */
uxBits = xEventGroupWaitBits(
xEventGroup, // 事件组句柄
EVENT_ALL_BITS, // 等待的位
pdTRUE, // 清除这些位(退出时自动清除)
pdTRUE, // 等待所有位都为1(AND)
xTimeout // 超时时间
);
/* 检查返回结果 */
if ((uxBits & EVENT_ALL_BITS) == EVENT_ALL_BITS) {
/* 所有事件都发生,执行任务(翻转LED) */
GPIO_WriteBit(GPIOC, GPIO_Pin_13,
(BitAction)(1 – GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)));
} else {
/* 超时未等到所有事件,可以记录错误或执行其他操作 */
// 此处简单忽略
}
}
}
4.3 中断服务函数示例(可选)
如果需要在中断中设置事件位,使用xEventGroupSetBitsFromISR:
void EXTI0_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
/* 在中断中设置事件位 */
xEventGroupSetBitsFromISR(xEventGroup, EVENT_DATA_READY, &xHigherPriorityTaskWoken);
/* 清除中断标志 */
EXTI_ClearITPendingBit(EXTI_Line0);
}
/* 如果设置了更高优先级任务就绪,则触发上下文切换 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
5. 代码解析
- 第2参数指定要等待的位(EVENT_ALL_BITS = 0x03)
- 第3参数为pdTRUE表示等待成功后自动清除这些位,否则需手动清除
- 第4参数为pdTRUE表示等待所有指定位都为1(AND),pdFALSE表示等待任意一位为1(OR)
- 第5参数为超时时间,这里设置为2秒。如果超时未等到,返回的实际位可能不满足条件。
6. 注意事项
- 中断中使用:必须使用xEventGroupSetBitsFromISR,并配合portYIELD_FROM_ISR。
- 内存占用:事件组基于RAM,创建后一直占用,删除时需调用vEventGroupDelete。
- 等待位时不能指定优先级继承,因此不适合用于互斥资源保护(应使用互斥信号量)。
- 位数量:事件组是32位,但最高8位(bit24~bit31)被FreeRTOS内部使用,用户只能使用低24位。
- 超时处理:务必检查返回值,避免误判事件已发生。
7. 总结
事件组是FreeRTOS中实现多事件同步的利器,通过简单的位操作即可完成复杂的等待逻辑。本文通过一个简单的生产者-消费者示例,展示了事件组的创建、设置和等待过程。在实际项目中,你可以根据需求灵活使用AND/OR等待、自动清除、超时等功能,让任务间同步更加优雅高效。
希望这篇博客能帮助你快速上手FreeRTOS事件组。如果觉得有用,欢迎点赞收藏!
参考资料 FreeRTOS官方文档 – Event Groups STM32F10x标准外设库




![[FreeRTOS]框架代码介绍+函数讲解-171主机测评](https://www.171host.com/wp-content/uploads/2026/08/20260822074021-6a895265e1708-220x150.png)
