FreeRTOS —— 8.事件组

8.1本章介绍与适用范围

已经注意到,实时嵌入式系统必须响应事件而采取行动。 前面的章节介绍了FreeRTOS的功能,这些功能允许将事件传达给任务。 此类功能的示例包括信号量和队列,它们均具有以下属性:

  • 它们允许任务在“阻止”状态下等待单个事件的发生。
  • 当事件发生时,它们将取消阻止单个任务-未被阻止的任务是等待事件的优先级最高的任务。

事件组是FreeRTOS的另一个功能,它允许将事件传达给任务。 与队列和信号量不同:

  • 事件组允许任务在“阻止”状态下等待发生的多个事件之一的组合。
  • 事件组发生时,事件组将解除阻止所有等待同一事件或事件组合的任务。

事件组的这些独特属性使它们可用于同步多个任务,将事件广播到多个任务,允许任务在“已阻止”状态下等待一组事件中的任何一个发生以及允许任务在事件中等待。 阻止状态,可完成多个操作。

事件组还提供了减少应用程序使用的RAM的机会,因为经常可以用单个事件组替换许多二进制信号量。

事件组功能是可选的。 要包括事件组功能,请在您的项目中构建FreeRTOS源文件event_groups.c。

范围

本章旨在使读者更好地理解:

  • 事件组的实际用途。
  • 事件组相对于其他FreeRTOS功能的优缺点。
  • 如何设置事件组中的位。
  • 如何在“阻塞”状态下等待事件组中的位被置1。
  • 如何使用事件组同步一组任务。

8.2事件组的特征

事件组,事件标志和事件位

事件“标志”是一个布尔值(1或0),用于指示事件是否发生。 事件“组”是一组事件标志。

事件标志只能为1或0,从而允许将事件标志的状态存储在单个位中,而将事件组中所有事件标志的状态存储在单个变量中; 事件组中每个事件标志的状态由EventBits_t类型的变量中的一位表示。 因此,事件标记也称为事件“位”。 如果将EventBits_t变量中的一位设置为1,则该位表示的事件已经发生。 如果将EventBits_t变量中的某个位设置为0,则该位表示的事件尚未发生。

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

在这里插入图片描述

例如,如果事件组的值为0x92(二进制1001 0010),则仅设置事件位1、4和7,因此仅发生了由位1、4和7表示的事件。 图72显示了EventBits_t类型的变量,该变量设置了事件位1、4和7,所有其他事件位清零,事件组的值为0x92。

在这里插入图片描述

应用程序编写者可以为事件组中的各个位分配含义。 例如,应用程序编写者可以创建一个事件组,然后:

  • 在事件组中定义位0,以表示“已从网络接收到消息”。
  • 在事件组中定义位1,以表示“已准备好将消息发送到网络上”。
  • 在事件组中定义位2,以表示“中止当前网络连接”。

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

事件组中事件位的数量取决于FreeRTOSConfig.h1中的configUSE_16_BIT_TICKS编译时间配置常量:

  • 如果configUSE_16_BIT_TICKS为1,则每个事件组均包含8个可用事件位。
  • 如果configUSE_16_BIT_TICKS为0,则每个事件组包含24个可用事件位。

通过多个任务进行访问

事件组本身就是对象,知道其存在的任何任务或ISR都可以访问。 任意数量的任务都可以在同一事件组中设置位,并且任意数量的任务都可以从同一事件组中读取位。

使用事件组的实际示例

FreeRTOS + TCP TCP / IP堆栈的实现提供了一个实际示例,说明如何使用事件组同时简化设计并最小化资源使用。

TCP套接字必须响应许多不同的事件。 事件的示例包括接受事件,绑定事件,读取事件和关闭事件。 套接字在任何给定时间可以预期的事件取决于套接字的状态。 例如,如果已创建一个套接字,但尚未将其绑定到地址,则它可以预期接收到绑定事件,但不会预期接收到读取事件(如果没有地址,则无法读取数据) 。

FreeRTOS + TCP套接字的状态保存在名为FreeRTOS_Socket_t的结构中。 该结构包含一个事件组,该事件组具有为套接字必须处理的每个事件定义的事件位。 FreeRTOS + TCP API调用会阻塞以等待一个事件或一组事件,而只是阻塞事件组。

事件组还包含一个“中止”位,无论套接字当时正在等待哪个事件,它都可以中止TCP连接。

8.3使用事件组进行事件管理

xEventGroupCreate()API函数

FreeRTOS V9.0.0还包括xEventGroupCreateStatic()函数,该函数在编译时静态分配创建事件组所需的内存:必须明确创建事件组,然后才能使用它。

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

