Thoroughly understand the event groups in FreeRTOS (Event Groups)

​I have learned before that two tasks can use semaphores and queues to communicate. Tasks can use these two mechanisms to wait for a certain event to occur, but what if we need to wait for multiple events to occur? This requires the use of event groups .

Event groups allow tasks to enter a blocking state , waiting for one or more combinations of events to occur. When an event occurs, the event group unblocks all tasks that were waiting for the same event or combination of events.

The nature of event groups makes them useful for synchronizing multiple tasks , broadcasting events to multiple tasks , allowing tasks to block waiting for any one of a set of events to occur , and allowing tasks to block waiting for multiple operations to complete . Often multiple binary semaphores can be replaced by a single event group, so RAM usage can be reduced.

The event group feature is optional. To include the event groups functionality, include the source file event_groups.c when building FreeRTOS.

Table of contents

1. Characteristics of event groups

2. How to use event groups

2.1 Create an event group

2.2 Set the event bit

2.3 Waiting for event bit

3. Synchronize tasks with event groups


1. Characteristics of event groups

Event flags and event bits

The event flag is a boolean value (1 or 0) that indicates whether the event occurred. An event group is actually a set of event flags.

An event flag can only be 1 or 0, the state of an event flag is stored in a bit, and the state of all event flags in an event group is stored in a variable.

The state of each event flag in an event group is represented by a single bit in a variable of type EventBits_t. Therefore, event flags are also called event "bits" . If a bit in the EventBits_t variable is set to 1, then the event represented by that bit has occurred. If a bit in the EventBits_t variable is set to 0, then the event represented by that bit has not yet occurred.

The number of event bits in an event group depends on configUSE_16_BIT_TICKS in FreeRTOSConfig.h1

If configUSE_16_BIT_TICKS is 1, each event group contains 8 available event bits.

If configUSE_16_BIT_TICKS is 0, each event group contains 24 available event bits.

Practical example using event groups:

The implementation of the FreeRTOS+TCP TCP/IP stack provides a practical example of how to use event groups to both design and minimize resource usage.

A TCP socket must respond to many different events. Including acceptance event (accept), binding event (bind), reading event (read) and closing event (close). A socket can respond to events depending on the state of the socket. For example, if a socket has been created but not yet bound to an address, it can receive bind events, but not read events (it cannot read data without an address).

The state of a FreeRTOS+TCP socket is kept in a structure called FreeRTOS_Socket_t. This structure contains an event group that defines an event bit for each event that the socket must handle. The FreeRTOS+TCP API calls this block to wait for an event or group of events, blocking on a group of events. The event group also contains an "abort" bit, which allows the TCP connection to be terminated regardless of which event the socket was waiting for at the time.

2. How to use event groups

2.1 Create an event group

The event group is a variable of type EventGroupHandle_t. The xEventGroupCreate() API function is used to create an event group and returns an EventGroupHandle_t.

EventGroupHandle_t xEventGroupCreate( void );

2.2 Set the event bit

The xEventGroupSetBits() API function can set one or more bits in the event group to notify the task that the event indicated by the set bit has occurred.

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet );
parameter effect
xEventGroup Handle to the event group whose bit is to be set.
uxBitsToSet

Specifies the event bits or bitmask of event bits in the event group to be set to 1. The value of the event group is updated by bitwise-ORing the existing value of the event group with the value passed in uxBitsToSet.

For example, setting uxBitsToSet to 0x04 (binary 0100) will cause event bit 3 in the event group to be set, leaving all other event bits in the event group unchanged.

return value Returns the value of the event group at the time xEventGroupSetBits() was called. Note that the return value does not necessarily contain the bits specified by uxBitsToSet, since these bits may have been cleared again by a different task.

xEventGroupSetBitsFromISR() is an interrupt-safe version of xEventGroupSetBits().

Giving a semaphore is a deterministic operation because it is known in advance that giving the semaphore will at most cause one task to leave the blocked state. But when setting a bit in an event group, it is not known in advance how many tasks will leave the blocked state, so setting a bit in an event group is not a deterministic operation.

