FreeRTOS(3):事件组、任务通知
1.事件组
学校组织秋游,组长在等待:
⚫ 张三:我到了
⚫ 李四:我到了
⚫ 王五:我到了
⚫ 组长说:好,大家都到齐了,出发!
秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好 就交谁的。
在这个日常生活场景中:
⚫ 出发:要等待这 3 个人都到齐,他们是"与"的关系
⚫ 交报告:只需等待这 3 人中的任何一个,他们是"或"的关系 在 FreeRTOS 中,可以使用事件组(event group)来解决这些问题。
1.1 事件组概念
事件组可以简单地认为就是一个整数:
⚫ 每一位表示一个事件
⚫ 每一位事件的含义由程序员决定,比如:Bit0 表示用来串口是否就绪,Bit1 表示按键是否被按下
⚫ 这些位,值为 1 表示事件发生了,值为 0 表示事件没发生
⚫ 一个或多个任务、ISR 都可以去写这些位;一个或多个任务、ISR 都可以去 读这些位
⚫ 可以等待某一位、某些位中的任意一个,也可以等待多位
事件标志组本质是一个 16 位或 32 位无符号的数据类型 EventBits_t ,由 configUSE_16_BIT_TICKS决定。
事件组操作
事件组和队列、信号量等不太一样,主要集中在 2 个地方:
⚫ 唤醒谁?
◼ 队列、信号量:事件发生时,只会唤醒一个任务
◼ 事件组:事件发生时,会唤醒所有符号条件的任务,简单地说它有"广播"的 作用
⚫ 是否清除事件?
◼ 队列、信号量:是消耗型的资源,队列的数据被读走就没了;信号量被获取 后就减少了
◼ 事件组:被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件
1.2 事件组相关API
参考链接:https://blog.csdn.net/qq_73379310/article/details/132052243
函数 | 描述 |
xEventGroupCreate() | 使用动态方式创建事件标志组 |
xEventGroupCreateStatic() | 使用静态方式创建事件标志组 |
xEventGroupClearBits() | 清零事件标志位 |
xEventGroupClearBitsFromISR() | 在中断中清零事件标志位 |
xEventGroupSetBits() | 设置事件标志位 |
xEventGroupSetBitsFromISR() | 在中断中设置事件标志位 |
xEventGroupWaitBits() | 等待事件标志位 |
1.2.1 创建事件组
1 | EventGroupHandle_t xEventGroupCreate( void ); |
参数:
无
返回值:
成功,返回对应事件标志组的句柄;
失败,返回 NULL 。
1.2.2 设置事件标志位
1 | EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet ); |
参数:
xEventGroup:对应事件组句柄。 uxBitsToSet:指定要在事件组中设置的一个或多个位的按位值。
返回值:
设置之后事件组中的事件标志位值。
1.2.3 等待事件标志位
1 2 3 4 5 6 | EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait ); |
参数:
xEventGroup:对应的事件标志组句柄
uxBitsToWaitFor:指定事件组中要等待的一个或多个事件位的按位值
xClearOnExit:pdTRUE——清除对应事件位,pdFALSE——不清除
xWaitForAllBits:
pdTRUE——所有等待事件位全为1(逻辑与),pdFALSE——等待的事件位有一个为1(逻辑或)
xTicksToWait:超时
返回值:
等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位
其他值:等待事件标志位失败,返回事件组中的事件标志位
1.2.4 清除事件标志位
1 2 | EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear ); |
参数:
xEventGroup:对应事件组句柄。 uxBitsToClear:指定要在事件组中清除的一个或多个位的按位值。
返回值:
清零之前事件组中事件标志位的值。
2.任务通知
所谓"任务通知",你可以反过来读"通知任务"。
我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可以明确指定:通知哪个任务。
使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构体通信:
使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的" 通知":
2.1 任务通知的特性
2.1.1优势及限制
任务通知的优势:
⚫ 效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高。比队 列、信号量、事件组都有大的优势。
⚫ 更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
任务通知的限制:
⚫ 不能发送数据给 ISR:
ISR 并没有任务结构体,所以无法使用任务通知的功能给 ISR 发送数据。
但是 ISR 可以使用任务通知的功能,发数据给任务。
⚫ 数据只能给该任务独享
使用队列、信号量、事件组时,数据保存在这些结构体中,其他任务、ISR 都可以访问这些数据。使用任务通知时,数据存放入目标任务中,只有它可以访问这些数据。
在日常工作中,这个限制影响不大。因为很多场合是从多个数据源把数据发给某个任务,而不是把一个数据源的数据发给多个任务。
⚫ 无法缓冲数据
使用队列时,假设队列深度为 N,那么它可以保持 N 个数据。
使用任务通知时,任务结构体中只有一个任务通知值,只能保持一个数据。
⚫ 无法广播给多个任务
使用事件组可以同时给多个任务发送事件。
使用任务通知,只能发个一个任务。
⚫ 如果发送受阻,发送方无法进入阻塞状态等待
假设队列已经满了,使用 xQueueSendToBack()给队列发送数据时,任务可以进入阻塞状态等待发送完成。
使用任务通知时,即使对方无法接收数据,发送方也无法阻塞等待,只能即刻返回错误。
2.1.2 通知状态和通知值
每个任务都有一个结构体:TCB(Task Control Block),里面有 2 个成员:
⚫ 一个是 uint8_t 类型,用来表示通知状态
⚫ 一个是 uint32_t 类型,用来表示通知值
1 2 3 4 5 6 7 8 | typedef struct tskTaskControlBlock { ...... /* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */ volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; ...... } tskTCB; |
通知状态有3种取值:
⚫ taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
⚫ taskWAITING_NOTIFICATION:任务在等待通知
⚫ taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为 pending(有 数据了,待处理)
通知值可以有很多种类型: ⚫ 计数值 ⚫ 位(类似事件组) ⚫ 任意数值
FreeRTOS 提供以下几种方式发送通知给任务 :
发送消息给任务,如果有通知未读, 不覆盖通知值
发送消息给任务,直接覆盖通知值
发送消息给任务,设置通知值的一个或者多个位
发送消息给任务,递增通知值
通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。
2.2 任务通知相关API
参考链接:https://blog.csdn.net/qq_73379310/article/details/132052243
2.2.1 发送通知
函数 | 描述 |
xTaskNotify() | 发送通知,带有通知值 |
xTaskNotifyAndQuery() | 发送通知,带有通知值并且保留接收任务的原通知值 |
xTaskNotifyGive() | 发送通知,不带通知值 |
xTaskNotifyFromISR() | 在中断中发送任务通知 |
xTaskNotifyAndQueryFromISR() | 在中断中发送任务通知 |
vTaskNotifyGiveFromISR() | 在中断中发送任务通知 |
1 2 3 | BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction ); |
参数:
xTaskToNotify:需要接收通知的任务句柄;
ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
eAction:一个枚举,代表如何使用任务通知的值;
枚举值 | 描述 |
eNoAction | 发送通知,但不更新值(参数ulValue未使用) |
eSetBits | 被通知任务的通知值按位或ulValue。(某些场景下可代替事件组,效率更高) |
eIncrement | 被通知任务的通知值增加1(参数ulValue未使用),相当于xTaskNotifyGive |
eSetValueWithOverwrite | 被通知任务的通知值设置为 ulValue。(某些场景下可代替xQueueOverwrite ,效率更高) |
eSetValueWithoutOverwrite |
如果被通知的任务当前没有通知,则被通知的任务的通知值设为ulValue。 如果被通知任务没有取走上一个通知,又接收到了一个通知,则这次通知值丢弃,在这种情况下视为调用失败并返回pdFALSE (某些场景下可代替xQueueSend ,效率更高) |
返回值:
如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回
pdFALSE, 而其他情况均返回pdPASS。
1 2 3 4 | BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue ); |
参数:
xTaskToNotify:需要接收通知的任务句柄;
ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
eAction:一个枚举,代表如何使用任务通知的值;
pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价于函数 xTaskNotify()。
返回值:
如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回pdFALSE, 而其他情况均返回pdPASS。
1 | BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); |
参数:
xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1。
返回值:
总是返回 pdPASS。
2.2.2 等待通知
等待通知API函数只能用在任务,不可应用于中断中!
函数 | 描述 |
ulTaskNotifyTake() |
获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。 当任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。 |
xTaskNotifyWait() | 获取任务通知,比 ulTaskNotifyTak()更为复杂,可获取通知值和清除通知值的指定位 |
1 2 | uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); |
参数:(关键)
xClearCountOnExit:指定在成功接收通知后,将通知值清零或减 1,
pdTRUE:把通知值清零(二值信号量);
pdFALSE:把通知值减一(计数型信号量);
xTicksToWait:阻塞等待任务通知值的最大时间;
返回值:
0:接收失败
非0:接收成功,返回任务通知的通知值
1 2 3 4 | BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ); |
ulBitsToClearOnEntry:函数执行前清零任务通知值那些位 。
ulBitsToClearOnExit:表示在函数退出前,清零任务通知值那些位,在清 0 前,接收到的任务通知值会先被保存到形参*pulNotificationValue 中。
pulNotificationValue:用于保存接收到的任务通知值。 如果不需要使用,则设置为 NULL 即可 。
xTicksToWait:等待消息通知的最大等待时间。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了