FreeRTOS 任务通知使用教程
任务通知是 FreeRTOS 提供的一种高效的线程间通信机制。每个 RTOS 任务都有一个 32 位的通知值,任务创建时,这个值被初始化为 0。任务通知相当于直接向任务发送一个事件,接收到通知的任务可以解除阻塞状态,前提是这个阻塞事件是因等待通知而引起的。发送通知的同时,也可以可选地改变接收任务的通知值。本文将详细介绍如何在 FreeRTOS 中使用任务通知。
任务通知的更新方式
可以通过以下方法向接收任务更新通知:
- 不覆盖接收任务的通知值
- 覆盖接收任务的通知值
- 设置接收任务通知值的某些位
- 增加接收任务的通知值
1. 任务控制块的通知定义
任务通知的定义在 taskTCB
任务控制块中,受控于宏 configUSE_TASK_NOTIFICATIONS
。该宏默认开启,所以任务通知功能默认启用。
2. 任务通知的基本用法
任务通知可以只发送通知,不附带任何值。这种用法类似于信号量,因此任务通知的接口也提供了类似信号量接口的形式,方便使用和区分:
xTaskNotifyGive
:释放任务通知(类似于xSemaphoreGive
释放信号量)ulTaskNotifyTake
:获取任务通知(类似于xSemaphoreTake
获取信号量)
2.1 使用示例
以下示例展示了 task1
每隔 8 秒释放一个任务通知给 task2
,task2
每隔 1 秒尝试获取任务通知,获取超时时间为 10 秒。
void NotifyTask1_entry(void* parameter)
{
uint32_t send1 = 1;
uint32_t ret_num;
while(1)
{
vTaskDelay(configTICK_RATE_HZ*8);
ret = xTaskNotifyGive(task2StaticHandle);
}
}
void NotifyTask2_entry(void* parameter)
{
while(1)
{
ret = ulTaskNotifyTake(pdTRUE, configTICK_RATE_HZ*10);
vTaskDelay(configTICK_RATE_HZ);
}
}
2.2 烧写验证
将上述代码烧写到设备中进行验证,可以观察到 task2
每获取通知后间隔 1 秒再尝试获取任务通知,并在超时时间内成功获取通知。
3. 附带值的任务通知用法
任务通知也可以附带值使用,以下是相关函数:
xTaskNotify
:释放任务通知,并附带更新的任务通知值,可以设定任务通知值的更新方式xTaskNotifyWait
:获取任务通知
3.1 使用示例
以下示例展示了 task3
每隔 6 秒释放一个任务通知给 task4
,通知值为 1,并以通知值累加的方式进行。task4
每隔 1 秒尝试获取任务通知,获取超时时间为 8 秒。由于没有清除通知值,且使用了通知值累加的方式,可以预期到获取到的通知值会一直累加。
void NotifyTask3_entry(void* parameter)
{
while(1)
{
vTaskDelay(configTICK_RATE_HZ*6);
ret = xTaskNotifyGive(task4StaticHandle, 1, eIncrement);
}
}
void NotifyTask4_entry(void* parameter)
{
uint32_t comeIMG = 0;
while(1)
{
ret = ulTaskNotifyWait(pdFALSE, pdFALSE, &comeIMG, configTICK_RATE_HZ*8);
vTaskDelay(configTICK_RATE_HZ);
}
}
3.2 烧写验证
将上述代码烧写到设备中进行验证,可以观察到 task4
每隔 1 秒尝试获取任务通知,并成功获取累加的通知值。如果在 8 秒内没有收到通知,则会超时。
4. 任务通知的高级用法
任务通知还提供一个接口,释放通知时可以获取到前一个未被获取的通知值。可以根据这个参数以及函数的返回值,判断前一个通知值是什么以及是否被更新:
xTaskNotifyAndQuery
:如果通知值更新方式被设置为eSetValueWithoutOverwrite
,则不能被覆写。如果前一次释放的任务通知值未被获取,就再次触发任务通知的释放,那么其会返回pdFALSE
,通知值更新失败。此时可以根据最后一个参数,查询前一个通知值是什么。
4.1 使用示例
以下示例展示了 task5
每隔 2 秒释放一次任务通知,通知值以非覆写的方式进行,通知值在 5 和 6 之间来回变化;task6
每隔 5 秒获取一次任务通知,获取超时时间同样为 5 秒。预期的结果是 task5
在成功释放一次任务通知后,会出现一次释放任务通知失败,第一次失败的通知值应该为 6。
void NotifyTask5_entry(void* parameter)
{
static uint8_t notify6 = 0;
uint32_t ret_num;
while(1)
{
vTaskDelay(configTICK_RATE_HZ*2);
if (notify6 == 1)
{
ret = xTaskNotifyAndQuery(task6StaticHandle, 6, eSetValueWithoutOverwrite, &ret_num);
notify6 = 0;
}
else
{
ret = xTaskNotifyAndQuery(task6StaticHandle, 5, eSetValueWithoutOverwrite, &ret_num);
notify6 = 1;
}
}
}
void NotifyTask6_entry(void* parameter)
{
uint32_t comeIMG = 0;
while(1)
{
ret = ulTaskNotifyWait(pdFALSE, pdFALSE, &comeIMG, configTICK_RATE_HZ*5);
vTaskDelay(configTICK_RATE_HZ*5);
}
}
4.2 烧写验证
将上述代码烧写到设备中进行验证,可以观察到 task5
在成功发送一次通知后,会出现一次发送失败,并打印前一个通知值。同时,task6
每隔 5 秒成功接收一次通知。
4.3 非覆写模式的效果
使用 xTaskNotifyAndQuery
接口时,如果通知值的更新方式没有设置为非覆写模式,则每次发送通知都会成功,之前的通知值会被覆写。
void NotifyTask5_entry(void* parameter)
{
static uint8_t notify6 = 0;
uint32_t ret_num;
while(1)
{
vTaskDelay(configTICK_RATE_HZ*2);
ret = xTaskNotifyAndQuery(task6StaticHandle, 6, eIncrement, &ret_num);
}
}
4.4 烧写验证
将上述代码烧写到设备中进行验证,可以观察到每次发送通知都会成功,通知值不断被覆写。
通过本教程,我们详细介绍了 FreeRTOS 任务通知的基本用法和高级用法。任务通知提供了一种高效、灵活的任务间通信机制,可以选择不同的通知值更新方式来满足不同的需求。希望这篇文章能帮助您更好地理解和使用 FreeRTOS 的任务通知功能。如果有任何疑问或需要进一步的指导,欢迎在评论区留言,我们将尽快回复。
对应的 demo 源码, 请点击 RtosExPro at freertos_communication_notify
也可扫码关注博主同名公众号"不解之榬",回复 “freeRTOS” 获取