FreeRTOS design and implementation standards do not allow non-deterministic operations in interrupt service routines or when interrupts are disabled. Therefore, xEventGroupSetBitsFromISR() does not directly set the event bits in the interrupt service routine, but defers the operation to the RTOS daemon task.

If you don't know the guardian task, you can read this article: FreeRTOS Full Analysis-7. Interrupt Security API and Postponed Interrupt Processing

BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,BaseType_t *pxHigherPriorityTaskWoken );
parameter effect
xEventGroup Handle to the event group whose bit is to be set.
uxBitsToSet Specifies the event bits or bitmask of event bits in the event group to be set to 1.
pxHigherPriorityTaskWoken

xEventGroupSetBitsFromISR() does not directly set the event bits in the interrupt service routine, but defers the operation to the RTOS daemon task by sending the command on the timer command queue. If the daemon task is in the blocked state, waiting for data to be available on the timer command queue, writing to the timer command queue will cause the daemon task to leave the blocked state. Internally, xEventGroupSetBitsFromISR() will set *pxhigherprioritytaskkoken to pdTRUE if the priority of the daemon task is higher than that of the currently executing task (the interrupted task).

If xEventGroupSetBitsFromISR() is pdTRUE, a context switch should be performed before the interrupt exits. This will ensure that the interrupt returns directly to the daemon task, since the daemon task will be the highest priority ready task.

If you still don’t know how to use it, you can read this FreeRTOS full analysis-7. Interrupt security API and postpone interrupt processing

return value

1.pdPASS, the data is successfully sent to the timer command queue.

2. pdFALSE, if the 'set bits' command could not be written to the timer command queue because the queue is full, return pdFALSE.

2.3 Waiting for event bit

The xEventGroupWaitBits() API function allows the task to read the value of the event group. If one or more event bits in the event group have not been set, you can choose to wait for one or more event bits in the event group to be set in the blocking state.

EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait );

The combination of uxBitsToWaitFor and xWaitForAllBits parameter values ​​determines whether the task that calls xEventGroupWaitBits() enters the blocking state and waits.

uxBitsToWaitFor specifies the event bits in the event group to be tested;

xWaitForAllBits specifies a bitwise OR test or a bitwise AND test. pdTRUE indicates that all the bits specified in uxBitsToWaitFor are set, and the blocking will be unblocked, and pdFALSE indicates that only one is set.

example:

The value of the event group uxBitsToWaitFor xWaitForAllBits result
0000 0101 pdFALSE Enters blocking state because neither bits 0 nor 2 are set
0100 0101 pdTRUE Enter the blocking state, bit 0 is not set, bit 2 is set, but pdTRUE indicates that both have to be set
0100 0110 pdFALSE No blocking, bit 1 is not set, bit 2 is set, pdFALSE means that there is a setting
0100 0110 pdTRUE Enter the blocking state, bit 1 is not set, bit 2 is set, pdTRUE means both must be set

If xClearOnExit is set to pdTRUE, the event bits specified by uxBitsToWaitFor will be cleared back to 0 before the calling task exits the xEventGroupWaitBits() API function. If xClearOnExit is set to pdFALSE, the state of the event bits in the event group will not be modified by the xEventGroupWaitBits() API function.

Event bits can also be manually cleared using the xEventGroupClearBits() API function. Manual clearing should pay attention to access violations. If xClearOnExit is set to pdTRUE, the test and clearing of event bits will be displayed when the task is called as an atomic operation (cannot be interrupted by other tasks or interrupt).

3. Synchronize tasks with event groups

It is very simple for a task to wait for multiple events. For example, the operation of task A requires an operation in tasks B, C, and D. Then task A calls xEventGroupWaitBits() to wait. When the operations in B, C, and D are completed, B, C, and D C and D call xEventGroupSetBits() respectively, and task A will continue to run after both are called.

But sometimes two or more tasks need to be synchronized with each other . For example:

Task A receives an event and then delegates some of the processing needed for that event to three other tasks: Task B, Task C, and Task D (that is, without A there is no BCD). If task A cannot receive another event until tasks B, C, and D have all finished processing (that is, there is no A without BCD), then the four tasks will need to synchronize with each other. The synchronization point for each task is after that task has completed processing and cannot proceed until every other task has completed processing. Task A cannot receive another event until all four tasks have reached the synchronization point.

