事件标志组简介
事件标志组适用于多个事件触发一个或多个任务运行,可以实现事件的广播,还可以实现多个任务的同步运行,如下所述
- 事件标志组允许任务等待一个或多个事件的组合
- 事件标志组会解除所有等待同一事件的任务的阻塞状态
事件标志组中每个事件标志的状态由 EventBits_t 类型变量中的单个位表示。如果 EventBits_t 变量中的某个位设置为 1 ,则该位表示的事件已发生,否则如果 EventBits_t 变量中的某个位设置为 0 ,则该位表示的事件尚未发生
一个事件标志组就包含了一个 EventBits_t 数据类型的变量,如下图所示显示了各个事件标志如何映射到 EventBits_t 类型变量中的各个位
EventBits_t 数据类型
一个事件标志组对象有一个变量类型为 EventBits_t 的内部变量用于存储事件标志位,该变量可以设置为 16 位或 32 位,具体由参数 configUSE_16_BIT_TICKS 所决定,当参数设置为 1 时,那么每个事件标志组包含 8 个可用的事件位(包括 8 个保留位),否则设置为 0 时,每个事件标志组包含 24 个可用的事件位(包括 8 个保留位)
队列和事件标志组的区别
功能 | 唤醒对象 | 事件清除 |
---|---|---|
队列、信号量 | 事件发生时,只会唤醒一个任务 | 是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了 |
事件标志组 | 事件发生时,会唤醒所有符合条件的任务,可以理解为“广播”的作用 | 被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件 |
创建事件标志组
一个事件标志组在使用之前必须先创建,如下所示为使用动态/静态内存分配创建一个事件标志组的 API 函数
/**
* @brief 动态分配内存创建事件标志组函数
* @retval 返回成功创建的事件标志组的句柄,返回NULL表示因内存空间不足创建失败
*/
EventGroupHandle_t xEventGroupCreate(void);
/**
* @brief 静态分配内存创建事件标志组函数
* @param pxEventGroupBuffer:指向StaticEventGroup_t类型的变量,该变量用于存储事件标志组数据结构体
* @retval 返回成功创建的事件标志组的句柄,返回NULL表示因pxEventGroupBuffer空间不足创建失败
*/
EventGroupHandle_t xEventGroupCreateStatic(
StaticEventGroup_t *pxEventGroupBuffer);
操作事件标志组
FreeRTOS 提供了两组 API 来对事件标志组的某些位进行置位和清零两种操作,具体如下所示
/**
* @brief 设置事件标志位
* @param xEventGroup:要设置位的事件标志组
* @param uxBitsToSet:指定要在事件标志组中设置的一个或多个位的按位值,例如设置为0x09表示置位3和位0(0x09 = Ob1001)
* @retval 返回事件标志组中的事件标志位值
*/
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet);
/**
* @brief 将事件标志组某些位清零
* @param xEventGroup:要在其中清除位的事件标志组
* @param uxBitsToSet:表示要在事件标志组中清除一个或多个位的按位值
* @retval 返回清零事件标志位之前事件标志组中事件标志位的值
*/
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear);
/**
* @brief 上述两个函数的中断安全版本
* @param pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
* @retval 消息已发送到RTOS软件定时器服务任务,则返回pdPASS,否则将返回pdFAIL
*/
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear);
同时 FreeRTOS 也提供了查询事件标志组当前值的 API 函数,具体如下所示
/**
* @brief 读取事件标志组的当前值
* @param xEventGroup:正在查询的事件标志组
* @retval 返回事件标志组当前的值
*/
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup);
/**
* @brief 上述函数的中断安全版本
*/
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup);
等待事件标志组函数
FreeRTOS 关于事件标志组提出了等待事件标志组和事件标志组同步两个比较重要的 API 函数,分别对应两种不同的使用场景,此函数主要用于使用事件标志组进行事件的管理,而另一个函数主要用于使用事件标志组进行任务间的同步,接下来主要详细介绍两个函数的具体用法
xEventGroupWaitBits() API 函数允许任务读取事件标志组的值,并且可以选择在阻塞状态下等待事件标志组中的一个或多个事件位被设置(如果事件位尚未设置),如下所示为其具体的函数声明
/**
* @brief 等待事件标志组中多个事件位表示的事件成立
* @param xEventGroup:所操作事件标志组的句柄
* @param uxBitsToWaitFor:所等待事件位的掩码,例如设置为0x05表示等待第0位和/或第2位
* @param xClearOnExit:pdTRUE表示事件标志组条件成立退出阻塞状态时将掩码指定的所有位清零;pdFALSE表示事件标志组条件成立退出阻塞状态时不将掩码指定的所有位清零
* @param xWaitForAllBits:pdTRUE表示等待掩码中所有事件位都置1,条件才算成立(逻辑与);pdFALSE表示等待掩码中所有事件位中一个置1,条件就成立(逻辑或)
* @param xTicksToWait:任务进入阻塞状态的节拍数
* @retval 返回事件位等待完成设置或阻塞时间过期时的事件标志组值
*/
EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait);
特点:
可以等待某一位、也可以等待多位
等到期望的事件后,还可以清除某些位
uxBitsToWaitFor 和 xWaitForAllBits 参数
调度程序用来确定任务是否进入阻塞状态以及任务何时离开阻塞状态的条件称为 “解除阻塞条件” 。解锁条件由 uxBitsToWaitFor 和 xWaitForAllBits 参数值的组合指定:
- uxBitsToWaitFor 指定要测试事件标志组中的哪些事件位
- xWaitForAllBits指定是使用按位 OR 测试还是按位 AND 测试
如果调用 xEventGroupWaitBits() 时满足解锁条件,任务将不会进入阻塞状态,下表提供了导致任务进入阻塞状态或退出阻塞状态的条件示例。表中列出的值仅显示事件标志组和 uxBitsToWaitFor 值的最低有效的四个二进制位,其他位均假定为零
现有事件标志组值 | uxBitsToWaitFor | xWaitForAllBits | 导致的结果 |
---|---|---|---|
0000 | 0101 | pdFALSE | 由于事件标志组中的位 0 或位 2 均未设置,调用任务将进入阻塞状态,并且当事件标志组中的位 0 或位 2 被设置时,调用任务将离开阻塞状态 |
0100 | 0101 | pdTRUE | 调用任务将进入阻塞状态,因为事件标志组中的位 0 和位 2 未同时设置,并且当事件标志组中的位 0 和位 2 均设置时,调用任务将离开阻塞状态 |
0100 | 0110 | pdFALSE | 调用任务不会进入阻塞状态,因为 xWaitForAllBits 为pdFALSE,并且 uxBitsToWaitFor 指定的两个位之一已在事件标志组中设置 |
0100 | 0110 | pdTRUE | 调用任务将进入阻塞状态,因为 xWaitForAllBits 为pdTRUE,并且事件标志组中仅已设置 uxBitsToWaitFor 指定的两个位之一。 当事件标志组中的位 2 和位 3 均被设置时,任务将离开阻塞状态 |
xClearOnExit 参数
调用任务使用 uxBitsToWaitFor 参数指定要测试的位,并且调用任务可能需要在满足其解锁条件后将这些位清零。可以使用 xEventGroupClearBits() API 函数清除事件位,但使用该函数手动清除事件位将导致应用程序代码中出现竞争条件
因此提供 xClearOnExit 参数就是为了避免这些潜在的竞争条件。如果 xClearOnExit 设置为 pdTRUE,则事件位的测试和清除对于调用任务来说是一个原子操作(不能被其他任务或中断中断),简单来说就是如果 xClearOnExit 设置为 pdTRUE,则调用任务退出后会将事件标志组所有位清零,否则不清零
如果 xEventGroupWaitBits() 由于满足调用任务的解锁条件而返回,则返回值是满足调用任务的解锁条件时事件标志组的值(如果 xClearOnExit 为 pdTRUE,则在自动清除任何位之前),在这种情况下,返回值也将满足解锁条件。如果 xEventGroupWaitBits() 因为 xTicksToWait 参数指定的退出阻塞时间到期而返回,则返回值为退出阻塞时间到期时事件标志组的值,在这种情况下,返回值将不满足解锁条件
事件标志组同步函数
提供 xEventGroupSync() 是为了允许两个或多个任务使用事件标志组来相互同步。该函数允许任务设置事件标志组中的一个或多个事件位,然后等待同一事件标志组中指定的事件位组合被设置
如下所示为 xEventGroupSync() API 函数的具体声明
/**
* @brief 事件标志组同步
* @param uxBitsToSet:设置和测试位的事件标志组
* @param uxBitsToWaitFor:指定事件标志组中要测试的一个或多个事件位的按位值
* @param xTicksToWait:任务进入阻塞状态的节拍数
* @retval 返回函数退出时事件标志组的值
*/
EventBits_t xEventGroupSync(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait);
函数返回值
xEventGroupSync() 函数返回函数退出时事件标志组的值,可能有以下两种情况
- xEventGroupSync() 函数的 uxBitsToWaitFor 参数指定了调用任务的解锁条件,如果该函数由于满足解锁条件而返回,则 uxBitsToWaitFor 指定的事件位将在 xEventGroupSync() 返回之前清回零,并且在自动清为零之前会将事件标志组的值作为函数返回值返回
- 如果 xEventGroupSync() 由于 xTicksToWait 参数指定的阻塞时间到期而返回,则返回值为阻塞时间到期时事件标志组的值,在这种情况下,返回值将不满足调用任务的解锁条件
应用举例
举个简单的例子就容易理解:
假设目前有两个任务,分别为 TASK1 和 TASK2 ,如果 TASK1 被执行过程中因为延时等原因先于 TASK2 调用了 xEventGroupSync() 函数,参数 uxBitsToSet 被设置为 0x01(0000 0001),参数 uxBitsToWaitFor 被设置为 0x05(0000 0101),则 TASK1 执行到该函数时会将事件标志组中位 0 的值置 1 ,然后进入阻塞状态,等待位 2 和位 0 同时被置 1 ;
如果 TASK2 与 TASK1 一样,只不过落后于 TASK1 执行 xEventGroupSync() 函数,并且参数 uxBitsToSet 被设置为 0x04(0000 0100),当 TASK2 执行该函数时会将事件标志组中位 2 的值置 1 ,此时满足解锁条件,所以 TASK2 不会进入阻塞状态,同时 TASK1 也满足解锁条件,从阻塞状态中退出,这时候假设任务优先级一致,则 TASK1 和 TASK2 会同时从同步点开始运行后续的程序代码,从而达到同步的目的
删除事件标志组
/**
* @brief 删除事件标志组
* @param xEventGroup:要删除事件标志组的句柄
* @retval None
*/
void vEventGroupDelete(EventGroupHandle_t xEventGroup);