22.3 任务通知的数据结构
下面第51-53行
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif
ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t *pxStack; /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack; /*< Points to the end of the stack on architectures where the stack grows up from low memory. */
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
struct _reent xNewLib_reent;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
// 任务通知的值, 可以保存一个 32 位整数或指针值。
volatile uint32_t ulNotifiedValue;
// 任务通知状态, 用于标识任务是否在等待通知。
volatile uint8_t ucNotifyState;
#endif
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
} tskTCB;
22.4 任务通知的函数接口讲解
功能 | 普通 | 中断 |
---|---|---|
+1 操作 | xTaskGenericNotify | vTaskNotifyGiveFromISR |
其它操作 | xTaskNotify | xTaskNotifyFromISR |
带返回值的(之前的通知) | xTaskNotifyAndQuery | xTaskNotifyAndQueryFromISR |
22.4.1 发送任务通知函数xTaskGenericNotify()
发送通知 API 函数。这类函数比较多,有 6 个。但仔细分析会发现它们只能完成 3 种操作,每种操作有两个 API 函数,分别为带中断保护版本和不带中断保护版本。
所有的函数都是一个宏定义, 在任务中发送任务通知的函数均是调用 xTaskGenericNotify()函数进行发送通知。
参数:
TaskHandle_t xTaskToNotify:被通知的任务句柄,指定通知的任务。
uint32_t ulValue:发送的通知值。
eNotifyAction eAction:枚举类型,指明更新通知值的方式。
uint32_t *pulPreviousNotificationValue:任务原本的通知值返回。
#if( configUSE_TASK_NOTIFICATIONS == 1 )
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotificationValue )
{
TCB_t * pxTCB;
BaseType_t xReturn = pdPASS;
uint8_t ucOriginalNotifyState;
configASSERT( xTaskToNotify );
// 获取要通知的任务
pxTCB = ( TCB_t * ) xTaskToNotify;
taskENTER_CRITICAL();
{
if( pulPreviousNotificationValue != NULL )
{
// 回传任务原本的任务通值,保存在 pulPreviousNotificationValue 中。
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
}
/* 获取任务通知的状态,看看任务是否在等待通知,方便在发送通知后恢复任务 */
ucOriginalNotifyState = pxTCB->ucNotifyState;
/* 标记任务为taskNOTIFICATION_RECEIVED有通知 标记位 */
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;
/* 指定更新任务通知的方式 */
switch( eAction )
{
/*通知值按位或上 ulValue。
使用这种方法可以某些场景下代替事件组,但执行速度更快。 */
case eSetBits :
pxTCB->ulNotifiedValue |= ulValue;
break;
/* 被通知任务的通知值增加 1,这种发送通知方式,参数 ulValue 未使用 */
case eIncrement :
( pxTCB->ulNotifiedValue )++;
break;
/* 将被通知任务的通知值设置为 ulValue。无论任务是否还有通知,
都覆盖当前任务通知值。使用这种方法,
可以在某些场景下代替 xQueueoverwrite()函数,但执行速度更快。 */
case eSetValueWithOverwrite :
pxTCB->ulNotifiedValue = ulValue;
break;
/* 如果被通知任务当前没有通知,则被通知任务的通知值设置为 ulValue;
在某些场景下替代长度为 1 的 xQueuesend(),但速度更快。 */
case eSetValueWithoutOverwrite :
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue = ulValue;
}
else
{
/*如果被通知任务还没取走上一个通知,本次发送通知
任务又接收到了一个通知,则这次通知值丢弃,
在这种情况下,函数调用失败并返回 pdFALSE。*/
xReturn = pdFAIL;
}
break;
/* 发送通知但不更新通知值,这意味着参数 ulValue 未使用。 */
case eNoAction:
break;
}
traceTASK_NOTIFY();
/* 如果被通知任务由于等待任务通知而挂起 */
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
/* 唤醒任务,将任务从阻塞列表中移除,添加到就绪列表中 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
#if( configUSE_TICKLESS_IDLE != 0 )
{
prvResetNextTaskUnblockTime();
}
#endif
// 刚刚唤醒的任务优先级比当前任务高
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
//任务切换
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return xReturn;
}
#endif /* configUSE_TASK_NOTIFICATIONS */
1.xTaskNotifyGive()
代替:二值信号量
xTaskNotifyGive()是一个宏,宏展开是调用函数 xTaskNotify( ( xTaskToNotify ), ( 0 ),eIncrement ), 即向一个任务发送通知,并将对方的任务通知值加 1。该函数可以作为二值信号量和计数信号量的一种轻量型的实现,速度更快, 在这种情况下对象任务在等待任务通 知 的 时 候 应 该 是 使 用 函 数 ulTaskNotifyTake() 而 不 是 xTaskNotifyWait() 。 xTaskNotifyGive() 不能在中断里面使用,而是使用具有中断保护功能的
vTaskNotifyGiveFromISR()来代替。
应用:
/* 函数声明 */
static void prvTask1( void *pvParameters );
static void prvTask2( void *pvParameters );
/*定义任务句柄 */
static TaskHandle_t xTask1 = NULL, xTask2 = NULL;
/* 主函数:创建两个任务,然后开始任务调度 */
void main( void )
{
xTaskCreate(prvTask1, "Task1", 200, NULL, tskIDLE_PRIORITY, &xTask1);
xTaskCreate(prvTask2, "Task2", 200, NULL, tskIDLE_PRIORITY, &xTask2);
vTaskStartScheduler();
}
static void prvTask1( void *pvParameters )
{
for ( ;; ) {
/* 向 prvTask2()发送一个任务通知,让其退出阻塞状态 */
xTaskNotifyGive( xTask2 );
/* 阻塞在 prvTask2()的任务通知上
如果没有收到通知,则一直等待*/
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
}
}
static void prvTask2( void *pvParameters )
{
for ( ;; ) {
/* 阻塞在 prvTask1()的任务通知上
如果没有收到通知,则一直等待*/
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
xTaskNotifyGive( xTask1 );
}
}
2.vTaskNotifyGiveFromISR()
vTaskNotifyGiveFromISR()是 vTaskNotifyGive()的中断保护版本。 用于在中断中向指定任务发送任务通知,并更新对方的任务通知值(加 1 操作) ,在某些场景中可以替代信号量操作,因为这两个通知都是不带有通知值的。
从上面的函数说明我们大概知道 vTaskNotifyGiveFromISR()函数作用, 每次调用该函数都会增加任务的通知值, 任务通过接收函数返回值是否大于零,判断是否获取到了通知,任务通知值初始化为 0, (如果与信号量做对比)则对应为信号量无效。当中断调用vTaskNotifyGiveFromISR()通知函数给任务的时候,任务的通知值增加,使其大于零,使其表示的通知值变为有效,任务获取有效的通知值将会被恢复。
#if( configUSE_TASK_NOTIFICATIONS == 1 )
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken )
{
TCB_t * pxTCB;
uint8_t ucOriginalNotifyState;
UBaseType_t uxSavedInterruptStatus;
// 获取通知的任务块
pxTCB = ( TCB_t * ) xTaskToNotify;
//进入中断
uxSavedInterruptSta tus = portSET_INTERRUPT_MASK_FROM_ISR();
{
//保存任务通知的原始状态,
//看看任务是否在等待通知,方便在发送通知后恢复任务
ucOriginalNotifyState = pxTCB->ucNotifyState;
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;
/* 通知值自加,类似于信号量的释放 */
( pxTCB->ulNotifiedValue )++;
traceTASK_NOTIFY_GIVE_FROM_ISR();
/* 如果任务在阻塞等待通知 */
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
//如果任务调度器运行中
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/* 唤醒任务,将任务从阻塞列表中移除,添加到就绪列表中 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
}
else
{
/* 调度器处于挂起状态,中断依然正常发生,但是不能直接操作就绪列表
将任务加入到就绪挂起列表,任务调度恢复后会移动到就绪列表 */
vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
}
// 判断优先级 是否需要切换上下文
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
// 切换标志
xYieldPending = pdTRUE;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
}
#endif /* configUSE_TASK_NOTIFICATIONS */
/*-----------------------------------------------------------*/
使用示例:
DMA使用
static TaskHandle_t xTaskToNotify = NULL;
/* 外设驱动的数据传输函数 */
void StartTransmission( uint8_t *pcData, size_t xDataLength )
{
/* 在这个时候, xTaskToNotify 应为 NULL,因为发送并没有进行。
如果有必要,对外设的访问可以用互斥量来保护*/
configASSERT( xTaskToNotify == NULL );
/* 获取调用函数 StartTransmission()的任务的句柄 */
xTaskToNotify = xTaskGetCurrentTaskHandle();
/* 开始传输,当数据传输完成时产生一个中断 */
// 一般这个是DMA发送的 DMA会产生一个中断
vStartTransmit( pcData, xDatalength );
}
/* 数据传输完成中断 */
void vTransmitEndISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 这个时候不应该为 NULL,因为数据传输已经开始 */
configASSERT( xTaskToNotify != NULL );
/* 通知任务传输已经完成 */
vTaskNotifyGiveFromISR( xTaskToNotify, &xHigherPriorityTaskWoken );
/* 传输已经完成,所以没有任务需要通知 */
xTaskToNotify = NULL;
/* 如果为 pdTRUE,则进行一次上下文切换 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/* 任务:启动数据传输,然后进入阻塞态,直到数据传输完成 */
void vAFunctionCalledFromATask( uint8_t ucDataToTransmit,
size_t xDataLength )
{
uint32_t ulNotificationValue;
const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );
/* 调用上面的函数 StartTransmission()启动传输 */
StartTransmission( ucDataToTransmit, xDataLength );
/* 等待任务传输完成发出通知,进入阻塞态。注意这里第一个参数被
设置为 pdTRUE ,意味着通知在被获取之后会被清0,使得与二值信号量
有相同的特性。*/
lNotificationValue = ulTaskNotifyTake( pdFALSE, xMaxBlockTime );
/* 当传输完成时,会产生一个中断
在中断服务函数中调用 vTaskNotifyGiveFromISR()向启动数据
传输的任务发送一个任务通知,并将对象任务的任务通知值加 1
任务通知值在任务创建的时候是初始化为 0 的,当接收到任务后就变成 1 */
if ( ulNotificationValue == 1 ) {
/* 传输按预期完成 */
} else {
/* 调用函数 ulTaskNotifyTake()超时 */
}
}
3. xTaskNotify()
推荐:代替事件组
FreeRTOS 每个任务都有一个 32 位的变量用于实现任务通知,在任务创建的时候初始化为 0。 这个 32 位的通知值在任务控制块 TCB 里面定义,具体见代码清单 22-6。xTaskNotify()用于在任务中直接向另外一个任务发送一个事件, 接收到该任务通知的任务有可能解锁。 如果你想使用任务通知来实现二值信号量和计数信号量,那么应该使用更加简单的函数 xTaskNotifyGive() , 而不是使用 xTaskNotify(), xTaskNotify()函数在发送任务通知的时候会指定一个通知值, 并且用户可以指定通知值发送的方式。
注意:该函数不能在中断里面使用, 而是使用具体中断保护功能的版本函数xTaskNotifyFromISR()。
任务通知在任务控制块中的定义:
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif
示例:
/* 设置任务 xTask1Handle 的任务通知值的位 8 为 1*/
xTaskNotify( xTask1Handle, ( 1UL << 8UL ), eSetBits );
/* 向任务 xTask2Handle 发送一个任务通知
有可能会解除该任务的阻塞状态,但是并不会更新该任务自身的任务通知值 */
xTaskNotify( xTask2Handle, 0, eNoAction );
/* 向任务 xTask3Handle 发送一个任务通知
并把该任务自身的任务通知值更新为 0x50
即使该任务的上一次的任务通知都没有读取的情况下
即覆盖写 */
xTaskNotify( xTask3Handle, 0x50, eSetValueWithOverwrite );
/* 向任务 xTask4Handle 发送一个任务通知
并把该任务自身的任务通知值更新为 0xfff
但是并不会覆盖该任务之前接收到的任务通知值*/
if(xTaskNotify(xTask4Handle,0xfff,eSetValueWithoutOverwrite)==pdPASS )
{
/* 任务 xTask4Handle 的任务通知值已经更新 */
} else {
/* 任务 xTask4Handle 的任务通知值没有更新
即上一次的通知值还没有被取走*/
}
4.xTaskNotifyFrmoISR()
xTaskNotifyFromISR()是 xTaskNotify()的中断保护版本,真正起作用的函数是中断发送任务通知通用函数 xTaskGenericNotifyFromISR(),而 xTaskNotifyFromISR()是一个宏定义,.
#define xTaskNotifyFromISR( xTaskToNotify, \
ulValue, \
eAction, \
pxHigherPriorityTaskWoken ) \
xTaskGenericNotifyFromISR( ( xTaskToNotify ), \
( ulValue ), \
( eAction ), \
NULL, \
( pxHigherPriorityTaskWoken ) )
5.中断中发送任务通知通用函数 xTaskGenericNotifyFromISR()
xTaskGenericNotifyFromISR() 是 一 个 在 中 断 中 发 送 任 务 通 知 的 通 用 函 数 ,xTaskNotifyFromISR()、 xTaskNotifyAndQueryFromISR()等函数都是以其为基础,采用宏定义的方式实现。
参数:
TaskHandle_t xTaskToNotify:指定接收通知的任务句柄
uint32_t ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定。
eNotifyAction eAction:任务通知值更新方式。
uint32_t pulPreviousNotificationValue:用于保存任务上一个通知值。
BaseType_t pxHigherPriorityTaskWoken:中断切换上下文
#if( configUSE_TASK_NOTIFICATIONS == 1 )
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotificationValue,
BaseType_t *pxHigherPriorityTaskWoken )
{
TCB_t * pxTCB;
uint8_t ucOriginalNotifyState;
BaseType_t xReturn = pdPASS;
UBaseType_t uxSavedInterruptStatus;
configASSERT( xTaskToNotify );
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
// 获取要通知任务的PCB
pxTCB = ( TCB_t * ) xTaskToNotify;
// 临界区
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if( pulPreviousNotificationValue != NULL )
{
// 保存之前的通知值
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
}
// 获取当前通知的状态
ucOriginalNotifyState = pxTCB->ucNotifyState;
// 设置标记位
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;
// 根据情况进行处理 到这里基本和普通任务是一样的
switch( eAction )
{
case eSetBits :
pxTCB->ulNotifiedValue |= ulValue;
break;
case eIncrement :
( pxTCB->ulNotifiedValue )++;
break;
case eSetValueWithOverwrite :
pxTCB->ulNotifiedValue = ulValue;
break;
case eSetValueWithoutOverwrite :
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue = ulValue;
}
else
{
xReturn = pdFAIL;
}
break;
case eNoAction :
break;
}
// 退出
traceTASK_NOTIFY_FROM_ISR();
/* 如果任务在阻塞等待通知 */
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
//如果任务调度器运行中,表示可用操作就绪级列表
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
/* 唤醒任务,将任务从阻塞列表中移除,添加到就绪列表中 */
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
}
else
{
// 否则放入挂起列表
vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
}
// 刚刚唤醒的任务优先级比当前任务高
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
if( pxHigherPriorityTaskWoken != NULL )
{
// 设置为TRUE 中断处理完要进行切换
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
xYieldPending = pdTRUE;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
#endif /* configUSE_TASK_NOTIFICATIONS */
/*-----------------------------------------------------------*/
使用示例:
/* 中断:向一个任务发送任务通知,并根据不同的中断将目标任务的
任务通知值的相应位置 1 */
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulStatusRegister;
/* 读取中断状态寄存器,判断到来的是哪个中断
这里假设了 Rx、 Tx 和 buffer overrun 三个中断 */
ulStatusRegister = ulReadPeripheralInterruptStatus();
/* 清除中断标志位 */
vClearPeripheralInterruptStatus( ulStatusRegister );
/* xHigherPriorityTaskWoken 在使用之前必须初始化为 pdFALSE */
xHigherPriorityTaskWoken = pdFALSE;
/* 向任务 xHandlingTask 发送任务通知,并将其任务通知值
与 ulStatusRegister 的值相或,这样可以不改变任务通知其它位的值*/
xTaskNotifyFromISR( xHandlingTask,
ulStatusRegister,
eSetBits,
&xHigherPriorityTaskWoken );
/* 如果 xHigherPriorityTaskWoken 的值为 pdRTUE
则执行一次上下文切换*/
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/* 任务:等待任务通知,然后处理相关的事情 */
void vHandlingTask( void *pvParameters )
{
uint32_t ulInterruptStatus;
for ( ;; ) {
/* 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值) */
xTaskNotifyWait( 0x00, /* 在进入的时候不清除通知值的任何位 */
ULONG_MAX, /* 在退出的时候复位通知值为 0 */
&ulNotifiedValue, /* 任务通知值传递到变量ulNotifiedValue 中*/
portMAX_DELAY ); /* 无限期等待 */
/* 根据任务通知值里面的各个位的值处理事情 */
if ( ( ulInterruptStatus & 0x01 ) != 0x00 ) {
/* Rx 中断 */
prvProcessRxInterrupt();
}
if ( ( ulInterruptStatus & 0x02 ) != 0x00 ) {
/* Tx 中断 */
prvProcessTxInterrupt();
}
if ( ( ulInterruptStatus & 0x04 ) != 0x00 ) {
/* 缓冲区溢出中断 */
prvClearBufferOverrun();
}
}
}
6.xTaskNotifyAndQuery()
xTaskNotifyAndQuery()与 xTaskNotify()很像,都是调用通用的任务通知发送函数xTaskGenericNotify() 来 实 现通知的发送,不同的是多了一个附加的参数pulPreviousNotifyValue 用于回传接收任务的上一个通知值。
#define xTaskNotifyAndQuery( xTaskToNotify, \
ulValue, \
eAction, \
pulPreviousNotifyValue ) \
xTaskGenericNotify( ( xTaskToNotify ), \
( ulValue ), \
( eAction ), \
( pulPreviousNotifyValue ) )
uint32_t ulPreviousValue;
/* 设置对象任务 xTask1Handle 的任务通知值的位 8 为 1
在更新位 8 的值之前把任务通知值回传存储在变量 ulPreviousValue 中*/
xTaskNotifyAndQuery( xTask1Handle, ( 1UL << 8UL ), eSetBits,&ulPreviousValue );
/* 向对象任务 xTask2Handle 发送一个任务通知,有可能解除对象任务的阻塞状态
但是不更新对象任务的通知值,并将对象任务的通知值存储在变量 ulPreviousValue 中 */
xTaskNotifyAndQuery( xTask2Handle, 0, eNoAction, &ulPreviousValue );
/* 覆盖式设置对象任务的任务通知值为 0x50
且对象任务的任务通知值不用回传,则最后一个形参设置为 NULL */
xTaskNotifyAndQuery( xTask3Handle, 0x50, eSetValueWithOverwrite, NULL );
/* 设置对象任务的任务通知值为 0xfff,但是并不会覆盖对象任务通过
xTaskNotifyWait()和 ulTaskNotifyTake()这两个函数获取到的已经存在
的任务通知值。对象任务的前一个任务通知值存储在变量 ulPreviousValue 中*/
if ( xTaskNotifyAndQuery( xTask4Handle,
0xfff,
eSetValueWithoutOverwrite,
&ulPreviousValue ) == pdPASS)
{
/* 任务通知值已经更新 */
} else {
/* 任务通知值没有更新 */
}
7.xTaskNotifyAndQueryFromISR()
void vAnISR( void )
{
/* xHigherPriorityTaskWoken 在使用之前必须设置为 pdFALSE */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint32_t ulPreviousValue;
/* 设置目标任务 xTask1Handle 的任务通知值的位 8 为 1
在任务通知值的位 8 被更新之前把上一次的值存储在变量 ulPreviousValue 中*/
xTaskNotifyAndQueryFromISR( xTask1Handle,
( 1UL << 8UL ),
eSetBits,
&ulPreviousValue,
&xHigherPriorityTaskWoken);
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
22.4.2 获取任务通知函数
获取任务通知函数只能用在任 中,没有带中断保护版本,因 此只有两 个 API 函 数: ulTaskNotifyTake() 和
xTaskNotifyWait ()。前者是为代替二值信号量和计数信号量而专门设计的,它和发送通知API 函数 xTaskNotifyGive()、 vTaskNotifyGiveFromISR()配合使用;后者是全功能版的等待通知,可以根据不同的参数实现轻量级二值信号量、计数信号量、事件组和长度为 1 的队列。
1.ulTaskNotifyTake()
ulTaskNotifyTake()作为二值信号量和计数信号量的一种轻量级实现,速度更快。 如果FreeRTOS 中使用函数 xSemaphoreTake() 来获取信号量, 这个时候则可以试试使用函数ulTaskNotifyTake()来代替。
对于这个函数,任务通知值为 0,对应信号量无效,如果任务设置了阻塞等待,任务被阻塞挂起。当其他任务或中断发送了通知值使其不为 0 后,通知变为有效,等待通知的任务将获取到通知, 并且在退出时候根据用户传递的第一个参数 xClearCountOnExit 选择清零通知值或者执行减一操作。
xTaskNotifyTake()在退出的时候处理任务的通知值的时候有两种方法:
- 在函数退出时将通知值清零,这种方法适用于实现二值信号量
- 在函数退出时将通知值减 1, 这种方法适用于实现计数信号量
当一个任务使用其自身的任务通知值作为二值信号量或者计数信号量时, 其他任务应该使用函数 xTaskNotifyGive()或者 xTaskNotify( ( xTaskToNotify ), ( 0 ), eIncrement )来向其发送信号量。
ulTaskNotifyTake()
#if( configUSE_TASK_NOTIFICATIONS == 1 )
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
{
uint32_t ulReturn;
taskENTER_CRITICAL();
{
// 如果通知值为 0 ,阻塞任务
// 默认初始化通知值为 0, 说明没有未读通知
if( pxCurrentTCB->ulNotifiedValue == 0UL )
{
/* 标记任务状态 : 等待消息通知 */
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;
//用户指定超时时间了,那就进入等待状态
if( xTicksToWait > ( TickType_t ) 0 )
{
//根据用户指定超时时间将任务添加到延时列表
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;
// 看看任务通知是否有效,有效则返回
if( ulReturn != 0UL )
{
//是否需要清除通知
if( xClearCountOnExit != pdFALSE )
{
// 清除
pxCurrentTCB->ulNotifiedValue = 0UL;
}
else
{
// -1
pxCurrentTCB->ulNotifiedValue = ulReturn - 1;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//恢复任务通知状态变量
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
taskEXIT_CRITICAL();
return ulReturn;
}
#endif /* configUSE_TASK_NOTIFICATIONS */
/*-----------------------------------------------------------*/
使用示例:
/* 中断服务程序:向一个任务发送任务通知 */
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
/* 清除中断 */
prvClearInterruptSource();
xHigherPriorityTaskWoken = pdFALSE;
/* 发送任务通知,并解锁阻塞在该任务通知下的任务 */
vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/* 任务:阻塞在一个任务通知上 */
void vHandlingTask( void *pvParameters )
{
for ( ;; ) {
ulTaskNotifyTake( pdTRUE, /* 在退出前清 0 任务通知值 */
portMAX_DELAY ); /* 无限阻塞 */
/* RTOS 任务通知被当作二值信号量使用
当处理完所有的事情后继续等待下一个任务通知*/
do {
xEvent = xQueryPeripheral();
if ( xEvent != NO_MORE_EVENTS ) {
vProcessPeripheralEvent( xEvent );
}
}while ( xEvent != NO_MORE_EVENTS );
}
}
2.xTaskNotifyWait()
xTaskNotifyWait()函数用于实现全功能版的等待任务通知,根据用户指定的参数的不同,可以灵活的用于实现轻量级的消息队列队列、二值信号量、计数信号量和事件组功能,并带有超时等待。
#if( configUSE_TASK_NOTIFICATIONS == 1 )
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 )
{
/* 使用任务通知值之前,根据用户指定参数 ulBitsToClearOnEntryClear
将通知值的某些或全部位清零 */
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry;
/* 设置任务状态标识:等待通知 */
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;
/* 设置阻塞,添加到延迟列表 */
if( xTicksToWait > ( TickType_t ) 0 )
{
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 )
{
/* 返回当前通知值,通过指针参数传递 */
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue;
}
/* 判断是否是因为任务阻塞超时,因为如果有
任务发送了通知的话,任务通知状态会被改变 */
if( pxCurrentTCB->ucNotifyState == taskWAITING_NOTIFICATION )
{
/* 没有收到任务通知,是阻塞超时 */
xReturn = pdFALSE;
}
else
{
/* 收到任务值,先将参数 ulBitsToClearOnExit 取反后与通知值位做按位与运算
在退出函数前,将通知值的某些或者全部位清零. */
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit;
xReturn = pdTRUE;
}
// 设置任务的通知状态
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
taskEXIT_CRITICAL();
return xReturn;
}
#endif /* configUSE_TASK_NOTIFICATIONS */
/*-----------------------------------------------------------*/
使用示例:
这个通知的话
/* 这个任务展示使用任务通知值的位来传递不同的事件
这在某些情况下可以代替事件标志组。 */
void vAnEventProcessingTask( void *pvParameters )
{
uint32_t ulNotifiedValue;
/* 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值)
这个任务的任务通知值的位由标志事件发生的任务或者中断来设置*/
xTaskNotifyWait( 0x00, /* 在进入的时候不清除通知值的任何位 */
ULONG_MAX, /* 在退出的时候复位通知值为 0 */
&ulNotifiedValue, /* 任务通知值传递到变量 */
portMAX_DELAY ); /* 无限期等待 */
/* 根据任务通知值里面的各个位的值处理事情 */
if ( ( ulNotifiedValue & 0x01 ) != 0 ) {
/* 位 0 被置 1 */
prvProcessBit0Event();
}
if ( ( ulNotifiedValue & 0x02 ) != 0 ) {
/* 位 1 被置 1 */
prvProcessBit1Event();
}
if ( ( ulNotifiedValue & 0x04 ) != 0 ) {
/* 位 1 被置 1 */
prvProcessBit2Event();
}
}