(1)所谓“任务通知”,可以反过来读"通知任务"。我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可以明确指定:通知哪个任务。
(2)使用队列、信号量、事件组时,我们都需要实先创建对应的结构体,双方通过中间的结构体通信:
(3)使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的“通知”:
(4)本文涉及的内容:
(1)任务通知的优势:
(2)任务通知的限制:
(1)每个任务都有一个结构体: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;
(2)通知状态有3种取值:
##define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) /* 也是初始状态 */
##define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
##define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )
(3)通知值可以有很多种类型:
(1)任务通知有2套函数,简化版、专业版,列表如下:
简化板 | 专业板 | |
---|---|---|
发出通知 | xTaskNotifyGive vTaskNotifyGiveFromISR | xTaskNotify xTaskNotifyFromISR |
取出通知 | ulTaskNotifyTake | xTaskNotifyWait |
(1)在任务中使用xTaskNotifyGive函数,在ISR中使用vTaskNotifyGiveFromISR函数;两个函数都是直接给其他任务发送通知:
(2)可以使用 ulTaskNotifyTake 函数来取出通知值:
(3)使用ulTaskNotifyTask函数可以实现轻量级的、高效的二进制信号量、计数型信号量。
(4)函数原型如下:
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BaseType_t *pxHigherPriorityTaskWoken );
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
(5)xTaskNotifyGive函数的参数说明如下:
参数 | 说明 |
---|---|
xTaskToNotify | 任务句柄(创建任务时得到),给哪个任务发通知 |
返回值 | 必定返回pdPASS |
(6)vTaskNotifyGiveFromISR函数的参数说明如下:
参数 | 说明 |
---|---|
xTaskHandle | 任务句柄(创建任务时得到),给哪个任务发通知 |
xTaskHandle | 被通知的任务,可能正处于阻塞状态。 如果被唤醒的任务的优先级,高于当前任务的优先级, |
(7)ulTaskNotifyTake函数的参数说明如下:
参数 | 说明 |
---|---|
xClearCountOnExit | 函数返回前是否清零: pdTRUE:把通知值清零 pdFALSE:如果通知值大于0,则把通知值减1 |
xTicksToWait | 任务进入阻塞态的超时时间,它在等待通知值大于0。 0:不等待,即刻返回; portMAX_DELAY:一直等待,直到通知值大于0; 其他值:Tick Count,可以用 pdMS_TO_TICKS() 把ms转换为Tick Count |
返回值 | 函数返回之前,在清零或减1之前的通知值。 如果xTicksToWait非0,则返回值有2种情况: 1. 大于0:在超时前,通知值被增加了 |
(1)xTaskNotify函数功能更强大,可以使用不同参数实现各种功能,比如:
(2)xTaskNotify() 比 xTaskNotifyGive() 更灵活、强大,使用上也就更复杂。
(3)xTaskNotifyFromISR() 是 xTaskNotify() 对应的ISR版本。
(4)xTaskNotifyFromISR() 和 xTaskNotify() 函数用来发出通知任务,使用 xTaskNotifyWait() 函数来取出任务通知。xTaskNotifyWait() 比 ulTaskNotifyTake() 更复杂:
(5)这几个函数的原型如下:
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 );
(6)xTaskNotify函数的参数说明如下:
参数 | 说明 |
---|---|
xTaskToNotify | 任务句柄(创建任务时得到),给哪个任务发通知 |
ulValue | 怎么使用ulValue,由eAction参数决定 |
eAction | 见下表 |
返回值 | pdPASS:成功,大部分调用都会成功 pdFAIL:只有一种情况会失败,当eAction为eSetValueWithoutOverwrite, |
eAction参数说明:
eAction取值 | 说明 |
---|---|
eNoAction | 仅仅是更新通知状态为“pending”,未使用ulValue。 这个选项相当于轻量级的、更高效的二进制信号量。 |
eSetBits | 通知值 = 原来的通知值 | ulValue,按位或。 相当于轻量级的、更高效的事件组。 |
eIncrement | 通知值 = 原来的通知值 + 1,未使用ulValue。 相当于轻量级的、更高效的二进制信号量、计数型信号量。 相当于 xTaskNotifyGive() 函数。 |
eSetValueWithoutOverwrite | 不覆盖。 则此次调用xTaskNotify不做任何事,返回pdFAIL。 则:通知值 = ulValue。 |
eSetValueWithOverwrite | 覆盖。 无论如何,不管通知状态是否为"pendng", 通知值 = ulValue。 |
(7)xTaskNotifyFromISR函数跟xTaskNotify很类似,就多了最后一个参数pxHigherPriorityTaskWoken 。在很多ISR函数中,这个参数的作用都是类似的,使用场景如下:
(8)xTaskNotifyWait 函数列表如下:
参数 | 说明 |
---|---|
ulBitsToClearOnEntry | 在xTaskNotifyWait入口处,要清除通知值的哪些位? 通知状态不是"pending"的情况下,才会清除。 它的本意是:我想等待某些事件发生,所以先把"旧数据"的某些位清零。 能清零的话:通知值 = 通知值 & ~(ulBitsToClearOnEntry)。 比如传入0x01,表示清除通知值的bit0; |
ulBitsToClearOnExit | 在xTaskNotifyWait出口处,如果不是因为超时退出, 而是因为得到了数据而退出时: 通知值 = 通知值 & ~(ulBitsToClearOnExit)。 |
pulNotificationValue | 用来取出通知值。 把通知值赋给"*pulNotificationValue"。 |
xTicksToWait | 任务进入阻塞态的超时时间,它在等待通知状态变为"pending"。 0:不等待,即刻返回; portMAX_DELAY:一直等待,直到通知状态变为"pending"; 其他值:Tick Count,可以用 pdMS_TO_TICKS() 把ms转换为Tick Count |
返回值 | 1. pdPASS:成功 这表示xTaskNotifyWait成功获得了通知: 可能是调用函数之前,通知状态就是"pending"; 也可能是在阻塞期间,通知状态变为了"pending"。 2. pdFAIL:没有得到通知。 |