在这里插入图片描述

表42,xEventGroupCreate()返回值

Parameter NameDescription
Return Value如果返回NULL,则无法创建事件组,因为FreeRTOS没有足够的堆内存来分配事件组数据结构。 第2章提供了有关堆内存管理的更多信息。返回的非NULL值表示事件组已成功创建。 返回的值应存储为创建的事件组的句柄。

xEventGroupSetBits()API函数

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

注意:切勿从中断服务程序中调用xEventGroupSetBits()。 应在其位置使用中断安全版本xEventGroupSetBitsFromISR()。

在这里插入图片描述
表43,xEventGroupSetBits()参数和返回值

Parameter NameDescription
xEventGroup设置了位的事件组的句柄。 事件组句柄将从用于创建事件组的xEventGroupCreate()的调用中返回。
uxBitsToSet一个位掩码,用于指定要在事件组中设置为1的一个或多个事件位。 通过将事件组的现有值与uxBitsToSet中传递的值进行按位或运算,可以更新事件组的值。例如,将uxBitsToSet设置为0x04(二进制0100)将导致事件组中的事件位3被置位(如果尚未设置),而事件组中的所有其他事件位均保持不变。
Returned Value返回对xEventGroupSetBits()的调用时事件组的值。 请注意,返回的值不一定必须设置uxBitsToSet指定的位,因为这些位可能已由其他任务再次清除。

xEventGroupSetBitsFromISR()API函数

xEventGroupSetBitsFromISR()是xEventGroupSetBits()的中断安全版本。

发出信号量是确定性操作,因为预先知道提供信号量最多可以导致一项任务离开“已阻止”状态。 如果在事件组中设置了位,则无法预先知道有多少任务将离开“已阻止”状态,因此在事件组中设置位不是确定性操作。

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

在这里插入图片描述

表44,xEventGroupSetBitsFromISR()参数和返回值

Parameter NameDescription
xEventGroup设置了位的事件组的句柄。 事件组句柄将从用于创建事件组的xEventGroupCreate()的调用中返回。
uxBitsToSet一个位掩码,用于指定要在事件组中设置为1的一个或多个事件位。 通过将事件组的现有值与uxBitsToSet中传递的值进行按位或运算,可以更新事件组的值。例如,将uxBitsToSet设置为0x05(二进制0101)将导致事件组中的事件位3和事件位0被置位(如果尚未设置),而事件组中的所有其他事件位均保持不变。
pxHigherPriorityTaskWokenxEventGroupSetBitsFromISR()不会直接在中断服务例程内部设置事件位,而是通过在计时器命令队列上发送命令来将操作延迟到RTOS守护程序任务。 如果守护程序任务处于“已阻止”状态,以等待数据在计时器命令队列中可用,则写入计时器命令队列将导致守护程序任务退出“已阻止”状态。 如果守护程序任务的优先级高于当前正在执行的任务(被中断的任务)的优先级,则内部xEventGroupSetBitsFromISR()会将* pxHigherPriorityTaskWoken设置为pdTRUE。如果xEventGroupSetBitsFromISR()将此值设置为pdTRUE,则应在退出中断之前执行上下文切换。 这将确保中断直接返回到守护程序任务,因为守护程序任务将是最高优先级的就绪状态任务。
Returned Value有两个可能的返回值:1. pdPASS 仅当数据成功发送到计时器命令队列时,才会返回pdPASS。 2. pdFALSE 如果由于队列已满而无法将“置位”命令写入计时器命令队列,则将返回pdFALSE。

xEventGroupWaitBits()API函数

xEventGroupWaitBits()API函数允许任务读取事件组的值,并可选地在Blocked状态下等待事件组中的一个或多个事件位被置位(如果尚未设置事件位)。

在这里插入图片描述

调度器用于确定任务是否将进入“阻止”状态以及任务何时退出“阻止”状态的条件称为“取消阻止条件”。取消阻止条件由uxBitsToWaitFor和xWaitForAllBits参数值的组合指定 :

  • uxBitsToWaitFor指定要测试的事件组中的哪个事件位
  • xWaitForAllBits指定是使用按位OR测试还是按位AND测试

如果在调用xEventGroupWaitBits()时满足其取消阻止条件,则该任务将不会进入“阻止”状态。

表45中提供了导致任务进入“已阻止”状态或退出“已阻止”状态的条件示例。表45仅显示了事件组的最低有效四位二进制位和uxBitsToWaitFor值-假定这两个值的其他位为零。

