1. 引言
在现代嵌入式系统开发中,实时操作系统(RTOS)已成为复杂项目的重要组成部分。FreeRTOS作为一款开源的实时操作系统,因其轻量级、可裁剪性强和丰富的功能特性而广受欢迎。本文将重点介绍FreeRTOS中的一个重要通信机制——事件组(Event Groups),并展示如何在STM32CubeIDE环境中使用它。
2. 事件标志组介绍
2.1 什么是事件标志组
事件标志组是FreeRTOS提供的一种任务间通信机制,它允许任务等待一个或多个事件的发生。每个事件由一个位(bit)表示,因此一个事件组可以同时跟踪多个事件的状态。
2.2 事件组的优势
-
多事件等待:任务可以同时等待多个事件
-
事件组合:支持"与"和"或"逻辑
-
高效通信:相比队列和信号量,事件组在某些场景下更高效
-
广播机制:可以同时通知多个等待任务
3. 应用场景
3.1 典型应用场景
-
多任务同步:多个任务需要等待某些条件同时满足
-
事件触发:中断服务程序(ISR)通知任务事件发生
-
状态监控:监控多个设备或传感器的状态变化
-
复杂条件等待:等待"A或B发生,且C不发生"等复杂条件
3.2 实际案例
-
智能家居系统中,主控任务需要等待"门磁传感器触发"或"红外传感器触发"或"烟雾报警触发"
-
工业控制中,设备需要等待"温度达标"且"压力达标"且"液位达标"才能开始工作
4. 运作机制
4.1 事件组数据结构
FreeRTOS中的事件组实际上是一个无符号的16/32位整数(EventBits_t),每一位代表一个事件标志。如果宏 configUSE_16_BIT_TICKS 定义为 1,那么 EventBits_t 就是16 位的 ,其中有 8 个位 用来存储事件组 , 如果宏 configUSE_16_BIT_TICKS 定义为 0,那么 EventBits_t 就是 32 位的,其中有 24 个位用来存储事件组,每一位代表一个事件的发生与否,利用逻辑或、逻辑与等实现不同事件的不同唤醒处理。在 STM32 中,可以在FreeRTOSConfig.h文件中设置宏,可以看见默认宏 configUSE_16_BIT_TICKS 定义为 0, EventBits_t 为 32 位。
4.2 事件组工作原理
-
事件设置:任务或中断可以设置事件组中的某些位
-
事件等待:任务可以等待事件组中的某些位被设置
-
事件清除:任务可以清除已处理的事件标志
4.3 事件组与任务状态
当任务等待的事件未发生时,任务会进入阻塞状态;当事件发生时,等待的任务会被唤醒。
5. 事件控制块
5.1 EventGroupHandle_t
在FreeRTOS中,事件组通过EventGroupHandle_t
类型的句柄来引用:
5.2 事件组控制块结构
虽然FreeRTOS没有直接暴露事件组控制块的结构,但其内部通常包含:
-
当前事件标志值
-
等待该事件组的任务列表
-
可能的同步原语
6. 事件组函数介绍
6.1 创建事件组xEventGroupCreate(void);
//函数原型
EventGroupHandle_t xEventGroupCreate(void);
//示例
EventGroupHandle_t xMyEventGroup = xEventGroupCreate();
if(xMyEventGroup == NULL) {
// 创建失败处理
}
6.2 设置事件位xEventGroupSetBits();
//函数原型
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet);
//示例
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
xEventGroupSetBits(xMyEventGroup, BIT_0 | BIT_1);
6.3 清除事件位xEventGroupClearBits();
//函数原型
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear);
//示例
xEventGroupClearBits(xMyEventGroup, BIT_0);
6.4 等待事件位xEventGroupWaitBits();
参数说明:
-
uxBitsToWaitFor
: 要等待的事件位 -
xClearOnExit
: 是否在退出时清除等待的事件位 -
xWaitForAllBits
: 是否等待所有指定事件位都置位 -
xTicksToWait
: 超时时间
//函数原型
EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait);
//示例
EventBits_t uxBits = xEventGroupWaitBits(
xMyEventGroup, // 事件组句柄
BIT_0 | BIT_1, // 等待BIT_0和BIT_1
pdTRUE, // 退出时清除这些位
pdTRUE, // 等待所有位都置位
portMAX_DELAY); // 无限等待
)
6.5 从中断中设置事件位xEventGroupSetBitsFromISR();
//函数原型
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken);
7. 事件实验
本文事件标志组实验是在FreeRTOS 中创建了多个任务,模拟多个传感器任务周期性地设置各自的事件位,以及等待事件任务。只有当所有传感器都已设置自己的事件位,以及模拟人工按键确认键后,等待事件任务则判断所有事件均已发生,输出相应信息,LED进行翻转,等待事件任务的等待时间是 portMAX_DELAY,一直在等待事件的发生,等待到事件之后清除对应的事件标志位。
7.1 实验环境搭建
-
打开STM32CubeIDE,创建新工程
-
选择正确的STM32芯片型号
-
在Middleware中启用FreeRTOS
-
配置FreeRTOS参数(堆大小、任务等)
7.2 实验代码实现
7.2.1 定义事件位和事件组
#define KEY1_OK_BIT (1 << 0) //设置事件掩码的位0
#define TEMPERATURE_OK_BIT (1 << 1) //设置事件掩码的位1
#define PRESSURE_OK_BIT (1 << 2) //设置事件掩码的位2
#define LEVEL_OK_BIT (1 << 3) //设置事件掩码的位3
7.2.2 创建任务和事件组
分别创建模拟按键任务、模拟温度传感器任务、模拟压力传感器任务、模拟液位传感器任务、控制事件组任务。STM32CubeIDE创建任务可参考文章【STM32CubeIDE实战教程】FreeRTOS动态任务创建详解(附代码示例)
创建事件组
STM32CubeIDE生成代码示例
Task1Handle = osThreadNew(Key_Task, NULL, &Task1_attributes);
Task2Handle = osThreadNew(Control_Task, NULL, &Task2_attributes);
Task3Handle = osThreadNew(Temperature_Task, NULL, &Task3_attributes);
Task4Handle = osThreadNew(Pressure_Task4, NULL, &Task4_attributes);
Task5Handle = osThreadNew(Level_Task5, NULL, &Task5_attributes);
myEventHandle = osEventFlagsNew(&myEvent_attributes);
//事件组创建成功
if (myEventHandle != NULL) {
printf("创建事件成功\n");
}
7.2.3 模拟传感器任务
//模拟按键任务
void Key_Task(void *argument) {
/* USER CODE BEGIN Key_Task */
/* Infinite loop */
for (;;) {
if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == GPIO_PIN_RESET) //如果KEY1被单击
{
printf("KEY1 OK!\n");
/* 触发一个事件 */
xEventGroupSetBits(myEventHandle, KEY1_OK_BIT);
}
vTaskDelay(200); //每200ms扫描一次
}
/* USER CODE END Key_Task */
}
void Temperature_Task(void *argument) {
/* USER CODE BEGIN Temperature_Task */
/* Infinite loop */
for (;;) {
// 模拟温度传感器
vTaskDelay(pdMS_TO_TICKS(1000));
printf("Temperature OK!\n");
xEventGroupSetBits(myEventHandle, TEMPERATURE_OK_BIT);
}
/* USER CODE END Temperature_Task */
}
void Pressure_Task4(void *argument) {
/* USER CODE BEGIN Pressure_Task4 */
/* Infinite loop */
for (;;) {
// 模拟压力传感器
vTaskDelay(pdMS_TO_TICKS(1500));
printf("Pressure OK!\n");
xEventGroupSetBits(myEventHandle, PRESSURE_OK_BIT);
}
/* USER CODE END Pressure_Task4 */
}
void Level_Task5(void *argument) {
/* USER CODE BEGIN Level_Task5 */
/* Infinite loop */
for (;;) {
// 模拟液位传感器
vTaskDelay(pdMS_TO_TICKS(2000));
printf("Level OK!\n");
xEventGroupSetBits(myEventHandle, LEVEL_OK_BIT);
}
/* USER CODE END Level_Task5 */
}
7.2.4 控制任务
void Control_Task(void *argument) {
/* USER CODE BEGIN Control_Task */
/* Infinite loop */
EventBits_t uxBits;
for (;;) {
printf("ControlTask waiting for all conditions...\n");
// 等待所有条件满足
uxBits = xEventGroupWaitBits(myEventHandle,
TEMPERATURE_OK_BIT | PRESSURE_OK_BIT | LEVEL_OK_BIT | KEY1_OK_BIT,
pdTRUE, // 退出时清除这些位
pdTRUE, // 等待所有位
portMAX_DELAY);
if ((uxBits
& (TEMPERATURE_OK_BIT | PRESSURE_OK_BIT | LEVEL_OK_BIT
| KEY1_OK_BIT))
== (TEMPERATURE_OK_BIT | PRESSURE_OK_BIT | LEVEL_OK_BIT
| KEY1_OK_BIT)) {
printf("All conditions met! Starting process...\n");
// 执行控制逻辑
}
}
/* USER CODE END Control_Task */
}
7.3 实验现象分析
-
三个传感器任务周期性地设置各自的事件位
-
控制任务等待所有三个事件位和按键任务都被设置
-
当所有条件满足时,控制任务执行相应操作
-
事件位被清除,等待下一轮条件满足
8. 注意事项与最佳实践
8.1 使用注意事项
-
事件位规划:合理规划事件位的使用,避免冲突
-
清除策略:明确何时清除事件位,避免丢失事件
-
优先级考虑:高优先级任务可能"饿死"低优先级任务
-
中断安全:在ISR中使用
xEventGroupSetBitsFromISR
8.2 最佳实践
-
定义宏:为事件位定义有意义的宏名称
-
文档记录:记录每个事件位的含义和使用场景
-
错误处理:检查事件组创建是否成功
-
性能考虑:对于高频事件,考虑其他通信机制
9. 与其他通信机制比较
特性 | 事件组 | 队列 | 信号量 | 任务通知 |
---|---|---|---|---|
多事件支持 | 是 | 否 | 否 | 有限 |
广播能力 | 是 | 否 | 否 | 否 |
内存占用 | 中等 | 较高 | 低 | 最低 |
灵活性 | 高 | 高 | 中 | 中 |
适用场景 | 多条件等待 | 数据传输 | 简单同步 | 高效通知 |
10. 总结
FreeRTOS的事件组提供了一种高效、灵活的任务间通信机制,特别适合需要等待多个条件或事件的场景。通过STM32CubeIDE,我们可以方便地在STM32微控制器上实现基于事件组的任务同步与通信。合理使用事件组可以简化复杂系统的设计,提高系统的响应能力和效率。
希望这篇博客能帮助您理解和使用FreeRTOS中的事件组功能。在实际项目中,根据具体需求选择最合适的通信机制,并合理设计事件位的使用策略,将有助于构建高效可靠的嵌入式系统。