FreeRTOS事件组与事件位的使用

        事件组是FreeRTOS的另一个特性,允许将事件传达给任务。事件组与队列和信号量的区别如下:

  • 事件组允许任务在阻塞状态下等待一个或多个事件组合的发生。
  • 事件发生时,事件组会解除所有等待相同事件或事件组合的任务的阻塞状态。

        事件组的这些独特属性使得事件组在同步多个任务、向多个任务广播事件、允许任务在阻塞状态下等待一组事件中的某个事件发生,以及允许任务在阻塞状态下等待多个操作完成等场景非常有用。

        事件组还提供了降低应用程序使用RAM的机会,因为通常可以将多个二进制信号量替换为一个事件组。

事件组、事件标志和事件位

        事件“标志”是一个布尔值(1或0),用于指示某个事件是否已发生。而事件“组”是一组事件标志的集合。需要注意的是,编程人员需要为事件组中的各个位分配意义.

        事件标志只能是1或0,这使得事件标志的状态可以存储在一个位中,而事件组中所有事件标志的状态则可以存储在一个变量中。事件组中每个事件标志的状态由EventBits_t类型变量中的一个位表示。因此,事件标志也被称为事件“位”。如果EventBits_t变量中的某一位被设置为1,则表示由该位所代表的事件已发生。如果EventBits_t变量中的某一位被设置为0,则表示由该位所代表的事件未发生。

        下图展示了如何将单个事件标志映射到EventBits_t类型变量中的各位。

        如果事件组的值为0x92(二进制为1001 0010),那么只有事件位1、4和7被设置,因此只有由位1、4和7表示的事件发生了。下图展示了一个EventBits_t类型的变量,其中事件位1、4和7被设置,其他所有事件位都被清除,这使得事件组的值为0x92。

关于EventBits_t数据类型的更多信息

        事件组中的事件位数取决于FreeRTOSConfig.h中configTICK_TYPE_WIDTH_IN_BITS编译时配置常量的设置:用于保存RTOS滴答计数的类型由configTICK_TYPE_WIDTH_IN_BITS配置,该配置常量看上去与事件组特性无关。该配置常量对EventBits_t类型的影响是FreeRTOS内部实现的结果,将configTICK_TYPE_WIDTH_IN_BITS设置为TICK_TYPE_WIDTH_16_BITS是可取的,但这只有当FreeRTOS在比32位类型更有效地处理16位类型的架构上运行时才这样做。

  • 如果configTICK_TYPE_WIDTH_IN_BITS设置为TICK_TYPE_WIDTH_16_BITS,则每个事件组包含8个可用的事件位。
  • 如果configTICK_TYPE_WIDTH_IN_BITS设置为TICK_TYPE_WIDTH_32_BITS,则每个事件组包含24个可用的事件位。
  • 如果configTICK_TYPE_WIDTH_IN_BITS为TICK_TYPE_WIDTH_64_BITS,则每个事件组包含56个可用的事件位。

使用事件组进行事件管理

函数xEventGroupCreate()

        FreeRTOS还包括xEventGroupCreateStatic() API函数,该函数在编译时静态地分配创建事件组所需的内存。在使用事件组之前,必须先明确的创建事件组。

        使用EventGroupHandle_t类型的变量来引用事件组。xEventGroupCreate() API函数用于创建事件组,并返回EventGroupHandle_t类型的变量以引用创建的事件组。

xEventGroupCreate() API 函数的原型如下所示:

EventGroupHandle_t xEventGroupCreate( void );

xEventGroupCreate() API函数的返回值说明如下所示:

返回值 返回值说明
返回值 如果返回NULL,则表示无法创建事件组。如果返回非NULL值,则表示事件组已成功创建。应将返回值作为已创建事件组的句柄存储起来。

        每个事件组都需要非常少量 RAM 来保存事件组的状态。如果使用 xEventGroupCreate() 创建事件组, 则会从 FreeRTOS 堆中自动分配所需的 RAM。xEventGroupCreate() API 函数的使用示例如下所示:

