20.4 事件控制块
事件标志组存储在一个 EventBits_t 类型的变量中, 该变量在事件组结构体中定义。
在 STM32 中, uxEventBits 是 32 位的, 所以我们有 24 个位用来实现事件组。 除了事件标志组变量之外, FreeRTOS 还使用了一个链表来记录等待事件的任务,所有在等待此事件的任务均会被挂载在等待事件列表 xTasksWaitingForBits。
typedef struct xEventGroupDefinition{EventBits_t uxEventBits;List_t xTasksWaitingForBits;#if( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxEventGroupNumber;#endif#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )uint8_t ucStaticallyAllocated;#endif} EventGroup_t;
20.5 事件函数接口讲解
xEventGroupCreate()用于创建一个事件组,并返回对应的句柄。 要想使用该函数必须在头文件 FreeRTOSConfig.h 定义宏 configSUPPORT_DYNAMIC_ALLOCATION 为 1 且需要把FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。
每一个事件组只需要很少的 RAM 空间来保存事件的发生状态。如果使用函数xEventGroupCreate()来创建一个事件,那么需要的 RAM 是动态分配的。
创建事件的函数 xEventGroupCreate():
当创建一个事件时, 系统会首先给我们分配事件控制块的内存空间,然后对该事件控制块进行基本的初始化,创建成功返回事件句柄;创建失败返回 NULL。
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )EventGroupHandle_t xEventGroupCreate( void ){EventGroup_t *pxEventBits;/* 分配事件控制块的内存 */pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) );if( pxEventBits != NULL ){// 初始化为0pxEventBits->uxEventBits = 0;// 初始化链表vListInitialise( &( pxEventBits->xTasksWaitingForBits ) );// 静态分配内存的,此处暂时不用理会#if( configSUPPORT_STATIC_ALLOCATION == 1 ){pxEventBits->ucStaticallyAllocated = pdFALSE;}#endif /* configSUPPORT_STATIC_ALLOCATION */traceEVENT_GROUP_CREATE( pxEventBits );}else{traceEVENT_GROUP_CREATE_FAILED();}return ( EventGroupHandle_t ) pxEventBits;}#endif /* configSUPPORT_DYNAMIC_ALLOCATION *//*-----------------------------------------------------------*/
使用实例:
static EventGroupHandle_t Event_Handle =NULL;/* 创建 Event_Handle */Event_Handle = xEventGroupCreate();if (NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n");else/* 创建失败,应为内存空间不足 */
20.5.2 事件删除函数vEventGroupDelete()
这个事件没用了,比如启动,各项指标达成后。启动完成,然后就可以删除了。
FreeRTOS 给我们提供了一个删除事件的函数——vEventGroupDelete(),使用它就能将事件进行删除了。
void vEventGroupDelete( EventGroupHandle_t xEventGroup ){EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;const List_t *pxTasksWaitingForBits = &( pxEventBits->xTasksWaitingForBits );vTaskSuspendAll();{traceEVENT_GROUP_DELETE( xEventGroup );while( listCURRENT_LIST_LENGTH( pxTasksWaitingForBits ) > ( UBaseType_t ) 0 ){/* 如果有任务阻塞在这个事件上,那么就要把事件从等待事件列表中移除 */configASSERT( pxTasksWaitingForBits->xListEnd.pxNext != ( ListItem_t * ) &( pxTasksWaitingForBits->xListEnd ) );( void ) xTaskRemoveFromUnorderedEventList( pxTasksWaitingForBits->xListEnd.pxNext, eventUNBLOCKED_DUE_TO_BIT_SET );}#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) ){/* 释放事件的内存*/vPortFree( pxEventBits );}}( void ) xTaskResumeAll();}/*-----------------------------------------------------------*/
- 挂起调度器,因为接下来的操作不知道需要多长的时间,并且在删除的时候,不希望其他任务来操作这个事件标志组,所以暂时把调度器挂起,让当前任务占有 CPU。
- 当有任务被阻塞在事件等待列表中的时候,我们就要把任务恢复过来,否则删除了事件的话,就无法对事件进行读写操作,那这些任务可能永远等不到事,件(因为任务有可能是一直在等待事件发生的) ,使用 while 循环保证所有的任务都会被恢复。
- 调用 xTaskRemoveFromUnorderedEventList()函数将任务从等待事件列表中移除,然后添加到就绪列表中,参与任务调度,当然,因为挂起了调度器,所以在这段时间里,即使是优先级更高的任务被添加到就绪列表,系统也不会进行任务调度,所以也就不会影响当前任务删除事件的操作,这也是为什么需要挂起调度器的原因。 使用事件删除函数 vEventGroupDelete()的时候需要注意,尽量在没有任务阻塞在这个事件的时候进行删除,否则任务无法等到正确的事件,因为删除之后,所有被恢复的任务都只能获得事件的值为 0。
使用实例:
static EventGroupHandle_t Event_Handle =NULL;/* 创建 Event_Handle */Event_Handle = xEventGroupCreate();if (NULL != Event_Handle){printf("Event_Handle 事件创建成功!\r\n");/* 创建成功,可以删除 */xEventGroupCreate(Event_Handle);}/* 创建失败,应为内存空间不足 */
20.5.3 (任务)事件组置位函数xEventGroupSetBits()
xEventGroupSetBits()用于置位事件组中指定的位, 当位被置位之后,阻塞在该位上的任务将会被解锁。
通过参数指定的事件标志来设定事件的标志位,然后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配,如果有,则唤醒该任务。简单来说,就是设置我们自己定义的事件标志位为 1,并且看看有没有任务在等待这个事件,有的话就唤醒它。
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet ){ListItem_t *pxListItem, *pxNext;ListItem_t const *pxListEnd;List_t *pxList;EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits;EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;BaseType_t xMatchFound = pdFALSE;configASSERT( xEventGroup );/* 断言,判断要设置的事件标志位是否有效 */configASSERT( ( uxBitsToSet & eventEVENT_BITS_CONTROL_BYTES ) == 0 ); // 1// pxList设置为有等待列表的链表pxList = &( pxEventBits->xTasksWaitingForBits );// 这个是首位pxListEnd = listGET_END_MARKER( pxList );// 挂起vTaskSuspendAll();{traceEVENT_GROUP_SET_BITS( xEventGroup, uxBitsToSet );// 指向首个ItempxListItem = listGET_HEAD_ENTRY( pxList );/* 设置事件标志位. */pxEventBits->uxEventBits |= uxBitsToSet;/* 设置这个事件标志位可能是某个任务在等待的事件就遍历等待事件列表中的任务 */while( pxListItem != pxListEnd ){pxNext = listGET_NEXT( pxListItem );uxBitsWaitedFor = listGET_LIST_ITEM_VALUE( pxListItem );xMatchFound = pdFALSE;/* 获取要等待事件的标记信息,是逻辑与还是逻辑或 */// 取出高8位uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES;// 清零 高8位uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES;// 如果只需要有任意一个事件标志位满足唤醒任务(也是我们常说// 的“逻辑或”),那么还需要看看是否有这个事件发生了。// 看看高8位的标记是否是等待所有的消息,不是的话就是在等待单个任务if( ( uxControlBits & eventWAIT_FOR_ALL_BITS ) == ( EventBits_t ) 0 ){/* 判断要等待的事件是否发生了 */if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) != ( EventBits_t ) 0 ){xMatchFound = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}/* 否则就要所有事件都发生的时候才能解除阻塞 */else if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) == uxBitsWaitedFor ){// 满足任务xMatchFound = pdTRUE;}else{}if( xMatchFound != pdFALSE ){/* 找到了,然后看下是否需要清除标志位如果需要,就记录下需要清除的标志位,等遍历完队列之后统一处理 */// 方式:获取对应的高8位的标志,查看是否需要删除标记if( ( uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT ) != ( EventBits_t ) 0 ){// 要清楚标记 用uxBitsToClear来记录标记uxBitsToClear |= uxBitsWaitedFor;}else{mtCOVERAGE_TEST_MARKER();}/* 将满足事件条件的任务从等待列表中移除,并且添加到就绪列表中 */// 这里设置了标记位 eventUNBLOCKED_DUE_TO_BIT_SET// 如果有任务被唤醒会判断这个标记位,是否是被置位了( void ) xTaskRemoveFromUnorderedEventList( pxListItem,pxEventBits->uxEventBits |eventUNBLOCKED_DUE_TO_BIT_SET );}/* 循环遍历事件等待列表,可能不止一个任务在等待这个事件 */pxListItem = pxNext;}/* 遍历完毕,清除事件标志位 */pxEventBits->uxEventBits &= ~uxBitsToClear;}( void ) xTaskResumeAll();return pxEventBits->uxEventBits;}/*-----------------------------------------------------------*/
- 断言:判断要设置的事件标志位是否有效,因为一个 32 位的事件标志组变量只有 24 位是用于设置事件的,而 16 位的事件标志组变量只有 8 位用于设置事件,高 8 位不允许设置事件。有其它用途
xEventGroupSetBits()的运用很简单,举个例子,比如我们要记录一个事件的发生,这个事件在事件组的位置是 bit0,当它还未发生的时候,那么事件组 bit0 的值也是 0,当它发生的时候,我们往事件集合 bit0 中写入这个事件,也就是 0x01,那这就表示事件已经发生了,为了便于理解,一般操作我们都是用宏定义来实现#if configUSE_16_BIT_TICKS == 1#define eventCLEAR_EVENTS_ON_EXIT_BIT 0x0100U#define eventUNBLOCKED_DUE_TO_BIT_SET 0x0200U#define eventWAIT_FOR_ALL_BITS 0x0400U#define eventEVENT_BITS_CONTROL_BYTES 0xff00U#else#define eventCLEAR_EVENTS_ON_EXIT_BIT 0x01000000UL#define eventUNBLOCKED_DUE_TO_BIT_SET 0x02000000UL#define eventWAIT_FOR_ALL_BITS 0x04000000UL#define eventEVENT_BITS_CONTROL_BYTES 0xff000000UL#endif
#define EVENT (0x01 << x),“<< x”表示写入事件集合的 bit x , 在使用该函数之前必须先创建事件。
使用示例:
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位 0#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位 1static EventGroupHandle_t Event_Handle =NULL;/* 创建 Event_Handle */Event_Handle = xEventGroupCreate();if (NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n");static void KEY_Task(void* parameter){/* 任务都是一个无限循环,不能返回 */while(1) {//如果 KEY1 被按下if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {printf ( "KEY1 被按下\n" );/* 触发一个事件 1 */xEventGroupSetBits(Event_Handle,KEY1_EVENT);}if ( Key_Scan(KEY2_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {printf ( "KEY2 被按下\n" );/* 触发一个事件 1 */xEventGroupSetBits(Event_Handle, KEY2_EVENT);}}}
20.5.4 (中断)xEventGroupSetBitsFromISR()(中断)
置位事件组中的标志位是一个不确定的操作,因为阻塞在事件组的标志位上的任务的个数是不确定的。 FreeRTOS 是不允许不确定的操作在中断和临界段中发生的, 所以EventGroupSetBitsFromISR()给 FreeRTOS 的守护任务发送一个消息,让置位事件组的操作在守护任务里面完成,守护任务是基于调度锁而非临界段的机制来实现的。
在中断中事件标志的置位是在守护任务(也叫软件定时器服务任务) 中完成的。 因此 FreeRTOS 的守护任务与其他任务一样, 都是系统调度器根据其优先级进行任务调度的, 但守护任务的优先级必须比任何任务的优先级都要高, 保证在需要的时候能立即切换任务从而达到快速处理的目的。
xEventGroupSetBitsFromISR()函数真正调用的也是 xEventGroupSetBits()函数,只不过是在守护任务中进行调用的。
使用实例
#define BIT_0 ( 1 << 0 )#define BIT_4 ( 1 << 4 )/* 假定事件组已经被创建 */EventGroupHandle_t xEventGroup;void anInterruptHandler( void ){BaseType_t xHigherPriorityTaskWoken, xResult;/* xHigherPriorityTaskWoken 在使用之前必须先初始化为 pdFALSE */xHigherPriorityTaskWoken = pdFALSE;/* 置位事件组 xEventGroup 的的 Bit0 和 Bit4 */xResult = xEventGroupSetBitsFromISR(xEventGroup,BIT_0 | BIT_4,&xHigherPriorityTaskWoken );/* 信息是否发送成功 */if ( xResult != pdFAIL ) {/* 如果 xHigherPriorityTaskWoken 的值为 pdTRUE 则进行一次上下文切换*/portYIELD_FROM_ISR( xHigherPriorityTaskWoken );}}
20.5.5 等待事件函数xEventGroupWaitBits()
FreeRTOS 提 供 了 一 个 等 待 指 定 事 件 的 函 数 — —xEventGroupWaitBits()
通过这个函数, 任务可以知道事件标志组中的哪些位,有什么事件发生了, 然后通过 “逻辑与”、“逻辑或”等操作对感兴趣的事件进行获取
并且这个函数实现了等待超时机制。
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait ){EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;EventBits_t uxReturn, uxControlBits = 0;BaseType_t xWaitConditionMet, xAlreadyYielded;BaseType_t xTimeoutOccurred = pdFALSE;// 断言configASSERT( xEventGroup );configASSERT( ( uxBitsToWaitFor & eventEVENT_BITS_CONTROL_BYTES ) == 0 );configASSERT( uxBitsToWaitFor != 0 );#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endifvTaskSuspendAll(); // 挂起调度器。{/* 获取当前设置的位 */const EventBits_t uxCurrentEventBits = pxEventBits->uxEventBits;/* 先看下当前事件中的标志位是否已经满足条件了 */xWaitConditionMet = prvTestWaitCondition( uxCurrentEventBits, //当前置位uxBitsToWaitFor, //等待的位置xWaitForAllBits ); //设置逻辑或与/* 满足条件了 说明有对应的事件产生 */if( xWaitConditionMet != pdFALSE ){/* 满足条件了,就可以直接返回了,注意这里返回的是的当前事件的所有标志位 */uxReturn = uxCurrentEventBits;xTicksToWait = ( TickType_t ) 0;/* 看看在退出的时候是否需要清除对应的事件标志位 */if( xClearOnExit != pdFALSE ){pxEventBits->uxEventBits &= ~uxBitsToWaitFor;}else{mtCOVERAGE_TEST_MARKER();}}/* 不满足条件,并且不等待 */else if( xTicksToWait == ( TickType_t ) 0 ){/* 同样也是返回当前事件的所有标志位 */uxReturn = uxCurrentEventBits;}else /* 用户指定超时时间了,那就进入等待状态 */{/* 保存一下当前任务的信息标记,以便在恢复任务的时候对事件进行相应的操作 */if( xClearOnExit != pdFALSE ){uxControlBits |= eventCLEAR_EVENTS_ON_EXIT_BIT;}else{mtCOVERAGE_TEST_MARKER();}// 这里标记了标志位,这个是判断是 与还是或。从上面唤醒事件可以印证if( xWaitForAllBits != pdFALSE ){uxControlBits |= eventWAIT_FOR_ALL_BITS;}else{mtCOVERAGE_TEST_MARKER();}/* 当前任务进入事件等待列表中,任务将被阻塞指定时间 xTicksToWait */vTaskPlaceOnUnorderedEventList( &( pxEventBits->xTasksWaitingForBits ),( uxBitsToWaitFor | uxControlBits ),xTicksToWait );uxReturn = 0;traceEVENT_GROUP_WAIT_BITS_BLOCK( xEventGroup, uxBitsToWaitFor );}}/* 恢复调度器 */xAlreadyYielded = xTaskResumeAll();if( xTicksToWait != ( TickType_t ) 0 ){if( xAlreadyYielded == pdFALSE ){/* 进行一次任务切换 */portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}/* 进入到这里说明当前的任务已经被重新调度了调用uxTaskResetEventItemValue()返回并重置 xEventListItem 的值,因为之前事件列表项的值被保存起来了,现在取出来看看是不是有事件发生。*/// uxReturn是调度会来之后的时间表uxReturn = uxTaskResetEventItemValue();// 如果仅仅是超时返回,那系统就会直接返回当前事件的所有标志位。// eventUNBLOCKED_DUE_TO_BIT_SET:这里是超时了,并且没有设置标志位if( ( uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET ) == ( EventBits_t ) 0 ){taskENTER_CRITICAL();{/* 超时返回时,直接返回当前事件的所有标志位 */uxReturn = pxEventBits->uxEventBits;// 再判断一次是否发生了事件 可能任务在被其它任务占据的时候,事件发生了if( prvTestWaitCondition( uxReturn, uxBitsToWaitFor, xWaitForAllBits ) != pdFALSE ){//如果发生了,那就清除事件标志位并且返回if( xClearOnExit != pdFALSE ){pxEventBits->uxEventBits &= ~uxBitsToWaitFor;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}taskEXIT_CRITICAL();xTimeoutOccurred = pdFALSE;}else{}/* 否则就返回事件所有标志位,然后退出 */uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;}traceEVENT_GROUP_WAIT_BITS_END( xEventGroup, uxBitsToWaitFor, xTimeoutOccurred );return uxReturn;}/*-----------------------------------------------------------*/
流程分析:
当用户调用这个函数接口时,系统首先根据用户指定参数和接收选项来判断它要等待的事件是否发生,如果已经发生,则根据参数 xClearOnExit 来决定是否清除事件的相应标志位,并且返回事件标志位的值,但是这个值并不是一个稳定的值,所以在等待到对应事件的时候,还需我们判断事件是否与任务需要的一致; 如果事件没有发生,则把任务添加到事件等待列表中, 把任务感兴趣的事件标志值和等待选项填用列表项的值来表示,直到事件发生或等待时间超时。等待超时的时候,还会进行一次检查,为的是,当前任务阻塞结束的时候,被其它任务抢占,然后完成了事件的标记。
实例:
static void LED_Task(void* parameter){EventBits_t r_event; /* 定义一个事件接收变量 *//* 任务都是一个无限循环,不能返回 */while(1) {/***************************************************************** 等待接收事件标志** 如果 xClearOnExit 设置为 pdTRUE,那么在 xEventGroupWaitBits()返回之前,* 如果满足等待条件(如果函数返回的原因不是超时),那么在事件组中设置* 的 uxBitsToWaitFor 中的任何位都将被清除。* 如果 xClearOnExit 设置为 pdFALSE,* 则在调用 xEventGroupWaitBits()时,不会更改事件组中设置的位。** xWaitForAllBits 如果 xWaitForAllBits 设置为 pdTRUE,则当 uxBitsToWaitFor 中* 的所有位都设置或指定的块时间到期时, xEventGroupWaitBits()才返回。* 如果 xWaitForAllBits 设置为 pdFALSE,则当设置 uxBitsToWaitFor 中设置的任何* 一个位置 1 或指定的块时间到期时, xEventGroupWaitBits()都会返回。* 阻塞时间由 xTicksToWait 参数指定。*********************************************************/r_event = xEventGroupWaitBits(Event_Handle, /* 事件对象句柄 */KEY1_EVENT|KEY2_EVENT,/* 接收任务感兴趣的事件 */pdTRUE, /* 退出时清除事件位 */pdTRUE, /* 满足感兴趣的所有事件 */portMAX_DELAY);/* 指定超时事件,一直等 */if ((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)) {/* 如果接收完成并且正确 */printf ( "KEY1 与 KEY2 都按下\n");LED1_TOGGLE; //LED1 反转} else {printf ( "事件错误! \n");}}}
20.5.6 xEventGroupClearBits()与 xEventGroupClearBitsFromISR()
xEventGroupClearBits()与 xEventGroupClearBitsFromISR()都是用于清除事件组指定的位, 如果在获取事件的时候没有将对应的标志位清除, 那么就需要用这个函数来进行显式清除。
xEventGroupClearBits()函数不能在中断中使用,而是由具有中断保护功能 的xEventGroupClearBitsFromISR() 来代替,中断清除事件标志位的操作在守护任务(也叫定时器服务任务)里面完成。
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear ){EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;EventBits_t uxReturn;configASSERT( xEventGroup );configASSERT( ( uxBitsToClear & eventEVENT_BITS_CONTROL_BYTES ) == 0 );taskENTER_CRITICAL();{traceEVENT_GROUP_CLEAR_BITS( xEventGroup, uxBitsToClear );/* The value returned is the event group value prior to the bits beingcleared. */uxReturn = pxEventBits->uxEventBits;/* Clear the bits. */pxEventBits->uxEventBits &= ~uxBitsToClear;}taskEXIT_CRITICAL();//返回值是原先没有清楚之前的return uxReturn;}
使用实例:
#define BIT_0 ( 1 << 0 )#define BIT_4 ( 1 << 4 )void aFunction( EventGroupHandle_t xEventGroup ){EventBits_t uxBits;/* 清楚事件组的 bit 0 and bit 4 */uxBits = xEventGroupClearBits(xEventGroup,BIT_0 | BIT_4 );if ( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) ) {/* 在调用 xEventGroupClearBits()之前 bit0 和 bit4 都置位但是现在是被清除了*/} else if ( ( uxBits & BIT_0 ) != 0 ) {/* 在调用 xEventGroupClearBits()之前 bit0 已经置位但是现在是被清除了*/} else if ( ( uxBits & BIT_4 ) != 0 ) {/* 在调用 xEventGroupClearBits()之前 bit4 已经置位但是现在是被清除了*/} else {/* 在调用 xEventGroupClearBits()之前 bit0 和 bit4 都没被置位 */}}
