Real-time operating system Freertos open pit study notes: (8): semaphore, event flag group, task notification mechanism

Tip: After the article is written, the table of contents can be automatically generated. How to generate it can refer to the help document on the right


Preface

The information in this section is still very large, and it is also very important.
insert image description here

1. Introduction to semaphores

insert image description here
insert image description here

If you look at this picture to understand: that is to say, the semaphore is the parking space. The semaphore count value is greater than 0, which means there is a parking space. =1 has one parking space, =3 has three parking spaces, and equal to 0 means there is no parking space. If a parking space is occupied, the count value is reduced by one, and if a parking space is vacant, the count value is increased by one .

A semaphore can be compared to a key, used to control multiple people's access to the same resource. Everyone must get this key before they want to use this resource. If the key is available, the person can use the resource; if the key is not available, the person must wait until someone else returns the key.

Give a practical example to illustrate the concept of semaphore. Suppose there is a public toilet with only one pit available. Multiple people need to use this pit, but only one person can use it at the same time. To avoid multiple people entering the toilet at the same time, semaphores can be used to control access.

Example of a binary semaphore: Suppose there are three people, named A, B, and C, who want to use a public toilet. Place a lock at the door of a public toilet, and the initial state is locked. When a person wants to use the toilet, he must first see if the lock is locked. If it is locked, wait. If it is not locked, lock it and enter the toilet. When he's done using it, he unlocks it so others can get in and use it.

Example of counting semaphores: Suppose there is a restaurant with only five seats available for guests to dine. Multiple guests come to the restaurant, but only five guests can dine at the same time. Place a counter at the entrance of the hotel with an initial value of 5. When a guest wants to enter the restaurant to dine, he must first check the counter value. If it is greater than 0, it means there are still seats available. He can enter the restaurant and reduce the counter value by 1; if the counter value is 0, it means there are no seats available. If a seat is available, he must wait until other guests dine and leave, releasing the seat.

In these examples, the semaphore is like a key that controls access to a resource (toilets, seats) by multiple people. Only when the key is available can you access the resource; otherwise, you need to wait for someone else to release the key. This mechanism of controlling resource access through semaphores can effectively avoid resource competition and confusion.

1. What is the difference between semaphore and queue?

insert image description here
Semaphores and queues are two different concepts, and they have different roles and uses in practical applications.

Functions and functions:

Semaphores: Semaphores are mainly used to implement task synchronization and mutual exclusion mechanisms. It is used to coordinate the concurrent execution of multiple tasks, control access to shared resources, and avoid race conditions and data inconsistencies.
Queue: A queue is a data structure used to store and manage data. It follows the first-in-first-out (FIFO) principle and implements the entry and exit of data into and out of the queue through the operations of the head and tail of the queue.
data structure:

Semaphore: The semaphore itself is not a data structure. It can be understood as a counter or flag, used to control the execution of tasks and access to shared resources.
Queue: A queue is a data structure that can be implemented using an array or a linked list. It maintains a collection of elements internally and provides enqueuing and dequeuing operations.
Application scenarios:

Semaphores: Mainly used in operating systems to achieve synchronization and mutual exclusion of tasks and control access to shared resources.
Queue: Queue is widely used in many fields, such as operating system scheduling algorithm, message passing, network communication, etc. It can be used to implement task queues, message queues, request queuing, etc.

2. Binary semaphores and their examples

1. What is a binary semaphore

insert image description here
A binary semaphore is a special type of semaphore that has only two possible values: 0 and 1. It can be used to implement mutual exclusion operations, that is, only one task or thread is allowed to access a shared resource at a certain time.

A binary semaphore works like a lock. When the value of the semaphore is 1, it means that the resource is available, and any task or thread can obtain the resource and perform operations. When a task or thread acquires a resource, it reduces the value of the semaphore to 0, indicating that the resource is occupied .

When the value of the semaphore is 0, it means that the resource is unavailable and other tasks or threads must wait. When a task or thread releases a resource, it increases the value of the semaphore to 1, indicating that the resource is available, and waiting tasks or threads will have the opportunity to obtain the resource.

The classic application of binary semaphores is to implement mutually exclusive access to shared resources, such as the protection of critical sections. When multiple tasks or threads need to access the critical section, a binary semaphore can be used to control access to the critical section to ensure that only one task or thread can enter the critical section to perform operations at the same time, thus avoiding race conditions and data inconsistencies. question.