Event groups are used to create synchronization points:

Each task that must participate in synchronization is assigned a unique event bit in the event group.

Each task sets its own event bit when it reaches a synchronization point.

After setting its own event bit, each task blocks on the event group, waiting for other synchronous tasks' event bits to be set as well.

However, the xEventGroupSetBits() and xEventGroupWaitBits() API functions cannot be used in this scenario. If they are used, the setting of the bit (indicating that a task has reached its synchronization point) and the testing of the bit (determining whether other synchronous tasks have reached its synchronization point) are performed as two separate operations. To see why this is a problem, consider a scenario where Task A, Task B and Task C try to synchronize using event groups:

1. Task A and task B have reached the synchronization point, so their event bits are set in the event group, and they are in a blocked state, waiting for task C's event bit to be set.

2. Task C reaches the synchronization point and uses xEventGroupSetBits() to set its bits in the event group. As soon as Task C's bit is set, Task A and Task B leave the blocked state and clear all three event bits.

3. Task C then calls xEventGroupWaitBits() to wait for all three event bits to be set, but by then, all three event bits have already been cleared, task A and task B have left their respective synchronization points, and thus synchronize fail.

To successfully create a synchronization point using an event group, the setting of the event bit and the subsequent test of the event bit must be performed as a single uninterruptible operation, using the xEventGroupSync() API function.

EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,const EventBits_t uxBitsToWaitFor,TickType_t xTicksToWait );

The parameter meaning is the same as that of xEventGroupWaitBits().

If xEventGroupSync() returns because the unblocking condition is satisfied, the corresponding bit will be automatically cleared before returning.

The return value is the value of the event group. When the condition is met, the corresponding bit of the event group will be cleared to 0, but the return value is not cleared to 0. Because waiting for the timeout to return is the value of the event group.

For example: the waiting bits are 1 and 2, when the event group is 0110, that is, bit 1 and 2 are set to 1, which means that the unblocking condition is met, the function will return at this time, and the return value is 0110.

The waiting bits are 1 and 2. When the event group is 0100, or 0000, or something other than 0110, and it has timed out, the function will return at this time, and the return value is 0100, or 0000, or something other than 0110 Condition.

A complete example:

static void vSyncingTask( void *pvParameters ){
   
     const TickType_t xMaxDelay = pdMS_TO_TICKS( 4000UL );  const TickType_t xMinDelay = pdMS_TO_TICKS( 200UL );  TickType_t xDelayTime;  EventBits_t uxThisTasksSyncBit;  const EventBits_t uxAllSyncBits = ( mainFIRST_TASK_BIT |mainSECOND_TASK_BIT |mainTHIRD_TASK_BIT );  uxThisTasksSyncBit = ( EventBits_t ) pvParameters;  for( ;; )  {
   
       xDelayTime = ( rand() % xMaxDelay ) + xMinDelay;    vTaskDelay( xDelayTime );    vPrintTwoStrings( pcTaskGetTaskName( NULL ), "reached sync point" );    xEventGroupSync( xEventGroup,uxThisTasksSyncBit,uxAllSyncBits,portMAX_DELAY );    vPrintTwoStrings( pcTaskGetTaskName( NULL ), "exited sync point" );  }}EventGroupHandle_t xEventGroup;int main( void ){
   
     xEventGroup = xEventGroupCreate();  xTaskCreate( vSyncingTask, "Task 1", 1000, mainFIRST_TASK_BIT, 1, NULL );  xTaskCreate( vSyncingTask, "Task 2", 1000, mainSECOND_TASK_BIT, 1, NULL );  xTaskCreate( vSyncingTask, "Task 3", 1000, mainTHIRD_TASK_BIT, 1, NULL );  vTaskStartScheduler();  for( ;; );  return 0;}

The task prints "reached sync point" when it reaches the sync point, then waits for sync, leaves the sync point after sync, prints "exited sync point"

Series of articles collection:

Collection of FreeRTOS articles

A Collection of Embedded Linux Basic Articles

ARM Learning Series Collection

C language learning series collection

Guess you like

Origin blog.csdn.net/freestep96/article/details/130227935