表45,uxBitsToWaitFor和xWaitForAllBits参数的影响

Existing Event Group ValueuxBitsToWaitFor valuexWaitForAllBits valueResultant Behavior
00000101pdFALSE因为在事件组中没有设置位0或位2,所以调用任务将进入阻止状态,并且在事件组中设置了位0或位2时,它将退出阻止状态。
01000101pdTRUE调用任务将进入阻止状态,因为在事件组中未同时设置位0和位2,并且在事件组中同时设置了位0和位2时将退出阻止状态。
01000110pdFALSE由于xWaitForAllBits为pdFALSE,并且在事件组中已设置uxBitsToWaitFor指定的两位之一,因此调用任务将不会进入“阻止”状态。
01000110pdTRUE调用任务将进入阻止状态,因为xWaitForAllBits为pdTRUE,并且在事件组中仅设置了由uxBitsToWaitFor指定的两位中的一位。 在事件组中同时设置了位2和位3时,任务将退出“阻止”状态。

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

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

提供xClearOnExit参数可以避免这些潜在的竞争情况。 如果xClearOnExit设置为pdTRUE,则对调用任务的测试和清除事件位似乎是原子操作(不可被其他任务或中断中断)。

表46,xEventGroupWaitBits()参数和返回值

Parameter NameDescription
xEventGroup包含正在读取的事件位的事件组的句柄。 事件组句柄将从用于创建事件组的xEventGroupCreate()的调用中返回。
uxBitsToWaitFor一个位掩码,指定要在事件组中测试的一个或多个事件位。例如,如果调用任务要等待事件位0和/或事件位2在事件组中被设置,则将uxBitsToWaitFor设置为0x05(二进制0101)。 有关更多示例,请参见表45。
xClearOnExit如果满足调用任务的解除阻止条件,并且xClearOnExit设置为pdTRUE,则在调用任务退出xEventGroupWaitBits()API函数之前,将在事件组中将uxBitsToWaitFor指定的事件位清除为0。如果xClearOnExit设置为pdFALSE,则xEventGroupWaitBits()API函数不会修改事件组中事件位的状态。
xWaitForAllBitsuxBitsToWaitFor参数指定事件组中要测试的事件位。 xWaitForAllBits指定在设置了uxBitsToWaitFor参数指定的一个或多个事件位时,还是仅在设置了uxBitsToWaitFor参数指定的所有事件位时,是否应从“已阻止”状态中删除调用任务。如果xWaitForAllBits设置为pdFALSE,则当uxBitsToWaitFor指定的任何位被置位(或者xTicksToWait参数指定的超时到期)时,进入“阻止”状态以等待其解除阻止条件的任务将退出“阻止”状态。 )。如果xWaitForAllBits设置为pdTRUE,则只有在设置了uxBitsToWaitFor指定的所有位(或xTicksToWait参数指定的超时)后,进入“阻止”状态以等待其解除阻止条件的任务才会离开“阻止”状态 过期)。有关示例,请参见表45。
xTicksToWait任务应保持在“阻塞”状态以等待其解除阻塞条件所需的最长时间。如果xTicksToWait为零,或者在调用xEventGroupWaitBits()时满足解锁条件,则xEventGroupWaitBits()将立即返回。阻塞时间以滴答周期指定,因此它表示的绝对时间取决于滴答频率。 宏pdMS_TO_TICKS()可用于将以毫秒为单位的时间转换为以滴答数为单位的时间。如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(无超时)。
Returned Value如果xEventGroupWaitBits()是由于满足调用任务的取消阻止条件而返回的,则返回值是满足调用任务的取消阻止条件时的事件组的值(如果xClearOnExit为pdTRUE,则自动清除任何位)。 在这种情况下,返回的值也将满足解锁条件。如果由于xTicksToWait参数指定的阻止时间到期而返回xEventGroupWaitBits(),则返回的值是阻止时间到期时事件组的值。 在这种情况下,返回的值将不满足解锁条件。

例子22.用事件组进行实验

本示例演示如何:

  • 创建一个事件组。
  • 在中断服务程序中设置事件组中的位。
  • 在任务的事件组中设置位。
  • 阻止事件组。

通过首先在xWaitForAllBits设置为pdFALSE的情况下执行示例,然后在xWaitForAllBits设置为pdTRUE的情况下执行示例,来演示xEventGroupWaitBits()xWaitForAllBits参数的效果。

从任务设置事件位0和事件位1。 通过中断服务程序设置事件位2。 使用清单136中所示的#define语句为这三位赋予描述性名称。

在这里插入图片描述

