TaskNotifications任务通知
任务通知TaskNotifications
简介
使用任务通知需要设置宏configUSE_TASK_NOTIFICATIONS定义为1。
FreeRTOS中每个任务都有一个32位的通知值,任务控制块中的成员变量ulNotifiedValue就是这个通知值。任务通知是一个事件,假如某个任务通知的接收任务因为等待任务通知而阻塞,向这个接收任务发送任务通知后就会解除这个任务的阻塞状态。任务通知可以通过如下方法更新接收任务的通知值:
- 不覆盖接收任务的通知值(如果上次发送给任务的通知还没被处理)
- 覆盖接收任务的通知值
- 更新接收任务通知值的一个或多个bit
- 增加接收任务的通知值
合理灵活使用任务通知可以在一些场合中替代队列、信号量和事件标志组。使用任务通知来实现信号量功能时,解除任务阻塞的时间比直接使用信号量要快45%(FreeRTOS官方测试结果,使用v8.1.2版本中的信号量,GCC编译器,-O2优化的条件下测试的,没有使能断言函数configASSERT()),并且使用的RAM更少!
任务通知的发送使用函数xTaskNotify()或xTaskNotifyGive()(以及中断版本)来完成,这个通知值会一直保存着,直到接收任务调用函数xTaskNotifyWait()或ulTaskNotifyTake()来获取这个通知值。假如接收任务因为等待任务通知而阻塞的话,那么在接收到任务通知以后会接触阻塞态。
任务通知虽然可以提高速度,并且减少RAM的使用,但是任务通知也有使用限制:
- FreeRTOS的任务通知只能有一个接收任务
- 接收任务可以因为接收任务通知而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞
发送任务通知
Function | Descriptions |
---|---|
xTaskNotify() | 任务级发送通知函数,带有通知值并且不保留接收任务原通知值 |
xTaskNotifyFromISR() | 中断级发送通知函数 |
xTaskNotifyGive() | 任务级发送通知函数,不带通知值并且不保留接收任务的通知值,此函数会将接收任务的通知值加一 |
vTaskNotifyGiveFromISR() | 中断级发送通知函数 |
xTaskNotifyAndQuery() | 任务级发送通知函数,带有通知值并且保留接收任务的原通知值 |
xTaskNotifyAndQueryFromISR() | 中断级发送通知函数 |
函数原型:
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction )
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
BaseType_t xTaskNotifyAndQuery ( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction
uint32_t * pulPreviousNotificationValue);
eNotifyAction是一个枚举类型,在task.h中定义:
typedef enum
{
eNoAction = 0,
eSetBits, //更新指定的 bit
eIncrement, //通知值加一
eSetValueWithOverwrite, //覆写的方式更新通知值
eSetValueWithoutOverwrite //不覆写通知值
} eNotifyAction;
以上三个任务级任务通知发送函数最终都会调用函数xTaskGenericNotify()函数,并在该任务中修改任务通知值,判断任务是否需要解除阻塞以及自动进行任务切换。
获取任务通知
Function | Descriptions |
---|---|
ulTaskNotifyTake() | 任务级获取任务通知函数,可以设置在退出此函数时将任务通知值清零或减一。当任务通知用作二值信号量或者计数信号量的时候使用此函数来获取信号量 |
xTaskNotifyWait() | 任务级获取任务通知函数,比ulTaskNotifyTake()更为强大,全功能版任务通知获取函数 |
函数原型:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
这里需要注意一点,返回的是任务通知值被减少或者清零之前的值。
Parameter | Descriptions |
---|---|
xClearCountOnExit | 为pdFALSE则退出函数时任务通知值减一,类似计数型信号量,为pdTRUE则退出时任务通知值清零,类似二值信号量 |
xTicksToWait | 阻塞时间 |
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue,
TickType_t xTicksToWait );
Parameter | Descriptions |
---|---|
ulBitsToClearOnEntry | 当没有接收到任务通知时将任务通知值与该参的取反值进行按位与运算,为0xffffffff或ULONG_MAX时会将任务通知值清零 |
ulBitsToClearOnExit | 当接收到了任务通知时将任务通知值与该参的取反值进行按位与运算,为0xffffffff或ULONG_MAX时会将任务通知值清零 |
pulNotificationValue | 此参数用来保存任务通知值 |
xTicksToWait | 阻塞时间 |
任务通知模拟二值信号量
因为任务通知的效率高于二值信号量,因此可以通过任务通知的方式来替代二值信号量实现任务同步,具体是在原本释放信号量函数的位置替换成发送任务通知函数,如下:
// xSemaphoreGiveFromISR(BinarySemaphore,&xHigherPriorityTaskWoken);
// 使用发送任务通知代替释放二值信号量
vTaskNotifyGiveFromISR(DataProcessTask_Handler,&xHigherPriorityTaskWoken);
而在原本获取信号量函数的位置替换成获取任务通知函数,如下:
// xSemaphoreTake(BinarySemaphore,portMAX_DELAY);
// 使用获取任务通知代替读取二值信号量
NotifyValue = ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
需要注意的是获取任务通知时需要设置xClearCountOnExit为pdTRUE,这样在退出函数后任务通知值才会减一。
任务通知模拟计数型信号量
使用任务通知替换计数型信号量与替换二值信号量操作基本一致,原理相同,唯一的区别在于Take获取任务通知时,第一个参数要改成pdFALSE,根据上面函数原型的介绍,为pdFALSE表示退出函数时任务通知值减1,而模拟二值信号量时为pdTRUE表示退出时清零,计数型信号量获取到之后只需要减一,不能清零,清零就全没了。
NotifyValue = ulTaskNotifyTake(pdFALSE,portMAX_DELAY);
释放发送任务通知与之前一样,这里返回值依然是没被减一之前的任务通知值。
任务通知模拟事件标志组
与上面模拟信号量不同点在于,模拟事件标志组时使用的任务通知发送和接收函数和上面的都不同,模拟事件标志组使用的任务通知发送函数应该是
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction )
该函数可以通过第三个参数,也就是eNotifyAction枚举类型指定更新的bit位,上面模拟信号量使用的发送任务通知函数只能加一,所以这点需要注意一下,同时获取任务通知函数也不同,应该是
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue,
TickType_t xTicksToWait );
该函数可以在执行完后可以单独对某个位进行与操作,而上面模拟信号量使用的获取任务通知函数只能减一或者清零。例子:
//下面是个伪代码
//这个函数来发送任务通知值为0x01
xTaskNotify((TaskHandle_t )EventGroupTask_Handler,
(uint32_t )0x01,
(eNotifyAction)eSetBits);
//这个函数来获取任务通知值
err = xTaskNotifyWait(0x00,
0xffffffff,
&NotifyValue,
portMAX_DELAY);
//判断获取结果
if(err == pdPASS)
{
if((NotifyValue&0x01)!=0) //事件0发生
{
event0x01flag = 1;
}
}