FreeRTOS task notification (use task notification to simulate binary semaphore)

Task notification is an event. FreeRTOS has added task notification function starting from version v8.2.0. There is a 32-bit member variable ulNotifiedValue in each TCB, which is specifically used for task notification. Task notifications can be used to replace semaphores, event flag groups, etc. on certain occasions, and have higher execution efficiency.
A task that receives a task notification can enter a blocked state due to waiting for task notification, and is unblocked after other tasks send task notifications to this task. According to FreeRTOS official data, using task notifications increases the speed of waking up blocked task times by 45% compared to using semaphores and event flag groups, and uses less RAM space. But using task notifications also has the following consequences.

(1) There can only be one task that receives task notifications.
(2) Only tasks that receive task notifications can enter the blocking state, and sending tasks will not be blocked due to failure to send task notifications.

1. Send and get task notifications

FreeRTOS provides 6 APIs for sending task notifications, namely xTaskNotify(), xTaskNotifyGive(), xTaskNotifyAndQuery() and their interrupt versions. There are two APIs for getting task notifications, namely ulTaskNotifyTake() and xTaskNotifyWait(). The API functions used to obtain task notifications cannot be used for interrupt service functions, and there is no corresponding interrupt version.

1.1 Send task notification

1.1.1 xTaskNotify()

xTaskNotifyO is used to send the specified task notification value to the specified task, and can specify the task notification update method.
What really implements the function is the xTaskGenericNotify() function. The macro is defined as follows.

#define xTaskNotify( xTaskToNotify, ulValue, eAction )
                        xTaskGenericNotify((xTaskToNotify),
                                              (ulValue),
                                               ( eAction ),
                                               NULL )

Parameter Description:

xTaskToNotify: Task handle, specifying the task to receive task notifications.

ulValue: Task notification value.

eAction: Task notification update method. It is an enumeration type with the following values.

eNoAction no action

eSetBits updates the specified bit

elncrement notification value plus one

eSetValueWithOverwrite updates the notification value in overwrite mode

eSetValueWithoutOverwrite updates notification value without overwriting

Return value: Returned when eAction is set to eSetValueWithoutOverwrite and the task notification is not updated successfully
pdFAlL; returns pdPASS when eAction is set to other options.

1.1.2 xTaskNotifyGive()

xTaskNotifyGive() is used to simply add one to the task notification value and then send it to the specified task. The actual function is the xTaskGenericNotify() function. The macro is defined as follows.

#define xTaskNotifyGive( xTaskToNotify )
                     xTaskGenericNotify((xTaskToNotify),
                                             (0),
                                             eIncrement,
                                             NULL )

Parameter Description:

xTaskToNotify: Task handle, specifying the task to receive task notifications.

Return value: pdPASS.

1.1.3 xTaskNotifyAndQuery()

xTaskNotifyAndQuery() is used to send the specified task notification value to the specified task and save the receiving task
The original value of the task notification is the xTaskGenericNotify() function that actually implements the function. The macro is defined as follows.

#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue )
                       xTaskGenericNotify((xTaskToNotify),
                                             (ulValue),
                                                ( eAction ),
                                        ( pulPreviousNotifyValue ) )

Parameter Description:

xTaskToNotify: Task handle, specifying the task to receive task notifications.

ulValue: Task notification value.

eAction: task notification update method, an enumeration type.

pulPreviousNotifyValue: used to save the task notification value before updating.

Return value: Returned when eAction is set to eSetValueWithoutOverwrite and the task notification is not updated successfully
pdFAlL; returns pdPASS when eAction is set to other options.

In addition to the above three send task notification functions, there are also three corresponding interrupt versions of the send task notification function. They all end with FromISR and are used in interrupt service functions. They mainly add the function of whether to perform task switching after exiting the interrupt. .

1.2 Get task notification

1.2.1 ulTaskNotifyTake()

ulTaskNotifyTake() is a simple function to obtain task notification. You can specify the behavior when the function exits and the blocking time of the task calling this function. The prototype of this function is as follows.

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ) 

The parameters are as follows:

xClearCountOnExit: Operation on the task notification value when the function exits. When the pdTRUE parameter is passed in to exit the function, the task notification value is cleared to 0. When the pdFALSE parameter is passed in and the function is exited, the task notification value is decremented by 1.
xTicksToWait: Get the blocking time of the notification task.

Return value: task notification value.

1.2.2 xTaskNotifyWait()

