目录
本章基于FreeRtos V9.0.0版本分析
一、事件简介
事件是一种实现任务间通信的机制,主要用于多任务间的同步,可以实现一对多,多对多的同步,可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理;事件通信只能是事件类型的通信,无数据传输。
二、事件结构
FreeRtos中的事件组是一个16位或32位的数据(EventBits_t ),数据的高8位表示阻塞任务对事件的处理属性,剩余的位置,每个bit位表示一个事件。
如果configUSE_16_BIT_TICKS定义为 1,那么变量uxEventBits就是16位,低8位用来存储事件组;如果宏 configUSE_16_BIT_TICKS 定义为 0,那么变量uxEventBits就是32 位,低24位用来存储事件组。
定义一个任务事件链表xTasksWaitingForBits,记录因等待事件而阻塞的任务。
// 事件结构体
typedef struct xEventGroupDefinition
{
EventBits_t uxEventBits; // 事件组(高8位表示操作过程标志,其他低比特位表示各个事件)
List_t xTasksWaitingForBits; // 等待事件阻塞任务链表
} EventGroup_t;
三、 事件接口统计
事件接口可分为创建、设置、等待、同步、清除;在event_groups.h中声明。
/*-----------------------事件组创建------------------------------------*/
// 动态创建(返回事件组句柄)
EventGroupHandle_t xEventGroupCreate( void );
// 静态创建(返回事件组句柄)
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer )
/*-----------------------设置事件(uxBitsToSet)-------------------------*/
// 线程模式,返回最新的事件组值(未清除)
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet )
// 中断模式(向定时器队列发送消息,由定时器任务进行事件设置)
#define xEventGroupSetBitsFromISR( xEventGroup, uxBitsToSet, pxHigherPriorityTaskWoken ) xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken )
/*-----------------------等待事件组------------------------------------*/
// uxBitsToWaitFor 表示等待事件
// xClearOnExit 1:表示等待事件成功后,清除相关事件,0:不清除
// xWaitForAllBits 1:与运算,需满足所有等待事件。0:或运算,满足等待事件中任一个
// xTicksToWait 表示等待事件不满足时阻塞时间
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait )
/*-----------------------清除事件------------------------------------*/
// 根据uxBitsToClear 清除事件组事件(线程)
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )
// 根据uxBitsToClear 清除事件组事件(中断)
#define xEventGroupClearBitsFromISR( xEventGroup, uxBitsToClear ) xTimerPendFunctionCallFromISR( vEventGroupClearBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToClear, NULL )
/*-----------------------多任务同步事件------------------------------------------*/
// uxBitsToSet 表示发送事件
// uxBitsToWaitFor 表示等待事件
// xTicksToWait 表示等待事件不满足时阻塞时间
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait )
/*-----------------------删除事件组------------------------------------*/
void vEventGroupDelete( EventGroupHandle_t xEventGroup )
/*-----------------------其他接口------------------------------------*/
// 事件组读取接口(线程)
#define xEventGroupGetBits(xEventGroup) xEventGroupClearBits( xEventGroup, 0 )
// 事件组读取接口(中断)
EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup )
// 事件发送回调
void vEventGroupSetBitsCallback( void *pvEventGroup, const uint32_t ulBitsToSet )
// 事件清除回调
void vEventGroupClearBitsCallback( void *pvEventGroup, const uint32_t ulBitsToClear )
四、事件组创建
事件组创建有2种方式,静态创建和动态创建,与任务创建类似。静态创建即事件组和任务阻塞链表均通过全局变量存储,不能释放;动态创建为动态内存,可以生灭;本章我们仅以动态创建为例进行分析。
事件组创建过程:
(1)申请动态内存(大小为事件组结构体大小);
(2)初始化事件全为0;
(3)初始化等待事件任务阻塞链表。
(4)返回事件组句柄。
// 动态新建
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
EventGroupHandle_t xEventGroupCreate( void ) PRIVILEGED_FUNCTION;
#endif
// 静态新建
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer ) PRIVILEGED_FUNCTION;
#endif
EventGroupHandle_t xEventGroupCreate( void )
{
EventGroup_t *pxEventBits;
// 【1.申请动态内存】
pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) );
if( pxEventBits != NULL )
{
// 【2.事件组初始化为0】
pxEventBits->uxEventBits = 0;
// 【3.初始化阻塞任务链表】
vListInitialise( &( pxEventBits->xTasksWaitingForBits ) );
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
pxEventBits->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
traceEVENT_GROUP_CREATE( pxEventBits );
}
else
{
traceEVENT_GROUP_CREATE_FAILED();
}
// 【4.返回事件组句柄】
return ( EventGroupHandle_t ) pxEventBits;
}
五、事件操作(线程)
5.1 事件阻塞机制
5.1.1 事件控制字
在任务因事件阻塞过程中,通过控制字(事件组高8位)记录处理方式及过程;控制字记录在阻塞任务事件条目值(xEventListItem->xItemValue)中,在事件组(uxEventBits)中不存储,事件组中高8位一直为0。
注:在信号、队列导致的阻塞中,xEventListItem->xItemValue存储阻塞任务的优先级,并根据优先级顺序对任务解锁;在事件导致的阻塞中,xEventListItem->xItemValue存储为“控制字+事件”模式,根据时间顺序对任务统一进行事件判断和解锁。
控制字定义如下:
// 16位事件阻塞属性位(xEventListItem->xItemValue)
#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 // 与逻辑位(1:与逻辑,所有等待事件都满足;0:或逻辑,任一等待事件满足 )
#define taskEVENT_LIST_ITEM_VALUE_IN_USE 0x8000U // 使能位(1:表示条目值为事件;0:表示条目值非事件)
#define eventEVENT_BITS_CONTROL_BYTES 0xff00U // 事件位控制字节
// 32位事件阻塞属性位(xEventListItem->xItemValue)
#else
#define eventCLEAR_EVENTS_ON_EXIT_BIT 0x01000000UL // 清除位(等待事件成功后,清除事件)
#define eventUNBLOCKED_DUE_TO_BIT_SET 0x02000000UL // 解锁位(设置时间后会对阻塞任务的等待事件判别,判别成功,置解锁位)
#define eventWAIT_FOR_ALL_BITS 0x04000000UL // 与逻辑位(1:与逻辑,所有等待事件都满足;0:或逻辑,任一等待事件满足 )
#define taskEVENT_LIST_ITEM_VALUE_IN_USE 0x80000000UL //使能位(1:表示条目值为事件;0:表示条目值非事件)
#define eventEVENT_BITS_CONTROL_BYTES 0xff000000UL // 事件位控制字节
#endif
【清除位】置1:当任务等待事件成功后,直接清除相应的等待事件;置0:当任务等待事件成功后,不清除。
【解锁位】设置事件后,会对所有事件阻塞任务进行等待事件判断,判断成功后,解锁任务,并对其事件条目值(xEventListItem->xItemValue)的【解锁位】置1,并更新当前事件组值;否则不置位;解锁后的任务,优先判断该标志,如果任务该标志为1,直接认为等待成功。
【与逻辑位】置1:与逻辑等待事件,即事件组满足所有的等待事件才算等待成功;置0:或逻辑等待事件,即事件组满足任一等待事件即可等待成功。
【使能位】置1:表示当前阻务塞任是事件阻塞,xEventListItem->xItemValue表示"控制字+事件";置0:xEventListItem->xItemValue表示任务优先级,任务非事件阻塞。
5.1.2 阻塞机制
1、任务等待事件阻塞:当事件组不满足等待条件,任务会被阻塞;任务事件条目插入到事件链表xTasksWaitingForBits的尾部,而事项条目值(xEventListItem->xItemValue)设置为"控制字+事件"的模式:
(1)【使能位】设置为1;
(2)【解锁位】设置为0 ;
(3)【清除位】和【与逻辑位】根据函数的形参设置;
(4)【事件】为函数形参uxBitsToWaitFor (即任务需要等待的事件)。
2、任务设置事件: 等待任务在阻塞过程中可能会有其他任务设置事件,任务设置事件不会有阻塞现象,也不会受当前事件状态影响,直接在对应事件bit位进行覆盖;同时会对所有被事件阻塞的任务进行事件判断,将满足条件的任务进行解锁;判断方式如下:
将任务按从头到尾的顺序从链表xTasksWaitingForBits中取出任务,根据xEventListItem->xItemValue解析出控制字和需要等待的事件uxBitsToWaitFor。判断当前事件组是否满足任务等待事件;如满足,将任务解锁后,更新xEventListItem->xItemValue值为"控制字+事件"的模式:
(1)【使能位】设置为1;
(2)【解锁位】设置为1,
(3)【清除位】和【与逻辑位】设置为0;
(4)【事件】为当前事件组最新事件;
每解锁一个任务,统计需要清除的事件位,当所有任务处理完成后,根据清除位的统计,统一清除事件。
3、等待任务解锁: 事件等待任务解锁后,会再进行一次判断,同时将任务事件事项条目值(xEventListItem->xItemValue复位为任务优先级。判断方式如下:
如果xEventListItem->xItemValue【解锁位】为1(即阻塞过程中被其他任务解锁),直接等待成功,返回xEventListItem->xItemValue中记录的事件值(即解锁时的最新事件组)。否则,根据最新事件和控制字再次判段和处理事件,并返回当前事件组值(未清除)。
注: 任务事件条目 xEventListItem插入到xTasksWaitingForBits仅在事件等待中进行,而任务解锁和xEventListItem移除在事件设置和删除中进行。
5.2 事件设置
5.2 .1 代码分析
事件设置函数形参为事件组句柄xEventGroup和需要设置的事件uxBitsToSet;返回值为事件组xEventGroup的最新值。
事件设置过程:
1、暂停任务调度器;
2、设置事件:使用uxBitsToSet直接覆盖旧事件;
3、解锁符合条件的阻塞任务(遍历所有的阻塞任务):
(1)通过任务事件条目值(xEventListItem->xItemValue),解析控制字和需要等待的事件。
(2)通过控制字【与逻辑位】判断当前事件组是否满足任务等待:
【与逻辑位】如果为1,事件组中所有等待的事件均为1;
【与逻辑位】如果为0,事件组中任一等待的事件为1。
(3)如果事件逻辑判断满足,判断【清除位】:
【清除位】为1,累计该任务需要清除的事件。
【清除位】为0,不处理。
(4)如果事件逻辑判断满足,进行任务解锁:
①将任务事件条目xEventListItem从事件链表xTasksWaitingForBits中移除;
②修改事件条目值xEventListItem->xItemValue(【使能位】和【解锁位】设置为1; 【清除位】和【与逻辑位】设置为0;【事件】为事件组最新事件)
③切换任务状态,从【阻塞态或挂起态】->【就绪态】 即将任务状态条目xStateListItemc
从阻塞或挂起链表移除,插入到就绪链表尾部。
(5)当所有任务处理完成后,根据清除位的统计,统一清除事件。
4、重启任务调度器;
5、返回当前事件组xEventGroup->uxEventBits;
/*--------------------------发送事件---------------------------------*/
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 );
// 链表
pxList = &( pxEventBits->xTasksWaitingForBits );
// 任务尾部
pxListEnd = listGET_END_MARKER( pxList );
// 【1】挂起调度器
vTaskSuspendAll();
{
traceEVENT_GROUP_SET_BITS( xEventGroup, uxBitsToSet );
pxListItem = listGET_HEAD_ENTRY( pxList );// 任务头部
//
pxEventBits->uxEventBits |= uxBitsToSet; // 设置事件
//【2】有被阻塞的任务
while( pxListItem != pxListEnd )
{
// 从最新的阻塞任务开始
pxNext = listGET_NEXT( pxListItem );
//取该任务等待件值
uxBitsWaitedFor = listGET_LIST_ITEM_VALUE( pxListItem );
xMatchFound = pdFALSE;
/* 分离等待位和控制位. */
uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES;
uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES;
//无控制位 或运算
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
{
/* Need all bits to be set, but not all the bits were set. */
}
// 匹配成功
if( xMatchFound != pdFALSE )
{
/*退出清除标志 */
if( ( uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT ) != ( EventBits_t ) 0 )
{
uxBitsToClear |= uxBitsWaitedFor;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
// 恢复任务
( void ) xTaskRemoveFromUnorderedEventList( pxListItem, pxEventBits->uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET );
}
pxListItem = pxNext;
}
// 清除事件
pxEventBits->uxEventBits &= ~uxBitsToClear;
}
( void ) xTaskResumeAll();
return pxEventBits->uxEventBits;
}
5.2.2 逻辑图分析
5.3 事件等待
5.3.1 代码分析
等待函数形参为:事件组句柄xEventGroup、任务等待的事件uxBitsToWaitFor、等待成功后清除标志xClearOnExit和等待与逻辑方式xWaitForAllBits 。
事件等待成功,函数返回等待成功时的事件组值(未清除)。
事件等待失败,函数返回当前事件组值
事件等待流程:
1、暂停任务调度器。
2、判断当前任务是否满足等待条件(判断条件见逻辑图)。
3、如果当前事件满足任务等待条件,进行清除判断及处理;然后重启调度器后返回事件组值(返回的事件组是清除之前的,方便对返回成功状态判断)。
4、如果事件组不满足任务等待条件,任务阻塞:
(1)修改任务事件条目值xEventListItem->xItemValue(【使能位】设置为1;【解锁位】设置为0;【清除位】与形参xClearOnExit一致;【与逻辑位】与形参xWaitForAllBits一致 ;【事件】为任务等待的事件uxBitsToWaitFor )。
(2)将任务事件条目xEventListItem插入链表xTasksWaitingForBits尾部。
(3)切换任务状态【运行态】->【就绪态或挂起态】,即将任务状态条目xStateListItemc从就绪链表移除,插入到阻塞或挂起链表。重启调度器后强制上下文切换。
5、任务解锁:任务阻塞时间到会解锁;或者,在任务阻塞过程中,有其他任务设置事件,从而解锁任务,并设置任务事件条目值xEventListItem->xItemValue(【解锁位】设置为1,【事件】为事件组最新事件)。
6、任务解锁后【运行态】处理:
(1)解析出xEventListItem->xItemValue的【解锁位】和【事件值】,并将xEventListItem->xItemValue的值恢复为任务优先级。
(2)如果【解锁位】为1,说明已被其他任务解锁,【事件值】便是解锁时的事件组值,函数直接返回【事件值】。
(3)如果【解锁位】为0,说明阻塞时间到,取当前最新事件重新判断等待条件,如果判断成功,进行事件清除处理;失败则不处理;最后返回当前最新事件组值(清除前);
/*-----------------------------------------------------------*/
// 等待事件(任务)
// uxBitsToWaitFor等待的事件
// xClearOnExit 等待成功后清除事件
// xWaitForAllBits 与运算、或运算
// 返回事件组原始值(非清除后的值)
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 ) ) );
}
#endif
vTaskSuspendAll();
{
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();
}
}
// 等待时间为0,直接返回当前值
else if( xTicksToWait == ( TickType_t ) 0 )
{
uxReturn = uxCurrentEventBits;
}
// 阻塞
else
{
// 置清除位 eventCLEAR_EVENTS_ON_EXIT_BIT
if( xClearOnExit != pdFALSE )
{
uxControlBits |= eventCLEAR_EVENTS_ON_EXIT_BIT;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
// 置与或位 eventWAIT_FOR_ALL_BITS
if( xWaitForAllBits != pdFALSE )
{
uxControlBits |= eventWAIT_FOR_ALL_BITS;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
// 压入事件 阻塞任务
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();
}
// 阻塞时间到(并取条目值)
uxReturn = uxTaskResetEventItemValue();
// 任务没有在事件设置函数里解锁
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;
}
5.3.2 逻辑图分析
5.4 事件同步
事件同步接口xEventGroupSync主要用于多任务同步,实际上是事件设置和事件等待的组合,固定【与逻辑】等待事件,等待完成后固定【清除】事件;可实现多任务共同到达同步点。
代码分析和逻辑图分析参照【5.2 事件设置】和【5.3 事件等待】章节;参考实例【5.7.3 多任务同步】了解如何应用多任务同步。
/*-----------------------------------------------------------*/
// xEventGroup事件组句柄,uxBitsToSet发送事件,uxBitsToWaitFor等待事件,xTicksToWait 阻塞时间
// 返回值:返回清除之前的实时事件组值
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait )
{
EventBits_t uxOriginalBitValue, uxReturn;
EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
BaseType_t xAlreadyYielded;
BaseType_t xTimeoutOccurred = pdFALSE;
// 断言
configASSERT( ( uxBitsToWaitFor & eventEVENT_BITS_CONTROL_BYTES ) == 0 );
configASSERT( uxBitsToWaitFor != 0 );
// 调度器断言
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
// 【1】任务挂起
vTaskSuspendAll();
// 事件判断
{
// 取事件原值
uxOriginalBitValue = pxEventBits->uxEventBits;
// 【2】设置事件
( void ) xEventGroupSetBits( xEventGroup, uxBitsToSet );
// 【3】等待事件,事件值包含本任务发送的事件
if(((uxOriginalBitValue|uxBitsToSet)&uxBitsToWaitFor)== uxBitsToWaitFor)// 事件原值检查(包含本任务发送的事件) 返回事件bit,并清除事件
{
// 返回值包含本任务发送的事件
uxReturn = ( uxOriginalBitValue | uxBitsToSet );
// 固定清除bit位
pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
xTicksToWait = 0;
}
// 【4】阻塞判断
else
{
if( xTicksToWait != ( TickType_t ) 0 )
{
traceEVENT_GROUP_SYNC_BLOCK( xEventGroup, uxBitsToSet, uxBitsToWaitFor );
// 设置事件条目值(【清除】、【与逻辑】位固定为1)
// 任务状态切换
vTaskPlaceOnUnorderedEventList( &( pxEventBits->xTasksWaitingForBits ), ( uxBitsToWaitFor | eventCLEAR_EVENTS_ON_EXIT_BIT | eventWAIT_FOR_ALL_BITS ), xTicksToWait );
uxReturn = 0;
}
else
{
// 取最新状态返回
uxReturn = pxEventBits->uxEventBits;
}
}
}
xAlreadyYielded = xTaskResumeAll();
if( xTicksToWait != ( TickType_t ) 0 )
{
// 需要上下文切换
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//【5】恢复运行态后,(1) 获取控制位+事件、复位事件条目值
uxReturn = uxTaskResetEventItemValue();
// 未被接收
if( ( uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET ) == ( EventBits_t ) 0 )
{
taskENTER_CRITICAL();
{
uxReturn = pxEventBits->uxEventBits;
/*虽然任务到达这里是因为它在等待的比特被设置之前超时了,但由于它解除了阻塞,另一个任务可能已经设置了比特。如果是这种情况,那么它需要在退出之前清除比特。*/
if( ( uxReturn & uxBitsToWaitFor ) == uxBitsToWaitFor )
{
pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
xTimeoutOccurred = pdTRUE;
}
else
{
/* The task unblocked because the bits were set. */
}
// 清除控制位
uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;
}
traceEVENT_GROUP_SYNC_END( xEventGroup, uxBitsToSet, uxBitsToWaitFor, xTimeoutOccurred );
return uxReturn;
}
5.5 事件清除
用于清除事件组指定的位, 如果在获取事件的时候没有将对应的标志位清除, 那么就需要用这个函数来进行清除;清除事件需要先进入临界区。
// 清除事件
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 );
// 【1】进入临界区
taskENTER_CRITICAL();
{
traceEVENT_GROUP_CLEAR_BITS( xEventGroup, uxBitsToClear );
uxReturn = pxEventBits->uxEventBits;
pxEventBits->uxEventBits &= ~uxBitsToClear;
}
// 【2】退出临界区
taskEXIT_CRITICAL();
return uxReturn;
}
5.6 事件删除
很多场合,事件只用一次,用完就可以调用vEventGroupDelete()将事件组销毁。
在事件组销毁前,会先释放被阻塞的任务,将这些任务强制移出阻塞链表,直接切换为就绪态,并且任务条目值强制为:使能位】设置为1、【解锁位】设置为1、其他均为0,这些等待该事件组的任务会直接判定等待完成,等待事件结果为0;
事件组一旦删除,将不能再进行设置、等待的操作。
void vEventGroupDelete( EventGroupHandle_t xEventGroup )
{
EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
const List_t *pxTasksWaitingForBits = &( pxEventBits->xTasksWaitingForBits );
//【1】 暂停调度器
vTaskSuspendAll();
{
traceEVENT_GROUP_DELETE( xEventGroup );
//【2】有任务阻塞,释放阻塞任务
while( listCURRENT_LIST_LENGTH( pxTasksWaitingForBits ) > ( UBaseType_t ) 0 )
{
// 断言
configASSERT( pxTasksWaitingForBits->xListEnd.pxNext != ( ListItem_t * ) &( pxTasksWaitingForBits->xListEnd ) );
// 设置事件条目值(使能位+解锁+0),并将任务事件条目从xTasksWaitingForBits 移除
// 解锁任务
( void ) xTaskRemoveFromUnorderedEventList( pxTasksWaitingForBits->xListEnd.pxNext, eventUNBLOCKED_DUE_TO_BIT_SET );
}
#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
{
// 动态申请,释放内存
vPortFree( pxEventBits );
}
#elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )
{
// 动态申请,释放内存
if( pxEventBits->ucStaticallyAllocated == ( uint8_t ) pdFALSE )
{
vPortFree( pxEventBits );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
}
( void ) xTaskResumeAll();
}
5.7 应用实例
5.7.1与逻辑
任务A等待任务B、任务C数据都采集完成后进行数据处理。任务B、任务C等待任务A处理完数据后进行数据采集。
#define CN_EVENT_B_STR (0x0001) // 任务A数据采集完成事件
#define CN_EVENT_C_STR (0x0002) // 任务B数据采集完成事件
#define CN_EVENT_STR (CN_EVENT_B_STR+CN_EVENT_C_STR) // 任务数据采集完成事件
#define CN_EVENT_B_END (0x0010) // 任务B数据处理结束事件
#define CN_EVENT_C_END (0x0020) // 任务C数据处理结束事件
#define CN_EVENT_END (CN_EVENT_B_END +CN_EVENT_C_END ) // 任务数据处理结束事件
int dataB[]; // 任务B数据
int dataC[]; // 任务C数据
EventGroupHandle_t synEventHandle;
// 任务A处理数据
void taskA()
{
for(;;)
{
// 【1】等待数据采集完成事件,与逻辑,等待完成后清除
xEventGroupWaitBits(synEventHandle,CN_EVENT_STR,1,1,portMAX_DELAY);
// 【2】数据ddataB[]、dataC[]处理
...............
// 【3】设置处理结束事件
xEventGroupSetBits(synEventHandle,CN_EVENT_END);
}
}
// 任务B数据采集
void taskB()
{
for(;;)
{
// 【1】数据dataB[]采集
...............
// 【2】设置采集结束事件
xEventGroupSetBits(synEventHandle,CN_EVENT_B_STR);
// 【3】等待数据处理完成事件,与逻辑,等待完成后清除
xEventGroupWaitBits(synEventHandle,CN_EVENT_B_END,1,1,portMAX_DELAY);
}
}
// 任务C数据采集
void taskC()
{
for(;;)
{
// 【1】数据dataC[]采集
...............
// 【2】设置采集结束事件
xEventGroupSetBits(synEventHandle,CN_EVENT_C_STR);
// 【3】等待数据处理完成事件,与逻辑,等待完成后清除
xEventGroupWaitBits(synEventHandle,CN_EVENT_C_END,1,1,portMAX_DELAY);
}
}
void main()
{
synEventHandle=xEventGroupCreate();
// 新建任务A
.....
// 新建任务B
.....
// 新建任务C
.....
//启动调度器
.....
}
5.7.2 或逻辑
任务A等待任务B或任务C数据任一个采集完成后进行数据处理;任务B、任务C等待任务A处理完数据后进行数据采集。
#define CN_EVENT_B_STR (0x0001) // 任务A数据采集完成事件
#define CN_EVENT_C_STR (0x0002) // 任务B数据采集完成事件
#define CN_EVENT_STR (CN_EVENT_B_STR+CN_EVENT_C_STR) // 任务数据采集完成事件
#define CN_EVENT_B_END (0x0010) // 任务B数据处理结束事件
#define CN_EVENT_C_END (0x0020) // 任务C数据处理结束事件
#define CN_EVENT_END (CN_EVENT_B_END +CN_EVENT_C_END ) // 任务数据处理结束事件
int dataB[]; // 任务B数据
int dataC[]; // 任务C数据
EventGroupHandle_t synEventHandle;
// 任务A处理数据
void taskA()
{
for(;;)
{
// 【1】等待数据采集完成事件,或逻辑,等待成功后清除
EventBits_t retn=xEventGroupWaitBits(synEventHandle,CN_EVENT_STR,1,0,portMAX_DELAY);
if(retn&CN_EVENT_B_STR )
{
// 【2.1】数据dataB处理
...............
// 【2.2】设置dataB处理结束事件
xEventGroupSetBits(synEventHandle,CN_EVENT_B_END);
}
if(retn&CN_EVENT_C_STR )
{
// 【3.1】数据dataC处理
...............
// 【3.2】设置dataC处理结束事件
xEventGroupSetBits(synEventHandle,CN_EVENT_C_END)
}
}
}
// 任务B数据采集
void taskB()
{
for(;;)
{
// 【1】数据dataB[]采集
...............
// 【2】设置采集结束事件
xEventGroupSetBits(synEventHandle,CN_EVENT_B_STR);
// 【3】等待数据处理完成事件,与逻辑,等待完成后清除
xEventGroupWaitBits(synEventHandle,CN_EVENT_B_END,1,1,portMAX_DELAY);
}
}
// 任务C数据采集
void taskC()
{
for(;;)
{
// 【1】数据dataC[]采集
...............
// 【2】设置采集结束事件
xEventGroupSetBits(synEventHandle,CN_EVENT_C_STR);
// 【3】等待数据处理完成事件,与逻辑,等待完成后清除
xEventGroupWaitBits(synEventHandle,CN_EVENT_C_END,1,1,portMAX_DELAY);
}
}
void main()
{
synEventHandle=xEventGroupCreate();
// 预先发送处理结束事件
xEventGroupSetBits(synEventHandle,CN_EVENT_END);
// 新建任务A
.....
// 新建任务B
.....
// 新建任务C
.....
//启动调度器
.....
}
5.7.3 多任务同步
任务A、任务B、任务C数据均采集完成后同步处理。
#define CN_EVENT_A_STR (0x0001) // 任务A数据处理开始事件
#define CN_EVENT_B_STR (0x0002) // 任务B数据处理开始事件
#define CN_EVENT_C_STR (0x0004) // 任务C数据处理开始事件
#define CN_EVENT_STR (CN_EVENT_A_STR+CN_EVENT_B_STR+CN_EVENT_C_STR) // 任务数据处理开始事件组
#define CN_EVENT_A_END (0x0010) // 任务A数据处理结束事件
#define CN_EVENT_B_END (0x0020) // 任务B数据处理结束事件
#define CN_EVENT_C_END (0x0040) // 任务C数据处理结束事件
#define CN_EVENT_END (CN_EVENT_A_END+CN_EVENT_B_END+CN_EVENT_C_END) // 任务数据处理结束事件组
int dataA[]; // 任务A数据
int dataB[]; // 任务B数据
int dataC[]; // 任务C数据
EventGroupHandle_t synEventHandle;
// 任务A处理数据
void taskA()
{
for(;;)
{
// 【1】数据dataA[]采集
...............
// 【2】同步任务数据处理开始事件
xEventGroupSync(synEventHandle,CN_EVENT_A_STR,CN_EVENT_STR ,portMAX_DELAY);
// 【3】数据dataA[]、dataB[]、dataC[]处理
...............
// 【4】同步任务数据处理结束事件
xEventGroupSync(synEventHandle,CN_EVENT_A_END,CN_EVENT_END ,portMAX_DELAY);
}
}
// 任务B数据采集
void taskB()
{
for(;;)
{
// 【1】数据dataB[]采集
...............
// 【2】同步任务数据处理开始事件
xEventGroupSync(synEventHandle,CN_EVENT_B_STR,CN_EVENT_STR ,portMAX_DELAY);
// 【3】数据dataA[]、dataB[]、dataC[]处理
...............
// 【4】同步任务数据处理结束事件
xEventGroupSync(synEventHandle,CN_EVENT_B_END,CN_EVENT_END ,portMAX_DELAY);
}
}
// 任务C数据采集
void taskC()
{
for(;;)
{
// 【1】数据dataC[]采集
...............
// 【2】同步任务数据处理开始事件
xEventGroupSync(synEventHandle,CN_EVENT_C_STR,CN_EVENT_STR ,portMAX_DELAY);
// 【3】数据dataA[]、dataB[]、dataC[]处理
...............
// 【4】同步任务数据处理结束事件
xEventGroupSync(synEventHandle,CN_EVENT_C_END,CN_EVENT_END ,portMAX_DELAY);
}
}
void main()
{
synEventHandle=xEventGroupCreate();
// 新建任务A
.....
// 新建任务B
.....
// 新建任务C
.....
//启动调度器
.....
}
六、事件操作(中断)
事件的发送和等待不支持在中断中直接调用(事件设置与等待均通过暂停调度实现临界区保护,对中断无防护);FreeRtos通过定时器任务和定时器队列实现中断对事件的操作,其本质还是任务操作,并且只能实现无阻塞的操作:事件设置、事件清除。
中断向定时器消息队列xTimerQueue发送回调函数消息,并附带函数指针和参数,定时器任务接收到消息后,调用回调函数,实现事件设置和清除。
注:定时器任务是系统自带任务,关于定时器的解析将在后面章节进行。
6.1 事件设置
中断:向定时器消息队列xTimerQueue发送函数回调消息,消息内容如下
消息参数 | 事件设置中断发送的参数 |
消息类型 | 中断回调:tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR |
回调函数指针 | 设置事件回调函数:vEventGroupSetBitsCallback |
回调函数参数1 | 事件组:xEventGroup,取自中断接口形参 |
回调函数参数2 | 设置的事件:uxBitsToSet,取自中断接口形参 |
/*--------------------消息结构---------------------------------*/
typedef struct tmrTimerQueueMessage
{
BaseType_t xMessageID; /* << 发送给定时器服务任务的命令. */
typedef struct tmrCallbackParameters
{
PendedFunction_t pxCallbackFunction; /* << 回调函数. */
void *pvParameter1; /* << 参数1. */
uint32_t ulParameter2; /* << 参数2. */
} CallbackParameters_t;
} DaemonTaskMessage_t;
/*--------------------设置事件(中断)-------------------------------*/
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken )
{
BaseType_t xReturn;
traceEVENT_GROUP_SET_BITS_FROM_ISR( xEventGroup, uxBitsToSet );
xReturn = xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken );
return xReturn;
}
// 发送回调消息
BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend, void *pvParameter1, uint32_t ulParameter2, BaseType_t *pxHigherPriorityTaskWoken )
{
DaemonTaskMessage_t xMessage;
BaseType_t xReturn;
xMessage.xMessageID = tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR;
xMessage.u.xCallbackParameters.pxCallbackFunction = xFunctionToPend;
xMessage.u.xCallbackParameters.pvParameter1 = pvParameter1;
xMessage.u.xCallbackParameters.ulParameter2 = ulParameter2;
xReturn = xQueueSendFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken );
tracePEND_FUNC_CALL_FROM_ISR( xFunctionToPend, pvParameter1, ulParameter2, xReturn );
return xReturn;
}
定时器任务:接收到消息后,获取到回调函数指针vEventGroupSetBitsCallback,事件组:xEventGroup,设置事件值:uxBitsToSet;执行vEventGroupSetBitsCallback(xEventGroup,uxBitsToSet);实现事件设置。
void vEventGroupSetBitsCallback( void *pvEventGroup, const uint32_t ulBitsToSet )
{
( void ) xEventGroupSetBits( pvEventGroup, ( EventBits_t ) ulBitsToSet );
}
6.2 事件清除
中断:向定时器消息队列xTimerQueue发送函数回调消息,消息内容如下
消息参数 | 事件清除中断发送的参数 |
消息类型 | 中断回调:tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR |
回调函数指针 | 清除事件回调函数:vEventGroupClearBitsCallback |
回调函数参数1 | 事件组:xEventGroup,取自中断接口形参 |
回调函数参数2 | 清除的事件:uxBitsToClear ,取自中断接口形参 |
/*--------------------消息结构---------------------------------*/
typedef struct tmrTimerQueueMessage
{
BaseType_t xMessageID; /* << 发送给定时器服务任务的命令. */
typedef struct tmrCallbackParameters
{
PendedFunction_t pxCallbackFunction; /* << 回调函数. */
void *pvParameter1; /* << 参数1. */
uint32_t ulParameter2; /* << 参数2. */
} CallbackParameters_t;
} DaemonTaskMessage_t;
/*--------------------清除事件(中断)-------------------------------*/
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )
{
BaseType_t xReturn;
traceEVENT_GROUP_CLEAR_BITS_FROM_ISR( xEventGroup, uxBitsToClear );
xReturn = xTimerPendFunctionCallFromISR( vEventGroupClearBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToClear, NULL );
return xReturn;
}
// 发送回调消息(任务)
BaseType_t xTimerPendFunctionCall( PendedFunction_t xFunctionToPend, void *pvParameter1, uint32_t ulParameter2, TickType_t xTicksToWait )
{
DaemonTaskMessage_t xMessage;
BaseType_t xReturn;
configASSERT( xTimerQueue );
// 发送至任务
xMessage.xMessageID = tmrCOMMAND_EXECUTE_CALLBACK;
xMessage.u.xCallbackParameters.pxCallbackFunction = xFunctionToPend;// 函数回调
xMessage.u.xCallbackParameters.pvParameter1 = pvParameter1; // 参数1
xMessage.u.xCallbackParameters.ulParameter2 = ulParameter2; // 参数2
xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait ); // 发送
tracePEND_FUNC_CALL( xFunctionToPend, pvParameter1, ulParameter2, xReturn );
return xReturn;
}
定时器任务:接收到消息后,获取到回调函数指针vEventGroupClearBitsCallback,事件组:xEventGroup,清除事件:uxBitsToClear ;执行vEventGroupClearBitsCallback(xEventGroup,uxBitsToClear )实现事件清除。
void vEventGroupClearBitsCallback( void *pvEventGroup, const uint32_t ulBitsToClear )
{
( void ) xEventGroupClearBits( pvEventGroup, ( EventBits_t ) ulBitsToClear );
}