/* 声明EventGroupHandle_t类型的变量来保存创建的事件组。 */
EventGroupHandle_t xCreatedEventGroup;
 
/* 尝试创建事件组。 */
xCreatedEventGroup = xEventGroupCreate();
 
/* 事件组是否创建成功? */
if( xCreatedEventGroup == NULL )
{
  /* 事件组未创建,因为 FreeRTOS 可用的堆内存不足。 */
}
else
{
  /* 事件组已创建。 */
}

        如果使用xEventGroupCreateStatic() API函数创建事件组,则 RAM 由编程人员提供,这需要用到一个附加参数,但允许在编译时静态分配 RAM。xEventGroupCreateStatic() API 函数的原型如下所示:

EventGroupHandle_t xEventGroupCreateStatic(
                              StaticEventGroup_t *pxEventGroupBuffer );

xEventGroupCreateStatic() API函数的参数与返回值说明如下所示:

参数名称 参数说明
pxEventGroupBuffer 必须指向 StaticEventGroup_t 类型的变量,该变量用于存储事件组数据结构体。
返回值 如果成功创建了事件组,则返回事件组的句柄。如果 pxEventGroupBuffer为 NULL,则返回 NULL。

xEventGroupCreateStatic() API 函数的使用示例如下所示:

/* 声明EventGroupHandle_t类型的变量来保存创建的事件组。 */
EventGroupHandle_t xEventGroupHandle;
 
/* 声明StaticEventGroup_t类型的变量来存储与创建的事件组相关的数据。 */
StaticEventGroup_t xCreatedEventGroup;

/* 尝试创建事件组。 */
xEventGroupHandle = xEventGroupCreateStatic( &xCreatedEventGroup );

/* pxEventGroupBuffer 不为空,因此期望事件组已经被创建? */
configASSERT( xEventGroupHandle );

函数xEventGroupSetBits()

        xEventGroupSetBits() API函数用于在事件组中设置一个或多个位,通常用于通知任务,任务所设置的位或位组表示的事件已经发生。

        注意:不要在中断服务程序中调用xEventGroupSetBits()API函数。应该使用中断安全版本的xEventGroupSetBitsFromISR()API函数来代替。

xEventGroupSetBits() API 函数的原型如下所示:

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );

xEventGroupSetBits() API函数的参数与返回值说明如下所示:

参数名称 参数说明
xEventGroup 设置位的事件组的句柄。该句柄从用于创建事件组的xEventGroupCreate() API函数调用中返回。
uxBitsToSet 位掩码,用于指定在事件组中要将哪个事件位或事件位组设置为1。事件组的值是通过将事件组的现有值与传入的uxBitsToSet值进行位或运算来更新的。例如,将uxBitsToSet设置为0x04(二进制0100)将导致事件组中的事件位3被设置(如果它尚未被设置),同时保持事件组中的其他所有事件位不变。
返回值 调用xEventGroupSetBits()API函数时返回事件组的值。需要注意,返回的值不一定包含由uxBitsToSet指定的位,因为这些位可能已被其他任务再次清除。

xEventGroupSetBits() API 函数的使用示例如下所示:

#define BIT_0  ( 1 << 0 )
#define BIT_4  ( 1 << 4 )
 
void aFunction( EventGroupHandle_t xEventGroup )
{
  EventBits_t uxBits;
 
  /* 在xEventGroup中设置位0和位4。 */
  uxBits = xEventGroupSetBits(
  xEventGroup,    /* 正在被更新的事件组。 */
  BIT_0 | BIT_4 );/* 正在被设置的位。 */

  if( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) )
  {
    /* 当函数返回时,位0和位4仍然被设置。 */
  }
  else if( ( uxBits & BIT_0 ) != 0 )
  {
    /* 当函数返回时,位0仍然被设置,但位4被清除了。这可能是因为等待位4的
    任务被从阻塞状态移除后,位4被自动清除了。 */
  }
  else if( ( uxBits & BIT_4 ) != 0 )
  {
    /* 当函数返回时,位4仍然被设置,但位0被清除了。这可能是因为等待位0的
    任务被从阻塞状态移除后,位0被自动清除了。 */
  }
  else
  {
    /* 位0和位4都没有被设置。这可能是因为有一个任务正在等待这两个位都被设
    置,当这个任务离开阻塞状态时,这两个位被清除了。 */
  }
}

