任务通知详解
前言
1、从 v8.2.0 版本开始,FreeRTOS 新增了任务通知(Task Notifictions)这个功能,可以使用任务通知来代替代队列、二值信号量、计数型信号量和事件标志组。使用任务通知来实现二值信号量功能的时候,解除任务阻塞的时间比直接使用二值信号量要快 45%,并且使用的 RAM 更少
2、使用限制:
● FreeRTOS 的任务通知只能有一个接收任务,其实大多数的应用都是这种情况。
● 接收任务可以因为接收任务通知而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞。
API
发送任务通知API
| API | 功能 | 参数 |
|---|---|---|
| BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction ) | 带有通知值并且不保留接收任务原通知值 | xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。ulValue: 任务通知值。eAction: 任务通知更新的方法,eNotifyAction 是个枚举类型,此参数可以选择枚举类型中的任意一个,不同的应用环境其选择也不同。返回值:pdFAIL: 当参数 eAction设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有更新成功就返回 pdFAIL。pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。 |
| BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t * pxHigherPriorityTaskWoken ); | xTaskNotify的中断版本 | 使用:BaseType_t xHigherPriorityTaskWoken; portYIELD_FROM_ISR(xHigherPriorityTaskWoken); |
| BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); | 不带通知值并且不保留接收任务的原通知值,此函数会将接收任务的通知值加一。二值信号量和计数信号量用的都是这个 | pdPASS: 此函数只会返回 pdPASS。 |
| void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BaseType_t * pxHigherPriorityTaskWoken ); | xTaskNotifyGive()的中断版本 | 没有返回值 |
| BaseType_t xTaskNotifyAndQuery ( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t * pulPreviousNotificationValue); | 带有通知值并且保留接收任务的原通知值。就是多了一个参数用来保存发送的值 | xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。ulValue: 任务通知值。eAction: 任务通知更新的方法。pulPreviousNotificationValue:用来保存更新前的任务通知值。返回值:pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有更新成功就返回 pdFAIL。pdPASS: eAction设置为其他选项的时候统一返回 pdPASS。 |
| BaseType_t xTaskNotifyAndQueryFromISR ( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t * pulPreviousNotificationValueBaseType_t * pxHigherPriorityTaskWoken ); | xTaskNotifyAndQuery()的中断版本 | 返回值同上 |
typedef enum
{
eNoAction = 0,
eSetBits, //更新指定的 bit
eIncrement, //通知值加一
eSetValueWithOverwrite, //覆写的方式更新通知值
eSetValueWithoutOverwrite //不覆写通知值
} eNotifyAction;
通用任务发送函数
xTaskNotify()、xTaskNotifyGive()和 xTaskNotifyAndQuery()调用的都是这个函数
(1)如果需要将保存,原有的值
(2)发送会将pxTCB->ucNotifyState改为taskNOTIFICATION_RECEIVED
(3)改变值
(4)解除阻塞
//任务句柄,任务通知值,任务通知更新方式,保存任务更新前的任务通知值
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )
{
TCB_t * pxTCB;
BaseType_t xReturn = pdPASS;
uint8_t ucOriginalNotifyState;
configASSERT( xTaskToNotify );
pxTCB = ( TCB_t * ) xTaskToNotify;
taskENTER_CRITICAL();
{
//判断参数pulPreviousNotificationValue 是否有效,此参数用来保存更新前的任务通知值
if( pulPreviousNotificationValue != NULL )
{
//保存更新前的任务通知值
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
}
//保存任务更新状态,因为下面会修改这个状态,根据这个状态来确定是否将任务从阻塞态解除
ucOriginalNotifyState = pxTCB->ucNotifyState;
//更新任务通知状态为 taskNOTIFICATION_RECEIVED
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;
switch( eAction )
{
case eSetBits :
//将指定的bit置1。也就是更新接收任务通知值的一个或多个bit
pxTCB->ulNotifiedValue |= ulValue;
break;
//将任务通知值加一
case eIncrement :
( pxTCB->ulNotifiedValue )++;
break;
//直接覆写原来的任务通知值
case eSetValueWithOverwrite :
pxTCB->ulNotifiedValue = ulValue;
break;
//判断原来的任务通知值是否被处理,没被处理就是为taskNOTIFICATION_RECEIVED
case eSetValueWithoutOverwrite :
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
//如果已经被处理了就更新为任务通知值
pxTCB->ulNotifiedValue = ulValue;
}
else
{
//如果此前的任务通知值没有被处理的话就标记 xReturn 为 pdFAIL,后面会返回这个值
/* The value could not be written to the task. */
xReturn = pdFAIL;
}
break;
case eNoAction:
/* The task is being notified without its notify value being
updated. */
break;
}
traceTASK_NOTIFY();
/* 根据ucOriginalNotifyState 中保存的接收任务之前的状态值来判断是否有任务需要解除阻塞*/
//如果在任务通知值被更新前任务处于 taskWAITING_NOTIFICATION 状态的话就说明有任务
//因为等待任务通知值而进入了阻塞态。
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
//将任务从状态列表中移除
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
//将任务重新添加到就绪列表中
prvAddTaskToReadyList( pxTCB );
//将任务重新添加到就绪列表中
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
/* 判断刚刚解除阻塞的任务优先级是否比当前正在运行的任务优先级高,
如果是的话需要进行一次任务切换 */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return xReturn;
}
获取任务通知API
| API | 功能 | 参数 |
|---|---|---|
| uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); | 获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当任务通知用作二值信号量或者计数信号量的时候推荐使用此函数来获取信号量 | xClearCountOnExit: 参数为 pdFALSE 的话在退出函数 ulTaskNotifyTake()的时候任务通知值减一,类似计数型信号量。当此参数为 pdTRUE 的话在退出函数的时候任务任务通知值清零,类似二值信号量。xTickToWait: 阻塞时间,portMAX_DELAY。返回值:任务通知值减少或者清零之前的值 |
| BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit, uint32_t * pulNotificationValue, TickType_t xTicksToWait ); | 比 ulTaskNotifyTak()更为强大,全功能版任务通知,什么情况都能用。获取函数队列和事件标志组时使用 | ulBitsToClearOnEntry:当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX 的时候就会将任务通知值清零。ulBitsToClearOnExit:如果接收到了任务通知,在做完相应的处理(将获得的任务值保存到pulNotificationValue中)退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者ULONG_MAX 的时候就会将任务通知值清零。pulNotificationValue:此参数用来保存任务通知值。xTickToWait: 阻塞时间。返回值:pdTRUE: 获取到了任务通知。pdFALSE: 任务通知获取失败。 |
ulTaskNotifyTake讲解
1、如果通知值为0:
(1)pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION(标志任务阻塞这个状态)(2)将任务添加到延时列表中
2、如果通知值不为0
(1)先获取任务通知值ulReturn = pxCurrentTCB->ulNotifiedValue
(2)根据xClearCountOnExit 的值,对pxCurrentTCB->ulNotifiedValue 进行清零/减1
(3)更新任务通知状态为 taskNOT_WAITING_NOTIFICATION(是not)
(4)返回任务通知值ulReturn
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
{
uint32_t ulReturn;
taskENTER_CRITICAL();
{
/*判断任务通知值是否为 0,如果为 0 的话说明还没有接收到任务通知 */
if( pxCurrentTCB->ulNotifiedValue == 0UL )
{
/*修改任务通知状态为 taskWAITING_NOTIFICATION(可以理解为阻塞标志),在发送任务最后一步中会将其解除*/
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;
//如果阻塞时间不为 0 的话就将任务添加到延时列表中,并且进行一次任务调度
if( xTicksToWait > ( TickType_t ) 0 )
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_TAKE_BLOCK();
/* 任务调度*/
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
taskENTER_CRITICAL();
{
traceTASK_NOTIFY_TAKE();
//如果任务通知值不为 0 的话就先获取任务通知值
ulReturn = pxCurrentTCB->ulNotifiedValue;
if( ulReturn != 0UL )//任务通知值大于 0
{
//参数 xClearCountOnExit 不为 pdFALSE,那就将任务通知值清零
if( xClearCountOnExit != pdFALSE )
{
pxCurrentTCB->ulNotifiedValue = 0UL;
}
else
{
//如果参数 xClearCountOnExit 为 pdFALSE 的话那就将任务通知值减一
pxCurrentTCB->ulNotifiedValue = ulReturn - 1;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//更新任务通知状态为 taskNOT_WAITING_NOTIFICATION
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
taskEXIT_CRITICAL();
return ulReturn;
}
xTaskNotifyWait函数讲解
1、没有接收到数据(pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED )
(1)将任务通知值与参数 ulBitsToClearOnEntry 的取反值进行按位与运算
(2)任务通知状态改为 taskWAITING_NOTIFICATION
(3)如果阻塞时间大于 0 的话就要将任务添加到延时列表中,并且进行一次任务切换
2、接收到数据
(1)将任务通知值保存到pulNotificationValue 中
(2)将任务通知的值与参数 ulBitsToClearOnExit 的取反值进行按位与运算,并且标记 xReturn 为
pdTRUE,表示获取任务通知成功
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
{
BaseType_t xReturn;
taskENTER_CRITICAL();
{
/* 有给任务发送了数据的话,pxCurrentTCB->ucNotifyState就会变为taskNOTIFICATION_RECEIVED */
if( pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED )
{
/*将任务通知值与参数 ulBitsToClearOnEntry 的取反值进行按位与运算 */
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry;
//任务通知状态改为 taskWAITING_NOTIFICATION
/* Mark this task as waiting for a notification. */
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;
//如果阻塞时间大于 0 的话就要将任务添加到延时列表中,并且进行一次任务切换
if( xTicksToWait > ( TickType_t ) 0 )
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_WAIT_BLOCK();
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
taskENTER_CRITICAL();
{
traceTASK_NOTIFY_WAIT();
//如果任务通知状态为 taskNOTIFICATION_RECEIVED,并且参数 pulNotificationValue
//有效的话就保存任务通知值
if( pulNotificationValue != NULL )
{
/* Output the current notification value, which may or may not
have changed. */
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue;
}
/* 如果任务通知的状态又变为 taskWAITING_NOTIFICATION 的话就标记 xRetur 为
pdFALSE */
if( pxCurrentTCB->ucNotifyState == taskWAITING_NOTIFICATION )
{
/* A notification was not received. */
xReturn = pdFALSE;
}
else
{
/* 如果任务通知的状态一直为 taskNOTIFICATION_RECEIVED 的话就将任务通知的值
与参数 ulBitsToClearOnExit 的取反值进行按位与运算,并且标记 xReturn 为 pdTRUE,表示获
取任务通知成功 */
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit;
xReturn = pdTRUE;
}
//标记任务通知的状态为 taskNOT_WAITING_NOTIFICATION
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
taskEXIT_CRITICAL();
return xReturn;
}
模拟二值信号量实验
将中断发送的语句通过任务打印出来
任务中
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
char len;
for(;;)
{
/*
此函数为获取任务通知函数,当任务通知用作二值信号量或者计数型信号量的时候可以使用此函数来获取信号量
参数:
xClearCountOnExit: 参数为 pdFALSE 的话在退出函数 ulTaskNotifyTake()的时候任务通知值
减一,类似计数型信号量。当此参数为 pdTRUE 的话在退出函数的时候
任务任务通知值清零,类似二值信号量。
xTickToWait: 阻塞时间。
返回值:任何值任务通知值减少或者清零之前的值,这里没啥用。
*/
ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
printf("\n成功接收到值:%s",rx_buffer);
memset(rx_buffer,0, 100);
osDelay(10);
}
/* USER CODE END StartDefaultTask */
}
it中
#include "usart.h"
#include <stdio.h>
#include <string.h>
#include "FreeRTOS.h"
#include "cmsis_os.h"
extern uint8_t rx_buffer[100];
extern osThreadId defaultTaskHandle;
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!= RESET))//idle标志被置位
{
BaseType_t xHigherPriorityTaskWoken;
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE);//清除标志位,先读SR,再读DR也可以清除中断标志位
//temp = huart1.Instance->SR; //清除状态寄存器SR,读取SR寄存器可以实现清除SR寄存器的功能
//temp = huart1.Instance->DR; //读取数据寄存器中的数据
//这两句和上面那句等效
HAL_UART_DMAStop(&huart1);
/*
发送任务通知
xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。
pxHigherPriorityTaskWoken: 记退出此函数以后是否进行任务切换,这个变量的值函数会自动
设置的,用户不用进行设置,用户只需要提供一个变量来保存这
个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之
前一定要进行一次任务切换。
*/
vTaskNotifyGiveFromISR(defaultTaskHandle,&xHigherPriorityTaskWoken);
HAL_UART_Receive_DMA(&huart1,rx_buffer,100);//重新开启接收
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
模拟计数型信号量
任务1:不同按键给不同个数的信号量
任务2:不断进入任务,不断消耗信号量,并打印出剩余还剩多少信号量
void CountGive_Task(void const * argument)
{
/* USER CODE BEGIN CountGive_Task */
/* Infinite loop */
for(;;)
{
uint8_t key=0;
key=KEY_Scan(0);
if(key != 0)
{
switch(key)
{
case 1:
/*
发送任务通知,相对于函数 xTaskNotify(),此函数发送任务通知的时候不带有通知值。此函数只是将任务通知值简单的加一
参数:
xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。
返回值:
pdPASS: 此函数只会返回 pdPASS。
*/
xTaskNotifyGive(CountTakeTaskHandle);//发送任务通知
break;
case 2:
xTaskNotifyGive(CountTakeTaskHandle);
xTaskNotifyGive(CountTakeTaskHandle);
break;
case 3:
xTaskNotifyGive(CountTakeTaskHandle);
xTaskNotifyGive(CountTakeTaskHandle);
xTaskNotifyGive(CountTakeTaskHandle);
break;
}
}
osDelay(10);
}
/* USER CODE END CountGive_Task */
}
void CountTake_Task(void const * argument)
{
/* USER CODE BEGIN CountTake_Task */
/* Infinite loop */
for(;;)
{
uint32_t len;
/*
此函数为获取任务通知函数,当任务通知用作二值信号量或者计数型信号量的时候可以使用此函数来获取信号量
参数:
xClearCountOnExit: 参数为 pdFALSE 的话在退出函数 ulTaskNotifyTake()的时候任务通知值
减一,类似计数型信号量。当此参数为 pdTRUE 的话在退出函数的时候
任务任务通知值清零,类似二值信号量。
xTickToWait: 阻塞时间。
返回值:任何值任务通知值减少或者清零之前的值,这里没啥用。
*/
len=ulTaskNotifyTake( pdFALSE ,portMAX_DELAY );
printf("剩余数目为:%d\n",len);
osDelay(1000);
}
/* USER CODE END CountTake_Task */
}

模拟消息邮箱实验
任务通知也可用来向任务发送数据,但是相对于用队列发送消息,任务通知向任务发送消
息会受到很多限制!
1、只能发送 32 位的数据值。
2、消息被保存为任务的任务通知值,而且一次只能保存一个任务通知值,相当于队列长度
为 1。
因此说任务通知可以模拟一个轻量级的消息邮箱而不是轻量级的消息队列。任务通知值就
是消息邮箱的值。
void Key_Scan(void const * argument)
{
/* USER CODE BEGIN Key_Scan */
/* Infinite loop */
for(;;)
{
char key;
uint32_t PreValue;
key=KEY_Scan(0);
if(key != 0)
{
switch(key)
{
case 1 :
xTaskNotify( KeyProcessHandle, key, eSetValueWithoutOverwrite);
break;
case 2:
//不覆写,就是保证了上一个发送的值被处理了,才能发送一个新的值。这里都是马上被处理掉的,所以有没有覆写都是无所谓的。
//看的是状态,而不是值是不是为0,值还没被处理的话就是taskNOTIFICATION_RECEIVED(保持数据发送时的状态,还有一个等待数据wait,数据处理好了这两个状态notwait)
//发送会将pxTCB->ucNotifyState改为taskNOTIFICATION_RECEIVED
xTaskNotify( KeyProcessHandle, key, eSetValueWithOverwrite);
break;
case 3:
xTaskNotifyAndQuery ( KeyProcessHandle,key,eSetValueWithoutOverwrite, &PreValue);
break;
}
}
osDelay(10);
}
/* USER CODE END Key_Scan */
}
void Key_Process(void const * argument)
{
/* USER CODE BEGIN Key_Process */
/* Infinite loop */
uint32_t temp;
for(;;)
{
//前面一个用在没有新的值进来时,用来清除现在的值
//后面一个,再将新的值读取到以后,0xffffffff将值给清除掉
xTaskNotifyWait( 0xffffffff,0xffffffff ,&temp,portMAX_DELAY);//如果有值过来的话,他先是将值保存在temp中,然后再给发过来的值清零,所以并不会影响到temp的值。
printf("接收到的值为:%d\n",temp);
osDelay(10);
}
/* USER CODE END Key_Process */
}

模拟事件标志组实验
我觉得不够完美还是直接创建事件标志组好用。
原理:只要有任何一个事件位置 1,函数 xTaskNotifyWait()就会因为获取任务通知成功而在退出函数的时候将这个事件位清零(函数 xTaskNotifyWait()的参数设置的)。所以这里我们要对每个发生的事件做个标志,比如本试验中用到了三个事件标志位:EVENTBIT_0、EVENTBIT_1、EVENTBIT_2。这三个事件标志位代表三个事件,每个事件都有一个变量来记录此事件是否已经发生,这三个变量在任务函数 eventgroup_task()中有定义,它们分别为:event0flag、event1flag 和 event2flag。当这三个变量都为 1 的时候就说明三个事件都发生了
更多推荐



所有评论(0)