目录
一、什么是任务通知
FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能,每个任务都有一个 32 位的通知值。按照 FreeRTOS官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加省内存(无需创建队列)。
在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值),并且任务通知速度更快、使用的RAM更少!
二、任务通知值的更新方式
FreeRTOS
提供以下几种方式发送通知给任务 :
1.发送消息给任务,如果有通知未读, 不覆盖通知值
2.发送消息给任务,直接覆盖通知值
3.发送消息给任务,设置通知值的一个或者多个位
4.发送消息给任务,递增通知值
通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等
三、任务通知的优势和劣势
3-1 任务通知的优势
1. 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。
2. 使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
3-2 任务通知的劣势
1. 只有任务可以等待通知,中断服务函数中不可以,因为中断没有 TCB 。
2. 通知只能一对一,因为通知必须指定任务。
3. 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。
4. 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据。
四、任务通知相关 API 函数
4-1 发送通知
/*
* -------------函数---------------------------------------描述-------------------
*| xTaskNotify() | 发送通知,带有通知值
*|------------------------------|----------------------------------------------
*| xTaskNotifyAndQuery() | 发送通知,带有通知值并且保留接收任务的原通知值
*|------------------------------|----------------------------------------------
*| xTaskNotifyGive() | 发送通知,不带通知值
*|------------------------------|----------------------------------------------
*| xTaskNotifyFromISR() | 在中断中发送任务通知
*|------------------------------|----------------------------------------------
*| xTaskNotifyAndQueryFromISR() | 在中断中发送任务通知
*|------------------------------|----------------------------------------------
*| vTaskNotifyGiveFromISR() | 在中断中发送任务通知
*|------------------------------|----------------------------------------------
*
* */
/*
* 参数:
* xTaskToNotify:需要接收通知的任务句柄;
* ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
* eAction:一个枚举,代表如何使用任务通知的值;
*
*
* -------------枚举值-------------------------------描述------------------------
* | eNoAction | 发送通知,但不更新值(参数ulValue未使用)
* |------------------------------|----------------------------------------------
* | eSetBits | 被通知任务的通知值按位或ulValue。(某些场景下可代替事件组,效率更高)
* |------------------------------|----------------------------------------------
* | eIncrement | 被通知任务的通知值增加1(参数ulValue未使用),相当于xTaskNotifyGive
* |------------------------------|----------------------------------------------
* | eSetValueWithOverWrite | 被通知任务的通知值设置为 ulValue。(某些场景下可代替xQueueOverwrite ,效率更高)
* |------------------------------|----------------------------------------------
* | eSetValueWithoutOverwrite | 如果被通知的任务当前没有通知,则被通知的任务的通知值设为ulValue。如果被通知任务没有取走上一个通知,又接收到了一个通知,则这次通知值丢弃,在这种情况下视为调用失败并返回 pdFALSE(某些场景下可代替 xQueueSend ,效率更高)
* |------------------------------|----------------------------------------------
*
* 返回值:
* 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他情况均返回pdPASS。
*
*
*
* BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction );
*
* */
/*
* 参数:
* xTaskToNotify:需要接收通知的任务句柄;
* ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
* eAction:一个枚举,代表如何使用任务通知的值;
* pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价于函数 xTaskNotify()。
*
* 返回值:
* 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他
* 情况均返回pdPASS。
*
*
* BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction,uint32_t *pulPreviousNotifyValue );
*
* */
/*
* 参数:
* xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1。
* 返回值:
* 总是返回 pdPASS。
*
* BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
*
* */
4-2 等待通知
等待通知API函数只能用在任务,不可应用于中断中!
/*
* -------------函数-------------------------------描述------------------------
* | ulTaskNotifyTake() | 获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。
* |------------------------------|----------------------------------------------
* | xTaskNotifyWait() | 获取任务通知,比 ulTaskNotifyTak()更为复杂,可获取通知值和清除通知值的指定位
* |------------------------------|----------------------------------------------
*
* */
/*
* 参数:
* xClearCountOnExit:指定在成功接收通知后,将通知值清零或减 1,pdTRUE:把通知值清零(二值信号量);pdFALSE:把通知值减一(计数型信号量);
* xTicksToWait:阻塞等待任务通知值的最大时间;
*
* 返回值:
* 0:接收失败
* 非0:接收成功,返回任务通知的通知值
*
*
* uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,TickType_t xTicksToWait );
*
*
* */
/*
* BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t *pulNotificationValue,TickType_t xTicksToWait );
*
*
* ulBitsToClearOnEntry:函数执行前清零任务通知值那些位 。
* ulBitsToClearOnExit:表示在函数退出前,清零任务通知值那些位,在清 0 前,接收到的任务通知值会先被
* 保存到形参*pulNotificationValue 中。
* pulNotificationValue:用于保存接收到的任务通知值。 如果 不需要使用,则设置为 NULL 即可 。
* xTicksToWait:等待消息通知的最大等待时间。
*
*
* */
五、测试程序
#include "wu_task.h"
/*
* FreeRTOS中任务共存在4种状态:
(1)Running 运行态
--------------------------当任务处于实际运行状态称之为运行态,即CPU的使用权被这个任务占用(同一时间仅一个任务处于运行态)。
(2)Ready 就绪态
--------------------------处于就绪态的任务是指那些能够运行(没有被阻塞和挂起),但是当前没有运行的任务,因为同优先级或更高优先级的任务正在运行。
(3)Blocked 阻塞态
--------------------------如果一个任务因延时,或等待信号量、消息队列、事件标志组等而处于的状态被称之为阻塞态。
(4)Suspended 挂起态
--------------------------类似暂停,通过调用函数 vTaskSuspend() 对指定任务进行挂起,挂起后这个任务将不被执行,只有调用函数 xTaskResume() 才可以将这个任务从挂起态恢复。
*
* 常用的一些函数用法:
* osThreadSuspend(WUTask02Handle);//挂起态 暂停任务
* osThreadResume(WUTask02Handle);//就绪态 恢复任务
* osThreadTerminate(defaultTaskHandle);//删除任务 终止一个线程的执行并将其从活动线程中移除
*
*
* */
extern osMessageQId WUQueue01Handle;//声明一个外部的信号量队列(消息队列)
extern osSemaphoreId WUBinarySem01Handle;//声明一个外部的二进制信号量句柄
extern osSemaphoreId WUCountingSem01Handle;//声明一个外部的计数信号量句柄
extern osMutexId WUMutex01Handle;//声明一个外部的互斥锁(互斥量)
EventGroupHandle_t eventgroup_handle;// 声明事件组句柄
//WUBinarySem01Handle = xSemaphoreCreateBinary();
u8 wu_cs=5;
u8 d1,d2;
uint16_t buf = 100;
// 声明一个事件位变量,初始值为0
EventBits_t wu_bit = 0;
void WU_Task_1(viod)
{
BaseType_t status;
if(click()==1)
{
wu_cs++;buf=123;
// status = xQueueSend(WUQueue01Handle, &buf, 0);//写入队列
// if (status == pdTRUE)
// d1=1;
// else
// d1=0;
// if (xSemaphoreGive(WUBinarySem01Handle) == pdTRUE)
// d1=1;
// else
// d1=0;
// if (xSemaphoreGive(WUCountingSem01Handle) == pdTRUE)
// d1=1;
// else
// d1=0;
// xEventGroupSetBits(eventgroup_handle, 0x01);// KEY1 按下
xTaskNotifyGive(WUBinarySem01Handle);//任务通知:模拟二值信号量发送成功
Seria1_Printf("TXD:任务通知:模拟二值信号量发送成功");
}
// xSemaphoreTake(WUMutex01Handle, portMAX_DELAY);
// Seria1_Printf("TaskH: ON! \r\n");
// HAL_Delay(1000);
// //osDelay(1000);
// Seria1_Printf("TaskH: OK! \r\n");
// xSemaphoreGive(WUMutex01Handle);
// osDelay(1000);
}
void WU_Task_2(viod)
{
LED_GREEN_TogglePin;
osDelay(1000);
// Seria1_Printf("TaskM: CPU-STOP!!! \r\n");
// osDelay(1000);
// wu_bit = xEventGroupWaitBits(eventgroup_handle, 0x01 | 0x02, pdTRUE, pdFALSE, portMAX_DELAY);
// Seria1_Printf("wu_bit:%#x\r\n", wu_bit);
}
void WU_Task_3(viod)
{
BaseType_t status;
uint32_t ydd = 0;
if(click_2()==1)
{
wu_cs--;
// status = xQueueReceive(WUQueue01Handle, &buf, 0);//读取队列
// if (status == pdTRUE)
// d2=1;
// else
// d2=0;
// if(xSemaphoreTake(WUBinarySem01Handle, portMAX_DELAY ) == pdTRUE)
// d2=1;
// else
// d2=0;
// if (xSemaphoreTake(WUCountingSem01Handle, 0 ) == pdTRUE)
// d2=1;
// else
// d2=0;
// xEventGroupSetBits(eventgroup_handle, 0x02);// KEY2 按下
ydd = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if(ydd != 0)
Seria1_Printf("RXD:任务通知:模拟二值信号量接收成功!\r\n");
}
// xSemaphoreTake(WUMutex01Handle, portMAX_DELAY);
// Seria1_Printf("TaskL: ON! \r\n");
// HAL_Delay(3000);
// //osDelay(3000);
// Seria1_Printf("TaskL: OK! \r\n");
// xSemaphoreGive(WUMutex01Handle);
// osDelay(1000);
}
void WU_Task_4(viod)
{
//Seria1_Printf("WU");
//Wu_xsp_Proc();
//Wu_Mai_Proc();
Wu_Led_Proc();
Wu_Key_Proc();
WU_OLED_U8G2_ClearBuffer();
WU_OLED_U8G2_SetFont(u8g2_font_wqy13_t_gb2312a);//选择字库
// WU_OLED_U8G2_Printf(0,15,CHINA,"温度:%d℃",16);
// WU_OLED_U8G2_Printf(0,30,CHINA,"湿度:%d",58);
//
// d1?WU_OLED_U8G2_Printf(0,45,CHINA,"写入队列值:%d",buf):WU_OLED_U8G2_Printf(0,45,CHINA,"写入队列失败");
// d2?WU_OLED_U8G2_Printf(0,60,CHINA,"读取队列值:%d",buf):WU_OLED_U8G2_Printf(0,60,CHINA,"读取队列失败");
WU_OLED_U8G2_SendBuffer();
}
void StartTask02(void const * argument)
{
while(1)
{
WU_Task_2();
}
}