十七、事件组

1、事件组是什么

1.1、举例说明

(1)学校组织秋游,组长在等待:

  • 张三:我到了
  • 李四:我到了
  • 王五:我到了
  • 组长说:好,大家都到齐了,出发!

(2)秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的。

(3)在这个日常生活场景中:

  • 出发:要等待这3个人都到齐,他们是"与"的关系
  • 交报告:只需等待这3人中的任何一个,他们是"或"的关系

(5)在FreeRTOS中,可以使用事件组(event group)来解决这类问题。

1.2、事件组的概念

(1)事件组可以简单地认为就是一个整数:

  • 整数的每一位表示一个事件。
  • 每一位事件的含义由程序员决定,比如:Bit0表示用来串口是否就绪,Bit1表示按键是否被按下。
  • 这些位,值为1表示事件发生了,值为0表示事件没发生。
  • 一个或多个任务、ISR都可以去写这些位;一个或多个任务、ISR都可以去读这些位。
  • 可以等待某一位、某些位中的任意一个,也可以等待多位。

(2)事件组用一个整数来表示,其中的高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位

1.3、事件组的操作

(1)事件组和队列、信号量等不太一样,主要集中在2个地方:

  • 唤醒谁?
    • 队列、信号量:事件发生时,只会唤醒一个任务。
    • 事件组:事件发生时,会唤醒所有符合条件的任务,简单地说它有"广播"的作用。
  • 是否清除事件?
    • 队列、信号量:是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了。
    • 事件组:被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件。

(2)以上图为例,事件组的常规操作如下:

  • 先创建事件组
  • 任务C、D等待事件:
    • 等待什么事件?可以等待某一位、某些位中的任意一个,也可以等待多位。简单地说就
      是"或"、"与"的关系。
    • 得到事件时,要不要清除?可选择清除、不清除。
  • 任务A、B产生事件:
    • 设置事件组里的某一位、某些位。

2、事件组函数

2.1、创建

(1)使用事件组之前,要先创建,得到一个句柄;使用事件组时,要使用句柄来表明使用哪个事件组。

(2)有两种创建方法:动态分配内存、静态分配内存。函数原型如下:

复制代码
/* 创建一个事件组,返回它的句柄。
 * 此函数内部会分配事件组结构体
 * 返回值: 返回句柄,非NULL表示成功
*/
EventGroupHandle_t xEventGroupCreate( void );

/* 创建一个事件组,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer );

2.2、删除

(1)对于动态创建的事件组,不再需要它们时,可以删除它们以回收内存。

(2)vEventGroupDelete可以用来删除事件组,函数原型如下:

复制代码
/*
 * xEventGroup: 事件组句柄,你要删除哪个事件组
 */
void vEventGroupDelete( EventGroupHandle_t xEventGroup )

2.3、设置事件

(1)可以设置事件组的某个位、某些位,使用的函数有2个:

  • 在任务中使用 xEventGroupSetBits()
  • 在ISR中使用 xEventGroupSetBitsFromISR()

(2)有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤醒。函数原型如下:

复制代码
/* 设置事件组中的位
 * xEventGroup: 哪个事件组
 * uxBitsToSet: 设置哪些位?
 * 如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
 * 可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
 * 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了)
 */
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, 
                                const EventBits_t uxBitsToSet );


/* 设置事件组中的位
 * xEventGroup: 哪个事件组
 * uxBitsToSet: 设置哪些位?
 * 如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
 * 可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
 * pxHigherPriorityTaskWoken: 有没有导致更高优先级的任务进入就绪态? pdTRUE-有, pdFALSE-没有
 * 返回值: pdPASS-成功, pdFALSE-失败
 */
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
                                      const EventBits_t uxBitsToSet,
                                      BaseType_t * pxHigherPriorityTaskWoken );

(3)值得注意的是,ISR中的函数,比如队列函数 xQueueSendToBackFromISR 、信号量函数

xSemaphoreGiveFromISR ,它们会唤醒某个任务,最多只会唤醒1个任务。但是设置事件组时,有可能导致多个任务被唤醒,这会带来很大的不确定性。所以 xEventGroupSetBitsFromISR 函数不是直接去设置事件组,而是给一个FreeRTOS后台任务(daemon task)发送队列数据,由这个任务来设置事件组。

(4)如果后台任务的优先级比当前被中断的任务优先级高, xEventGroupSetBitsFromISR 会设置

*pxHigherPriorityTaskWoken 为pdTRUE。

(5)如果daemon task成功地把队列数据发送给了后台任务,那么 xEventGroupSetBitsFromISR 的返回值就是pdPASS。(return pass:返回通过)

2.4、等待事件

(1)使用 xEventGroupWaitBits 来等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位;等到期望的事件后,还可以清除某些位。

(2)函数原型如下:

复制代码
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
                                 const EventBits_t uxBitsToWaitFor,
                                 const BaseType_t xClearOnExit,
                                 const BaseType_t xWaitForAllBits,
                                 TickType_t xTicksToWait );

(3)先引入一个概念:unblock condition。一个任务在等待事件发生时,它处于阻塞状态;当期望的事件发生时,这个状态就叫"unblock condition",非阻塞状态,或称为"非阻塞条件成立";当"非阻塞条件成立"后,该任务就可以变为就绪态。