清单137显示了设置事件位0和事件位1的任务的实现。它位于一个循环中,重复设置一个位,然后再设置另一个位,每次调用xEventGroupSetBits()之间的延迟为200毫秒。 在设置每个位之前,将打印出一个字符串,以允许在控制台中看到执行的顺序。

在这里插入图片描述

清单138显示了设置事件组中的位2的中断服务例程的实现。 再次,在设置该位之前打印出一个字符串,以允许在控制台中看到执行的顺序。 但是,在这种情况下,由于不应在中断服务例程中直接执行控制台输出,因此xTimerPendFunctionCallFromISR()用于在RTOS守护程序任务的上下文中执行输出。

与前面的示例一样,中断服务例程由强制执行软件中断的简单定期任务触发。 在此示例中,中断每500毫秒生成一次。

在这里插入图片描述

清单139显示了调用xEventGroupWaitBits()阻止事件组的任务的实现。 该任务为事件组中设置的每个位打印出字符串。

xEventGroupWaitBits()xClearOnExit参数设置为pdTRUE,因此在xEventGroupWaitBits()返回之前,将自动清除导致返回对xEventGroupWaitBits()的调用的一个或多个事件位。

在这里插入图片描述

main()函数在启动调度程序之前创建事件组和任务。 有关其实现,请参见清单140。 从事件组读取的任务的优先级高于写入事件组的任务的优先级,确保每次满足读取任务的解除阻止条件时,读取任务将抢占写入任务。

在这里插入图片描述

图73显示了在将xEventGroupWaitBits()xWaitForAllBits参数设置为pdFALSE的情况下执行示例22时产生的输出。在图73中,可以看到,由于在xEventGroupWaitBits()的调用中将xWaitForAllBits参数设置为pdFALSE, 从事件组读取的任务将保持“已阻止”状态,并在每次设置任何事件位时立即执行。

在这里插入图片描述

在图74中显示了在将xEventGroupWaitBits()xWaitForAllBits参数设置为pdTRUE的情况下执行示例22时产生的输出。在图74中可以看到,由于xWaitForAllBits参数设置为pdTRUE,因此该任务从事件组读取 只有在所有三个事件位都置1之后,才离开“阻塞”状态。

在这里插入图片描述

8.4使用事件组进行任务同步

有时,应用程序的设计需要两个或多个任务才能彼此同步。 例如,考虑一个设计,其中任务A接收一个事件,然后将事件所需的一些处理委托给其他三个任务:任务B,任务C和任务D。如果任务A在任务B,C和 D已全部完成对上一个事件的处理,那么所有四个任务都需要彼此同步。 每个任务的同步点将在该任务完成其处理之后,并且无法继续进行,直到其他每个任务都完成了相同的任务。 在所有四个任务都达到其同步点之后,任务A才能接收另一个事件。

在FreeRTOS + TCP演示项目之一中,找到了此类任务同步需求的一个不太抽象的示例。 该演示在两个任务之间共享一个TCP套接字。 一个任务将数据发送到套接字,而另一个任务从同一套接字接收数据2。 在确定另一个任务不会再次尝试访问该套接字之前,关闭这两个套接字是不安全的。 如果两个任务中的任何一个希望关闭套接字,则它必须将其意图通知另一个任务,然后等待另一个任务停止使用套接字,然后再继续操作。 清单140所示的伪代码演示了将任务发送到希望关闭套接字的数据的任务的场景。

清单140演示的场景是微不足道的,因为只有两个任务需要彼此同步,但是很容易看出该场景将如何变得更加复杂,并且如果有其他任务,则需要更多的任务才能加入同步。 执行依赖于套接字打开的处理。

在这里插入图片描述
事件组可用于创建同步点:

  • 每个必须参与同步的任务都在事件组中分配了唯一的事件位。
  • 每个任务在到达同步点时都会设置自己的事件位。
  • 设置了自己的事件位后,事件组中的每个任务块都将等待事件位,代表所有其他同步任务的事件位也被置位。

但是,在这种情况下不能使用xEventGroupSetBits()和xEventGroupWaitBits()API函数。 如果使用它们,则将位的设置(以指示任务已到达其同步点)和位的测试(以确定其他同步任务是否已到达其同步点)将作为两个单独的操作执行。 若要查看为什么会出现问题,请考虑以下情形:任务A,任务B和任务C尝试使用事件组进行同步:

  1. 任务A和任务B已经到达同步点,因此它们的事件位在事件组中已设置,并且它们处于“阻止”状态,等待任务C的事件位也被置位。

  2. 任务C到达同步点,并使用xEventGroupSetBits()设置其在事件组中的位。 设置任务C的位后,任务A和任务B就会退出“阻止”状态,并清除所有三个事件位。

  3. 然后,任务C调用xEventGroupWaitBits()等待所有三个事件位被置位,但是到那时,所有三个事件位已经被清除,任务A和任务B离开了它们各自的同步点,因此同步 失败了。

