事件标志组是实现多任务同步的有效机制之一。任务间事件标志组的实现是指各个任务之间使用事件标志组实现任务的通信或者同步机制。FreeRTOS 在 event_groups.c/h
文件中提供了事件标志组的具体实现。
事件标志组简介
根据具体平台的不同,FreeRTOS 根据 configUSE_16_BIT_TICKS
定义的不同,事件组支持的数量有些区别,具体如下:
#define configUSE_16_BIT_TICKS 1 // 每创建一个事件标志组,用户可以使用的事件标志是 8 个
或者
#define configUSE_16_BIT_TICKS 0 // 每创建一个事件标志组,用户可以使用的事件标志是 24 个
那么这个 8 或者 24 是怎么来的呢?FreeRTOS 根据 configUSE_16_BIT_TICKS
的定义,如果该值为 1 就定义一个 16 位的变量,只使用其中的低 8 位作为事件标志位;如果其值为 0 就定义一个 32 位的变量,使用其中的低 24 位作为事件标志位。具体见event_groups.h
文件中,关于 EventBits_t
的定义:
/*
* The type that holds event bits always matches TickType_t - therefore the
* number of bits it holds is set by configUSE_16_BIT_TICKS (16 bits if set to 1,
* 32 bits if set to 0.
*
* \defgroup EventBits_t EventBits_t
* \ingroup EventGroup
*/
typedef TickType_t EventBits_t;
从上面的定义可知,其类型应该为 TickType_t
类型,该类型具体定义如下:
#if( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
/* 32-bit tick type on a 32-bit architecture, so reads of the tick count do
not need to be guarded with a critical section. */
#define portTICK_TYPE_IS_ATOMIC 1
#endif
至于为何只是用事件标志组其中的一部分,这个在官方文档中没有找到说明!
事件标志组 API 函数
FreeRTOS 的事件标志组提供了 11 个 API 函数,下面对比较常用的几个做一下介绍:
-
EventGroupHandle_t xEventGroupCreate( void );
用来创建一个事件标志组。每个事件组需要一个非常少量的 RAM,用于保存事件组的状态。如果使用xEventGroupCreate()
创建事件组,则会从 FreeRTOS 堆中自动分配所需的 RAM(具体为使用pvPortMalloc
从堆上申请)。- 参数: 无
- 返回值: 如果创建了事件组,则返回事件组的句柄。 如果没有足够的FreeRTOS堆可用于创建事件组,则返回NULL。
- 用法:
/* 定义事件组句柄,用来指向创建的事件标志组. */ EventGroupHandle_t xCreatedEventGroup; /* 创建事件标志组 */ xCreatedEventGroup = xEventGroupCreate(); /* 判断是否创建成功? */ if( xCreatedEventGroup == NULL ) { /* 没有足够堆时,可能会失败 */ } else { /* 创建成功. */ }
-
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer );
该函数同样用来创建一个事件标志组。用来存放事件标志组状态的 RAM 空间需要编程者提供。通过参数pxEventGroupBuffer
传递该 ARM 空间 。该函数允许在编译时静态分配 RAM。- 参数:
pxEventGroupBuffer
指向用来存储事件标志组的 RAM 空间
- 返回值: 如果创建了事件组,则返回事件组的句柄。 如果
pxEventGroupBuffer
为 NULL,则返回 NULL。 - 用法:
/* 事件组句柄 */ EventGroupHandle_t xEventGroupHandle; /* 定义一个事件标志组 */ StaticEventGroup_t xCreatedEventGroup; /* 创建. */ xEventGroupHandle = xEventGroupCreateStatic( &xCreatedEventGroup ); /* 检查是否创建成功 */ configASSERT( xEventGroupHandle );
- 注意:
要使用该函数,configSUPPORT_STATIC_ALLOCATION
必须被置为 1
- 参数:
-
**
void vEventGroupDelete( EventGroupHandle_t xEventGroup );
**删除以前使用xEventGroupCreate()
创建的事件组。- 参数:
xEventGroup:
要被删除的事件标志组的句柄
- 返回值: 无
- 注意:
阻塞在事件标志组上的任务被删除时,将强制取消阻塞,并且返回事件组标志值为 0
- 参数:
-
EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait );
读 RTOS 事件组中的位,可选择进入阻塞状态(具有超时)以等待一个位或一组位被置位。- 参数:
xEventGroup:
被读取的事件标志组uxBitsToWaitFor:
要检测的位(对要检测的位为1。该值不能为 0)xClearOnExit:
退出时是否清楚该位(取值:pdTRUE 或pdFALSE
)xWaitForAllBits:
是否检测到所有位都被置位再返回xTicksToWait :
延时
- 返回值: 测试返回值可以知道哪些位被置位了。 如果
xEventGroupWaitBits()
返回是因为它的超时了,则肯定有等待的位没有被成功置位。 如果xEventGroupWaitBits()
返回是因为它等待的所有位均被设置,则返回的值是任何位被自动清除之前的事件组值(xClearOnExit 参数设置为 pdTRUE 时会清楚结果) - 用法:
#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; uxBits = xEventGroupWaitBits( xEventGroup, /* 要检测事件组句柄. */ BIT_0 | BIT_4, /* 需要检测的位. */ pdTRUE, /* BIT_0 & BIT_4 退出时要被清楚. */ pdFALSE, /* 不用等待BIT_0 和 BIT_4 均被置位(任一一个被置位均返回)*/ xTicksToWait );/* 超时时间 */ if( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) ) { /* 均被置位. */ } else if( ( uxBits & BIT_0 ) != 0 ) { /* 只有 BIT_0 被置位. */ } else if( ( uxBits & BIT_4 ) != 0 ) { /* 只有 BIT_4 被置位. */ } else { /* 超时 */ } }
- 注意:
- 不能再中断中调用该函数
- 该函数的返回值并不可靠,因为有可能会有更高优先级的任务将该值给更改了。需要用户自己确保该值的可靠性!
- 参数:
-
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );
设置 RTOS 事件组中的位(标志)。- 参数:
xEventGroup:
被设置的事件标志组uxBitsToSet :
要设置的位- 返回值: 返回在调用xEventGroupSetBits()时事件组的值。
- 用法:
#define BIT_0 ( 1 << 0 ) #define BIT_4 ( 1 << 4 ) void aFunction( EventGroupHandle_t xEventGroup ) { EventBits_t uxBits; /* 将 bit 0 和 bit 4 置位. */ uxBits = xEventGroupSetBits( xEventGroup, /* 将被更新的事件组. */ BIT_0 | BIT_4 );/* 要被设置的位. */ if( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) ) { /* bit 0 and bit 4 均被置位. */ } else if( ( uxBits & BIT_0 ) != 0 ) { /* 只有Bit 0 被置位 */ } else if( ( uxBits & BIT_4 ) != 0 ) { /* 只有Bit 4 被置位 . */ } else { /* 置位失败. */ } }
- 注意:
- 不能再中断中调用该函数
- 该函数的返回值并不可靠,由以下两个原因
- 因为有可能会有更高优先级的任务在该函数返回前将该值给更改了。
- 可能被
xEventGroupWaitBits()
给更改了
-
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken );
设置 RTOS 事件组中的位(标志)。 可以从中断服务程序(ISR)调用的xEvent GroupSet Bits()
版本。- 参数:
xEventGroup:
被设置的事件标志组uxBitsToSet:
要设置的位pxHigherPriorityTaskWoken:
调用此函数将导致消息被发送到RTOS守护进程任务。 如果守护进程任务的优先级高于当前运行任务的优先级(中断任务中断),则* pxHigherPriorityTaskWoken将被xEventGroupSetBitsFromISR()设置为pdTRUE,表示在中断退出之前应该请求上下文切换。 因此,*pxHigherPriorityTaskWoken必须初始化为pdFALSE。
- 返回值: 如果消息发送到RTOS守护程序任务,则返回pdPASS,否则返回pdFAIL。 如果定时器服务队列已满,将返回pdFAIL。
- 用法:
#define BIT_0 ( 1 << 0 ) #define BIT_4 ( 1 << 4 ) /* 事件组句柄 */ EventGroupHandle_t xEventGroup; void anInterruptHandler( void ) { BaseType_t xHigherPriorityTaskWoken, xResult; /* xHigherPriorityTaskWoken 必须被初始化位 pdFALSE. */ xHigherPriorityTaskWoken = pdFALSE; /* 对 bit 0 和 bit 4 置位. */ xResult = xEventGroupSetBitsFromISR( xEventGroup, BIT_0 | BIT_4, &xHigherPriorityTaskWoken ); /* Was the message posted successfully? */ if( xResult != pdFAIL ) { /* 如果xHigherPriorityTaskWoken现在设置为pdTRUE,那么应该请求上下文切换。 The macro used is port specific and will be either portYIELD_FROM_ISR() or portEND_SWITCHING_ISR() - refer to the documentation page for the port being used. */ portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); } }
- 注意:
INCLUDE_xEventGroupSetBitFromISR
必须被置 1configUSE_TIMERS
必须被置 1INCLUDE_xTimerPendFunctionCall
必须被置 1- 从 ISR 设置位将把设置操作推迟到 RTOS 守护进程任务(也称为定时器服务任务)
- 如果设置操作必须立即完成(在应用程序执行创建的任务之前),那么 FreeRTOS 守护程序任务的优先级必须高于使用事件组的任何应用程序任务的优先级。
- 设置事件组中的位不是确定性操作,因为可能正在等待一个或多个位被设置的未知数量的任务。 FreeRTOS 不允许在中断或临界段中执行非确定性操作。 因此,
xEventGroupSetBitFromISR()
发送消息到 RTOS 守护进程任务,以便在守护进程任务的上下文中执行设置操作 - 其中使用调度程序锁来代替临界段。
- 参数:
-
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear );
清除 RTOS 事件组中的位(标志)。- 参数:
xEventGroup:
被清除的事件标志组uxBitsToSet:
要清除的位
- 返回值: 清除指定位前的事件组的值。
- 用法:
#define BIT_0 ( 1 << 0 ) #define BIT_4 ( 1 << 4 ) void aFunction( EventGroupHandle_t xEventGroup ) { EventBits_t uxBits; /* Clear bit 0 and bit 4 in xEventGroup. */ uxBits = xEventGroupClearBits( xEventGroup, /* The event group being updated. */ BIT_0 | BIT_4 );/* The bits being cleared. */ if( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) ) { /* Both bit 0 and bit 4 were set before xEventGroupClearBits() was called. Both will now be clear (not set). */ } else if( ( uxBits & BIT_0 ) != 0 ) { /* Bit 0 was set before xEventGroupClearBits() was called. It will now be clear. */ } else if( ( uxBits & BIT_4 ) != 0 ) { /* Bit 4 was set before xEventGroupClearBits() was called. It will now be clear. */ } else { /* Neither bit 0 nor bit 4 were set in the first place. */ } }
- 注意:
- 不能再中断中调用该函数
- 参数:
-
**
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear );
**可以从中断调用的xEventGroupClearBits()
版本。 清除操作推迟到 RTOS 守护程序任务(也称为定时器服务任务)。 守护程序任务的优先级由FreeRTOSConfig.h
中的configTIMER_TASK_PRIORITY
设置。- 参数:
xEventGroup:
被清除的事件标志组uxBitsToSet:
要清除的位- 返回值: 清除指定位前的事件组的值。
- 用法:
#define BIT_0 ( 1 << 0 ) #define BIT_4 ( 1 << 4 ) /* This code assumes the event group referenced by the xEventGroup variable has already been created using a call to xEventGroupCreate(). */ void anInterruptHandler( void ) { BaseType_t xSuccess; /* Clear bit 0 and bit 4 in xEventGroup. */ xSuccess = xEventGroupClearBitsFromISR( xEventGroup, /* The event group being updated. */ BIT_0 | BIT_4 );/* The bits being cleared. */ if( xSuccess == pdPASS ) { /* The command was sent to the daemon task. */ } else { /* The clear bits command was not sent to the daemon task. */ } }
-
EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup );
返回RTOS事件组中事件位(事件标志)的当前值。- 参数:
xEventGroup:
被读取的事件标志组
- 返回值: 该函数被调用时,事件标志组的值
- 注意:
- 不能再中断中调用该函数
- 参数:
-
EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup );
可以从中断调用的xEventGroupGetBits()
版本。- 参数:
xEventGroup:
被读取的事件标志组
- 返回值: 该函数被调用时,事件标志组的值
- 注意:
- 不能再中断中调用该函数
- 参数:
-
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait );
可以从中断调用的xEventGroupGetBits()
版本。- 参数:
xEventGroup:
正在设置和测试位的事件组。uxBitsToSet:
在确定是否(并且可能等待)由uxBitsToWait参数指定的所有位之前,在事件组中设置的一个或多个位。 例如,将uxBitsToSet 设置为 0x04 以在置位事件组中第2位。uxBitsToWaitFor:
指示要在事件组中测试的一个或多个位的按位值。 例如,将 uxBitsToWaitFor 设置为 0x05 以等待位 0 和位 2;将uxBitsToWaitFor 设置为 0x07 以等待位 0 和位 1 和位 2。xTicksToWait :
超时时间。
- 返回值: 事件组在等待位被置位或块时间到期时的值。 测试返回值以知道设置了哪些位。
- 注意:
- 不能再中断中调用该函数
- 参数:
事件组实现看门狗
在嵌入式系统中,看门狗是一个必不可少的组件。在使用了操作系统后,如何正确的实现看门狗成了一个问题!思路: 让所有线程每隔一段时间上报一次“我还活着”事件给监视程序,当监视程序发现其中一个线程在这段时间内没有上报“我还活着”事件时就停止喂狗。
定义监视的任务事件标志组位
/**
* @brief 最多一次监测MAX_TASK_NUM个任务,如果多于该数,则需要定义多个事件标志组
*/
#define WDG_BIT_DOWN_TASK_1 (1 << 0)
#define WDG_BIT_DOWN_TASK_2 (1 << 1)
#define WDG_BIT_DOWN_TASK_3 (1 << 2)
#define WDG_BIT_DOWN_TASK_4 (1 << 3)
#define WDG_BIT_TASK_ALL ( WDG_BIT_DOWN_TASK_1 | WDG_BIT_DOWN_TASK_2 | WDG_BIT_DOWN_TASK_3 | WDG_BIT_DOWN_TASK_4 )
监视任务
监视任务负责在规定时间内检测个事件标志组位,已达到监测其他任务运行的目的。
/**
* @brief 看门狗任务
* @param argument
* @retval None
*/
void vTaskWDG(void * argument)
{
static TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS*10; /* 最大延迟1s */
EventBits_t uxBits;
/* 创建事件标志组 */
xCreatedEventGroup = xEventGroupCreate();
if(xCreatedEventGroup == NULL)
{
/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
return;
}
while(1)
{
/* 等待所有任务发来事件标志 */
uxBits = xEventGroupWaitBits(xCreatedEventGroup, /* 事件标志组句柄 */
WDG_BIT_TASK_ALL, /* 等待WDG_BIT_TASK_ALL被设置 */
pdTRUE, /* 退出前WDG_BIT_TASK_ALL被清除,这里是TASK_BIT_ALL都被设置才表示“退出”*/
pdTRUE, /* 设置为pdTRUE表示等待TASK_BIT_ALL都被设置*/
xTicksToWait); /* 等待延迟时间 */
if((uxBits & WDG_BIT_TASK_ALL) == WDG_BIT_TASK_ALL)
{
vWDG_Feed();
}
else
{
/* 通过变量uxBits简单的可以在此处检测那个任务长期没有发来运行标志 */
}
}
}
这样,在其他任务中,不断调用 xEventGroupSetBits,给相应的位进行置位即可!一旦有任务没有正常置位,则该任务停止喂狗!
当然,这样就出现了一个问题:有的任务可能需要阻塞相当长的时间,这个时间已经远远超过了看门狗的时限。又或者说,用户手动挂起了一个任务,看门狗必须暂停监视该任务。解决这个问题也很简单,只需要稍微更改一下看门狗任务即可:
- 首先,必须有函数可以同步看门狗任务,处理任务挂起、恢复问题。
- 其次,对于长时间等待任务,看门狗任务可以以固定频率喂狗,在规定的最大时间到时,检测所有事件,如果这时还有没有置位的事件,则认为出错!