(4)函数的参数列表说明如下:

| 参数 | 说明 |
| xEventGroup | 等待哪个事件组? |
| uxBitsToWaitFor | 等待哪些位?哪些位要被测试? |
| xClearOnExit | 函数退出时是否要清除事件? pdTRUE: 清除uxBitsToWaitFor指定的位 pdFALSE: 不清除 |
| xWaitForAllBits | 怎么测试?是"AND"还是"OR"? pdTRUE: 等待的位,全部为1; pdFALSE: 等待的位,某一个为1即可 |
| xTicksToWait | 如果期待的事件未发生,阻塞多久。 可以设置为0:判断后即刻返回; 可设置为portMAX_DELAY:一定等到成功才返回; 可以设置为期望的Tick Count,一般用 pdMS_TO_TICKS() 把ms转换为Tick Count |

返回值 返回的是事件值, 如果期待的事件发生了,返回的是"非阻塞条件成立"时的事件值; 如果是超时退出,返回的是超时时刻的事件值。

(5)举例如下:

事件组的值 uxBitsToWaitFor (等待的位) xWaitForAllBits (与还是或) 说明
0100 0101 pdTRUE 任务期望bit0,bit2都为1, 当前值只有bit2满足,任务进入阻塞态; 当事件组中bit0,bit2都为1时退出阻塞态
0100 0110 pdFALSE 任务期望bit0,bit2某一个为1, 当前值满足,所以任务成功退出
0100 0110 pdTRUE 任务期望bit1,bit2都为1, 当前值不满足,任务进入阻塞态; 当事件组中bit1,bit2都为1时退出阻塞态

(6)可以使用 xEventGroupWaitBits() 等待期望的事件,它发生之后再使用 xEventGroupClearBits()

来清除。但是这两个函数之间,有可能被其他任务或中断抢占,它们可能会修改事件组。可以使用设置 xClearOnExit 为pdTRUE,使得对事件组的测试、清零都在 xEventGroupWaitBits() 函数内部完成,这是一个原子操作。

2.5、同步点

(1)有一个事情需要多个任务协同,比如:

  • 任务A:炒菜
  • 任务B:买酒
  • 任务C:摆台
  • A、B、C做好自己的事后,还要等别人做完;大家一起做完,才可开饭

(2)使用 xEventGroupSync() 函数可以同步多个任务:

  • 可以设置某位、某些位,表示自己做了什么事。
  • 可以等待某位、某些位,表示要等等其他任务。
  • 期望的时间发生后, xEventGroupSync() 才会成功返回。
  • xEventGroupSync 成功返回后,会清除事件。

(3)xEventGroupSync 函数原型如下:

复制代码
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
                             const EventBits_t uxBitsToSet,
                             const EventBits_t uxBitsToWaitFor,
                             TickType_t xTicksToWait );

(4)函数的参数列表说明如下:

| 参数 | 说明 |
| xEventGroup | 哪个事件组? |
| uxBitsToSet | 要设置哪些事件?我完成了哪些事件? 比如0x05(二进制为0101)会导致事件组的bit0,bit2被设置为1 |
| uxBitsToWaitFor | 等待哪个位、哪些位? 比如0x15(二级制10101),表示要等待bit0,bit2,bit4都为1 |
| xTicksToWait | 如果期待的事件未发生,阻塞多久。 可以设置为0:判断后即刻返回; 可设置为portMAX_DELAY:一定等到成功才返回; 可以设置为期望的Tick Count,一般用 pdMS_TO_TICKS() 把ms转换为Tick Count |

返回值 返回的是事件值, 如果期待的事件发生了,返回的是"非阻塞条件成立"时的事件值; 如果是超时退出,返回的是超时时刻的事件值。
相关推荐
清风66666614 小时前
基于单片机的螺旋藻生长大棚PH智能控制设计
单片机·嵌入式硬件·毕业设计·课程设计
ting_zh15 小时前
微控制器(Micro Controller Unit, MCU)基础整理
单片机·嵌入式硬件
清风66666616 小时前
基于单片机的图书馆智能座位管理平台
数据库·单片机·嵌入式硬件·毕业设计·课程设计
得单片机的运17 小时前
STM32的以太网的搭建
stm32·单片机·嵌入式硬件·物联网·以太网·iot·w5500
酷飞飞18 小时前
RTC和看门狗基于GD32F407VE的天空星的配置
stm32·单片机·嵌入式硬件·mcu
WD1372980155719 小时前
WD5030A,24V降5V,15A 大电流,应用于手机、平板、笔记本充电器
stm32·单片机·嵌入式硬件·智能手机·汽车·电脑·51单片机
日更嵌入式的打工仔19 小时前
GPIO 中断通用配置指南
stm32·单片机·嵌入式硬件
平凡灵感码头20 小时前
基于 STM32 的智能门锁系统,系统界面设计
stm32·单片机·嵌入式硬件
报错小能手21 小时前
linux学习笔记(32)网络编程——UDP
单片机·嵌入式硬件
xyx-3v1 天前
SPI四种工作模式
stm32·单片机·嵌入式硬件·学习