概述
不同于队列、信号量、互斥量,有一个额外的空间(结构体)作为通信的纽带,任务通知只需借助任务TCB中的相关变量,无需另外创建;
下面是TCB中有关任务通知的变量:
typedef struct tskTaskControlBlock
{
......
/* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
......
} tskTCB;
其中,ulNotifiedValue是通知值,ucNotifyState是通知状态。由此定义可知通知值、通知状态在同一时刻都只能存储一个数据/状态。(configTASK_NOTIFICATION_ARRAY_ENTRIES = 1)
通知状态具体包含以下几种:
##define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 )//也是初始状态
##define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
##define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )
可见,通知状态是站在接收者的角度,即当任务作为通知的接收者时的状态。其中taskNOTIFICATION_RECEIVED也被称作pending(待处理的)。
函数
任务通知的函数有简化版和专业版:
1. 简化版
发出通知
BseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
/*中断过程中使用*/
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BseType_t *pxHigherPriorityTaskWoken );
xTaskNotifyGive函数必定返回pdPASS;
xTaskToNotify、xTaskHandle:通知给哪个任务;
pxHigherPriorityTaskWoken:有更高优先级被唤醒时会被设置为pdTRUE,是提示需要进行上下文切换的标志位;
取出通知
uint32_t ulTaskNotifyTask( BaseType_t xClearCountOnExit, TickType_t xTicksToWait);
返回值为清零/减一前接收到的通知值;但如果超出阻塞时间(不为0),则返回0;
xClearCountOnExit:是否清零,如果pdTRUE,则清零;若 pdFALSE且通知值>0,则通知值减一;
xTicksToWait:阻塞时长;
2. 专业版
发出通知
BseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction);
BseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BseType_t *pxHigherPriorityTaskWoken );
(xTaskNotify的返回值包含pdPASS和pdFAIL)
xTaskToNotify:接收通知的任务的句柄
ulValue:一个参数,如何使用取决于eAction;
eAction:
- eNoAction:更新接收者任务的通知状态为pending,未使用ulValue
- eSetBits:接收通知者的通知值 = 原来的通知值 | ulValue(按位或)
- eIncrement:接收通知者的通知值 = 原来的通知值 + 1;
- eSetValueWithoutOverWrite:接收通知者的通知值 = ulVaule;由于是不覆盖,当接收者通知状态为“pending”(有待处理的数据),通知失败,函数返回pdFAIL;
- eSetValueWithOverWrite:接收通知者的通知值 = ulVaule,且能覆盖原有数据,不会失败;
前面的简化版发出通知就是基于xTaskNotify:
#define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )
取出通知
BseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
ulBitsToClearOnEntry:入口处清除,将原本的通知值某些位清零(&~ulBitsToClearOnEntry);
ulBitsToClearOnExit:出口处清除(不包括超时退出),将通知值某些位清零(&~ulBitsToClearOnExit);
pulNotificationValue:取出通知值(TCB中的通知值并不对外展示1,要另外传入一个变量接收)
xTicksToWait :阻塞时长;
之所以需要ulBitsToClearOnEntry对某些位做操作,是因为eAction中有对位的操作命令。假如需要像事件组那样根据某一位(事件)来做出决策,就需要将以前的遗留的位数据清零;
练习
创建一个发送通知任务和一个接收通知任务(接收的优先级设置得更高)。发送者会在计数值为奇数时候发送0x01,偶数时候发送0x02,并采用eSetBits。接收者接收0x01即将第一位置1,接收0x02即将第二位置1;
【全局变量】:
TaskHandle_t SetTask_Handle;
TaskHandle_t ReceiveTask_Handle;
【创建任务】:略
【发送通知任务函数】:
void Set_Task(void* argument){
uint16_t cnt = 0;
while(1){
printf("%d\r\n", cnt);
if(cnt%2 == 0){
//一开始错写成SetTask,才导致Receive_Task一直没被唤醒
xTaskNotify(ReceiveTask_Handle, 0x02, eSetBits); //让通知值的第二位置1
}
else
xTaskNotify(ReceiveTask_Handle, 0x01, eSetBits); //让通知值的第一位置1
cnt++;
}
}
【接收通知任务函数】:
void Receive_Task(void* argument){
uint32_t pulNotificationValue = 0x00;
while(1){
xTaskNotifyWait(0x03, 0x00, &pulNotificationValue, portMAX_DELAY);
printf("Waiting End\r\n");
if(pulNotificationValue == 0x01 || pulNotificationValue == 0x02){ //判断通知值第二位是否为1
printf("%d\r\n", pulNotificationValue);
}
else
printf("No\r\n");
printf("----------------\r\n");
}
}
【最终结果】:
(GPT生成)通知值在 FreeRTOS 中是被设计为私有的,即不会对外部公开。这是为了确保任务间通信的安全性和一致性,同时防止意外访问或篡改通知值导致系统出现问题。 ↩︎