【STM32CubeIDE实战教程】FreeRTOS事件组的全面指南(附代码示例)

1. 引言

在现代嵌入式系统开发中,实时操作系统(RTOS)已成为复杂项目的重要组成部分。FreeRTOS作为一款开源的实时操作系统,因其轻量级、可裁剪性强和丰富的功能特性而广受欢迎。本文将重点介绍FreeRTOS中的一个重要通信机制——事件组(Event Groups),并展示如何在STM32CubeIDE环境中使用它。

2. 事件标志组介绍

2.1 什么是事件标志组

事件标志组是FreeRTOS提供的一种任务间通信机制,它允许任务等待一个或多个事件的发生。每个事件由一个位(bit)表示,因此一个事件组可以同时跟踪多个事件的状态。

2.2 事件组的优势

  • 多事件等待:任务可以同时等待多个事件

  • 事件组合:支持"与"和"或"逻辑

  • 高效通信:相比队列和信号量,事件组在某些场景下更高效

  • 广播机制:可以同时通知多个等待任务

3. 应用场景

3.1 典型应用场景

  1. 多任务同步:多个任务需要等待某些条件同时满足

  2. 事件触发:中断服务程序(ISR)通知任务事件发生

  3. 状态监控:监控多个设备或传感器的状态变化

  4. 复杂条件等待:等待"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 事件组工作原理

  1. 事件设置:任务或中断可以设置事件组中的某些位

  2. 事件等待:任务可以等待事件组中的某些位被设置

  3. 事件清除:任务可以清除已处理的事件标志

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 实验环境搭建

  1. 打开STM32CubeIDE,创建新工程

  2. 选择正确的STM32芯片型号

  3. 在Middleware中启用FreeRTOS

  4. 配置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 实验现象分析

  1. 三个传感器任务周期性地设置各自的事件位

  2. 控制任务等待所有三个事件位和按键任务都被设置

  3. 当所有条件满足时,控制任务执行相应操作

  4. 事件位被清除,等待下一轮条件满足

8. 注意事项与最佳实践

8.1 使用注意事项

  1. 事件位规划:合理规划事件位的使用,避免冲突

  2. 清除策略:明确何时清除事件位,避免丢失事件

  3. 优先级考虑:高优先级任务可能"饿死"低优先级任务

  4. 中断安全:在ISR中使用xEventGroupSetBitsFromISR

8.2 最佳实践

  1. 定义宏:为事件位定义有意义的宏名称

  2. 文档记录:记录每个事件位的含义和使用场景

  3. 错误处理:检查事件组创建是否成功

  4. 性能考虑:对于高频事件,考虑其他通信机制

9. 与其他通信机制比较

特性事件组队列信号量任务通知
多事件支持有限
广播能力
内存占用中等较高最低
灵活性
适用场景多条件等待数据传输简单同步高效通知

10. 总结

FreeRTOS的事件组提供了一种高效、灵活的任务间通信机制,特别适合需要等待多个条件或事件的场景。通过STM32CubeIDE,我们可以方便地在STM32微控制器上实现基于事件组的任务同步与通信。合理使用事件组可以简化复杂系统的设计,提高系统的响应能力和效率。

希望这篇博客能帮助您理解和使用FreeRTOS中的事件组功能。在实际项目中,根据具体需求选择最合适的通信机制,并合理设计事件位的使用策略,将有助于构建高效可靠的嵌入式系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值