In summary, a binary semaphore is a special type of semaphore with only two possible values: 0 and 1. It can be used to implement mutual exclusion access to shared resources, ensuring that only one task or thread can perform operations at a certain time.

2. Binary semaphore related API functions

insert image description here
These functions are used in FreeRTOS to create and operate binary semaphores. Here is a brief description of these functions:

xSemaphoreCreateBinary(): Create a binary semaphore dynamically. This function returns a handle to the created binary semaphore. This handle can be used within a task to acquire or release the semaphore.

xSemaphoreCreateBinaryStatic(): Create a binary semaphore in a static way. It is similar to the dynamically created function, but it needs to provide a statically allocated memory as the storage space of the semaphore. This function returns a handle to the created binary semaphore.

xSemaphoreGive(): Releases a binary semaphore. Increase the semaphore counter by 1 to indicate that the resource is available. If there are other tasks waiting for the semaphore, one of the tasks will acquire the semaphore.

xSemaphoreGiveFromISR(): Releases a binary semaphore in the interrupt service routine (ISR). Similar to the xSemaphoreGive() function, but can be used in interrupt context. This is because some blocking functions cannot be called directly in the interrupt context.

xSemaphoreTake(): Get a binary semaphore. Decrement the counter of the semaphore by 1, indicating that the resource is not available. If the semaphore's counter is zero, the task blocks and waits until another task releases the semaphore.

xSemaphoreTakeFromISR(): Obtain a binary semaphore in the interrupt service routine (ISR). Similar to the xSemaphoreTake() function, but can be used in interrupt context.

3. Binary semaphore example

insert image description here
Code:

/* 任务一,释放二值信号量 */
void task1( void * pvParameters )
{
    
    
    uint8_t key = 0;
    BaseType_t err;
    while(1) 
    {
    
    
        key = key_scan(0);
        if(key == KEY0_PRES)
        {
    
    
            if(semphore_handle != NULL)
            {
    
    
                err = xSemaphoreGive(semphore_handle);
                if(err == pdPASS)
                {
    
    
                    printf("信号量释放成功!!\r\n");
                }else printf("信号量释放失败!!\r\n");
            }
            
        }
        vTaskDelay(10);
    }
}

