FreeRTOS的事件标志组
事件标志组是实现多任务同步的有效机制之一。也许有不理解的初学者会问采用事件标志组多麻烦, 搞个全局变量不是更简单?其实不然,在裸机编程时,使用全局变量的确比较方便,但是在加上 RTOS 后 就是另一种情况了。使用全局变量相比事件标志组主要有如下三个问题:
◆ 使用事件标志组可以让 RTOS 内核有效地管理任务,而全局变量是无法做到的,任务的超时等机制需要用户自己去实现。
◆ 使用了全局变量就要防止多任务的访问冲突,而使用事件标志组则处理好了这个问题,用户无需担心。
◆ 使用事件标志组可以有效地解决中断服务程序和任务之间的同步问题。
FreeRTOS 任务间事件标志组的实现
任务间事件标志组的实现是指各个任务之间使用事件标志组实现任务的通信或者同步机制。 FreeRTOS 中事件标志的实现,根据用户在 FreeRTOSConfig.h 文件中的配置:
◆ #define configUSE_16_BIT_TICKS 1
配置宏定义 configUSE_16_BIT_TICKS 为 1 时,每创建一个事件标志组,用户可以使用的事件标志是 8 个。
◆ #define configUSE_16_BIT_TICKS 0
配置宏定义 configUSE_16_BIT_TICKS 为 0 时,每创建一个事件标志组,用户可以使用的事件标志是 24 个。
8 个和 24 个事件标志应该怎么理解呢?其实就是定义了一个 16 位变量,仅使用了低 8bit
或者定义了一个 32 位变量,仅使用了低 24bit。每一个 bit 用 0 和 1 两种状态来代表事件标志。FreeRTOS 上就是将事件标志存储到了 EventBits_t 类型的变量中:
下面通过如下的框图来说明一下 FreeRTOS 事件标志的实现,

运行过程描述如下:
◆ 任务 Task1 运行过程中调用函数 xEventGroupWaitBits,等待事件标志位被设置,任务 Task1 由运行态进入到阻塞态。
◆ 任务 Task2 设置 Task1 等待的事件标志,任务 Task1 由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态。
FreeRTOS 中断方式事件标志组的实现
FreeRTOS 中断方式事件标志组的实现是指中断函数和 FreeRTOS 任务之间使用事件标志。下图来说明一下 FreeRTOS 事件标志的实现:

