任务通知也是一种通信机制,可替代二值信号量,计数信号量,事件组,也可以替代长度为1,大小为4字节地消息队列。
任务通知不需要像消息队列或者二值量创建,而是直接在任务地TCB中通过一个宏定义而定义
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue; //任务通知值
volatile uint8_t ucNotifyState; //任务通知状态,标识任务是否在等待通知
#endif
任务通知函数
可以看到,发送通知的函数有六种,细分下来有三种,分别用于信号量、消息发送、以及发送消息并返回原来的通知值。而它们都是通过宏定义来定义函数,其底层实际调用的是xTaskGenericNotify函数。
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, //被通知的任务句柄
uint32_t ulValue, //通知值
eNotifyAction eAction, //指明更新方式
uint32_t *pulPreviousNotificationValue ) //返回原本被通知任务的的通知值
来看看它的源码,首先判断一些是不是需要回传被通知任务的原本的通知值,只要pulPreviousNotificationValue不等于null就回回传,因此,只有在函数xTaskNotifyAndQuery中才会使用到这里,专门定义一个变量并接收先前通知值。
uint32_t t1;
xTaskNotifyAndQuery(taskhandle1,0xffff,eSetValueWithoutOverwrite,&t1)
然后保存任务通知的状态并使得任务的通知状态更改为收到任务通知
然后通过一个switch语句,执行不同的通知方式。case1: eSetBits(按位或操,代替事件组)。case2: eIncrement(作为信号量)。case3:eSetValueWithOverwrite(可替代xQueueoverwrite()函数,这里用在队列),case4: eSetValueWithoutOverwrite(不覆盖写入,判断一下被通知任务是不是已经收到通知了,收到了通知就不能覆盖写入,返回pdfalse). case5:eNoaction(只是发送通知,没有使用值,那就是更改被通知任务状态为已经收到通知)。刚刚的这些操作不管任务是在哪个队列,都可以操作,接下来就要判断任务之前的通知状态,如果是在等待通知状态(与已经收到通知相反状态),说明此时任务挂起态,就需要把它从阻塞列表移除(阻塞列表是哪个列表?后面来看),如果被通知的任务的优先级更高,那么就应该执行一次任务切换。
来看看各发送函数宏定义
xTaskNotifyGive():用作信号量
#define xTaskNotifyGive( xTaskToNotify )
xTaskGenericNotify( ( xTaskToNotify ),//被通知任务句柄
( 0 ), //通知值0
eIncrement, //被通知任务的原本通知值加1
NULL ) //不需要返回值
来看看中断版本 vTaskNotifyGiveFromISR(),它不是一个宏定义了(在发送通知的函数中中,只有它不是由宏定义来定义的),但是它的源码中和xTaskGenericNotify()函数类似,只是考虑到中断中上下文环境不同而有些操作不能实现,并通过老朋友pxHigherPriorityTaskWoken来判断是不是需要在中断退出后执行任务切换。
xTaskNotify():发送通知,带通知值
xTaskNotify( xTaskToNotify, ulValue, eAction )
eAction就是上面讨论xTaskGenericNotify时候已经说了的几种情况。
xTaskNotifyFromISR()是中断版本。
#define xTaskNotifyFromISR( xTaskToNotify,
ulValue,
eAction,
pxHigherPriorityTaskWoken )
xTaskGenericNotifyFromISR( ( xTaskToNotify ),
( ulValue ),
( eAction ),
NULL,
( pxHigherPriorityTaskWoken ) )
它是宏定义的,其展开是调用了一个 xTaskGenericNotifyFromISR()函数,它和xTaskGenericNotify函数实现过程几乎一模一样,只是任务切换不能在中断中使用,通过pxHigherPriorityTaskWoken 在退出中断的时候切换。
uint32_t pxHigherPriorityTaskWoken =pdFalse;
xTaskNotifyFromISR( xTaskhandel, 0xff,eSetBits, &pxHigherPriorityTaskWoken )
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken)//切换任务
xTaskNotifyAndQuery(),(发送通知,带通知值,带返回值)
它和xTaskNotify()只有一个参数不同
#define xTaskNotify( xTaskToNotify, ulValue, eAction )
xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL )
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue )
xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )
那就是xTaskNotifyAndQuery需要多传入一个参数pulPreviousNotifyValue用于接收被通知的任务先前的通知值,而在展开函数中,xTaskGenericNotify函数的最后一个是NULL,一个是pulPreviousNotifyValue,在函数中,通过一个if判断要不要返回值。
xTaskNotifyAndQueryFromISR()也是一个宏定义,展开也是执行的xTaskGenericNotifyFromISR()。区别就和刚刚说的xTaskNotifyAndQuery()和xTaskNotify()一样。
任务通知函数
获取任务通知的函数只能用在任务中,且没有中断版本,只有两个函数
ulTaskNotifyTake()(获取信号量(二值,计数)时候使用), ulTaskNotifywait()(消息,事件),
来看看 ulTaskNotifyTake(),等价于xSemaphoreTake()函数
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
在该函数中,首先看看当前任务的通知值是不是0(既然能执行这个函数,说明pxCurrentTCB一定是调用这个函数的TCB),是0就把通知状态改为等待通知,然后根据阻塞时间加入延时列表。如果消息来了,查看是否需要清除通知,如果需要清除,则直接变为0(用作二值量),如果不需要清除,则减一,(用作计数信号量),然后把任务通知的状态更换为等待通知状态。
xTaskNotifyWait()
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,//进入函数前清除自己的通知值的哪些位
uint32_t ulBitsToClearOnExit,//退出函数前哪些位清0,清0之前的值
//保存到pulNotificationValue
uint32_t *pulNotificationValue,
TickType_t xTicksToWait ) //延时阻塞时间