/* 任务二,获取二值信号量 */
void task2( void * pvParameters )
{
    
    
    uint32_t i = 0;
    BaseType_t err;
    while(1)
    {
    
    
        err = xSemaphoreTake(semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
        if(err == pdTRUE)
        {
    
    
            printf("获取信号量成功\r\n");
        }else printf("已超时%d\r\n",++i);
        
    }
}

The above code is an example that demonstrates how to use binary semaphores to perform synchronization operations between tasks in FreeRTOS.

Task 1 is a periodic task that detects keystrokes through the key_scan() function and releases the binary semaphore when KEY0 is pressed. If the handle of the binary semaphore is not empty, call the xSemaphoreGive() function to release the semaphore. If the release is successful, print "Semaphore release successful!", otherwise print "Semaphore release failed!".

Task 2 is a cyclic task that obtains the binary semaphore by calling the xSemaphoreTake() function. Using the portMAX_DELAY parameter means that the task will wait until the semaphore is not obtained. If the acquisition is successful, print "Acquisition of semaphore successful", otherwise print "Timed out" and increment the counter.

In this way, when task one releases the semaphore, task two can acquire the semaphore and perform corresponding operations. Through the acquisition and release of binary semaphores, the synchronization and mutual exclusion operations between tasks can be realized to ensure safe access to shared resources.

It should be noted that the semphore_handle in the example is a handle pointing to a binary semaphore, which needs to be initialized when the task is created.

3. Counting semaphore

insert image description here
insert image description here

4. Priority flipping

insert image description here
For example: Suppose there are three tasks: Task1, Task2 and Task3, with their priorities being high, medium and low respectively. These three tasks need to access a shared resource, such as a global variable.

At a certain point in time, Task1 starts executing first and obtains the mutex of the shared resource. At this time, Task2 and Task3 are in a blocking state because their priority is lower than Task1.
Then, Task2's medium-priority task needs to handle an emergency event, but since Task1 holds a shared resource, Task2 cannot access the resource. At this time, Task2 cannot handle emergency events in time, causing the real-time performance of the system to be affected.

To solve this problem, you can use the priority flipping technology in FreeRTOS. When Task1 acquires the mutex of the shared resource, its priority will be promoted to Task2's priority. In this way, Task2 can access shared resources and handle emergency events in a timely manner, ensuring the real-time performance of the system.

When Task1 releases the mutex of the shared resource, its priority will be restored to its original value, that is, high priority. In this way, Task3 can continue to execute, and the priority of Task2 will also be restored to medium priority.

By using priority inversion technology, the priority inversion problem in real-time task scheduling can be solved, ensuring that high-priority tasks can access shared resources in a timely manner and improving the real-time performance of the system.

5. Mutually exclusive semaphore

insert image description here
A mutex semaphore is a mechanism used to implement mutually exclusive access to shared resources. It is a binary semaphore that can only take on two values: 0 and 1. When the value of the mutex semaphore is 1, it means that the shared resource is accessible; when the value of the mutex semaphore is 0, it means that the shared resource is occupied.

In a multitasking system, when a task needs to access a shared resource, it will first try to obtain the mutex semaphore. If the value of the mutex semaphore is 1, indicating that the shared resource is accessible, the task will set the value of the mutex semaphore to 0, marking the shared resource as occupied. If the value of the mutex semaphore is 0, it means that the shared resource has been occupied by other tasks, and the task will enter the blocking state and wait for the value of the mutex semaphore to become 1.
When the task occupying the shared resource completes access to the shared resource, it releases the mutex semaphore and resets its value to 1, indicating that the shared resource can be accessed by other tasks. At this time, the task with the highest priority among the tasks in the blocked state will be awakened, obtain the mutually exclusive semaphore, and continue to access the shared resources.

Mutually exclusive access to shared resources can be achieved by using a mutex semaphore, avoiding competition and conflicts caused by multiple tasks accessing shared resources at the same time. This helps to improve the reliability and concurrency of the system. In FreeRTOS, mutex semaphores can be created using the xSemaphoreCreateMutex() function, and acquired and released through the xSemaphoreTake() and xSemaphoreGive() functions.

insert image description here
insert image description here
insert image description here
When multiple tasks need to access shared resources, the role of semaphores can be illustrated by the following example:

Suppose there are two tasks: Task1 and Task2, which need to access a shared serial port resource at the same time, such as a serial port buffer.

In this case, a binary semaphore (mutex semaphore) can be used to implement mutually exclusive access to resources.

Initialize the semaphore: When the system is initialized, create a mutex semaphore with an initial value of 1.

xSemaphoreHandle mutex = xSemaphoreCreateMutex();

Implementation of Task1 and Task2:

// Task1
void Task1(void *parameters) {
    
    
    while (1) {
    
    
        // 尝试获取互斥信号量
        if (xSemaphoreTake(mutex, portMAX_DELAY) == pdTRUE) {
    
    
            // 临界区代码,访问共享资源
            // ...
            
            // 释放互斥信号量
            xSemaphoreGive(mutex);
        }
    }
}

// Task2
void Task2(void *parameters) {
    
    
    while (1) {
    
    
        // 尝试获取互斥信号量
        if (xSemaphoreTake(mutex, portMAX_DELAY) == pdTRUE) {
    
    
            // 临界区代码,访问共享资源
            // ...
            
            // 释放互斥信号量
            xSemaphoreGive(mutex);
        }
    }
}

In this example, when Task1 and Task2 need to access the shared resource, they first try to obtain the mutex semaphore. If the value of the mutex semaphore is 1, indicating that the resource is accessible, the task can enter the critical section code and set the value of the mutex semaphore to 0 to mark that the shared resource is occupied. When a task completes access to the shared resource, it releases the mutex semaphore and resets its value to 1, indicating that the shared resource can be accessed by other tasks.

By using mutually exclusive semaphores, you can ensure that only one task can access shared resources at any time, avoiding data conflicts and corruption. This ensures the correctness and consistency of the system.

6. Important knowledge in this section about semaphores:

1. The most important thing is to ensure synchronization and mutually exclusive access to resources.

Resource synchronization refers to the need to coordinate access to shared resources between multiple tasks to ensure data correctness and consistency. Through semaphores, the execution order of tasks can be controlled, so that before a task uses a shared resource, it must wait for other tasks to release the resource. This avoids race conditions and data inconsistencies .

Mutually exclusive access to resources means that only one task can access shared resources at the same time to prevent data conflicts and damage. By using a mutually exclusive semaphore, you can ensure that when one task accesses a shared resource, other tasks cannot access the resource at the same time, thus avoiding problems caused by concurrent access .

In concurrent systems, synchronization and mutually exclusive access to resources are very important, especially in multi-tasking environments. They can ensure collaboration and correctness between tasks, avoid data competition and conflicts, and improve system reliability and performance.

2. Some knowledge points summarized:

In semaphores, there are some commonly used knowledge points and concepts, including:

Binary semaphore and counting semaphore: Binary semaphore can only take on two values ​​(0 and 1) and is used to achieve mutually exclusive access to shared resources; counting semaphore can take on multiple non-negative integer values ​​and is used to control access to shared resources. concurrent access.

Creation and deletion of semaphores: You can use specific functions to create and delete semaphores, such as xSemaphoreCreateBinary() to create a binary semaphore, xSemaphoreCreateCounting() to create a counting semaphore, and vSemaphoreDelete() to delete a semaphore.

Acquisition and release of semaphore: The task can use the xSemaphoreTake() function to obtain the semaphore. If the semaphore is unavailable, the task will enter the blocking state and wait; the task can use the xSemaphoreGive() function to release the semaphore so that other tasks can obtain it. The semaphore.

Semaphore priority reversal problem: When a low-priority task holds a semaphore and a high-priority task needs to acquire the semaphore, a priority reversal problem may occur. In order to solve this problem, techniques such as priority inheritance or priority masking can be used.

Timeout processing of semaphores: When a task obtains a semaphore, it can specify a timeout period. If the semaphore cannot be obtained within the specified time, the task can perform corresponding timeout processing.

Deadlock problem: When using multiple semaphores, if the mutual exclusion and synchronization relationships between tasks are incorrect, it may cause a deadlock problem, that is, tasks wait for each other to release resources and cannot continue to execute. In order to avoid deadlock, it is necessary to reasonably design the acquisition and release sequence of semaphores and avoid resource competition.

Seven, event flag group

1. Event flag group concept

insert image description here
insert image description here

2. What is the difference between event flag group, queue and semaphore?

insert image description here
Event flag groups, queues and semaphores are commonly used synchronization mechanisms in FreeRTOS. They have some differences in functions and application scenarios:

Event Flag Group: Event Flag Group is used for event notification and waiting between tasks. It allows a task to wait for any one or more of multiple events to occur and return immediately after the event occurs. Events can be represented by setting and clearing bits, and tasks can wait for specific bits or combinations of bits. The event flag group provides a flexible way to implement event notification between tasks, and is suitable for multi-task concurrent processing scenarios.

Queue: Queue is used for data transmission and sharing between tasks. It can pass messages, data and events between tasks. Queues have a fixed size and can be used to implement asynchronous communication and synchronous operations between tasks. Tasks can transmit and share data by sending messages to or receiving messages from the queue.

Semaphore: Semaphore is used for mutual exclusion and synchronization between tasks. It can be used to protect access to shared resources and prevent multiple tasks from accessing shared resources at the same time. Semaphores are usually used to limit concurrent access to resources, control the execution order of tasks, or for synchronization operations between tasks.

The differences are summarized as follows:

The functions are different: the event flag group is used for event notification and waiting, the queue is used for data transmission and sharing, and the semaphore is used for mutual exclusion and synchronization.
Data transmission: Queues can transmit data and messages, while event flag groups and semaphores do not directly transmit data.
Synchronization mode: The event flag group is used to wait for events to occur; queues and semaphores are used for synchronization and mutual exclusion operations between tasks.
Size limit: Queues have a fixed size, while event flag groups and semaphores have no size limit.
Usage scenarios: Event flag groups are suitable for event notification and concurrent processing between tasks; queues are suitable for data transmission and sharing between tasks; semaphores are suitable for mutual exclusion and synchronization operations between tasks.
Depending on the specific application requirements, a suitable synchronization mechanism can be selected to achieve communication and coordination between tasks. Typically, these three synchronization mechanisms can be used in combination with each other to meet complex application scenarios.

3.API functions

insert image description here
These functions are the functions in FreeRTOS used for event flag group manipulation. Below is the function and usage of each function:

xEventGroupCreate(): Create an event flag group dynamically. It returns a handle of type EventGroupHandle_t, which is used to operate the event flag group.

xEventGroupCreateStatic(): Use static method to create event flag group. Similar to xEventGroupCreate(), but can use statically allocated memory to create event flag groups.

xEventGroupClearBits(): Clear event flag bits. Clear one or more bits in an event flag group by specifying a bitmask of the bits to clear.

xEventGroupClearBitsFromISR(): Clear event flag bits in interrupts. Similar to xEventGroupClearBits(), but can be used in interrupt service functions.

xEventGroupSetBits(): Set event flag bits. Sets one or more bits in the event flag group to 1 by specifying a bitmask of the bits to set.

xEventGroupSetBitsFromISR(): Set the event flag bit in the interrupt. Similar to xEventGroupSetBits(), but can be used in interrupt service functions.

xEventGroupWaitBits(): Wait for event flag bits. A task can call this function to wait for one or more bits in the event flag group to meet specified conditions, along with an optional timeout. The function will return the bits that satisfy the condition.

xEventGroupSync(): Set the event flag and wait for the event flag. This function is used to set the event flag group to the specified bit and wait for the bit to meet the condition. Similar to xEventGroupWaitBits(), but setting and waiting can be done in one atomic operation.

4. Give a routine of a smart door lock that applies the knowledge of event flag group

The application of smart door locks is very suitable for the event flag group, because there will be many tasks triggering events, causing the same task: unlocking, to occur.
A start task and four control tasks are created based on the FreeRTOS operating system.
Event flag groups are used to communicate between tasks. When the TFTLCD screen touches the screen to enter a password, and the password is entered correctly, an event flag is sent to the servo task; when the fingerprint recognition is successful, an event flag is sent to the servo task
. An event flag;
when the radio frequency identification card recognizes the card number successfully, an event flag is sent to the servo task;
when Bluetooth generates a password from the mobile phone serial port, when the password is successfully recognized, an event flag is sent to the servo task.

As long as the steering gear task receives one of the event flags, the steering gear will rotate 180° to simulate the successful unlocking. When each unlock fails, the err global variable will be incremented by one, and when err is equal to 3, the servo task will be suspended.

①Macro definition event flag group

EventGroupHandle_t EventGroupHandler;	//事件标志组句柄
#define EVENTBIT_0	(1<<0)				//事件位
#define EVENTBIT_1	(1<<1)
#define EVENTBIT_2	(1<<2)
#define EVENTBIT_ALL	(EVENTBIT_0|EVENTBIT_1|EVENTBIT_2)

These macro definitions can be used to specify specific event bits in code for setting, clearing, and checking events. For example, by using EVENTBIT_0, you can specify bit 0 in the event flag group for the corresponding operation. At the same time, EVENTBIT_ALL can be used to check whether all event bits are triggered.

②Task 1: Enter the password on the touch screen, if it is correct, set the event flag group to send the unlock command

void LCD_task(void * pvParameters)
{
    
    
	while(1)
	{
    
    
			  if(sg90flag==1||GET_NUM())
				{
    
    
					 BEEP=1;
					 delay_xms(100);
					 BEEP=0;
				   printf("密码输入正确\r\n");
		       xEventGroupSetBits(EventGroupHandler,EVENTBIT_0);
	 
				}
        else
				{
    
    
					 BEEP=1;
					 delay_xms(50);
					 BEEP=0;
					 delay_xms(50);
					 BEEP=1;
					 delay_xms(50);
					 BEEP=0;
					delay_xms(50);
					 BEEP=1;
					 delay_xms(50);
					 BEEP=0;
					printf("密码输入错误\r\n");
					err++;
					if(err==3)
					{
    
    
					  vTaskSuspend(SG90Task_Handler);
						printf("舵机任务挂起\r\n");
					}			
				}					
			vTaskDelay(100); //延时10ms,也就是10个时钟节拍
	}	
}

The task enters an infinite loop (while(1)), which means that the task will continue to execute.

In the loop, the task checks the value of the sg90flag variable and the return value of the GET_NUM() function. These conditions are used to determine whether the password is entered correctly. If the password is entered correctly, the task prints the message "Password entered correctly" and performs a series of operations. These operations include sounding a buzzer, delaying for a period of time, displaying a password match message on the LCD, and setting bits in the event flag group.

If the password is entered incorrectly, the task will print a "Password entered incorrectly" message and perform a series of operations. These operations include continuously sounding a buzzer, delaying for a period of time, displaying a password error message on the LCD, and incrementing the error counter err. If the error count reaches 3 times, the task will suspend the servo task, print the message "Servo task suspended", and display the corresponding information on the LCD.

Finally, the task calls the vTaskDelay(100) function to delay, and the delay time is 100 clock ticks.

③Task 2: Swipe the NFC card. If it is recognized, set the event flag group to send the unlocking command.

void RFID_task(void * pvParameters)
{
    
    
	
//	 rfidflag=shibieka();
   while(1)
	 {
    
    
	    if(rfidflag==1||shibieka())
			{
    
    
				   BEEP=1;
					 delay_xms(100);
					 BEEP=0;
				xEventGroupSetBits(EventGroupHandler,EVENTBIT_1);
				printf("识别卡号成功\r\n");
			}
			else if(shibieka()==0)
			{
    
    
				BEEP=1;
					 delay_xms(50);
					 BEEP=0;
					 delay_xms(50);
					 BEEP=1;
					 delay_xms(50);
					 BEEP=0;
					delay_xms(50);
					 BEEP=1;
					 delay_xms(50);
					 BEEP=0;
			  printf("识别卡号失败\r\n");
				err++;
					if(err==3)
					{
    
    
					  vTaskSuspend(SG90Task_Handler);
						printf("舵机任务挂起\r\n");
					}
			}
			
	   vTaskDelay(100); //延时10ms,也就是10个时钟节拍
	 }
 
}

Entering an infinite loop while(1) means that the task will always be executed.

In the loop, first judge whether the rfidflag is 1 or the return value of the shibieka() function is true. If it is, a series of operations are performed, including controlling the buzzer sound, setting the first bit in the event flag group (by calling xEventGroupSetBits(EventGroupHandler, EVENTBIT_1)), and outputting a prompt message indicating that the card number is successfully identified.

If the above conditions are not met, that is, rfidflag is not 1 and the return value of the shibieka() function is false, perform another series of operations. These operations include controlling the buzzer to emit a specific sound sequence, outputting a prompt message indicating failure to identify the card number, and increasing an error counter err. If the error counter reaches 3, the servo task named SG90Task_Handler will be suspended and a prompt message that the servo task is suspended will be output.

Finally, delay is performed by calling vTaskDelay(100), that is, the task will pause for 100 clock beats (about 10 milliseconds) after each execution, and then enter the next cycle again.

④Task 3: Servo task, receive each event flag to respond

void SG90_task(void * pvParameters)
{
    
    
	 volatile EventBits_t EventValue;	
	while(1)
	{
    
    
   EventValue=xEventGroupWaitBits(EventGroupHandler,EVENTBIT_ALL,pdTRUE,pdFALSE,portMAX_DELAY);
		   
				  printf("接收事件成功\r\n");
		      set_Angle(180);
			    delay_xms(1000);
			    delay_xms(1000);
			    set_Angle(0);
			    LCD_ShowString(80,150,260,16,16,"              ");
 
			vTaskDelay(100); //延时10ms,也就是10个时钟节拍
				
	}	
}

First, a variable EventValue of type EventBits_t is defined at the beginning of the task to store the value of the event flag group.

Next, enter an infinite loop while(1), indicating that the task will always be executed.

In the loop, wait for all bits in the event flag group to be set by calling xEventGroupWaitBits(EventGroupHandler, EVENTBIT_ALL, pdTRUE, pdFALSE, portMAX_DELAY). The parameters of the function are described as follows:

EventGroupHandler: The handle of the event flag group.
EVENTBIT_ALL: The event bits to wait for, here is waiting for all event bits to be set.
pdTRUE: Set to pdTRUE, which means waiting for all event bits to be set.
pdFALSE: Set to pdFALSE, indicating that there is no need to wait for all event bits to be cleared.
portMAX_DELAY: Indicates the maximum value of waiting time, that is, infinite waiting.
When all event bits are set, a series of operations are performed, including outputting a prompt message indicating successful event reception, setting the servo angle to 180 degrees, delaying for 1 second, delaying for another 1 second, and setting the servo angle to 0 degree, and display a blank string on the LCD.

Finally, delay is performed by calling vTaskDelay(100), that is, the task will pause for 100 clock beats (about 10 milliseconds) after each execution, and then enter the next cycle again.

When the RFID task or LCD task executes the xEventGroupSetBits(EventGroupHandler,EVENTBIT_1) statement, the execution of the SG90 task will be triggered immediately.
In the RFID task or LCD task, when the xEventGroupSetBits(EventGroupHandler, EVENTBIT_1) function is called, the first bit in the event flag group (i.e. EVENTBIT_1) will be set. Then, the SG90 task waits for all event bits to be set by calling the xEventGroupWaitBits(EventGroupHandler, EVENTBIT_ALL, pdTRUE, pdFALSE, portMAX_DELAY) function. When all bits in the event flag group are set, the SG90 task will immediately perform a series of operations.
Therefore, when the RFID task or LCD task executes the xEventGroupSetBits(EventGroupHandler, EVENTBIT_1) statement, the execution of the SG90 task will be triggered immediately.

This is the application of the event flag group.

8. Task notification-message mailbox

1. The concept of task notification

insert image description here
insert image description here

2. Advantages and disadvantages of task notifications

insert image description here
Task Notification is a mechanism used for communication and synchronization between tasks in a real-time operating system (RTOS). Task notifications allow simple notification messages to be sent directly between tasks without using more complex mechanisms such as queues, semaphores, or event flag groups.

Advantages:
①Lightweight: Task notification is a lightweight communication mechanism that does not require additional data structures and memory, and only uses a 32-bit integer as the notification value. Therefore, it consumes less system resources and is suitable for resource-constrained embedded systems.
②Fast response: The sending and receiving operations of task notifications are direct and do not require intermediate buffers or queues, so the response time is short. Tasks can be notified immediately and processed accordingly, which is suitable for application scenarios with high real-time requirements.
③Flexibility: Task notifications can pass any 32-bit integer value, and tasks can be processed differently based on the specific meaning of the notification value. This flexibility makes task notifications suitable for a variety of applications and can achieve a variety of communication and synchronization needs.

Disadvantages:
① Only applicable to one-to-one communication: Task notification is a one-to-one communication mechanism, and notifications sent by a task can only be received by a specific task. If multiple tasks need to receive the same notification, copying operations need to be performed between multiple tasks, adding additional overhead.
② Unable to transfer large amounts of data: Task notifications can only transfer a 32-bit integer value and cannot directly transfer large amounts of data. If you need to pass a large amount of data, you need to use other mechanisms (such as queues or message mailboxes) to pass pointers or handles to the data.
③No blocking option: The sending and receiving operations of task notifications are non-blocking. The task cannot be suspended while waiting for notifications and needs to be polled continuously. This can waste processor resources, especially if the wait time is long.

To sum up, task notification is a lightweight, fast-responding and flexible inter-task communication mechanism, which is suitable for application scenarios with high real-time requirements, small communication volume and one-to-one communication. But be aware of the limitations of its inability to pass large amounts of data, non-blocking options, and only being suitable for one-to-one communications. In specific applications, it is necessary to select an appropriate communication mechanism based on actual needs and system resource considerations.

3. Related API functions

insert image description here

insert image description here

4. Routines using the task notification mechanism

(1) Simulate the task of obtaining a binary semaphore:

/* 任务一,发送任务通知值 */
void task1( void * pvParameters )
{
    
    
    uint8_t key = 0;
    
    while(1) 
    {
    
    
        key = key_scan(0);
        if(key == KEY0_PRES)
        {
    
    
            printf("任务通知模拟二值信号量释放!\r\n");
            xTaskNotifyGive(task2_handler);
        }
        vTaskDelay(10);
    }
}

/* 任务二,接收任务通知值 */
void task2( void * pvParameters )
{
    
    
    uint32_t rev = 0;
    while(1)
    {
    
    
        rev = ulTaskNotifyTake(pdTRUE , portMAX_DELAY);
        if(rev != 0)
        {
    
    
            printf("接收任务通知成功,模拟获取二值信号量!\r\n");
        }
    }
}

In this example, the task notification value acts like a binary semaphore. Task 1 sending the notification value is equivalent to releasing the semaphore, and task 2 receiving the notification value is equivalent to acquiring the semaphore. Through the sending and receiving of task notification values, one-to-one communication and synchronization is realized between task one and task two.

It should be noted that task notification is a non-blocking operation, that is, the functions that send and receive task notifications return immediately. Therefore, Task 1 and Task 2 will continuously poll to check whether any task notifications are sent or received.

(2) Simulate the task of obtaining the counting semaphore:

/* 任务一,发送任务通知值 */
void task1( void * pvParameters )
{
    
    
    uint8_t key = 0;
    
    while(1) 
    {
    
    
        key = key_scan(0);
        if(key == KEY0_PRES)
        {
    
    
            printf("任务通知模拟计数型信号量释放!\r\n");
            xTaskNotifyGive(task2_handler);
        }
        vTaskDelay(10);
    }
}

/* 任务二,接收任务通知值 */
void task2( void * pvParameters )
{
    
    
    uint32_t rev = 0;
    while(1)
    {
    
    
        rev = ulTaskNotifyTake(pdFALSE , portMAX_DELAY);
        if(rev != 0)
        {
    
    
            printf("rev:%d\r\n",rev);
        }
        vTaskDelay(1000);
    }
}

In this example, the task notification value acts like a counting semaphore. Task 1 sending the notification value is equivalent to releasing the semaphore, and task 2 can perform corresponding processing according to the received notification value after receiving the notification value. In this example, task two simulates the action of processing by printing the received notification value.

(3) Simulate the task of obtaining the counting semaphore:

/* 任务一,发送任务通知值*/
void task1( void * pvParameters )
{
    
    
    uint8_t key = 0;
    while(1) 
    {
    
    
        key = key_scan(0);
        if(key == KEY0_PRES)
        {
    
    
            printf("将bit0位置1\r\n");
            xTaskNotify( task2_handler, EVENTBIT_0, eSetBits );
        }else if(key == KEY1_PRES)
        {
    
    
            printf("将bit1位置1\r\n");
            xTaskNotify( task2_handler, EVENTBIT_1, eSetBits );
        }
        vTaskDelay(10);
    }
}

/* 任务二,接收任务通知值 */
void task2( void * pvParameters )
{
    
    
    uint32_t notify_val = 0,event_bit = 0;
    while(1)
    {
    
    
        xTaskNotifyWait( 0, 0xFFFFFFFF, &notify_val, portMAX_DELAY );
        if(notify_val & EVENTBIT_0)
        {
    
    
            event_bit |= EVENTBIT_0;
        }
        if(notify_val & EVENTBIT_1)
        {
    
    
            event_bit |= EVENTBIT_1;
        }
        if(event_bit == (EVENTBIT_0|EVENTBIT_1))
        {
    
    
            printf("任务通知模拟事件标志组接收成功!!\r\n");
            event_bit = 0;
        }
    }
}

Task one sends the task notification value to task two by calling the xTaskNotify(task2_handler, EVENTBIT_0, eSetBits) function. The task2_handler here is the handle of task two, which is used to specify the task to receive the notification. EVENTBIT_0 is a macro-defined event flag, which means setting bit0 to 1.

Task 2 waits to receive the task notification value by calling the xTaskNotifyWait(0, 0xFFFFFFFF, ¬ify_val, portMAX_DELAY) function. The second parameter 0xFFFFFFFF means waiting for all event flag bits to be set, ¬ify_val is a variable that saves the received notification value, and portMAX_DELAY means the waiting time is set to the maximum value, that is, waiting infinitely. When task one calls xTaskNotify(task2_handler, EVENTBIT_0, eSetBits) to send the notification value, task two will receive the notification and save the received notification value in the notify_val variable.

In task two, it is detected whether the expected notification is received by judging whether the event flag bit in notify_val is set. If EVENTBIT_0 and EVENTBIT_1 are received, it means that the task notification simulation event flag group is successfully received, and the corresponding prompt information will be printed.

In this example, the task notification value acts like an event flags group. Task one sending a notification value is equivalent to setting the corresponding event flag bit. Task two determines whether the expected notification has been received by detecting whether the event flag bit is set.

Guess you like

Origin blog.csdn.net/qq_53092944/article/details/132673042