所谓"任务通知",你可以反过来读"通知任务"。
我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可以明确指定:通知哪个任务。
使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构体通信:
使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的"通知":
本章涉及如下内容:
任务通知的优势:
任务通知的限制:
xQueueSendToBack()
给队列发送数据时,任务可以进入阻塞状态等待发送完成。
使用任务通知时,即使对方无法接收数据,发送方也无法阻塞等待,只能即刻返回错误。每个任务都有一个结构体:TCB(Task Control Block),里面有2个成员:
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;
通知状态有3种取值:
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) /* 也是初始状态 */
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )
通知值可以有很多种类型:
使用任务通知,可以实现轻量级的队列(长度为1)、邮箱(覆盖的队列)、计数型信号量、二进制信号量、事件组。
任务通知有2套函数,简化版、专业版,列表如下:
简化版 | 专业版 | |
---|---|---|
发出通知 | xTaskNotifyGivevTaskNotifyGiveFromISR | xTaskNotifyxTaskNotifyFromISR |
取出通知 | ulTaskNotifyTake | xTaskNotifyWait |
在任务中使用xTaskNotifyGive函数,在ISR中使用vTaskNotifyGiveFromISR函数,都是直接给其他任务发送通知:
taskNOTIFICATION_RECEIVED
,表示有数据了、待处理可以使用ulTaskNotifyTake函数来取出通知值:
使用ulTaskNotifyTake函数可以实现轻量级的、高效的二进制信号量、计数型信号量。
这几个函数的原型如下:
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BaseType_t *pxHigherPriorityTaskWoken );
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
xTaskNotifyGive函数的参数说明如下:
参数 | 说明 |
---|---|
xTaskToNotify | 任务句柄(创建任务时得到),给哪个任务发通知 |
返回值 | 必定返回pdPASS |
vTaskNotifyGiveFromISR函数的参数说明如下:
参数 | 说明 |
---|---|
xTaskHandle | 任务句柄(创建任务时得到),给哪个任务发通知 |
pxHigherPriorityTaskWoken | 被通知的任务,可能正处于阻塞状态。此函数发出通知后,会把它从阻塞状态切换为就绪态。如果被唤醒的任务的优先级,高于当前任务的优先级,则"*pxHigherPriorityTaskWoken"被设置为pdTRUE,这表示在中断返回之前要进行任务切换。 |
ulTaskNotifyTake函数的参数说明如下:
参数 | 说明 |
---|---|
xClearCountOnExit | 函数返回前是否清零:pdTRUE:把通知值清零pdFALSE:如果通知值大于0,则把通知值减一 |
xTicksToWait | 任务进入阻塞态的超时时间,它在等待通知值大于0。0:不等待,即刻返回;portMAX_DELAY:一直等待,直到通知值大于0;其他值:Tick Count,可以用pdMS_TO_TICKS()把ms转换为Tick Count |
返回值 | 函数返回之前,在清零或减一之前的通知值。如果xTicksToWait非0,则返回值有2种情况:1. 大于0:在超时前,通知值被增加了2. 等于0:一直没有其他任务增加通知值,最后超时返回0 |
xTaskNotify
函数功能更强大,可以使用不同参数实现各类功能,比如:
xTaskNotify()
等同于xTaskNotifyGive()
xQueueOverwrite()
函数,这就是轻量级的邮箱。xTaskNotify()
比xTaskNotifyGive()
更灵活、强大,使用上也就更复杂。xTaskNotifyFromISR()
是它对应的ISR版本。
这两个函数用来发出任务通知,使用哪个函数来取出任务通知呢?
使用xTaskNotifyWait()
函数!它比ulTaskNotifyTake()
更复杂:
这几个函数的原型如下:
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
xTaskNotify函数的参数说明如下:
参数 | 说明 |
---|---|
xTaskToNotify | 任务句柄(创建任务时得到),给哪个任务发通知 |
ulValue | 怎么使用ulValue,由eAction参数决定 |
eAction | 见下表 |
返回值 | pdPASS:成功,大部分调用都会成功pdFAIL:只有一种情况会失败,当eAction为eSetValueWithoutOverwrite, 并且通知状态为"pending"(表示有新数据未读),这时就会失败。 |
eNotifyAction参数说明:
eNotifyAction取值 | 说明 |
---|---|
eNoAction | 仅仅是更新通知状态为"pending",未使用ulValue。这个选项相当于轻量级的、更高效的二进制信号量。 |
eSetBits | 通知值 = 原来的通知值 | ulValue,按位或。相当于轻量级的、更高效的事件组。 |
eIncrement | 通知值 = 原来的通知值 + 1,未使用ulValue。相当于轻量级的、更高效的二进制信号量、计数型信号量。相当于xTaskNotifyGive()函数。 |
eSetValueWithoutOverwrite | 不覆盖。如果通知状态为"pending"(表示有数据未读),则此次调用xTaskNotify不做任何事,返回pdFAIL。如果通知状态不是"pending"(表示没有新数据),则:通知值 = ulValue。 |
eSetValueWithOverwrite | 覆盖。无论如何,不管通知状态是否为"pendng",通知值 = ulValue。 |
xTaskNotifyFromISR函数跟xTaskNotify很类似,就多了最后一个参数pxHigherPriorityTaskWoken
。在很多ISR函数中,这个参数的作用都是类似的,使用场景如下:
xTaskNotifyFromISR
函数发出通知后,会把接收任务从阻塞状态切换为就绪态xTaskNotifyWait函数列表如下:
参数 | 说明 |
---|---|
ulBitsToClearOnEntry | 在xTaskNotifyWait入口处,要清除通知值的哪些位?通知状态不是"pending"的情况下,才会清除。它的本意是:我想等待某些事件发生,所以先把"旧数据"的某些位清零。能清零的话:通知值 = 通知值 & ~(ulBitsToClearOnEntry)。比如传入0x01,表示清除通知值的bit0;传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0 |
ulBitsToClearOnExit | 在xTaskNotifyWait出口处,如果不是因为超时推出,而是因为得到了数据而退出时:通知值 = 通知值 & ~(ulBitsToClearOnExit)。在清除某些位之前,通知值先被赋给"*pulNotificationValue"。比如入0x03,表示清除通知值的bit0、bit1;传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0 |
pulNotificationValue | 用来取出通知值。在函数退出时,使用ulBitsToClearOnExit清除之前,把通知值赋给"*pulNotificationValue"。如果不需要取出通知值,可以设为NULL。 |
xTicksToWait | 任务进入阻塞态的超时时间,它在等待通知状态变为"pending"。0:不等待,即刻返回;portMAX_DELAY:一直等待,直到通知状态变为"pending";其他值:Tick Count,可以用pdMS_TO_TICKS()把ms转换为Tick Count |
返回值 | 1. pdPASS:成功这表示xTaskNotifyWait成功获得了通知:可能是调用函数之前,通知状态就是"pending";也可能是在阻塞期间,通知状态变为了"pending"。2. pdFAIL:没有得到通知。 |
本节源码是FreeRTOS_22_tasknotify_tansfer_count
,基于FreeRTOS_13_semaphore_circle_buffer
修改。
本程序创建2个任务:
xTaskNotifyGive()
让通知值加一ulTaskNotifyTake()
取出通知值,这表示字符数,打印字符main函数代码如下:
int main( void )
{
prvSetupHardware();
/* 创建1个任务用于发送任务通知
* 优先级为2
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 创建1个任务用于接收任务通知
* 优先级为1
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, &xRecvTask );
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
发送任务、接收任务的代码和执行流程如下:
运行结果如下图所示:
本程序使用xTaskNotifyGive/ulTaskNotifyTake
实现了轻量级的计数型信号量,代码更简单:
信号量是个公开的资源,任何任务、ISR都可以使用它:可以释放、获取信号量。
而本节程序中,发送任务只能给指定的任务发送通知,目标明确;接收任务只能从自己的通知值中得到数据,来源明确。
本节源码是FreeRTOS_23_tasknotify_tansfer_value
。
在上述例子中使用任务通知来传输计数值、传输通知。
本节程序使用任务通知来传输任意数据,它创建2个任务:
xTaskNotify()
发送给其他任务xTaskNotifyWait
取出通知值,这表示字符,并打印出来main函数代码如下:
int main( void )
{
prvSetupHardware();
/* 创建1个任务用于发送任务通知
* 优先级为2
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 创建1个任务用于接收任务通知
* 优先级为1
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, &xRecvTask );
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
发送任务、接收任务的代码和执行流程如下:
运行结果如下图所示:
本程序使用xTaskNotify/xTaskNotifyWait
实现了轻量级的队列(该队列长度只有1),代码更简单:
队列是个公开的资源,任何任务、ISR都可以使用它:可以存入数据、取出数据。
而本节程序中,发送任务只能给指定的任务发送通知,目标明确;接收任务只能从自己的通知值中得到数据,来源明确。
注意:任务通知值只有一个,数据可能丢失,设计程序时要考虑这点。