# 事件位(或标志)与事件组

事件位:用于指示事件是否发生;事件位通常称为事件标志。

事件组:是一组事件位;事件组中的各个事件位由位号引用(即每一 bit 代表某个事件)

# 事件组和事件位数据类型

事件组由 EventGroupHandle_t 类型的变量引用

如果 configUSE_16_BIT_TICKS 设置为 1 ,则事件组中存储的位数(或标志)为 8;如果 configUSE_16_BIT_TICKS 设置为 0 ,则为 24;对 configUSE_16_BIT_TICKS 的依赖性是由于在内部实现中用于线程本地存储的数据类型任务。

configUSE_16_BIT_TICKS 的具体的描述可以看 FreeRTOS 篇章之 FreeRTOSConfig.h 分析

事件组中的所有事件位都存储在 EventBits_t 类型的单个无符号变量中。事件位 0 存储在位位置 0,事件位 1 存储在位位置 1,依此类推。

下图显示了一个 24 位事件组,该组使用三个位来保存已描述的三个示例事件。在图像中,仅事件位 2 被设置。

# 使用事件组必须克服的问题

实施事件组时,RTOS 必须克服的两个主要挑战是:

1、避免在用户的应用程序中创建竞争条件:

在以下情况下,事件组的实现将会在应用程序中创建竞争条件:

  • 目前尚不清楚谁负责清除单个位(或标志)。
  • 尚不清楚何时清除一点。
  • 尚不清楚在任务退出测试该位值的 API 函数时是否设置了位或清除了位(这可能是因为另一个任务或中断已更改了位的状态)。

FreeRTOS 事件组的实现通过智能的建立来确保设置、测试和清除位的原子性,从而消除了竞争条件的可能性。线程本地存储和谨慎使用 API 函数返回值使这成为可能。

2、避免不确定性:

事件组的概念隐含了不确定性行为,因为它不知道事件组上有多少个任务被阻止,因此,当设置了事件位时,也不知道需要测试多少条件或解除多少任务阻塞。

FreeRTOS 质量标准不允许不确定的行为在中断禁用或者中断服务中发生。为确保在设置事件位时不会违反这些严格的质量标准,需要以下两点:

  • RTOS 调度器的锁定机制用于确保从 RTOS 任务设置事件位时,中断保持启用状态。
  • 中央延迟中断机制用于在试图从中断服务例程设置事件位时,将设置位的动作延迟到任务。

# 事件组的 RTOS API 函数

事件组的 API 函数允许任务或者其他事项来设置事件组中的一个或多个事件位,清除事件组中的 一个或多个事件位,并暂挂(进入 “阻止” 状态,因此任务不会占用任何处理时间)以等待一个或多个事件位在事件组中被置位。

事件组还可用于同步任务,创建通常称为任务 “会合” 的任务。任务同步点是应用程序代码中的一个位置,在这个位置上,任务将处于阻塞状态 (不消耗任何 CPU 时间) 等待,直到参与同步的所有其他任务也到达它们的同步点。

需要 #include "event_groups.h"

功能API 接口实际执行函数其它
事件组创建(动态)xEventGroupCreate()
事件组响应等待xEventGroupWaitBits()
事件组位清除xEventGroupClearBits()
事件组位清除(用于中断中)xEventGroupClearBitsFromISR()xTimerPendFunctionCallFromISR()实际执行函数由宏决定
事件组置位xEventGroupSetBits()
事件组置位(用于中断中)xEventGroupSetBitsFromISR()xTimerPendFunctionCallFromISR()实际执行函数由宏决定
事件组位获取xEventGroupGetBits()xEventGroupClearBits()
事件组位获取(用于中断中)xEventGroupGetBitsFromISR()
事件组释放删除vEventGroupDelete()
事件组创建(静态)xEventGroupCreateStatic()

1、xEventGroupCreate () API 函数

EventGroupHandle_t  xEventGroupCreate( void );

返回参数(此返回值应当保存下来,以作为操作此队列的句柄):

  • NULL:表示事件组创建失败。原因是内存堆空间不足导致 FreeRTOS 无法为互斥量分配结构数据空间。
  • 非 NULL:值表示事件组创建成功。返回值应当保存起来作为该事件组的句柄。

2、xEventGroupWaitBits () API 函数

EventBits_t  xEventGroupWaitBits( EventGroupHandle_t  xEventGroup,
                                  const EventBits_t   uxBitsToWaitFor,
                                  const BaseType_t    xClearOnExit,
                                  const BaseType_t    xWaitForAllBits,
                                  const TickType_t    xTicksToWait );