函数xEventGroupSetBitsFromISR()

xEventGroupSetBitsFromISR() API函数是 xEventGroupSetBits() API函数的中断安全版本

释放信号量是确定性操作,因为事先知道释放信号量只能导致一个任务离开阻塞状态。当事件组的事件位被设置时,无法提前知道会有多少个任务离开阻塞状态,因此在事件组中设置事件位不是确定性操作。

FreeRTOS 的设计和实现标准不允许在中断服务程序内部或中断被禁用时执行非确定性操作。因此,xEventGroupSetBitsFromISR() 不会直接在中断服务程序中设置事件位,而是将该操作推迟到 RTOS 守护任务中执行。

xEventGroupSetBitsFromISR() API 函数的原型如下所示:

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

xEventGroupSetBitsFromISR() API函数的参数与返回值说明如下所示:

参数名称 参数说明
xEventGroup 设置位的事件组的句柄。该句柄从用于创建事件组的xEventGroupCreate() API函数调用中返回。
uxBitsToSet 位掩码,用于指定在事件组中需要设置为1的事件位或事件位组。事件组的值通过按位或操作来更新,即事件组现有的值与uxBitsToSet中传入的值进行按位或运算。
pxHigherPriorityTaskWoken 如果 xEventGroupSetBitsFromISR()函数将此值设置为 pdTRUE,那么在退出中断之前应该执行上下文切换。
返回值 只有当数据成功发送到定时器命令队列时,才会返回pdPASS。如果因为队列已满,“设置位”命令无法写入定时器命令队列,则返回 pdFALSE。

        注意: 从中断服务程序中设置事件位会将设置操作推迟到 RTOS守护任务(也叫定时器服务任务)。RTOS守护任务与其他RTOS任务一样,都是根据优先级进行调度的。因此,如果置位操作必须立即完成(在应用程序创建的任务执行之前),那么RTOS守护任务的优先级必须要高于其他使用事件组的任务。RTOS守护任务的优先级由 configTIMER_TASK_PRIORITY 在FreeRTOSConfig.h中定义。

        INCLUDE_xEventGroupSetBitFromISR,configUSE_TIMERS和INCLUDE_xTimerPendFunctionCall 必须在FreeRTOSConfig.h中全部被设置为1,才能使用xEventGroupSetBitsFromISR() 函数。

xEventGroupSetBitsFromISR() API 函数的使用示例如下所示:

#define BIT_0    ( 1 << 0 )
#define BIT_4    ( 1 << 4 )

/* 一个事件组,假设已经通过调用 xEventGroupCreate() 创建了。 */
EventGroupHandle_t xEventGroup;

void anInterruptHandler( void )
{
  BaseType_t xHigherPriorityTaskWoken, xResult;

  /* xHigherPriorityTaskWoken 必须初始化为 pdFALSE。 */
  xHigherPriorityTaskWoken = pdFALSE;

  /* 在 xEventGroup 中设置位 0 和位 4。 */
  xResult = xEventGroupSetBitsFromISR(
                                     xEventGroup,   /* 正在更新的事件组。 */
                                     BIT_0 | BIT_4, /* 正在设置的位。 */
                                     &xHigherPriorityTaskWoken );
  /* 消息是否成功发布? */
  if( xResult != pdFAIL )
  {
    /* 如果 xHigherPriorityTaskWoken 现在被设置为 pdTRUE,那么应该
    请求上下文切换。所使用的宏是特定于端口的,它将是 portYIELD_FROM_ISR()
    或 portEND_SWITCHING_ISR() 中的一个-参考在使用的移植的文档页面。 */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  }
}