xTaskNotifyWait() is to obtain the task notification function. It can specify the behavior when the function exits, the blocking time of the task calling this function, and save the original value of the task notification of the receiving task. The prototype of this function is as follows.

 BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
                                uint32_t ulBitsToClearOnExit,
                                uint32_t *pulNotificationValue,
                                TickType_t xTicksToWait )

Parameter Description:

ulBitsToClearOnEntry: If the task notification is not received, perform a bitwise AND with the inverse value of this parameter and the task notification value.

ulBitsToClearOnExit: After receiving the task notification, perform a bitwise AND with the inverse value of this parameter and the task notification value before exiting the function.

pulNotificationValue: Points to the variable used to save the task notification value

xTicksToWait: Get the blocking time of the notification task.

Return value: pdTRUE, if the task notification is obtained successfully; pdFALSE, if the task notification is obtained failed.

2. Use of task notification

This example is rewritten using a binary semaphore routine, replacing the binary semaphore with a task notification, and the rest of the code is the same.

2.1 Task function

static char pcToPrint[80]; //Buffer of content to be printed
xQueueHandle xQueuePrint; //Message queue handle
volatile uint8_t uIRQCounter; //Used to count the number of interrupts



static TaskHandle_t Led0TaskHandle = NULL; //Task LED0 task handle
TaskHandle_t Led1TaskHandle = NULL; //Task LED1 task handle
static TaskHandle_t printTaskHandle = NULL;//Task printTask task handle
static TaskHandle_t keyTaskHandle = NULL; //Key scanning task handle