传入参数:

  • xEventGroup:事件组的目标句柄。这个句柄即是调用 xEventGroupCreate() 创建该事件组时的返回值。
  • uxBitsToWaitFor:一个按位的值,表示在事件组中可设置的事件位(或标志)。
  • xClearOnExit:如果设置为 pdTRUE ,那么如果满足了等待条件 (如果函数返回的原因不是超时),则在事件组中设置的 uxBitsToWaitFor 中的任何位都将在 xEventGroupWaitBits() 返回之前被清除;否则,如果设置为 pdFALSE ,那么当调用 xEventGroupWaitBits() 返回时,事件组中设置的位不会改变。
  • xWaitForAllBits:如果设置为 pdTRUE ,那么当 uxBitsToWaitFor 中的所有位被设置或者指定的块时间过期时, xEventGroupWaitBits() 将返回;否则,如果设置为 pdFALSE ,那么当 uxBitsToWaitFor 中设置的任何一个位或者指定的块时间过期时, xEventGroupWaitBits() 将返回。
  • xTicksToWait:阻塞超时时间。

返回参数:

  • 事件组中事件位的设置情况。如果 xEventGroupWaitBits() 因为超时而返回,则不会设置所有正在等待的事件位;在 xClearOnExit 参数被设置为 pdTRUE 的情况下,如果 xEventGroupWaitBits() 是因为它正在等待的位被设置而返回,那么返回的值就是在任何位被自动清除之前的事件组值。

3、xEventGroupClearBits () API 函数

EventBits_t  xEventGroupClearBits( EventGroupHandle_t  xEventGroup,
                                   const EventBits_t   uxBitsToClear );

传入参数:

  • xEventGroup:要清除其中位的事件组。
  • uxBitsToClear :位值。表示在事件组中要清除的事件位(或标志)。

返回参数:

  • 清除指定位之前的事件组的值

4、xEventGroupClearBitsFromISR () API 函数

BaseType_t  xEventGroupClearBitsFromISR( EventGroupHandle_t  xEventGroup,
                                         const EventBits_t   uxBitsToClear );

传入参数:

  • xEventGroup:要清除其中位的事件组。
  • uxBitsToClear :位值。表示在事件组中要清除的事件位(或标志)。

返回参数(有两个可能的返回值):

  • pdPASS:成功发送请求。
  • pdFALSE:发送请求失败。计时器服务队列已满。

5、xEventGroupSetBits () API 函数

EventBits_t  xEventGroupSetBits( EventGroupHandle_t  xEventGroup,
                                 const EventBits_t   uxBitsToSet );

传入参数:

  • xEventGroup:要在其中位设置的事件组。
  • uxBitsToSet :一个按位的值,表示要设置的事件位(或标志)。

返回参数:

  • 事件组在调用 xEventGroupSetBits() 时的值。

注意:用户通过参数 uxBitsToSet 设置的标志位并不一定会保留到此函数的返回值中,返回的值可能清除有以下两种情况:
a. 调用此函数的过程中,其它高优先级的任务就绪了,并且也修改了事件标志,此函数返回的事件标志位会发生变化。
b. 调用此函数的任务是一个低优先级任务,通过此函数设置了事件标志后,让一个等待此事件标志的高优先级任务就绪了,会立即切换到高优先级任务去执行,相应的事件标志位会被函数 xEventGroupWaitBits 清除掉,等从高优先级任务返回到低优先级任务后,函数 xEventGroupSetBits 的返回值已经被修改。

6、xEventGroupSetBitsFromISR () API 函数

BaseType_t  xEventGroupSetBitsFromISR( EventGroupHandle_t  xEventGroup,
                                       const EventBits_t   uxBitsToSet,
                                       BaseType_t          *pxHigherPriorityTaskWoken );

传入参数:

  • xEventGroup:要在其中位设置的事件组。
  • uxBitsToSet :一个按位的值,表示要设置的事件位(或标志)。
  • pxHigherPriorityTaskWoken:用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE ,说明有高优先级任务要执行,否则没有;必须将 xHigherPriorityTaskWoken 初始化为 pdFALSE

返回参数(有两个可能的返回值):

  • pdPASS:成功发送请求。
  • pdFALSE:发送请求失败。计时器服务队列已满。

7、xEventGroupGetBits () API 函数

EventBits_t  xEventGroupGetBits( EventGroupHandle_t  xEventGroup );

传入参数:

  • xEventGroup:需要查询的事件组。

返回参数:

  • 调用 xEventGroupGetBits() 时的事件组位。

8、xEventGroupGetBitsFromISR () API 函数

EventBits_t  xEventGroupGetBitsFromISR( EventGroupHandle_t  xEventGroup );

传入参数:

  • xEventGroup:需要查询的事件组。

返回参数:

  • 调用 xEventGroupGetBitsFromISR() 时的事件组位。

9、xEventGroupDelete () API 函数

void xEventGroupDelete( EventGroupHandle_t  xEventGroup );

传入参数:

  • xEventGroup:需要删除的事件组。

10、xEventGroupCreateStatic () API 函数

EventGroupHandle_t  xEventGroupCreateStatic( EventGroupHandle_t  *pxEventGroupBuffer );

该函数是用于在静态的时候,利用该函数创建一个事件组,具体可以去看他的注释,这里就不说了。

# 实例代码

相关的实例代码及文档,参看保存在官方文件路径 FreeRTOS/Demo/Common/Minimal 下的 EventGroupsDemo.c 文件