文章目录
- 前言
- 一、事件组是什么
- 案例
- 1.1 事件组的概念
- 二、事件组的操作
- 2.1 事件组与其他实现同步与互斥方法的区别
- 2.2 创建事件组
- 2.3 删除事件组
- 2.4 设置事件
- 2.5 等待事件
- 2.6 示例代码
- 三、同步点
- 3.1 同步点是什么
- 3.2 设置同步点
- 总结
前言
FreeRTOS是一个广泛应用于嵌入式系统的实时操作系统内核,提供了丰富的功能和组件,以帮助开发者构建可靠的嵌入式应用程序。其中,事件组(Event Groups)是一种重要的机制,用于任务之间的事件通知和同步。本文将重点介绍FreeRTOS中事件组的基础知识,以帮助初学者更好地理解和应用这一功能。
在嵌入式系统中,多个任务可能需要协同工作,彼此之间需要进行事件通知和同步。事件组提供了一种有效的方式来管理和触发事件,从而实现任务之间的同步与通信。通过学习事件组的概念、使用方法以及实际应用场景,开发者可以更好地设计和实现复杂的嵌入式系统。
一、事件组是什么
案例
学校组织秋游,组长在等待:
- 张三:我到了
- 李四:我到了
- 王五:我到了
组长说:好,大家都到齐了,出发!
秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的。
在这个日常生活场景中: - 出发:要等待这 3 个人都到齐,他们是"与"的关系
- 交报告:只需等待这 3 人中的任何一个,他们是"或"的关系
在 FreeRTOS 中,可以使用事件组(event group)来解决这些问题。
1.1 事件组的概念
事件组可以简单的认为就是一个整数,你可以去设置整数的某一位为1代表有事件,设置0为无事件。
你可以去等待一个位或者多个位
下面的为事件组结构体:
typedef struct xEventGroupDefinition
{
EventBits_t uxEventBits;
List_t xTasksWaitingForBits; /*< List of tasks waiting for a bit to be set. */
#if( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxEventGroupNumber;
#endif
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the event group is statically allocated to ensure no attempt is made to free the memory. */
#endif
} EventGroup_t;
其中:uxEventBits
成员为事件组,xTasksWaitingForBits
用来存储等待事件的任务
事件组用一个整数来表示,其中的高 8 位留给内核使用,只能用其他的位来表示事件。
那么这个整数是多少位的?
如果 configUSE_16_BIT_TICKS 是 1,那么这个整数就是 16 位的,低 8 位
用来表示事件
如果 configUSE_16_BIT_TICKS 是 0,那么这个整数就是 32 位的,低 24 位
用来表示事件
configUSE_16_BIT_TICKS 是用来表示 Tick Count 的,怎么会影响事件组?
这只是基于效率来考虑
如果 configUSE_16_BIT_TICKS 是 1,就表示该处理器使用 16 位更高效,所
以事件组也使用 16 位
如果 configUSE_16_BIT_TICKS 是 0,就表示该处理器使用 32 位更高效,所
以事件组也使用 32 位
二、事件组的操作
2.1 事件组与其他实现同步与互斥方法的区别
事件组和队列、信号量等不太一样,主要集中在 2 个地方:
唤醒谁?
队列、信号量:事件发生时,只会唤醒一个任务
事件组:事件发生时,会唤醒所有符号条件的任务,简单地说它有"广播"的
作用
是否清除事件?
队列、信号量:是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了
事件组:被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件以上图为列,事件组的常规操作如下:
先创建事件组
任务 C、D 等待事件:
等待什么事件?可以等待某一位、某些位中的任意一个,也可以等待多位。
简单地说就是"或"、"与"的关系。
得到事件时,要不要清除?可选择清除、不清除。
任务 A、B 产生事件:设置事件组里的某一位、某些位
2.2 创建事件组
我们可以使用下面这个函数进行动态的创建事件组
EventGroupHandle_t xEventGroupCreate( void );
他返回的是一个事件组的handle
我们也可以加个static表示要创建静态的事件组
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );
你只需要提供事件组的Buffer即可。
2.3 删除事件组
我们可以使用下面这个函数删除一个事件组:
void vEventGroupDelete( EventGroupHandle_t xEventGroup );
参数为要删除事件组的handle
2.4 设置事件
在任务中,你可以使用下面这个函数进行设置某一位:
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
参数二怎么填写?
他的定义是这样的:typedef TickType_t EventBits_t;
类型就是uint32_t,如果你设置的是32位的话
我们可以来让他的某一个位为1,代表这个位有事件了
比如我们想让第5位就这样:1<<5
,如果想要两个就使用1 << 5 | 1 << 4
,使用或运算符
如果你需要在中断中使用,函数名称只需要加个ISR
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken );
他的参数3用来告诉调用者,在设置事件组位的过程中是否有更高优先级的任务被唤醒了。
因此,当你调用 xEventGroupSetBitsFromISR 函数时,如果有更高优先级的任务被唤醒了,那么 pxHigherPriorityTaskWoken 参数会被设置为非零值;如果没有更高优先级的任务被唤醒,那么 pxHigherPriorityTaskWoken 参数会被设置为零。这样,在 ISR 结束后,FreeRTOS 就知道是否需要进行任务切换。
2.5 等待事件
我们可以使用下面这个函数来等待某个事件:
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
const TickType_t xTicksToWait );
参数2意义为等待哪些位。参数3为是否在这个函数结束时清楚等待位。参数4为,如果你的参数2等待了多个位,比如0和5,是否要等待这两个位全部有了事件在唤醒任务,还是任意一个都行,他们可以填pdTRUE
或者pdFLASE
2.6 示例代码
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
// 定义事件组句柄
EventGroupHandle_t xEventGroup;
// 定义事件组位
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
// 任务1,等待事件组位 BIT_0
void vTask1(void *pvParameters) {
while (1) {
// 等待事件组位 BIT_0,设置 clear_on_exit 标志位,表示等待后清除
EventBits_t uxBits = xEventGroupWaitBits(xEventGroup, BIT_0, pdTRUE, pdFALSE, portMAX_DELAY);
if (uxBits & BIT_0) {
// 收到 BIT_0 位的信号
// 执行相应操作...
}
}
}
// 任务2,等待事件组位 BIT_1
void vTask2(void *pvParameters) {
while (1) {
// 等待事件组位 BIT_1,设置 clear_on_exit 标志位,表示等待后清除
EventBits_t uxBits = xEventGroupWaitBits(xEventGroup, BIT_1, pdTRUE, pdFALSE, portMAX_DELAY);
if (uxBits & BIT_1) {
// 收到 BIT_1 位的信号
// 执行相应操作...
}
}
}
// 主函数
int main(void) {
// 创建事件组
xEventGroup = xEventGroupCreate();
// 创建任务1,优先级较高
xTaskCreate(vTask1, "Task 1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
// 创建任务2,优先级较低
xTaskCreate(vTask2, "Task 2", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动失败,进行错误处理
for (;;);
return 0;
}
三、同步点
3.1 同步点是什么
有一个事情需要多个任务协同,比如:
任务 A:炒菜
任务 B:买酒
任务 C:摆台
A、B、C 做好自己的事后,还要等别人做完;大家一起做完,才可开饭
同步点就是如果你做好了,你需要等待其他和你一起干这个事情的任务,如果没有做完就接着做,直到做完,当需要同步的任务都ok了,就进行下一步的执行,少一个没做完都不行
3.2 设置同步点
我们可以使用下面这个函数设置一个同步点:
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait );
参数2为你要设置哪些事件,表示你完成了这个事件
参数3表示你要等待哪些事件完成。
当你设置的参数3事件都ok之后,他就会往下走
总结
事件组作为FreeRTOS的一个重要特性,为嵌入式系统的开发提供了强大的事件管理机制。通过使用事件组,开发者可以轻松地实现任务之间的事件通知和同步,提高系统的灵活性和效率。在实际应用中,合理利用事件组可以简化任务之间的协作逻辑,使系统更加可靠和稳定。希望本文能够为初学者提供一个清晰的入门指南,让大家更好地利用FreeRTOS的事件组功能进行嵌入式开发。