任务通知在freeRTOS中是一个可选的功能,通过设置configUSE_TASK_NOTIFICATIONS变量来开启或关闭
FreeRTOS的每个任务都有一个32位的通知值,任务控制块中的成员变量ulNotifiedValue就是这个通知值。任务通知是一个事件,加入某个任务通知的接收任务因为等待任务通知而阻塞的话,向这个接收任务发送任务通知以后就会解除这个任务的阻塞状态。也可以更新接收任务的任务通知值,任务通知可以通过以下方法更新接收任务的通知值:
- 不覆盖接收任务的通知值(如果上次发送给接收任务的通知还没被处理)
- 覆盖接收任务的通知值
- 更新接收任务通知值的一个或多个bit
- 增加接收任务的通知值
合理、灵活的使用上面这些更改任务通知值的方法可以在一些场合中替代队列、二值信号量、计数型信号量和事件标志组。使用任务通知来实现二值信号量功能时,解除任务阻塞的时间比直接使用二值信号量要快45%,并且使用的RAM更少。
任务通知的发送使用函数xTaskNotify()或xTaskNotifyGive()(还有此函数的中断版本)来完成,这个通知值会一直被保存着,直到接受任务调用函数xTaksNotifyWait()或者UlTaskNotifyTake()来获取这个通知。假如接收任务因为等待任务通知而阻塞的话,那么在接收到任务通知以后就会解除阻塞态。
任务通知虽然可以提高速度,并且减少RAM使用,但是任务通知也是有使用限制的:
- FreeRTOS的任务通知只能有一个接收任务,其实大多数的应用都是这种情况。
- 接收任务可以因为接收任务通知而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞。
1.xTaskNotify() 发送通知(带通知值)
此函数用于发送任务通知,此函数发送任务通知的时候带有通知值。
函数原型:
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL )
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
ulValue:任务通知值
eAction:任务通知更新的方法。该参数是个枚举类型,定义如下:
typedef enum
{
eNoAction = 0, /* 通知任务而不更新通知值 */
eSetBits, /* 更新指定的Bit */
eIncrement, /* 通知值加一 */
eSetValueWithOverwrite, /*覆写的方式更新通知值,即是之前的值没有准备好 */
eSetValueWithoutOverwrite /* 不覆写通知值. */
} eNotifyAction;
此参数可以选择枚举类型中的任意一个,不同的应用环境其选择也不同。
返回值:
pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite时,如果任务通知值没有更新成功就返回pdFAIL
pdPASS:eAction设置为其他选项的时候统一返回pdPASS
2.xTaskNotifyFromISR() 中断中发送通知(带通知值)
函数原型:
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
ulValue:任务通知值
eAction:任务通知更新的方法。
pxHigherPriorityTaskWoken :退出此函数以后是否进行任务切换。当设置为pdTRUE时,退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite时,如果任务通知值没有更新成功就返回pdFAIL
pdPASS:eAction设置为其他选项的时候统一返回pdPASS
3.xTaskNotifyGive()发送任务通知(不带通知值)
发送通知,不带通知值并且不保留接受任务的通知值。此函数只是将任务通知值简单的加一。
函数原型:
#define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )
参数:
xTaskToNotify :任务句柄
返回值:
paPASS:此函数只会返回pdPASS
4.xTaskNotifyGiveFromISR()从中断中发送任务通知(不带通知值)
函数原型;
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
pxHigherPriorityTaskWoken :退出此函数以后是否进行任务切换。当设置为pdTRUE时,退出中断服务函数之前一定要进行一次任务切换。
返回值:无
5.xTaskNotifyAndQuery()发送通知(带有通知值,且保存原通知值)
此函数跟xTaskNotify()很类似,此函数多一个参数,此参数用来保存更新前的通知值。
函数原型:
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
ulValue:任务通知值
eAction:任务通知更新的方法。
pulPreviousNotifyValue :用来保存更新前的任务通知值。
返回值:
pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite时,如果任务通知值没有更新成功就返回pdFAIL
pdPASS:eAction设置为其他选项的时候统一返回pdPASS
6.xTaskNotifyAndQueryFromISR()中断中发送通知(带有通知值,且保存原通知值)
函数原型:
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )
参数:
xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。
ulValue:任务通知值
eAction:任务通知更新的方法。
pulPreviousNotifyValue :用来保存更新前的任务通知值。
pxHigherPriorityTaskWoken :退出此函数以后是否进行任务切换。当设置为pdTRUE时,退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite时,如果任务通知值没有更新成功就返回pdFAIL
pdPASS:eAction设置为其他选项的时候统一返回pdPASS
7.ulTaskNotifyTake()获取任务通知
此函数为获取任务通知函数,可以设置再退出此函数的时候将任务通知值清零或减一。当任务通知用作二值信号量或计数信号量的时候可以用此函数来获取信号量。
函数原型:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
参数:
xClearCountOnExit:参数为pdFALSE时,在退出函数时任务通知值减一,类似于计数信号量。当此参数为pdTRUE时,在退出函数时任务通知值清零,类似二值信号量。
xTicksToWait :阻塞时间。
返回值:
任务通知值减少或者清零之前的值。
8.xTaskNotifyWait() 获取任务通知
此函数是ulTaskNotifyTake()函数的加强版,不管任务通知用作二值信号量、计数型信号量、队列型信号量、队列和时间标志组的哪一种,都可以使用此函数来获取任务通知。但是当任务通知用作二值信号量和计数型信号量的时候推荐使用ulTaskNotifyTake()函数。
函数原型:
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
参数:
ulBitsToClearOnEntry:当没有接收到任务通知的时候,将任务通知值与此参数的取反值进行按位‘与’运算,当此参数为0xffffffff或者ULONG_MAX的时候就会将任务通知值清零。
ulBitsToClearOnExit:如果接收到了任务通知,在做完响应的处理退出函数之前将任务通知值与此参数的取‘反’值进行按位‘与’运算。当此参数为0xffffffff或者ULONG_MAX的时候就会将任务通知值清零。
pulNotificationValue:此参数用来保存任务通知值
xTicksToWait :阻塞时间
返回值:
PdTRUE:获取到了任务通知
pdFALSE:获取失败。
代码验证;
模拟二值信号量
创建一个任务,在任务中启动100ms的周期软件定时器,在定时器中置通知值。在任务中等待通知。
TaskHandle_t task1_hander; //任务句柄
void vTimerCallback( TimerHandle_t pxTimer )
{
configASSERT( pxTimer );
static uint32_t time_cnt = 0;
time_cnt++;
int32_t lArrayIndex = 0;
lArrayIndex = ( int32_t ) pvTimerGetTimerID( pxTimer );
LOG_I(common,"[DEBUG]:timer name:%stimer id:%d",pcTimerGetName(pxTimer),lArrayIndex);
if(lArrayIndex = timer_id)
{
LOG_I(common,"[DEBUG]:time cnt:%d",time_cnt);
LOG_I(common,"[TIMER]:notify gived");
xTaskNotifyGive(task1_hander); //置通知值,即值+1
}
}
static void vTestTask1_H(void *pvParameters)
{
xTimer_test = xTimerCreate("Test timer", /* Just a text name, not used by the kernel. */
(100 / portTICK_PERIOD_MS), /* The timer period in ticks. */
pdTRUE, /* The timers will auto-reload themselves when they expire. */
(void *)timer_id, /* Assign each timer a unique id equal to its array index. */
vTimerCallback /* Each timer calls the same callback when it expires. */
);
if(xTimer_test == NULL)
{
LOG_I(common,"[TASK1]:Timer create fail"); //失败
}
else
{
LOG_I(common,"[TASK1]:Timer create sucess"); //成功
if( xTimerStart( xTimer_test, 0 ) != pdPASS )
{
LOG_I(common,"[TASK1]:Timer start fail"); //失败
}
}
uint32_t notifyValue = 0;
EventBits_t uxBits;
while(1)
{
LOG_I(common,"[TASK1]:wait notify");
notifyValue = ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
LOG_I(common,"[TASK1]:get notify:%d",notifyValue);
vTaskDelay(500);
}
}
结果:
结果分析:
- 任务1等待通知
- 定时器周期到,发送通知
- 任务1检测到通知,读取通知值为1。因为xClearCountOnExit参数设置为pdTRUE,所以读取后清除所有通知值。挂起等待500ms。
- 发送通知
- 发送通知
- 发送通知
- 发送通知
- 发送通知
- 挂起结束,继续等待通知。等到通知,获取到通知值为5。即4-8发布的通知。
- 再次获取到通知,值为5
从结果可以看出来,调用xTaskNotifyGive()函数可以累加通知值。而当xClearCountOnExit参数设置为pdTRUE时,调用ulTaskNotifyTake函数获取通知值,结束后会把所有通知值清除掉。
模仿计数型信号量
修改xClearCountOnExit参数设置为pdFALSE。任务1的延迟等待时间修改为200ms。
结果分析:
- 等待通知
- 定时器发布通知
- 任务1获取到通知,值为1。结束后值减一。
- 定时器发布通知
- 定时器发布通知
- 任务1获取到通知,值为2。结束后值减一。
- 定时器发布通知
- 定时器发布通知
- 任务1获取到通知,值为3。结束后值减一。
- 定时器发布通知
- 定时器发布通知
- 任务1获取到通知,值为4。结束后值减一。
可以看到,当xClearCountOnExit参数设置为pdFALSE后,每次调用ulTaskNotifyTake函数,通知值只会减一,并不会全部清零。
模拟消息邮件
创建3个任务,任务1启动100ms周期定时器,在回调中置通知值1。在任务1中等待通知值。任务2中500ms周期上传通知值2,任务3中700ms周期上报通知值3。通知值1为0x01。通知值2为0x55,通知值3为0xaa。
任务1
#define TIME_NOITY 0x01
#define TIME_TASK2 0x55
#define TIME_TASK3 0xaa
TaskHandle_t task1_hander; //任务句柄
void vTimerCallback( TimerHandle_t pxTimer )
{
configASSERT( pxTimer );
static uint32_t time_cnt = 0;
time_cnt++;
int32_t lArrayIndex = 0;
lArrayIndex = ( int32_t ) pvTimerGetTimerID( pxTimer );
BaseType_t err;
LOG_I(common,"[DEBUG]:timer name:%stimer id:%d",pcTimerGetName(pxTimer),lArrayIndex);
if(lArrayIndex = timer_id)
{
LOG_I(common,"[DEBUG]:time cnt:%d",time_cnt);
LOG_I(common,"[TIMER]:notify gived TIME_NOITY");
err = xTaskNotify(task1_hander, //句柄
TIME_NOITY, //通知值
eSetValueWithOverwrite); //覆写
if(err == pdFAIL)
{
LOG_I(common,"[TIMER]:notify gived TIME_NOITY fail");
}
}
}
static void vTestTask1_H(void *pvParameters)
{
xTimer_test = xTimerCreate("Test timer", /* Just a text name, not used by the kernel. */
(100 / portTICK_PERIOD_MS), /* The timer period in ticks. */
pdTRUE, /* The timers will auto-reload themselves when they expire. */
(void *)timer_id, /* Assign each timer a unique id equal to its array index. */
vTimerCallback /* Each timer calls the same callback when it expires. */
);
if(xTimer_test == NULL)
{
LOG_I(common,"[TASK1]:Timer create fail"); //失败
}
else
{
LOG_I(common,"[TASK1]:Timer create sucess"); //成功
if( xTimerStart( xTimer_test, 0 ) != pdPASS )
{
LOG_I(common,"[TASK1]:Timer start fail"); //失败
}
}
uint32_t notifyValue = 0;
EventBits_t uxBits;
BaseType_t err = 0;
while(1)
{
LOG_I(common,"[TASK1]:wait notify");
err = xTaskNotifyWait(0, //进入函数的时候不清楚任务bit
ULONG_MAX, //退出时清除所有的bit
¬ifyValue, //保存任务通知值
portMAX_DELAY); //阻塞时间
if(err == pdTRUE)
{
LOG_I(common,"[TASK1]:get notify:%p",notifyValue);
switch(notifyValue)
{
case TIME_NOITY:
{
LOG_I(common,"[TASK1]:get nitify TIME_NOITY");
}break;
case TIME_TASK2:
{
LOG_I(common,"[TASK1]:get nitify TIME_TASK2");
}break;
case TIME_TASK3:
{
LOG_I(common,"[TASK1]:get nitify TIME_TASK3");
}break;
default: LOG_I(common,"[TASK1]:get nitify UNKNOW"); break;
}
}
else
{
LOG_I(common,"[TASK1]:get notify fail");
}
}
任务2
static void vTestTask2_M(void *pvParameters)
{
BaseType_t err = 0;
while(1)
{
LOG_I(common,"[TIMER]:notify gived TIME_TASK2");
err = xTaskNotify(task1_hander, //句柄
TIME_TASK2, //通知值
eSetValueWithOverwrite); //覆写
if(err == pdFAIL)
{
LOG_I(common,"[TIMER]:notify gived TIME_TASK2 fail");
}
vTaskDelay(500);
}
}
任务3
static void vTestTask3_L(void *pvParameters)
{
BaseType_t err = 0;
while(1)
{
LOG_I(common,"[TIMER]:notify gived TIME_TASK3");
err = xTaskNotify(task1_hander, //句柄
TIME_TASK3, //通知值
eSetValueWithOverwrite); //覆写
if(err == pdFAIL)
{
LOG_I(common,"[TIMER]:notify gived TIME_TASK3 fail");
}
vTaskDelay(700);
}
}
结果:
结果分析:
- 等待通知
- 任务2发送通知,值0x55
- 任务1等待到通知,值为0x55
- 任务1等待通知
- 任务3发送通知,值0xaa
- 任务1等待到通知,值为0xaa
- 任务1等待通知
- 定时器发送通知,值0x01
- 任务1等待到通知,值0x01
从上面结果可以看到,发送通知可以发送具体的值,通知等待函数可以获取到具体的值,根据这个值来进行处理。比如有些简单值传递的应用场景就可以使用。比如按键键值传递。