/****************************************************** *********************
Function name: Led0Task
Function: Make LED0 flash, achieve synchronization between tasks through binary semaphores, and output information to the serial port
Formal parameters: pvParameters are the parameters passed when creating the task
Return value: None
Priority: 3
*************************************************** ************************/
static void Led0Task(void *pvParameters)
{
uint32_t ulNotifyValue; //Save task notification value
while(1)
{
\t\t
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_4))); //LED0 flashes
vTaskDelay(pdMS_TO_TICKS(500));//delay 500ms
\t\t
//Get the task notification sent by pressing the button. After calling the function, the task notification value is cleared to 0.
ulNotifyValue = ulTaskNotifyTake(pdTRUE,10);
\t\t
if(ulNotifyValue)
{
//Generate information to be printed out
sprintf(pcToPrint,"The key task is notified through the task synchronous task 1 \r\\
\r\\
");
//Print information. All information sent to the serial port cannot be output directly. It is sent to the serial port guardian task through the queue.
xQueueSendToBack(xQueuePrint,pcToPrint,0);
}
}
}
/****************************************************** *********************
Function name: Led1Task
Function: Make LED1 flash, achieve synchronization between tasks and interrupts through task notification, and output information to the serial port
Formal parameters: pvParameters are the parameters passed when creating the task
Return value: None
Priority: 3
*************************************************** ************************/
static void Led1Task(void *pvParameters)
{
\t
uint32_t ulNotifyValue; //Save task notification value
while(1)
{
\t\t
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_5))); //LED1 flashes
\t\t
vTaskDelay(pdMS_TO_TICKS(500));//Delay 500ms
\t\t
//Get the task notification sent through the interrupt. After calling the function, the task notification value is cleared to 0.
ulNotifyValue = ulTaskNotifyTake(pdTRUE,10);
if(ulNotifyValue)
{
//Generate information to be printed out
sprintf(pcToPrint,"Tim2 interrupts synchronization task 2, interrupt = \r\\
\r\\
",uIRQCounter);
//Print information. All information sent to the serial port cannot be output directly. It is sent to the serial port guardian task through the queue.
xQueueSendToBack(xQueuePrint,pcToPrint,0);
}
\t
}
}
/****************************************************** *********************
Function name: printTask
Function description: The serial port daemon task uses a FreeRTOS queue to achieve serial access to the serial port. This daemon task is the only task that can directly access the serial port.
The serial port daemon task spends most of its time in the blocking state waiting for messages to arrive in the queue. When a message arrives,
The serial port daemon task simply sends the received message to the serial port, then returns to the blocking state and continues to wait for the arrival of the next message.
Formal parameters: pvParameters are the parameters passed when creating the task
Return value: None
Priority: 3
*************************************************** ************************/
void printTask(void *pvParameters)
{
char pcTowrite[80]; //Cache the data received from the queue
while(1)
{
/*When the queue is empty, that is, there are no characters to output, the blocking timeout is portMAX_DELAY, and the task will wait indefinitely.
Status, you can not detect the return value of the queue reading function*/
xQueueReceive(xQueuePrint,pcTowrite,portMAX_DELAY);
printf("%s",pcTowrite);
}
}
/****************************************************** *********************
Function name: keyTask
Function description: Key scanning task, perform corresponding operations according to the key value
Formal parameters: pvParameters are the parameters passed when creating the task
Return value: None
Priority: 4
*************************************************** ************************/
static void keyTask(void *pvParameters)
{
uint8_t keyValue = 0; //Storage key value
\t
while(1)
{
keyValue =Key_getNum();
if(keyValue == 1)
{
sprintf(pcToPrint,"Key1 button is pressed, sending task notification...\r\\
\r\\
");
xQueueSendToBack(xQueuePrint,pcToPrint,0);
//Send task notification to task 1 for synchronization
xTaskNotifyGive(Led0TaskHandle);
}else if(keyValue == 2)
{
//Start TIM2 timer
TIM_Cmd(TIM2,ENABLE);
sprintf(pcToPrint,"Key2 button is pressed, start TIM2 update interrupt...\r\\
\r\\
");
xQueueSendToBack(xQueuePrint,pcToPrint,0);
//In the TIM2 interrupt, synchronize by sending task notification to task 2
}else if(keyValue == 3)
{
//Close TIM2 timer
TIM_Cmd(TIM2,DISABLE);
sprintf(pcToPrint,"Key3 button is pressed, close TIM2...\r\\
\r\\
");
xQueueSendToBack(xQueuePrint,pcToPrint,0);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}

2.2 TIM function

#include "stm32f10x.h" // Device header
#include "Timer.h"
#include "appTask.h"
#include "Serial.h"
extern volatile uint8_t uIRQCounter; //Count the number of interrupts

extern TaskHandle_t Led1TaskHandle;
BaseType_t xHighPriorityWaskWoken = pdFALSE; //Whether to perform task switching after exiting the interrupt


void Timer_Init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);//Turn on the internal clock
\t
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=100 -1;//ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=7200 -1;//PSC
//Timing for 1 second is also 1HZ 72M/PSC/ARR =1HZ
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2, & amp;TIM_TimeBaseInitStructure);
\t
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//Interrupt enable update interrupt
\t
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//Clear the update interrupt request bit
\t
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init( & amp;NVIC_InitStructure);
\t
\t
//TIM_Cmd(TIM2,ENABLE);//Enable timer
\t
}

void TIM2_IRQHandler(void)
{
\t
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_Update)==SET)
{
\t
uIRQCounter + + ; //Number of interrupts
//Synchronize by sending task notification to task 2
vTaskNotifyGiveFromISR(Led1TaskHandle, & amp;xHighPriorityWaskWoken);
\t\t
//If there is a higher priority task, perform task switching after exiting the interrupt
portYIELD_FROM_ISR(xHighPriorityWasWoken);
\t\t\t
\t
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
\t\t
}

}

2.3 Creating functions

/********************************************** ************************
Function name: appStartTask
Function description: Task start function, used to create other functions and start the scheduler
Formal parameters: pvParameters are the parameters passed when creating the task
Return value: None
*************************************************** ************************/
void appStartTask(void)
{
\t
/*Create a queue with a length of 2 and a queue item large enough to accommodate the characters to be output*/
xQueuePrint = xQueueCreate(2,sizeof(pcToPrint));
\t\t

\t
if(xQueuePrint)//If the queue is created successfully
{
\t\t\t\t
taskENTER_CRITICAL(); /*Enter critical section, turn off interrupts*/
\t
xTaskCreate(Led0Task,"Led0Task",128,NULL,3, & amp;Led0TaskHandle);
xTaskCreate(Led1Task,"Led1Task",128,NULL,3, & amp;Led1TaskHandle);
xTaskCreate(printTask,"printTask",128,NULL,3, & amp;printTaskHandle);
xTaskCreate(keyTask,"keyTask",128,NULL,4, & amp;keyTaskHandle);
taskEXIT_CRITICAL(); /*Exit the critical section and turn off interrupts*/
vTaskStartScheduler();/*Start the scheduler*/
}
\t\t
}

2.4 Download Test

The test results are exactly the same as the previous examples. In some places, task notifications can completely replace binary semaphores.