信号量可以用来完成同步,但是使用信号量来同步的话任务只能与单个事件或者任务进行同步。有事件某个任务可能会需要与多个事件或者任务同步,此时信号量就无法满足要求了。FreeRTOS提供了一个解决方案,就是事件标记组。
事件位:
事件位用来表明某个事件是否发送,事件位通常用作事件标记,比如:
1、当收到一条消息并且把这条消息处理掉以后就可以将某个位置1,当队列中没有消息需要处理的时候就可以将这个位置0.
2、当把队列中的消息通过网络发送输出以后就可以将某个位置1,没有数据发送时将位置0.
3、现在需要向网络中发送一个心跳信息,将位置1,不需要发送置0.
事件组:
一个事件组就是一组的事件位,事件组中的事件通过编号来访问。
1、事件标记组的bit表示队列中的消息是否处理掉。
2、事件标记组的bit1表示是否有消息需要从网络发送出去。
3、事件标记组的bit2表示现在是否需要向网络发送心跳消息。
数据类型:
typedef struct EventGroupDef_t
{
EventBits_t uxEventBits;
List_t xTasksWaitingForBits; /**< List of tasks waiting for a bit to be set. */
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxEventGroupNumber;
#endif
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /**< Set to pdTRUE if the event group is statically allocated to ensure no attempt is made to free the memory. */
#endif
} EventGroup_t;
#if ( configTICK_TYPE_WIDTH_IN_BITS == TICK_TYPE_WIDTH_16_BITS )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#elif ( configTICK_TYPE_WIDTH_IN_BITS == TICK_TYPE_WIDTH_32_BITS )
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
uxEventBits就是一个16或者32位的整型。
xEventGroupCreate()
EventGroupHandle_t xEventGroupCreate( void )
{
EventGroup_t * pxEventBits;
traceENTER_xEventGroupCreate();
//申请内存
pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) ); /*lint !e9087 !e9079 see comment above. */
if( pxEventBits != NULL )
{
//初始为0
pxEventBits->uxEventBits = 0;
//xTasksWaitingForBits是一个list。也就是等待这个事件的所有task都挂在这个list下。
vListInitialise( &( pxEventBits->xTasksWaitingForBits ) );
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
{
/* Both static and dynamic allocation can be used, so note this
* event group was allocated statically in case the event group is
* later deleted. */
pxEventBits->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
traceEVENT_GROUP_CREATE( pxEventBits );
}
else
{
traceEVENT_GROUP_CREATE_FAILED(); /*lint !e9063 Else branch only exists to allow tracing and does not generate code if trace macros are not defined. */
}
traceRETURN_xEventGroupCreate( pxEventBits );
return pxEventBits;
}
xEventGroupSetBits
设置事件位。
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet )
{
ListItem_t * pxListItem;
ListItem_t * pxNext;
ListItem_t const * pxListEnd;
List_t const * pxList;
EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits;
EventGroup_t * pxEventBits = xEventGroup;
BaseType_t xMatchFound = pdFALSE;
traceENTER_xEventGroupSetBits( xEventGroup, uxBitsToSet );
/* Check the user is not attempting to set the bits used by the kernel
* itself. */
configASSERT( xEventGroup );
configASSERT( ( uxBitsToSet & eventEVENT_BITS_CONTROL_BYTES ) == 0 );
//获取plist
pxList = &( pxEventBits->xTasksWaitingForBits );
//获取pllist的尾部
pxListEnd = listGET_END_MARKER( pxList ); /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM. This is checked and valid. */
vTaskSuspendAll();
{
traceEVENT_GROUP_SET_BITS( xEventGroup, uxBitsToSet );
pxListItem = listGET_HEAD_ENTRY( pxList );
/* Set the bits. */
//设置标志位
pxEventBits->uxEventBits |= uxBitsToSet;
/* See if the new bit value should unblock any tasks. */
//循环遍历list里的每个列表项,因为列表项就是task,查看哪个task在等待标志位。
while( pxListItem != pxListEnd )
{
//获取下一个列表项
pxNext = listGET_NEXT( pxListItem );
uxBitsWaitedFor = listGET_LIST_ITEM_VALUE( pxListItem );
xMatchFound = pdFALSE;
/* Split the bits waited for from the control bits. */
uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES;
uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES;
if( ( uxControlBits & eventWAIT_FOR_ALL_BITS ) == ( EventBits_t ) 0 )
{ //如果不是等待所有的事件,那就寻找单个标志位
/* Just looking for single bit being set. */
if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) != ( EventBits_t ) 0 )
{
xMatchFound = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) == uxBitsWaitedFor )
{
//等待所有的标志位,一定是match
/* All bits are set. */
xMatchFound = pdTRUE;
}
else
{
/* Need all bits to be set, but not all the bits were set. */
}
if( xMatchFound != pdFALSE )
{
//根据标记决定是否请标记。这里的意思应该就是只有一个task可以接受事件,还是可以多个task接受事件。
/* The bits match. Should the bits be cleared on exit? */
if( ( uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT ) != ( EventBits_t ) 0 )
{
uxBitsToClear |= uxBitsWaitedFor;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Store the actual event flag value in the task's event list
* item before removing the task from the event list. The
* eventUNBLOCKED_DUE_TO_BIT_SET bit is set so the task knows
* that is was unblocked due to its required bits matching, rather
* than because it timed out. */
//match成功后,就需要将task从list里移除。
vTaskRemoveFromUnorderedEventList( pxListItem, pxEventBits->uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET );
}
/* Move onto the next list item. Note pxListItem->pxNext is not
* used here as the list item may have been removed from the event list
* and inserted into the ready/pending reading list. */
pxListItem = pxNext;
}
/* Clear any bits that matched when the eventCLEAR_EVENTS_ON_EXIT_BIT
* bit was set in the control word. */
//根据清掉标记。如果有一个任务设置了清标记位,那么都可以使任务不会再次获取事件标志位。
//见下列demo
pxEventBits->uxEventBits &= ~uxBitsToClear;
}
( void ) xTaskResumeAll();
traceRETURN_xEventGroupSetBits( pxEventBits->uxEventBits );
return pxEventBits->uxEventBits;
}
void vTaskRemoveFromUnorderedEventList( ListItem_t * pxEventListItem,
const TickType_t xItemValue )
{
TCB_t * pxUnblockedTCB;
traceENTER_vTaskRemoveFromUnorderedEventList( pxEventListItem, xItemValue );
/* THIS FUNCTION MUST BE CALLED WITH THE SCHEDULER SUSPENDED. It is used by
* the event flags implementation. */
configASSERT( uxSchedulerSuspended != ( UBaseType_t ) 0U );
/* Store the new item value in the event list. */
listSET_LIST_ITEM_VALUE( pxEventListItem, xItemValue | taskEVENT_LIST_ITEM_VALUE_IN_USE );
/* Remove the event list form the event flag. Interrupts do not access
* event flags. */
pxUnblockedTCB = listGET_LIST_ITEM_OWNER( pxEventListItem ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
configASSERT( pxUnblockedTCB );
listREMOVE_ITEM( pxEventListItem );
#if ( configUSE_TICKLESS_IDLE != 0 )
{
/* If a task is blocked on a kernel object then xNextTaskUnblockTime
* might be set to the blocked task's time out time. If the task is
* unblocked for a reason other than a timeout xNextTaskUnblockTime is
* normally left unchanged, because it is automatically reset to a new
* value when the tick count equals xNextTaskUnblockTime. However if
* tickless idling is used it might be more important to enter sleep mode
* at the earliest possible time - so reset xNextTaskUnblockTime here to
* ensure it is updated at the earliest possible time. */
prvResetNextTaskUnblockTime();
}
#endif
/* Remove the task from the delayed list and add it to the ready list. The
* scheduler is suspended so interrupts will not be accessing the ready
* lists. */
listREMOVE_ITEM( &( pxUnblockedTCB->xStateListItem ) );
prvAddTaskToReadyList( pxUnblockedTCB );
#if ( configNUMBER_OF_CORES == 1 )
{
if( pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority )
{
/* The unblocked task has a priority above that of the calling task, so
* a context switch is required. This function is called with the
* scheduler suspended so xYieldPending is set so the context switch
* occurs immediately that the scheduler is resumed (unsuspended). */
xYieldPendings[ 0 ] = pdTRUE;
}
}
#else /* #if ( configNUMBER_OF_CORES == 1 ) */
{
#if ( configUSE_PREEMPTION == 1 )
{
taskENTER_CRITICAL();
{
prvYieldForTask( pxUnblockedTCB );
}
taskEXIT_CRITICAL();
}
#endif
}
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */
traceRETURN_vTaskRemoveFromUnorderedEventList();
}
将任务由阻塞列表添加到就绪列表。
这个函数会判断,如果阻塞的任务优先级比当前task高,那么就需要进行一次任务切换。
xEventGroupClearBits
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear )
{
EventGroup_t * pxEventBits = xEventGroup;
EventBits_t uxReturn;
traceENTER_xEventGroupClearBits( xEventGroup, uxBitsToClear );
/* Check the user is not attempting to clear the bits used by the kernel
* itself. */
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 being
* cleared. */
uxReturn = pxEventBits->uxEventBits;
/* Clear the bits. */
pxEventBits->uxEventBits &= ~uxBitsToClear;
}
taskEXIT_CRITICAL();
traceRETURN_xEventGroupClearBits( uxReturn );
return uxReturn;
}
清标志位就是简单的运算,没有task相关操作。
xEventGroupWaitBits
某个任务可能要与多个事件进行同步,那么这个任务就需要等待并判断多个时间位。
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait )
xEventGroup:
指定要等待的事件标志组。
uxBitsToWaitFord:
指定要等待的事件位,比如要等待bit0 和(或)bit2的时候此参数就是 0X05,如果要等待 bit0 和(或)bit1 和(或)bit2 的时候此参数就是 0X07,以此类推。此参数要是为pdTRUE的话,那么在退出此函数之前由参数uxBitsToWaitFor所设置的这些事件位就会清零。如果设置位pFALSE 的话这些事件位就不会改变。
xClearOnExit:
此参数如果设置为 pdTRUE 的话,当 uxBitsToWaitFor 所设置的这些事件位都置 1,或者指定的阻塞时间到的时候函数 xEventGroupWaitBitsO)才会返回。当此函数为 pdFALSE 的话,只要 uxBitsToWaitFor 所设置的这些事件位其中的任意一个置1,或者指定的阻塞时间到的话函数xEventGroupWaitBitsO)就会返回。
xWaitForAllBits:
xTicksToWait:
设置阻塞时间,单位为节拍数。
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait )
{
EventGroup_t * pxEventBits = xEventGroup;
EventBits_t uxReturn, uxControlBits = 0;
BaseType_t xWaitConditionMet, xAlreadyYielded;
BaseType_t xTimeoutOccurred = pdFALSE;
traceENTER_xEventGroupWaitBits( xEventGroup, uxBitsToWaitFor, xClearOnExit, xWaitForAllBits, xTicksToWait );
/* Check the user is not attempting to wait on the bits used by the kernel
* itself, and that at least one bit is being requested. */
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;
/* Check to see if the wait condition is already met or not. */
//先判断等待的标记位和当前事件的标志位有没有匹配
xWaitConditionMet = prvTestWaitCondition( uxCurrentEventBits, uxBitsToWaitFor, xWaitForAllBits );
if( xWaitConditionMet != pdFALSE ) //如果匹配成功了。不需要block
{
/* The wait condition has already been met so there is no need to
* block. */
uxReturn = uxCurrentEventBits;
xTicksToWait = ( TickType_t ) 0;
/* Clear the wait bits if requested to do so. */
//如果设置了清标记位,那么就清标志
if( xClearOnExit != pdFALSE )
{
pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else if( xTicksToWait == ( TickType_t ) 0 )
{
/* The wait condition has not been met, but no block time was
* specified, so just return the current value. */
//没有匹配的标志位,也不需要延时,那么就返回
uxReturn = uxCurrentEventBits;
xTimeoutOccurred = pdTRUE;
}
else
{
/* The task is going to block to wait for its required bits to be
* set. uxControlBits are used to remember the specified behaviour of
* this call to xEventGroupWaitBits() - for use when the event bits
* unblock the task. */
//否则进入阻塞等待中
//如果设置了clear,那么设置标志(setbits)唤醒task时,会清掉标志位。
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();
}
/* Store the bits that the calling task is waiting for in the
* task's event list item so the kernel knows when a match is
* found. Then enter the blocked state. */
//将task添加到delay list里
vTaskPlaceOnUnorderedEventList( &( pxEventBits->xTasksWaitingForBits ), ( uxBitsToWaitFor | uxControlBits ), xTicksToWait );
/* This is obsolete as it will get set after the task unblocks, but
* some compilers mistakenly generate a warning about the variable
* being returned without being set if it is not done. */
uxReturn = 0;
traceEVENT_GROUP_WAIT_BITS_BLOCK( xEventGroup, uxBitsToWaitFor );
}
}
xAlreadyYielded = xTaskResumeAll();
if( xTicksToWait != ( TickType_t ) 0 )
{
//如果需要进行任务切换
if( xAlreadyYielded == pdFALSE )
{
taskYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* The task blocked to wait for its required bits to be set - at this
* point either the required bits were set or the block time expired. If
* the required bits were set they will have been stored in the task's
* event list item, and they should now be retrieved then cleared. */
//走到这里说明task已经超时或者事件被置位了。因为task已经运行了。
uxReturn = uxTaskResetEventItemValue();
if( ( uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET ) == ( EventBits_t ) 0 )
{
taskENTER_CRITICAL();
{
/* The task timed out, just return the current event bit value. */
uxReturn = pxEventBits->uxEventBits;
/* It is possible that the event bits were updated between this
* task leaving the Blocked state and running again. */
//有可能task在再次运行时事件被置位了,因此再次判断一次。
if( prvTestWaitCondition( uxReturn, uxBitsToWaitFor, xWaitForAllBits ) != pdFALSE )
{
if( xClearOnExit != pdFALSE )
{
pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
xTimeoutOccurred = pdTRUE;
}
taskEXIT_CRITICAL();
}
else
{
/* The task unblocked because the bits were set. */
//task因为获取了事件标志位而得到运行
}
/* The task blocked so control bits may have been set. */
uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;
}
traceEVENT_GROUP_WAIT_BITS_END( xEventGroup, uxBitsToWaitFor, xTimeoutOccurred );
/* Prevent compiler warnings when trace macros are not used. */
( void ) xTimeoutOccurred;
traceRETURN_xEventGroupWaitBits( uxReturn );
return uxReturn;
}
demo;
static void dds_event_send(void* pvParameters)
{
/* Prevent compiler warning about unused parameter in the case that
configASSERT() is not defined. */
(void)pvParameters;
int idx = 0;
while (1)
{
idx++;
xEventGroupSetBits(xEventGroup, 0xff);
printf("dds task send sem %d\r\n", idx);
vTaskDelay(1000);
}
}
static void dds_event_recv(void* pvParameters)
{
/* Prevent compiler warning about unused parameter in the case that
configASSERT() is not defined. */
(void)pvParameters;
int idx = 0;
while (1)
{
xEventGroupWaitBits(xEventGroup, 0xff, pdTRUE, pdFALSE, 10000);
// vTaskDelay(100);
printf("dds task recv sem %d\r\n", idx);
}
}
static void dds_event_recv1(void* pvParameters)
{
/* Prevent compiler warning about unused parameter in the case that
configASSERT() is not defined. */
(void)pvParameters;
int idx = 0;
while (1)
{
xEventGroupWaitBits(xEventGroup, 0xff, pdFALSE, pdFALSE, 10000);
// vTaskDelay(100);
printf("dds task recv1 sem %d\r\n", idx);
}
}
一个发送,两个接受,两个接收者,启动一个设置了clear。可以发现,两个task不会一直循环执行。所以一般都设置clear标记位。注意:清标记位不会导致其他任务获取不到事件,因为清之前已经while循环处理所有任务了。
和信号量不同,如果是1对多,那么只能有一个任务获取信号量。因为信号量的用途是用于同步的。从这里看事件的方式更智能。