函数xEventGroupWaitBits()

        xEventGroupWaitBits() API 函数允许任务读取事件组的值,并且如果事件位尚未设置,则可以选择在阻塞状态下等待事件组中的一个或多个事件位被设置(如果这些事件位还没有被设置)

xEventGroupWaitBits() API 函数的原型如下所示:

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

        调度器用来确定任务是否将进入阻塞状态,以及任务何时离开阻塞状态的条件被称为“解除阻塞条件”。解除阻塞条件由uxBitsToWaitFor和xWaitForAllBits参数值的组合指定,具体情况如下:

  • uxBitsToWaitFor指定要测试的事件组中的哪些事件位。
  • xWaitForAllBits指定是使用按位或测试还是按位与测试。

        如果在调用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指定的两个位中只有一个已经在事件组中设置了。当事件组中同时设置了位1与位2时,任务将离开阻塞状态。

        调用任务使用 uxBitsToWaitFor 参数指定要测试的位,并且调用任务可能在解除阻塞条件满足后,需要将这些位清零。可以使用 xEventGroupClearBits() API 函数来清除事件位,但如果满足以下条件,则手动使用该函数清除事件位将导致应用程序代码中的竞争条件:

  • 有多个任务使用同一个事件组。
  • 事件组中的位由不同的任务或中断服务程序设置。

        提供了 xClearOnExit 参数以避免这些潜在的竞争条件。如果 xClearOnExit 被设置为 pdTRUE,则对事件位的测试和清除对于调用任务来说将表现为原子操作(不会被其他任务或中断打断)。

xEventGroupWaitBits() API函数的参数与返回值说明如下所示:

参数名称 参数说明
xEventGroup 事件组的句柄,该事件组包含正在被读取的事件位。
uxBitsToWaitFor 位掩码,用于指定在事件组中需要测试的一个事件位或多个事件位。例如,如果调用任务想要等待事件组中的事件位0和/或事件位2被设置,则将uxBitsToWaitFor设置为0x05(二进制0101)。
xClearOnExit 如果调用任务的解除阻塞条件已满足,且 xClearOnExit 被设置为 pdTRUE,则在调用任务退出 xEventGroupWaitBits() API 函数之前,由 uxBitsToWaitFor 指定的事件位将在事件组中被清零。如果 xClearOnExit 被设置为 pdFALSE,则 xEventGroupWaitBits() API 函数不会修改事件组中事件位的状态。
xWaitForAllBits 如果 xWaitForAllBits 参数被设置为 pdFALSE,那么进入阻塞状态以等待其解除阻塞条件满足的任务,将在 uxBitsToWaitFor 参数指定的任何一个比特位被设置时(或者在 xTicksToWait 参数指定的超时时间到期时)离开阻塞状态。如果 xWaitForAllBits 参数被设置为 pdTRUE,那么进入阻塞状态以等待其解除阻塞条件满足的任务,仅当 uxBitsToWaitFor参数指定的所有比特位都被设置时(或者在
xTicksToWait 任务应该保持在阻塞状态以等待其解除阻塞条件满足的最长时间。
返回值 如果 xEventGroupWaitBits()函数是因为调用任务的解除阻塞条件已满足而返回,则返回值是在调用任务的解除阻塞条件满足时事件组的值。如果 xEventGroupWaitBits() 函数是因为 xTicksToWait 参数指定的阻塞时间已到期而返回,则返回值是阻塞时间到期时事件组的值。

xEventGroupWaitBits() API 函数的使用示例如下所示:

#define BIT_0  ( 1 << 0 )
#define BIT_4  ( 1 << 4 )
 
void aFunction( EventGroupHandle_t xEventGroup )
{
  EventBits_t uxBits;
  const TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS;
 
  /* 在事件组中等待最多 100 毫秒,以等待位 0 或位 4 被设置。
  在退出前清除这些位。 */
  uxBits = xEventGroupWaitBits(
                              xEventGroup,   /* 正在测试的事件组。*/
                              BIT_0 | BIT_4, /* 要等待的事件组中的位。 */
                              pdTRUE,        /* 在返回之前应清除 BIT_0和 BIT_4。 */
                              pdFALSE,       /* 不用等待两个位都到达,任意一位到达即可。 */
                              xTicksToWait );/* 最多等待100毫秒以等待任一位被设置。 */

  if( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) )
  {
    /* xEventGroupWaitBits() 返回是因为两个位都被设置了。 */
  }
  else if( ( uxBits & BIT_0 ) != 0 )
  {
    /* xEventGroupWaitBits() 返回是因为只有 BIT_0 被设置了。 */
  }
  else if( ( uxBits & BIT_4 ) != 0 )
  {
    /* xEventGroupWaitBits() 返回是因为只有 BIT_4 被设置了。 */
  }
  else
  {
    /* xEventGroupWaitBits() 返回是因为 xTicksToWait 个滴答时间已过,
    而 BIT_0 或 BIT_4 都没有被设置。 */
  }
}

