任务通知在FreeRTOS中是一个可选的功能,要使用任务通知的话就需要将宏 configUSR_TASK_NOTIFICATIONS定义为1。
FreeRTOS的每个任务都有一个32位的通知值,任务控制款中的成员变量ulNotifiedValue就是这个通知值。任务通知是一个事件,假如某个任务通知的接收任务因为等待任务通知而阻塞的话,向这个接收任务发送任务通知以后就会解除这个任务的阻塞状态。也可以更新接收任务的任务通知值,任务通知可以通过如下方法更新接收任务的通知值:
1. 不覆盖接收任务的通知值(如果上次发送给接收任务的通知还没有被处理)。
2. 覆盖接收任务的通知值。
3. 更新接收任务通知值的一个或多个bit。
4. 增加接收任务的通知值。
合理、灵活地使用上面这些更改任务通知值的方法可以在一些场合中替代队列、二值信号量、计数型信号量和时间标志组。使用任务通知来实现二值信号量功能的时候,解除任务阻塞的时间比直接使用二值信号量要快45%(FreeRTOS官方测试结果,使用v8.1.2版本中的二值信号量,GCC编译器,-O2优化条件下测试的,没有使能断言函数configASSERT()),并且使用的RAM更少!
任务通知的发送使用函数xTaskNotify()或者xTaskNotifyGive()(还有次函数的中断版本)来完成,这个通知值会一直保存着,直到接收任务调用函数xTaskNotifyWait()或者ulTaskNotifyTake()来获取这个通知值。假如接收任务因为等待任务通知而阻塞的话那么在接收到任务通知以后就会解除阻塞态。
任务通知虽然可以提高速度,并且减少RAM的使用,但是任务通知也是由使用限制的:
1. FreeRTOS的任务通知只能有一个接收任务,其实大多数的应用都是这种情况。
2. 接收任务可以因为接收任务通知而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞。
此函数用于发送任务通知,此函数发送任务通知的时候带有通知值,此函数是个宏,真正执行的函数xTaskGenericNotify(),函数原型如下:
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction )
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
ulValue:任务通知值。
eAction:任务通知更新的方法,eNotifyAction是个枚举类型,在文件task.h中有如下定义:
/* Actions that can be performed when vTaskNotify() is called. */
typedef enum
{
eNoAction = 0, /* Notify the task without updating its notify value. */
eSetBits, /* 更新指定bit。Set bits in the task's notification value. */
eIncrement, /* 任务通知值加一。Increment the task's notification value. */
eSetValueWithOverwrite, /* 覆写的方式更新通知值。Set the task's notification value to a specific value even if the previous value has not yet been read by the task. */
eSetValueWithoutOverwrite /* 不覆写通知值。Set the task's notification value if the previous value has been read by the task. */
} eNotifyAction;
此参数可以选择枚举类型中的任意一个,不同的应用环境选择也不同。
返回值:
pdFAIL:但参数eAction设置为eSetValueWithoutOverwrite的时候,如果任务通知值没有更新成功就返回pdFAIL。
pdPASS:eAction设置为其他选项的时候统一返回pdPASS。
此函数用于发送任务通知,是函数xTaskNotify()的中断版本,此函数是个宏,真正执行的是函数xTaskGenericNotifyFromISR(),此函数原型如下:
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken )
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
ulValue:任务通知值。
eAction:任务通知更新的方法。
pxHigherPriorityTaskWoken :退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,如果任务通知值没有更新成功就返回pdFAIL。
pdPASS:eAction 设置为其他选项的时候统一返回pdPASS。
发送任务通知,相对于函数xTaskNotify(),此函数发送任务通知的时候不带有通知值。此函数只是将任务通知值简单加一。此函数是个宏,真正执行的是函数xTaskGenericNotify(),此函数原型如下:
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify )
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
返回值:
pdPASS:此函数只会返回pdPASS。
此函数为xTaskNotifyGive()的中断版本,用在中断服务函数中,函数原型如下:
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle,
BaseType_t *pxHigherPriorityTaskWoken )
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
pxHigherPriorityTaskWoken:退出此函数以后是否进行任务切换,这个变量的值会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
无。
此函数和xTaskNotify()很类似,此函数比xTaskNotify()多一个参数,此函数用来保存更新前的通知值。此函数是个宏,真正执行的是函数xTaskGenericNotify(),此函数原型如下:
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotificationValue )
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
ulValue:任务通知值。
eAction:任务通知更新的方法。
pulPreviousNotificationValue:用来保存更新前的任务通知值。
返回值:
pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,如果任务通知值没有更新成功就返回pdFAIL。
pdPASS:eAction设置为其他选项的时候统一返回pdPASS。
此函数为xTaskNortifyAndQuery()的中断版本,用在中断服务函数中。此函数同样为宏,真正执行的是函数xTaskGenericNotifyFromISR(),此函数的原型如下:
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken )
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
ulValue:任务通知值。
eAction:任务通知更新的方法。
pulPreviousNotificationValue:用来保存更新前的任务通知值。
pxHigherPriorityTaskWoken:退出此函数以后是否进行任务切换,这个变量的值函数会自动设置,用户不进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,如果任务通知值没有更新成功就返回pdFAIL。
pdPASS:eAction设置为其他选项的时候统一返回pdPASS。
获取任务通知的函数有两个,如下表所示:
函数 | 描述 |
---|---|
ulTaskNotifyTake() | 获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当任务通知用作二值信号量或者计数信号量的时候使用此函数来获取信号量。 |
xTaskNotifyWait() | 等待任务通知,比ulTaskNotifyTak()更为强大,全功能版任务通知获取函数。 |
此函数为获取任务通知函数,当任务通知用作二值信号量或者计数型信号量的时候可以使用此函数来获取信号量,函数原型如下:
uint32_tulTaskNotifyTake(BaseType_t xClearCountOnExit,
TickType_t xTicksToWait);
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
{
uint32_t ulReturn;
taskENTER_CRITICAL();
{
if( pxCurrentTCB->ulNotifiedValue == 0UL )(1)
{
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;(2)
if( xTicksToWait > ( TickType_t ) 0 )(3)
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_TAKE_BLOCK();
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
taskENTER_CRITICAL();
{
traceTASK_NOTIFY_TAKE();
ulReturn = pxCurrentTCB->ulNotifiedValue;(4)
if( ulReturn != 0UL )(5)
{
if( xClearCountOnExit != pdFALSE )(6)
{
pxCurrentTCB->ulNotifiedValue = 0UL;
}
else
{
pxCurrentTCB->ulNotifiedValue = ulReturn - 1;(7)
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;(8)
}
taskEXIT_CRITICAL();
return ulReturn;
}
(1)、判断任务通知值是否为0,如果为0的话说明还没有接收到任务通知。
(2)、修改任务通知状态为taskWAITING_NOTIFICATION。
(3)、如果阻塞时间不为0的话就将任务添加到延时列表中,并且进行一次任务调度。
(4)、如果任务通知值不为0的话就先获取任务通知值。
(5)、任务通知值大于0。
(6)、参数xClearCountOnExit不为pdFALSE,那就将任务通知值清零。
(7)、如果参数xClearCountOnExit为pdFALSE的话那就将任务通知值减一。
(8)、更新任务通知状态为taskNOT_WAITING_NOTIFICATION。
此函数也是用来获取任务通知的,不过此函数比ulTaskNotifyTake()更为强大,不管任务通知用作二值信号量、计数型信号量、队列和事件标志组中的哪一种,都可以使用此函数来获取任务通知。但是当任务通知用作二值信号量和计数型信号量的时候推荐使用函数ulTaskNotifyTake()。此函数原型如下:
BaseType_txTaskNotifyWait(uint32_tulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t*pulNotificationValue,
TickType_t xTicksToWait);
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
{
BaseType_t xReturn;
taskENTER_CRITICAL();
{
if( pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED )(1)
{
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry;(2)
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;(3)
if( xTicksToWait > ( TickType_t ) 0 )(4)
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_WAIT_BLOCK();
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
taskENTER_CRITICAL();
{
traceTASK_NOTIFY_WAIT();
if( pulNotificationValue != NULL )(5)
{
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue;
}
if( pxCurrentTCB->ucNotifyState == taskWAITING_NOTIFICATION )(6)
{
xReturn = pdFALSE;
}
else
{
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit;(7)
xReturn = pdTRUE;
}
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;(8)
}
taskEXIT_CRITICAL();
return xReturn;
}
(1)、任务同通状态不为taskNOTIFICATION_RECEIVED。
(2)、将任务通知值与参数ulBitsToClearOnEntry的取反值进行按位与运算。
(3)、任务通知状态改为taskWAITING_NOTIFICATION。
(4)、如果阻塞时间大于0的话就要将任务添加到延时列表中,并且进行一次任务切换。
(5)、如果任务通知状态为taskNOTIFICATION_RECEIVED,并且参数pulNotificationValue有效的话就保存任务通知值。
(6)、如果任务通知的状态又变为taskWAITING_NOTIFICATION的话就标记xReturn为pdFALSE。
(7)、如果任务通知的状态一直为taskNOTIFICATION_RECEIVED的话就将任务通知的值与参数ulBitsToClearOnExit的取反值进行按位与运算,并且标记xReturn为pdTRUE,表示获取任务通知成功。
(8)、标记任务通知的状态为taskNOT_WAITING_NOTIFICATION。