FreeRTOS 事件标志组详解
- 一、Freertos事件标志组详解
-
- 1、概念与作用
- [2、 核心特性](#2、 核心特性)
- [3、关键 API 函数详解](#3、关键 API 函数详解)
-
- [3.1 、创建事件标志组](#3.1 、创建事件标志组)
- [3.2、 设置事件标志位](#3.2、 设置事件标志位)
- [3.3 、设置事件标志位(中断服务程序中使用)](#3.3 、设置事件标志位(中断服务程序中使用))
- [3.4 、清除事件标志位](#3.4 、清除事件标志位)
- [3.5 、等待事件标志位(核心)](#3.5 、等待事件标志位(核心))
- 4、注意事项与最佳实践
- [5、 总结](#5、 总结)
- 二、代码示例

一、Freertos事件标志组详解
1、概念与作用
事件标志组是 FreeRTOS 提供的一种任务间通信和同步机制。其核心思想是使用一组二进制标志位(bit)来表示不同的事件状态(例如:事件发生/未发生)。任务可以:
- 设置标志位(Set Bits): 当一个任务完成某项工作或检测到某个事件发生时,它可以设置(置 1)事件组中对应的标志位,通知其他等待该事件的任务。
- 清除标志位(Clear Bits): 任务可以清除(置 0)事件组中的标志位,通常表示事件尚未发生或重新等待。
- 等待标志位(Wait for Bits): 任务可以阻塞自身,等待事件组中一个或多个特定的标志位被设置(满足某种条件)。这是事件组实现同步的关键。
主要作用:
- 任务同步: 允许多个任务等待一个或多个事件的发生。例如,任务 A 等待按键按下(事件 1)且数据接收完成(事件 2)后再继续执行。
- 事件通知: 一个任务(或中断服务程序)可以通知其他任务,某个或多个事件已经发生。
- 简化通信: 相比于使用多个信号量或队列来分别表示不同事件,事件标志组使用一个数据结构即可管理多个事件状态,更高效。
2、 核心特性
- 位操作 : 事件标志组本质上是一个
EventBits_t类型的变量(通常是uint32_t),它的每一位(bit)都代表一个独立的事件标志。FreeRTOS 最多支持 24 个事件标志(受限于configUSE_16_BIT_TICKS配置)。 - 原子操作: FreeRTOS 确保了设置、清除和等待标志位等操作是原子的。这意味着这些操作在执行过程中不会被其他任务或中断打断,保证了数据的一致性,避免了竞态条件。
- 灵活的等待条件 : 任务在等待标志位时,可以指定需要哪些位被设置,并且可以选择逻辑关系:
- 逻辑或 (OR) : 等待的位中任意一个 被设置即可解除阻塞。
xEventGroupWaitBits(..., xBitsToWaitFor, pdTRUE, pdFALSE, ...)。 - 逻辑与 (AND) : 等待的位全部 被设置才能解除阻塞。
xEventGroupWaitBits(..., xBitsToWaitFor, pdTRUE, pdTRUE, ...)。 - 自动清除 : 任务可以选择在成功等待到所需标志位后,是否自动清除这些位(方便下次等待)。通过
xClearOnExit参数控制。
- 逻辑或 (OR) : 等待的位中任意一个 被设置即可解除阻塞。
- 阻塞时间 : 任务可以指定一个超时时间(
xTicksToWait),如果在指定时间内没有等到所需的事件标志组合,任务将解除阻塞并返回当前事件组的状态(可能包含其他已设置的位)。
3、关键 API 函数详解
3.1 、创建事件标志组
c
EventGroupHandle_t xEventGroupCreate(void);
- 功能: 动态创建一个新的事件标志组。
- 返回值 :
- 成功: 返回事件标志组的句柄(
EventGroupHandle_t)。 - 失败: 返回
NULL(通常是因为内存不足)。
- 成功: 返回事件标志组的句柄(
- 初始状态: 所有事件标志位初始化为 0(未设置)。
3.2、 设置事件标志位
c
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet);
- 功能 : 设置指定事件标志组中的一个或多个标志位(置 1)。此函数可以在任务或中断中使用,但中断中应使用带 FromISR 的版本。
- 参数 :
xEventGroup: 目标事件标志组的句柄。uxBitsToSet: 一个位掩码(bitmask),指定要设置哪些位。例如,要设置位 0 和位 2,则uxBitsToSet = (1 << 0) | (1 << 2)。
- 返回值: 设置操作完成后事件标志组的新值(所有位的状态)。注意,这个值可能包含其他任务在本次设置操作期间或之后设置的位。
- 行为 :
- 如果设置操作导致某个正在等待的任务所要求的条件得到满足,则该任务将被解除阻塞。
- 该操作是原子的。
3.3 、设置事件标志位(中断服务程序中使用)
c
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken);
- 功能: 在中断服务程序(ISR)中设置事件标志位。
- 参数 :
xEventGroup: 目标事件标志组的句柄。uxBitsToSet: 位掩码,指定要设置哪些位。pxHigherPriorityTaskWoken: 指向一个BaseType_t变量的指针。函数会将这个变量设置为pdTRUE,如果设置操作导致一个优先级高于 当前被中断任务的任务就绪(解除阻塞)。如果不需要此信息,可传入NULL。
- 返回值 :
pdPASS: 设置请求已成功发送到守护任务(Daemon Task)。pdFAIL: 设置请求未能发送到守护任务(通常是因为守护任务的事件队列已满)。
- 行为 :
- 中断中不能直接调用
xEventGroupSetBits(),因为其中可能包含阻塞操作(如唤醒任务)。 xEventGroupSetBitsFromISR()会将设置请求发送给一个特殊的 FreeRTOS 守护任务(通常是定时器服务任务)。该守护任务稍后会实际执行设置操作。- 因此,设置操作在中断中不是即时生效 的,而是在守护任务中延迟执行。
- 如果
pxHigherPriorityTaskWoken被设为pdTRUE,中断退出前应进行一次上下文切换(portYIELD_FROM_ISR())。
- 中断中不能直接调用
3.4 、清除事件标志位
c
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear);
- 功能: 清除指定事件标志组中的一个或多个标志位(置 0)。
- 参数 :
xEventGroup: 目标事件标志组的句柄。uxBitsToClear: 位掩码,指定要清除哪些位。例如,清除位 1 和位 3,uxBitsToClear = (1 << 1) | (1 << 3)。
- 返回值: 清除操作完成后事件标志组的新值。
- 行为 :
- 该操作是原子的。
- 清除操作不会解除任何等待任务(因为清除通常表示事件未发生)。
3.5 、等待事件标志位(核心)
c
EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait);
- 功能: 阻塞调用任务,等待指定事件标志组中的一个或多个标志位满足特定条件。
- 参数 :
xEventGroup: 要等待的事件标志组的句柄。uxBitsToWaitFor: 位掩码,指定任务关心哪些位(要等待哪些事件)。xClearOnExit:- 如果设置为
pdTRUE: 当函数因为满足等待条件而返回时(不是因为超时),它会自动清除uxBitsToWaitFor中指定的那些位(无论是否设置了xWaitForAllBits)。 - 如果设置为
pdFALSE: 函数返回时不会改变事件组中的任何位。
- 如果设置为
xWaitForAllBits:- 如果设置为
pdTRUE: 任务将等待uxBitsToWaitFor中指定的所有位都被设置(逻辑与 AND)。 - 如果设置为
pdFALSE: 任务将等待uxBitsToWaitFor中指定的位中至少一个被设置(逻辑或 OR)。
- 如果设置为
xTicksToWait: 任务愿意阻塞等待的最大时间(以系统节拍周期为单位)。可以使用portMAX_DELAY表示无限期等待(需确保configUSE_TIMEOUTS为 1)。
- 返回值 :
- 如果满足等待条件: 返回事件组的值(此时满足条件)。如果
xClearOnExit是pdTRUE,则返回的是清除指定位之前的值。 - 如果超时: 返回事件组当前的值(可能包含一些已设置的位,但不满足等待条件)。
- 如果满足等待条件: 返回事件组的值(此时满足条件)。如果
- 行为 :
- 该函数只能在任务中调用,不能在中断中使用。
- 在调用此函数时,如果事件组的状态已经满足了等待条件(即
uxBitsToWaitFor指定的位根据xWaitForAllBits的逻辑已经满足),则函数会立即返回,不会阻塞任务。 - 如果不满足条件,任务将进入阻塞状态,直到满足条件、超时或其他任务/中断设置了标志位使其满足条件。
- 操作是原子的。
4、注意事项与最佳实践
- 位选择: 仔细规划每个事件标志位的含义。避免位冲突。
- 清除时机 : 理解
xClearOnExit的作用。如果多个任务等待同一个事件,自动清除可能不合适(第一个任务清除后,后续任务就等不到了)。这种情况下,可能需要由设置事件的任务或在更高层逻辑中手动清除。 - 中断中的使用 : 务必在中断中使用
xEventGroupSetBitsFromISR而不是xEventGroupSetBits。注意其延迟执行的特性。 - 优先级反转: 虽然事件标志组本身不会导致优先级反转,但在高优先级任务长期占用事件组(例如长时间等待)而低优先级任务需要设置事件时,也可能间接影响。需合理设计任务优先级。
- 调试 : 使用 FreeRTOS 的跟踪功能(如
configUSE_TRACE_FACILITY)可以帮助调试事件标志组的状态变化。 - 性能: 事件标志组操作通常非常高效,因为本质上是位操作和任务状态管理。
5、 总结
FreeRTOS 的事件标志组是一种强大且灵活的任务同步机制,特别适合于需要等待多个事件发生的场景。通过位操作、原子性保证以及灵活的等待条件(OR/AND、自动清除),它为复杂的任务协调提供了简洁高效的解决方案。理解其核心 API(Create, SetBits, SetBitsFromISR, ClearBits, WaitBits)的工作原理和使用场景是有效利用该机制的关键。
二、代码示例
c
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
// 事件标志组句柄
EventGroupHandle_t xEventGroup;
// 事件位定义
#define BIT_SENSOR_READY (1 << 0)
#define BIT_KEY_PRESSED (1 << 1)
#define BIT_UART_RECV_DONE (1 << 2)
// ==================================================
// 任务1:等待【所有】事件:BIT_SENSOR_READY | BIT_KEY_PRESSED
// ==================================================
void vTask_Wait_All(void *pvParameters)
{
EventBits_t uxBits;
while (1)
{
// 等待 BIT0 + BIT1 同时满足
// pdTRUE = 满足后自动清除这两个位
// pdFALSE = 等待所有位(逻辑与)
uxBits = xEventGroupWaitBits(
xEventGroup,
BIT_SENSOR_READY | BIT_KEY_PRESSED,
pdTRUE,
pdFALSE,
portMAX_DELAY
);
if ((uxBits & (BIT_SENSOR_READY | BIT_KEY_PRESSED)) != 0)
{
printf("任务All:传感器就绪 + 按键按下,开始处理...\r\n");
}
}
}
// ==================================================
// 任务2:等待【任意】事件:BIT_SENSOR_READY 或 BIT_UART_RECV_DONE
// ==================================================
void vTask_Wait_Any(void *pvParameters)
{
EventBits_t uxBits;
while (1)
{
// 等待 BIT0 或 BIT2 任意一个
// pdTRUE = 满足后自动清除触发的位
// pdTRUE = 等待任意位(逻辑或)
uxBits = xEventGroupWaitBits(
xEventGroup,
BIT_SENSOR_READY | BIT_UART_RECV_DONE,
pdTRUE,
pdTRUE,
portMAX_DELAY
);
if ((uxBits & BIT_SENSOR_READY) != 0)
{
printf("任务Any:传感器数据就绪\r\n");
}
if ((uxBits & BIT_UART_RECV_DONE) != 0)
{
printf("任务Any:串口接收完成\r\n");
}
}
}
// ==================================================
// 任务3:模拟产生各种事件(设置标志位)
// ==================================================
void vEventSetterTask(void *pvParameters)
{
while (1)
{
// 1. 模拟:传感器数据就绪
vTaskDelay(pdMS_TO_TICKS(1000));
xEventGroupSetBits(xEventGroup, BIT_SENSOR_READY);
printf("设置:传感器就绪 BIT0\r\n");
// 2. 模拟:串口接收完成
vTaskDelay(pdMS_TO_TICKS(1500));
xEventGroupSetBits(xEventGroup, BIT_UART_RECV_DONE);
printf("设置:串口完成 BIT2\r\n");
// 3. 模拟:按键按下
vTaskDelay(pdMS_TO_TICKS(2000));
xEventGroupSetBits(xEventGroup, BIT_KEY_PRESSED);
printf("设置:按键按下 BIT1\r\n");
// 循环往复
}
}
// ==================================================
// main 函数
// ==================================================
int main(void)
{
// 创建事件组
xEventGroup = xEventGroupCreate();
if (xEventGroup != NULL)
{
// 创建任务
xTaskCreate(vTask_Wait_All, "TaskAll", 1024, NULL, 2, NULL);
xTaskCreate(vTask_Wait_Any, "TaskAny", 1024, NULL, 2, NULL);
xTaskCreate(vEventSetterTask,"SetEvent", 1024, NULL, 1, NULL);
}
while (1);
}