若要成功使用事件组创建同步点,必须将事件位的设置以及事件位的后续测试作为单个不间断操作执行。 为此提供了xEventGroupSync()API函数。

xEventGroupSync()API函数

提供了xEventGroupSync(),以允许两个或多个任务使用事件组相互同步。 该功能允许一项任务将一个或多个事件位设置在一个事件组中,然后等待事件位的组合在同一事件组中被设置为一个不可中断的操作。

xEventGroupSync()uxBitsToWaitFor参数指定调用任务的解锁条件。 如果xEventGroupSync()因为已满足解除阻止条件而返回,则在xEventGroupSync()返回之前,将由uxBitsToWaitFor指定的事件位清零。

在这里插入图片描述

表47,xEventGroupSync()参数和返回值

Parameter NameDescription
xEventGroup事件组的句柄,将在其中设置事件位,然后对其进行测试。 事件组句柄将从用于创建事件组的xEventGroupCreate()的调用中返回。
uxBitsToSet一个位掩码,用于指定要在事件组中设置为1的一个或多个事件位。 通过将事件组的现有值与uxBitsToSet中传递的值进行按位或运算,可以更新事件组的值。 例如,将uxBitsToSet设置为0x04(二进制0100)将导致事件位3被置位(如果尚未置位),而事件组中的所有其他事件位均保持不变。
uxBitsToWaitFor一个位掩码,指定要在事件组中测试的一个或多个事件位。例如,如果调用任务想要等待事件位0、1和2在事件组中被置位,则将uxBitsToWaitFor设置为0x07(二进制111)。
xTicksToWait任务应保持在“阻塞”状态以等待其解除阻塞条件所需的最长时间。如果xTicksToWait为零,或者在调用xEventGroupSync()时满足解锁条件,则xEventGroupSync()将立即返回。阻塞时间以滴答周期指定,因此它表示的绝对时间取决于滴答频率。 宏pdMS_TO_TICKS()可用于将以毫秒为单位的时间转换为以刻度为单位的时间。如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(无超时)。
Returned Value如果xEventGroupSync()是由于满足调用任务的取消阻止条件而返回的,则返回的值是满足调用任务的取消阻止条件时的事件组的值(在自动将所有位清零之前)。 在这种情况下,返回的值也将满足调用任务的解锁条件。如果由于xTicksToWait参数指定的阻止时间到期而返回xEventGroupSync(),则返回的值是阻止时间到期时事件组的值。 在这种情况下,返回的值将不满足调用任务的解锁条件。

例子23.同步任务

示例23使用xEventGroupSync()同步单个任务实现的三个实例。 task参数用于将任务在调用xEventGroupSync()时将设置的事件位传递到每个实例中。

该任务在调用xEventGroupSync()之前以及在返回对xEventGroupSync()的调用之后再次打印一条消息。 每个消息都包含一个时间戳。 这样可以在产生的输出中观察执行顺序。 伪随机延迟用于防止所有任务同时到达同步点。

有关任务的实现,请参见清单143。

在这里插入图片描述

main()函数创建事件组,创建所有三个任务,然后启动调度程序。 有关其实现,请参见清单144。

在这里插入图片描述

执行示例23时产生的输出如图75所示。可以看出,即使每个任务在不同的时间(伪随机)到达同步点,每个任务也会在相同的时间退出同步点3(即 最后一个任务到达同步点的时间)。

在这里插入图片描述


  1. configUSE_16_BIT_TICKS配置用于保存RTOS滴答计数的类型,因此似乎与事件组功能无关。 它对EventBits_t类型的影响是FreeRTOS的内部实现的结果,它是可取的,因为只有当FreeRTOS在可以比32位类型更有效地处理16位类型的体系结构上执行时,才应将configUSE_16_BIT_TICKS设置为1。 ↩︎

  2. 在撰写本文时,这是可以在任务之间共享单个FreeRTOS + TCP套接字的唯一方法。 ↩︎

  3. 图75显示了在FreeRTOS Windows端口中运行的示例,该示例未提供真正的实时行为(尤其是在使用Windows系统调用将其打印到控制台时),因此将显示一些时间变化。 ↩︎

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值