一、简介
1、 基本概念
事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。 与信号量不同的是,它可以实现一对多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。
每一个事件组只需要很少的 RAM 空间来保存事件组的状态。事件组存储在一个 EventBits_t 类型的变量中,该变量在事件组结构体中定义 。在 STM32 中 , 我们一般 configUSE_16_BIT_TICKS 定义为 0,那么 uxEventBits 是 32 位的,有 24 个位用来实现事 件标志组。每一位代表一个事件,任务通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,形成一个事件组。 事件的“逻辑或”也被称作是独立型同步,指的是任务感兴趣的所有事件任一件发生即可被唤醒;事件“逻辑与”则被称为是关联型同步,指的是任务感兴趣的若干事件都发生时才被唤醒,并且事件发生的时间可以不同步。
多任务环境下,任务、中断之间往往需要同步操作,一个事件发生会告知等待中的任务,即形成一个任务与任务、中断与任务间的同步。 事件可以提供一对多、多对多的同步操作。一对多同步模型:一个任务等待多个事件的触发,这种情况是比较常见的;多对多同步模型:多个任务等待多个事件的触发。
任务可以通过设置事件位来实现事件的触发和等待操作。FreeRTOS 的事件仅用于同步,不提供数据传输功能。
FreeRTOS 提供的事件具有如下特点:
- 事件只与任务相关联,事件相互独立,一个 32 位的事件集合(EventBits_t 类型的变量,实际可用与表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生),一共 24 种事件类型。
- 事件仅用于同步,不提供数据传输功能。
- 事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走),等效于只设置一次。
- 允许多个任务对同一事件进行读写操作。
- 支持事件等待超时机制。
在 FreeRTOS 事件中,每个事件获取的时候,用户可以选择感兴趣的事件,并且选择读取事件信息标记,它有三个属性,分别是逻辑与,逻辑或以及是否清除标记。当任务等待事件同步时,可以通过任务感兴趣的事件位和事件信息标记来判断当前接收的事件是否满足要求,如果满足则说明任务等待到对应的事件,系统将唤醒等待的任务;否则,任务会根据用户指定的阻塞超时时间继续等待下去。
2、 运作机制
接收事件时,可以根据感兴趣的参事件类型接收事件的单个或者多个事件类型。事件接收成功后,必须使用 xClearOnExit 选项来清除已接收到的事件类型,否则不会清除已接收到的事件,这样就需要用户显式清除事件位。用户可以自定义通过传入参数 xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。
设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为 1,可以一次同时写多个事件类型,设置事件成功可能会触发任务调度。清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清 0 操作。事件不与任务相关联,事件相互独立,一个 32 位的变量(事件集合,实际用于表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生),一共 24 种事件类型。
事件唤醒机制,当任务因为等待某个或者多个事件发生而进入阻塞态,当事件发生的时候会被唤醒。
任务 1 对事件 3 或事件 5 感兴趣(逻辑或),当发生其中的某一个事件都会被唤醒,并且执行相应操作。而任务 2 对事件 3 与事件 5 感兴趣(逻辑与),当且仅当事件 3 与事件 5 都发生的时候,任务 2 才会被唤醒,如果只有一个其中一个事件发生,那么任务还是会继续等待事件发生。如果接在收事件函数中设置了清除事件位 xClearOnExit,那么当任务唤醒后将把事件 3 和事件 5 的事件标志清零,否则事件标志将依然存在。
3、事件的应用场景
FreeRTOS 的事件用于事件类型的通讯,无数据传输,也就是说,我们可以用事件来做 标志位,判断某些事件是否发生了,然后根据结果做处理,那很多人又会问了,为什么我 不直接用变量做标志呢,岂不是更好更有效率?非也非也,若是在裸机编程中,用全局变 量是最为有效的方法,这点我不否认,但是在操作系统中,使用全局变量就要考虑以下问 题了:
如何对全局变量进行保护呢,如何处理多任务同时对它进行访问?
如何让内核对事件进行有效管理呢?使用全局变量的话,就需要在任务中轮询查 看事件是否发送,这简直就是在浪费 CPU 资源啊,还有等待超时机制,使用全局 变量的话需要用户自己去实现。
所以,在操作系统中,还是使用操作系统给我们提供的通信机制就好了,简单方便还 实用。
在某些场合,可能需要多个时间发生了才能进行下一步操作,比如一些危险机器的启 动,需要检查各项指标,当指标不达标的时候,无法启动,但是检查各个指标的时候,不 能一下子检测完毕啊,所以,需要事件来做统一的等待,当所有的事件都完成了,那么机 器才允许启动,这只是事件的其中一个应用。
事件可使用于多种场合,它能够在一定程度上替代信号量,用于任务与任务间,中断 与任务间的同步。一个任务或中断服务例程发送一个事件给事件对象,而后等待的任务被 唤醒并对相应的事件进行处理。但是它与信号量不同的是,事件的发送操作是不可累计的, 而信号量的释放动作是可累计的。事件另外一个特性是,接收任务可等待多种事件,即多 个事件对应一个任务或多个任务。同时按照任务等待的参数,可选择是“逻辑或”触发还 是“逻辑与”触发。这个特性也是信号量等所不具备的,信号量只能识别单一同步动作, 而不能同时等待多个事件的同步。
各个事件可分别发送或一起发送给事件对象,而任务可以等待多个事件,任务仅对感 兴趣的事件进行关注。当有它们感兴趣的事件发生时并且符合感兴趣的条件,任务将被唤 醒并进行后续的处理动作。
4、事件位(事件标志)
事件位用来表明某个事件是否发生,事件位通常用作事件标志,比如下面的几个例子:
● 当收到一条消息并且把这条消息处理掉以后就可以将某个位(标志)置 1,当队列中没有 消息需要处理的时候就可以将这个位(标志)置 0。
● 当把队列中的消息通过网络发送输出以后就可以将某个位(标志)置 1,当没有数据需要 从网络发送出去的话就将这个位(标志)置 0。
● 现在需要向网络中发送一个心跳信息,将某个位(标志)置 1。现在不需要向网络中发送 心跳信息,这个位(标志)置 0。
5、事件组
一个事件组就是一组的事件位,事件组中的事件位通过位编号来访问,同样,以上面列出 的三个例子为例:
● 事件标志组的 bit0 表示队列中的消息是否处理掉。
● 事件标志组的 bit1 表示是否有消息需要从网络中发送出去。
● 事件标志组的 bit2 表示现在是否需要向网络发送心跳信息。
二、STM32CubeMX设置
1、配置RCC、USART1、时钟72M
2、配置SYS,将Timebase Source修改为除滴答定时器外的其他定时器。
3、初始化LED的两个引脚
4、开启FreeRTOS,v1与v2版本不同,一般选用v1即可
5、创建两个线程:任务LED1用作发送,LED2用作接收。
以上步骤可参考:STM32CubeMX学习笔记22---FreeRTOS(任务创建和删除)-CSDN博客
6、创建事件组Event
要想使用事件必须在 Middleware
中选择 FREERTOS
设置,并选择 CMSIS_V2
接口版本。
在 Events
进行配置。
- Event flags Name: 事件组名称
- Allocation: 分配方式:
Dynamic
动态内存创建 - Conrol Block Name: 控制块名称
三、程序编程
1、创建一个事件组:osEventFlagsNew
用于创建一个事件组,并返回对应的ID。
函数 | osEventFlagsId_t osEventFlagsNew (const osEventFlagsAttr_t *attr) |
---|---|
参数 | attr: 引用由osEventFlagsAttr_t定义的事件属性 |
返回值 | 成功返回事件组ID,失败返回0 |
例:
osEventFlagsId_t myEvent01Handle;
myEvent01Handle = osEventFlagsNew(&myEvent01_attributes);
2、删除事件组:osEventFlagsDelete
当系统不再使用事件对象时,可以通过删除事件对象控制块来释放系统资源。
函数 | osStatus_t osEventFlagsDelete (osEventFlagsId_t ef_id) |
---|---|
参数 | ef_id: 事件组ID |
返回值 | 错误码 |
3、置位事件组中指定的位:osEventFlagsSet
用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的任务将会被解锁。使用该函数接口时,通过参数指定的事件标志来设定事件的标志位,然后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配,如果有,则唤醒该任务。简单来说,就是设置我们自己定义的事件标志位为 1,并且看看有没有任务在等待这个事件,有的话就唤醒它。该函数可以在中断中使用。
要想在中断中使用该函数必须在 Include parameters
中把 xEventGroupSetBitFromISR
选择 Enabled
来使能。
4、获取事件组中的一个或者多个事件发生标志:osEventFlagsWait
用于获取事件组中的一个或多个事件发生标志,当要读取的事件标志位没有被置位时任务将进入阻塞等待状态。
函数 | uint32_t osEventFlagsWait (osEventFlagsId_t ef_id, uint32_t flags, uint32_t options, uint32_t timeout) |
---|---|
参数 | ef_id: 事件组ID flags: 指定事件中的事件标志位。如设置 uxBitsToSet 为 0x08 则只置位位 3,如果设置 uxBitsToSet 为 0x09 则位 3 和位 0 都需要被置位 options: osFlagsNoClear是否清除flags指定的事件标志位,osFlagsWaitAll是否 等待flags指定的位都置位的时候才满足任务唤醒的条件 timeout: 最大超时时间,单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助把时间转换成 MS |
返回值 | 返回事件中的哪些事件标志位被置位,返回值很可能并不是用户指定的事件位,需要对返回值进行判断再处理 |
5、清除事件组中指定的位:osEventFlagsClear
用于清除事件组指定的位,如果在获取事件的时候没有将对应的标志位清除,那么就需要用这个函数来进行显式清除。该函数可以在中断中使用。
函数 | uint32_t osEventFlagsClear (osEventFlagsId_t ef_id, uint32_t flags) |
---|---|
参数 | ef_id: 事件组ID flags: 指定事件组中的哪个位需要清除。如设置 uxBitsToSet 为 0x08 则只清除位 3,如果设置 uxBitsToSet 为 0x09 则位 3 和位 0 都需要被清除 |
返回值 | 事件在还没有清除指定位之前的值 |
6、事件组实验
LED1运行到10次时触发事件1,运行到20次时触发事件2,对应的事件位置1。
void LED1_Task1(void *argument)
{
/* USER CODE BEGIN LED1_Task1 */
/* Infinite loop */
int i=0;
for(;;)
{
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("LED2 run %d\r\n",i);
if(i++==10)
{
//触发事件1(位0)
osEventFlagsSet(myEvent01Handle,1<<0);
}
if(i>20)
{
i=0;
//触发事件2(位1)
osEventFlagsSet(myEvent01Handle,1<<1);
}
osDelay(300);
}
/* USER CODE END LED1_Task1 */
}
LED2等待事件触发:
void LED2_Task03(void *argument)
{
/* USER CODE BEGIN LED2_Task03 */
/* Infinite loop */
uint32_t r_event; /* 定义一个事件接收变量 */
for(;;)
{
r_event = osEventFlagsWait(myEvent01Handle, /* 事件对象句柄 */
1<<0|1<<1, /* 接收任务感兴趣的事件 */
osFlagsWaitAll, /* 退出时清除事件位,同时满足感兴趣的所有事件 */
osWaitForever); /* 指定超时事件,一直等 */
if((r_event&(1<<0|1<<1))==(1<<0|1<<1))
printf("LED2 run %d\r\n",r_event);
else printf("LED2 run err\r\n");
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); //LED1状态每500s翻转一次
osDelay(300);
}
/* USER CODE END LED2_Task03 */
}
等待事件的标记:
osFlagsWaitAny | 等待事件位中的任意一位成立时退出等待,并清除事件位 |
osFlagsWaitAll | 等待事件位中的所有一位成立时退出等待,并清除事件位 |
osFlagsNoClear | 等待事件位中的任意一位成立时退出等待,但不清除事件位 |
下载验证:
程序编译无误后,下载到板子上可以看到LED1运行到20,两个事件都触发了之后LED2才运行。
四、参考文献
STM32CubeMX学习笔记(32)——FreeRTOS实时操作系统使用(事件)_oseventflagsnew-CSDN博客
韦东山freeRTOS系列教程之【第八章】事件组(event group)-CSDN博客