◆ 任务 Task1 运行过程中调用函数 xEventGroupWaitBits,等待事件标志位被设置,任务 Task1 由运行态进入到阻塞态。
◆ Task1 阻塞的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中设置 Task1 等待的事件标志,任务 Task1 由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态。
上面就是一个简单的 FreeRTOS 中断方式事件标志通信过程。
实际应用中,中断方式的消息机制要注意以下四个问题:
◆ 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
◆ 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
◆ 中断服务程序中一定要调用专用于中断的事件标志设置函数,即以 FromISR 结尾的函数。 ◆ 如果 FreeRTOS 工程的中断函数中没有调用 FreeRTOS 的事件标志组 API 函数,与裸机编程是 一样的;如果调用了,退出的时候要检测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换。
重点的了解以下 4 个函数:
◆ xEventGroupCreate()
◆ xEventGroupWaitBits()
◆ xEventGroupSetBits()
◆ xEventGroupSetBitsFromISR()
函数 xEventGroupCreate 用于创建事件标志组。
◆ 返回值,如果创建成功,此函数返回事件标志组的句柄,
如果 FreeRTOSConfig.h 文件中定义的 heap 空间不足会返回 NULL
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
函数 xEventGroupSetBits 用于设置指定的事件标志位为 1。
◆ 第 1 个参数是事件标志组句柄。
◆ 第 2 个参数表示 24 个可设置的事件标志位,EventBits_t 是定义的 32 位变量,低 24 位用于事件标志设置。变量 uxBitsToSet 的低 24 位的某个位设置为 1,那么被设置的事件标志组的相应位就设置为 1。变量 uxBitsToSet 设置为 0 的位对事件标志相应位没有影响。比如设置变量 uxBitsToSet = 0x0003 就表示将事件标志的位 0 和位 1 设置为 1,其余位没有变化。
◆ 返回当前的事件标志组数值。
使用这个函数要注意以下问题:
1. 使用前一定要保证事件标志组已经通过函数 xEventGroupCreate 创建了。
2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是 xEventGroupSetBitsFromISR
3. 用户通过参数 uxBitsToSet 设置的标志位并不一定会保留到此函数的返回值中,下面举两种情况:
a. 调用此函数的过程中,其它高优先级的任务就绪了,并且也修改了事件标志,此函数返回的事件标志位会发生变化。
b. 调用此函数的任务是一个低优先级任务,通过此函数设置了事件标志后,让一个等待此事件标志 的高优先级任务就绪了,会立即切换到高优先级任务去执行,相应的事件标志位会被函数 xEventGroupWaitBits 清除掉,等从高优先级任务返回到低优先级任务后,函数 xEventGroupSetBits 的返回值已经被修改。
函数描述: 函数 xEventGroupSetBits 用于设置指定的事件标志位为 1。
前两个参数和xEventGroupSetBits 一样
◆ 第 3 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE, 说明有高优先级任务要执行,否则没有。
◆ 返回值,如果消息成功发送给 daemon 任务(就是 FreeRTOS 的定时器任务)返回 pdPASS,否则返回 pdFAIL,另外 daemon 任务中的消息队列满了也会返回 pdFAIL。
使用这个函数要注意以下问题:
1. 使用前一定要保证事件标志已经通过函数 xEventGroupCreate 创建了。
同时要在 FreeRTOSConfig.h 文件中使能如下三个宏定义:
#define INCLUDE_xEventGroupSetBitFromISR 1
#define configUSE_TIMERS 1
#define INCLUDE_xTimerPendFunctionCall 1
2. 函数是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是 xEventGroupSetBits。
3. 函数 xEventGroupSetBitsFromISR 对事件标志组的操作是不确定性操作,因为不知道当前有多少个任务在等待此事件标志。
而 FreeRTOS 不允许在中断服务程序和临界段中执行不确定性操作。为了不在中断服务程序中执行,就通过此函数给 FreeRTOS 的 daemon 任务(就是 FreeRTOS 的定时器任务)发送消息,在 daemon 任务中执行事件标志的置位操作。同时也为了不在临界段中执行此不确定 操作,将临界段改成由调度锁来完成。这样不确定性操作在中断服务程序和临界段中执行的问题就都得到解决了。
4. 由于函数 xEventGroupSetBitsFromISR 对事件标志的置位操作是在 daemon 任务里面执行的,如果想让置位操作立即生效,即让等此事件标志的任务能够得到及时执行,需要设置 daemon 任务的优先级高于使用此事件标志组的所有其它任务。
5. 通过下面的使用举例重点一下函数 xEventGroupSetBitsFromISR 第三个参数的规范用法
函数 xEventGroupWaitBits 等待事件标志被设置。
◆ 第 1 个参数是事件标志组句柄。
◆ 第 2 个参数表示等待 24 个事件标志位中的指定标志,此参数切不可设置为 0。
◆ 第 3 个参数选择是否清除已经被置位的事件标志
如果这个参数设置为 pdTRUE,且函数 xEventGroupWaitBits 在参数 xTicksToWait 设置的溢出时间内返回,那么相应被设置的事件标志位会被清零。如果这个参数设置为 pdFALSE,对已经被设置的事件标志位没有影响。
◆ 第 4 个参数选择是否等待所有的标志位都被设置
如果这个参数设置为 pdTRUE,要等待第 2 个参 数 uxBitsToWaitFor 所指定的标志位全部被置 1,函数才可以返回。当然,超出了在参数 xTicksToWait 设置的溢出时间也是会返回的。
如果这个参数设置为 pdFALSE,第 2 个参数 uxBitsToWaitFor 所指定的任何标志位被置 1,函数都会返回,超出溢出时间也会返回。
◆ 第 5 个参数设置等待时间,单位时钟节拍周期。如果设置为 portMAX_DELAY,表示永久等待。
◆ 返回值,由于设置的时间超时或者指定的事件标志位被置 1,导致函数退出时返回的事件标志组数值。
使用这个函数要注意以下问题:
1. 此函数切不可在中断服务程序中调用。
2. 通过函数的返回值,用户可以检测是哪个事件标志位被置 1 了
◆ 如果由于设置的等待时间超时,函数的返回值可会有部分事件标志位被置 1。
◆ 如果由于指定的事件标志位被置1而返回,并且设置了这个函数的参数xClearOnExit为pdTRUE,那么此函数的返回值是清零前的事件标志组数值。
另外,调用此函数的任务在离开阻塞状态到退出函数 xEventGroupWaitBits 之间这段时间,如果一个 高优先级的任务抢占执行了,并且修改了事件标志位,那么此函数的返回值会跟当前的事件标志组数值不同
使用举例: