FreeRTOS-任务通知
简介
在 FreeRTOS 中,每一个任务都有两个用于任务通知功能的数组,分别为任务通知数组和任务通知状态数组。其中任务通知数组中的每一个元素都是一个 32 位无符号类型的通知值;而任务通知状态数组中的元素则表示与之对应的任务通知的状态。
任务通知数组:用于任务到任务,中断到任务发送通知的媒介。为0表示没有任务通知;非0则表示有任务通知,且通知值就是该内容。
任务通知状态数组:用于标记任务通知数组中通知的状态。分别为未等待通知状态、等待通知状态和等待接收通知状态
任务通知的优缺点
优点
1.速度快:任务通知是直接地往任务中发送的通知。
2.占用的内存小:不同于队列、事件标志组和信号量在使用之前都需要被创建,任务通知功能中的每个通知只需要在每个任务中占用固定的 5 字节内存。
缺点
1.不能从任务给中断发生事件或数据。原因是中断不是任务缺少任务控制块中的两个成员变量。
2.每个任务只能接受一个通知。
3.只能用一个任务接收通知消息。因为在发送时已经指定了要通知的任务。
API函数
发送:
任务中:
xTaskNotify();
xTaskNotifyAndQuery() ;
xTaskNotifyGive();
中断中:
xTaskNotifyFromISR();
xTaskNotifyAndQueryFromISR();
vTaskNotifyGiveFromISR();
接收:
ulTaskNotifyTake();
xTaskNotifyWait();
发送
三个发送任务通知函数实际都是通过define将xTaskGenericNotify函数的某些传参设置为不同默认值。
xTaskGenericNotify()函数原型是:
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue
);
参数一:要通知的任务的任务句柄。
参数二:任务的指定通知(任务通知相关数组下标)。
参数三:与通知一起发送的值。这个值可以用来传递信息。
参数四:通知方式。
参数五:用于获取发送通知前的通知值。
返回值:任务通知发送成功返回pdPASS,失败返回pdFALL;
xTaskNotifyGive()
用于在任务中向指定任务发送任务通知,通知方式为将通知值加 1,并且发送任务通知的通知值。(用于二值信号量或计数信号量,实验二/三)
void xTaskNotifyGive( xEventGroupHandle xEvent );
参数一:要通知的任务的任务句柄。
一般与获取ulTaskNotifyTake()函数连用。
在中断中:
带有中断保护功能。
void xTaskNotifyGiveFromISR( xTaskToNotify, pxHigherPriorityTaskWoken );
参数一:要通知的任务的任务句柄。
参数二:退出这个函数时是否进行任务的切换。
xTaskNotify()
用于往指定任务发送任务通知,通知方式可以自由指定,并且不获取发送任务通知前任务通知的通知值。(事件组)
uint32_t xTaskNotify(xTaskHandle xTaskToNotify, uint32_t u32Value, eNotifyAction eAction)
参数一:要通知的任务句柄。
参数二:与通知一起发送的值。这个值可以用来传递信息。
参数三:通知方式。
通知方式 | Value |
---|---|
eNoAction | 对象任务接收任务通知,但任务自身的任务通知值不更新。(ulValue没有用,也就是说只能使接收任务退出阻塞,不能获取发送的信息) |
eSetValueWithOverwrite | 对象任务接收任务通知,且任务自身的任务通知值会无条件的被设置为ulValue。(可以看成函数xQueueOverwrite()一种轻量型的实现,速度跟快。代替信息队列,如实验一) |
eSetBits | 对象任务接收任务通知,同时自身的通知值与ulValue按位或。(如果ulValue设置为0x04,那么任务的通知值的位2被置为一,其他位保持不变;用作事件组) |
eIncrement | 对象任务接收任务通知,任务自身通知值加一。(ulValue没有用,同等于xTaskNotifyGive()) |
eSetValueWithoutOverwrite | 对象任务接收任务通知,如果对象任务没有通知值,那么通知值被设置为ulValue。有的话(上回通知值没有被取走),通知值保持不变,同时返回pdFALSE. |
xTaskNotifyAndQuery()
用于往指定任务发送任务通知,通知方式可以自由指定,并且获取发送任务通知前任务通知的通知值。
获取
用 于 获 取 任 务 通 知 的 API 函 数 有 两 个 , 分 别 为 函 数 ulTaskNotifyTake() 和函数xTaskNotifyWait()。只能在任务中使用,不能在中断里使用。
ulTaskNotifyTake()
用于获取任务通知的通知值,并且在成功获取任务通知的通知值后,可以指定将通知值清零或减 1。
(与xTaskNotifyGive()函数,做轻量型的二值信号量和计数信号量)
ulTaskNotifyTake( xBitsToClear, uxBitsToWait );
参数一:设置为pdFALSE时,函数退出前,任务的通知值减一,用来实现计数量。设置为pdTRUE时,函数退出前,将任务通知值清零,用来实现二值信号量。
参数二:超时时间。
返回值:返回任务的当前通知值,在其减一或清零之前。
ulTaskNotifyWait()
用于等待一个任务通知,并带有超时等待。
xTaskNotifyWait( ulBitsToClearOnEntry,
ulBitsToClearOnExit,
pulNotificationValue,
xTicksToWait
) ;
参数一:表示在使用通知之前,将任务通知值的哪些位给清零。
参数二:决定在函数退出前,通知值哪些位被清零。在清零前,通知值被保存在形参*pulNotificationValue中。(如设置为0x03,则位0,位1被清零,其他位保持不变)
参数三:用于保存接收到的任务通知值。如果不需要使用,设置位NULL。
参数四:等待超时时间。
返回值:获取成功则返回pdTRUE,失败则返回pdFALSE.
实验
确保任务通知开启,宏configUSE_TASK_NOTIFICATIONS为一。
//开启任务通知功能,默认开启
#define configUSE_TASK_NOTIFICATIONS 1
代替信息队列
实验现象:
按下key2,任务ADD_task退出阻塞,接收发来的信息89。
按下key3,任务ADD_task退出阻塞,接收发来的信息23。
按键任务
向接收任务发送23和89。
void KEY_task(void *pvParameters)
{
BaseType_t xreturn = pdPASS;
while(1)
{
key=KEY_Scan();
if(key == 2)
{
//发送任务通知 要发送任务句柄,发送信息23,覆盖当前值
xreturn=xTaskNotify(ADDTask_Handler,23,eSetValueWithOverwrite);
if(xreturn == pdPASS)
{
OLED_ShowString(2,5,"send success");
}
else
{
OLED_ShowString(2,5,"send false ");
}
}
else if(key == 3)
{
//发送任务通知 要发送任务句柄,发送信息89,覆盖当前值
xreturn=xTaskNotify(ADDTask_Handler,89,eSetValueWithOverwrite);
if(xreturn == pdPASS)
{
OLED_ShowString(2,5,"send success");
}
else
{
OLED_ShowString(2,5,"send false ");
}
}
vTaskDelay(20);
}
}
接收任务
接收显示按键任务传来的消息后进入到阻塞状态。
void ADD_task(void *pvParameters)
{
BaseType_t xreturn = pdTRUE;
uint32_t r_num;
while(1)
{
//获取任务通知,没获取到一直等待。进入函数前不清除任务bit,退出函数时清除所有bit,将任务通知值保存到r_num,阻塞
xreturn=xTaskNotifyWait(0x0,clear_all,&r_num,portMAX_DELAY);
OLED_ShowNum(3,5,num1++,2);
OLED_ShowNum(4,5,r_num,3);
}
}
代替二值信号量
实验现象:
按下key2,给任务ADDTask_Handler发送任务通知,其通知值加一。当任务ADDTask_Handler接受到之后退出阻塞状态,并且将通知值给清零。
发送任务:
xreturn=xTaskNotifyGive(ADDTask_Handler);
参数1:要通知的任务的任务句柄。
功能:发送任务通知 ,通知方式为将通知值加1,不发送任务通知的通知值。
void KEY_task(void *pvParameters)
{
BaseType_t xreturn = pdPASS;
while(1)
{
key=KEY_Scan();
if(key == 2)
{
//发送任务通知 要发送任务句柄 通知方式为将通知值加1,不发送任务通知的通知值。
xreturn=xTaskNotifyGive(ADDTask_Handler);
if(xreturn == pdTRUE)
{
OLED_ShowString(2,5,"send success");
}
else
{
OLED_ShowString(2,5,"send false ");
}
}
vTaskDelay(20);
}
}
接收任务
xreturn=ulTaskNotifyTake(pdTRUE , portMAX_DELAY);
参数一:pdTRUE:退出时通知值清零;pdFALSE;退出时通知值减一;
参数二:等待时间,一直等待。
功能:没有任务通知的时候处于阻塞状态,有通知时任务进入到就绪态,函数运行完将通知值给减一或清零。
void ADD_task(void *pvParameters)
{
BaseType_t xreturn = pdTRUE;
uint32_t r_num;
while(1)
{
//获取任务通知 , 没获取到一直等待。pdTRUE:退出时通知值清零;pdFALSE: 退出时通知值减一
xreturn=ulTaskNotifyTake(pdTRUE , portMAX_DELAY);
OLED_ShowNum(3,5,r_num++,2);
OLED_ShowNum(4,5,xreturn,3);
}
}
代替计数信号量
实验现象:
模拟停车场入库和出库。按键2按下表示有车出去,空闲停车位加一(发送一个通知,通知值加一)。按键3按下表示有人进入,空闲停车位减一(获取通知值,获取完通知值减一)。当有空余停车位的时候(通知值不为零),车辆成功进入。没有空余停车位的时候(通知值为零),车辆不能进入。
出库,发送通知值
xreturn=xTaskNotifyGive(ADDTask_Handler);
参数1:要通知的任务的任务句柄。
功能:发送任务通知 ,通知方式为将通知值加1,不发送任务通知的通知值。
//释放车位(出库)
void KEY_task(void *pvParameters)
{
BaseType_t xreturn = pdPASS;
u8 key=0;
while(1)
{
key=KEY_Scan();
if(key == 2)
{
//发送任务通知 要发送任务句柄 通知方式为将通知值加1,不发送任务通知的通知值。
xreturn=xTaskNotifyGive(KEY2Task_Handler);
if(xreturn == pdTRUE)
{
OLED_ShowString(3,3,"release ");
}
else
{
OLED_ShowString(3,3," false ");
}
}
vTaskDelay(20);
}
}
入库,接收通知值
take_num=ulTaskNotifyTake(pdFAIL,0);
参数一:pdTRUE:退出时通知值清零;pdFALSE;退出时通知值减一;
参数二:等待时间,不等待。
功能:有任务通知时,其通知值在结束之后减一。
//获取车位(入库)
void KEY2_task(void *pvParameters)
{
uint32_t take_num = pdTRUE;
u8 key=0;
while(1)
{
key=KEY_Scan();
if(key == 3)
{
//接收任务通知 不等待 退出时通知值减一
take_num=ulTaskNotifyTake(pdFAIL,0);
if(take_num > 0)
{
OLED_ShowString(3,3,"Storage ");
OLED_ShowNum(4,13,take_num-1,3);
}
else
{
OLED_ShowString(3,3,"Nowhere ");
}
}
vTaskDelay(20);
}
}
代替事件组
总结
任务通知通过模拟可以更轻量的实现消息队列,信号量,事件组等。
代替信息队列/事件组:
发送:
信息队列发送:
xTaskNotify(发送任务句柄,信息,eSetValueWithOverwrite);
eSetValueWithOverwrite:如果对象任务没有通知值,那么通知值被设置为ulValue。
有的话保持不变,同时返回pdFALSE;
事件组发送:
xTaskNotify(发送任务句柄,key1_event,eSetBits);
key1_event:设置哪位为1 #define key1_event (0x01<<0) //设置事件掩码的位0
eSetBits:自身的通知值与ulValue(就是key1_event这个值)按位或
接收
信息队列接收:
xTaskNotifyWait(0x0,clear_all,&接收信息的变量,portMAX_DELAY)
clear_all:为 #define clear_all 0xffffffff
事件组接收:
xTaskNotifyWait(0x0,clear_all,&event,portMAX_DELAY);
几位同时满足
last_event |= event;
if(last_event == (key1_event | key2_event))
{
last_event=0;
OLED_ShowString(3,3,"all down ");
}
else
last_event=event;
相当于
xEventGroupWaitBits(Event_Handle,key1_event|key2_event,pdTRUE,pdTRUE,portMAX_DELAY);
代替二值/计数信号量
发送
xTaskNotifyGive(ADDTask_Handler);
通知值加 1
接收
二值量接收:
ulTaskNotifyTake(pdTRUE , portMAX_DELAY);
pdTRUE:函数退出时,通知值清零。阻塞
计数量接收:
ulTaskNotifyTake(pdFAIL,0);
pdFAIL:函数退出时,通知值减一。不阻塞