函数xEventGroupGetStaticBuffer()

        xEventGroupGetStaticBuffer() API 函数提供了一种方法来获取一个静态创建的事件组的缓冲区的指针。这个缓冲区是在创建事件组时提供的同一个缓冲区。 注意:不要在中断服务程序中调用 xEventGroupGetStaticBuffer()函数。

xEventGroupGetStaticBuffer() API 函数的原型如下所示:

BaseType_t xEventGroupGetStaticBuffer( 
                                      EventGroupHandle_t xEventGroup,
                                      StaticEventGroup_t ** ppxEventGroupBuffer );

xEventGroupGetStaticBuffer() API函数的参数与返回值说明如下所示:

参数名称 参数说明
xEventGroup 需要获取缓冲区的事件组。
ppxEventGroupBuffer 用于返回指向事件组数据结构缓冲区的指针。这个缓冲区与创建时提供的缓冲区相同。
返回值 如果成功检索到缓冲区,将返回 pdTRUE。如果未成功检索到缓冲区,将返回 pdFALSE。

函数xEventGroupSync()

        xEventGroupSync()API函数允许两个或更多任务使用事件组进行相互同步。该函数允许任务在事件组中设置一个或多个事件位,然后等待同一事件组中的事件位组合被设置,该过程作为一个单独的不可中断操作。

xEventGroupSync() 函数的uxBitsToWaitFor参数指定调用任务的解除阻塞条件。如xEventGroupSync() API函数因满足解除阻塞条件而返回,则uxBitsToWaitFor所指定的事件位将在xEventGroupSync()返回之前被清空为零。

xEventGroupSync() API 函数的原型如下所示:

EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
                             const EventBits_t uxBitsToSet,
                             const EventBits_t uxBitsToWaitFor,
                             TickType_t xTicksToWait );

xEventGroupSync() API函数的参数与返回值说明如下所示:

参数名称 参数说明
xEventGroup 用于设置事件位,随后进行测试的事件组的句柄。
uxBitsToSet 位掩码,用于指定在事件组中设置为1的事件位或事件位组合。事件组的值通过将事件组的现有值与传入的uxBitsToSet值进行位或运算来更新。例如,将uxBitsToSet参数设置为0x04(二进制0100)将导致事件位2被设置(如果该事件位之前未被设置),同时保持事件组中的其他事件位不变。
uxBitsToWaitFor 位掩码,用于指定在事件组中待检测的事件位或事件位组合。例如,如果调用任务想要等待事件组中的事件位0、1和2被设置,那么将uxBitsToWaitFor设置为0x07(二进制111)。
xTicksToWait 任务保持在阻塞状态,以等待其解除阻塞条件满足的最大时间。
返回值 如果xEventGroupSync()函数返回是因为调用任务的解除阻塞条件已经满足,那么返回值是调用任务的解除阻塞条件满足时事件组的值(在事件位被自动清零之前)。在这种情况下,返回值也将满足调用任务的解除阻塞条件。如果xEventGroupSync()函数返回是因为xTicksToWait参数指定的阻塞时间已到期,那么返回值是阻塞时间到期时事件组的值。在这种情况下,返回值将不满足调用任务的解除阻塞条件。
Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