FreeRTOS任务通知
一、什么是任务通知
FreeRTOS从版本V8.2.0开始提供通知这个功能,每个任务都有一个32位的通知值。按照官方说法,使用消息通知比通过二进制信号量方式解除阻塞任务快45%,且更加省内存(无需创建队列)。
(也就是说,一个任务被创建的时候,就会有一个任务通知)
在大多数情况下,任务通知可以替代二值信号量,计数信号量,事件标志组,可以替代长度位1的队列(可以保存一个32位整数或指针数),并且任务通知速度更快,使用的RAM更少!
任务通知值的更新方式
FreeRTOS提供以下几种方式发送通知給任务:
- 发送消息给任务,如果有通知未读,不覆盖通知值
- 发送消息给任务,直接覆盖通知值
- 发送消息给任务,设置通知值的一个或者多个位
- 发送消息给任务,递增通知值
通过以上方式合理使用,可以在一定场合下替代原本的队列,信号量,信号标志组等。
任务通知的优势
- 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多
- 使用其它方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体
任务通知的劣势
- 只有任务可以等待通知,中断服务函数中不可以,一位中断没有TCB。(任务创建时内存开辟的空间)
- 通知只能一对一,因为通知必须指定任务。
- 等待通知的任务可以被阻塞,但是发送消息的任务,任何情况下都不会被阻塞等待。
- 任务通知时通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据。
二、相关API函数
1.发送通知
函数 | 描述 |
xTaskNotify() | 发送通知,带有通知值 |
xTaskNotifyAndQuery() | 发送通知,带有通知值并且保留接受任务的原通知值 |
xTaskNotifyGive() | 发送通知,不带有通知值 |
xTaskNotifyFromISR() | 在中断中发送任务通知 |
xTaskNotifyAndQueryFromISR() | 在中断中发送任务通知 |
xTaskNotifyGiveFromISR() | 在中断中发送任务通知 |
BaseType_t xTaskNotify (TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction );
参数:
xTaskToNotify:需要接收通知的任务句柄;
ulValue:用于更新接收任务通知值,具体如何更新由形参eAction决定;
eAction:一个枚举,代表如何使用任务通知的值;
枚举值 | 描述 |
eNoAction | 发送通知,但不更新值(参数ulValue未使用) |
eSetBits | 被通知任务的通知值按位或ulValue。(某些场景下可代替事件组,效率更高) |
elncrement | 被通知任务的通知值加1(参数ulValue未使用),相当于xTaskNotifyGive (信号量) |
eSetValueWithOverwrite | 被通知任务的通知值设置为ulValue。(某些场景下可代替xQueueOverwrite,效率更高)(队列覆写) |
eSetValueWithoutOverwrite | 如果被通知的任务当前没有通知,则被通知的任务的通知值设为ulValue。 如果被通知任务没有取走上一个通知,由接收到一个通知,则这次通知值丢弃,在这种情况下视为调用失败并返回pdFALSE (某些场景下可带起xQueueSend,效率更高) (没有被取走,就会阻塞等待) |
返回值:
前面四个的返回值,都是pdPASS,最后一个,当通知没有取走,就会返回pdFALSE,否则返回pdPASS。
BaseType_t xTaskNotifyAndQuery (TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction,uint32_t *pulPreviousNotifyValue);
参数:
xTaskToNotify:需要接收通知的任务句柄;
ulValue:用于更新接收任务通知值,具体如何更新由形参eAction决定;
eAction:一个枚举,代表如何使用任务通知的值;
pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为NULL,则不需要回传,这个时候就像前面一个函数xTaskNotify()。
返回值:
当通知没有取走,就会返回pdFALSE,否则返回pdPASS。
BaseType_t xTaskNotifyGive (TaskHandle_t xTaskToNotify);
参数:
xTaskToNotify:需要接收通知的任务句柄,且让其自身的任务通知值加1;
返回值:
总是返回pdPASS。
2.等待通知
等待通知API函数只能用在任务,不可应用于中断中!
函数 | 描述 |
xTaskNotifyTake() | 获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。 |
xTaskNotifyWait() | 获取任务通知,比xTaskNotifyTake()更为复杂,可获取通知值和清除通知值的指定位 |
uint32_t xTaskNotifyTake (BaseType_t xClearCountOnExit,TickType_t xTicksToWait);
参数:
xClearCountOnExit:指定在成功接收通知后,将通知值清零或减一,pdTRUE:把通知值清零(相当于二值信号量);pdFALSE:把通知值减一(计数型信号量);
xTicksToWait:阻塞等待任务通知值的最大时间;
返回值:
0:接收失败
非0:接收成功,返回任务通知的通知值
uint32_t xTaskNotifyWait (uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t *pulPreviousNotifyValue,TickType_t xTicksToWait);
ulBitsToClearOnEntry:函数执行前清零任务通知值那些位。(置0)
ulBitsToClearOnExit:表示在函数退出前,清零任务通知值那些位,(置1)在清零前,接收到的任务通知值会先被保存到形参*pulPreviousNotifyValue中。(0xffff ffff) (一个 ’f‘ 4位1,8个 ’f‘ 刚好32位)
*pulPreviousNotifyValue:用于保存接收到的任务通知值,如果不需要使用,则设置为NULL即可
xTicksToWait:等待消息通知的最大等待时间
接下来实操一下
先创建两个任务名:TaskSend 和 TaskReceive
一、模拟二值信号量
//发送二值信号量xTaskNotifyGive(TaskReceiveHandle) //返回值都是pdTRUE
printf("任务通知:模拟二值信号量发送成功\r\n");//接收二值信号量uint32_t rev= 0;
rev = xTaskNotifyTake(pdTRUE,portMAX_DELAY)
if(rev != 0)
{printf("任务通知:模拟二值信号量接收成功\r\n");
}
二、模拟计数信号量
//发送计数信号量xTaskNotifyGive(TaskReceiveHandle) //返回值都是pdTRUE
printf("任务通知:模拟计数信号量发送成功\r\n");//接收二值信号量uint32_t rev= 0;
rev = xTaskNotifyTake(pdFALSE,portMAX_DELAY) //只有这里参数不一样,其它都是跟二值一样的
if(rev != 0)
{printf("任务通知:模拟二值信号量接收成功\r\n");
}
三、模拟事件标志组
//发送事件标志组printf("将bit0位置1\r\n");
xTaskNotify(TaskReceiveHandle,0x01,eSetBits);printf("将bit1位置1\r\n");
xTaskNotify(TaskReceiveHandle,0x02,eSetBits);//接收事件标志组uint32_t notify_val = 0,event_bit = 0;
xTaskNotifyWait(0,0xFFFFFFFF,¬ify_val,portMAX_DELAY);
if(notify_val & 0x01) //判断第0位是否为1event_bit | 0x01; //将第0位置为1
if(notify_val & 0x02) //判断第1位是否为1event_bit | 0x02; //将第1位置为1
if(event_bit == (0x01 | 0x02))
{printf("任务通知模拟事件标志组接收成功!\r\n");event_bit = 0;
}
四、模拟邮箱(就是覆写队列)
//发送队列的值//任务1中
printf("按键1按下");
xTaskNotify(TaskReceiveHandle,1,eSetValueWithOverwrite); //发送1//任务2中
printf("按键2按下");
xTaskNotify(TaskReceiveHandle,2,eSetValueWithOverwrite); //发送2//接收事件标志组uint32_t notify_val = 0,event_bit = 0;
xTaskNotifyWait(0,0xFFFFFFFF,¬ify_val,portMAX_DELAY);
printf("接收到的通知值为:%d\r\n",notify_val);//notify_val存储发送的值,发送什么就打印什么