FreeRTOS(3):事件组、任务通知

发布时间 2023-11-15 21:20:40作者: xsgcumt

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 创建事件组

EventGroupHandle_t xEventGroupCreate( void );

参数:

        无

返回值:

        成功,返回对应事件标志组的句柄;

        失败,返回 NULL 。

1.2.2 设置事件标志位

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet );

参数:

        xEventGroup:对应事件组句柄。 uxBitsToSet:指定要在事件组中设置的一个或多个位的按位值。

返回值:

        设置之后事件组中的事件标志位值。

1.2.3 等待事件标志位

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 清除事件标志位

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 类型,用来表示通知值

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() 在中断中发送任务通知

 

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。

 

BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
                        uint32_t ulValue,
                        eNotifyAction eAction,
                        uint32_t *pulPreviousNotifyValue );

  

参数:

xTaskToNotify:需要接收通知的任务句柄;

ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;

eAction:一个枚举,代表如何使用任务通知的值;

pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价于函数 xTaskNotify()。

返回值:

如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回pdFALSE, 而其他情况均返回pdPASS。

BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

  

参数:

        xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1。

返回值:

        总是返回 pdPASS。

 

2.2.2 等待通知

  等待通知API函数只能用在任务,不可应用于中断中!

函数 描述
ulTaskNotifyTake()

获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。

当任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。

xTaskNotifyWait() 获取任务通知,比 ulTaskNotifyTak()更为复杂,可获取通知值和清除通知值的指定位

 

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
                            TickType_t xTicksToWait );

  

参数:(关键)

xClearCountOnExit:指定在成功接收通知后,将通知值清零或减 1,

  pdTRUE:把通知值清零(二值信号量);

  pdFALSE:把通知值减一(计数型信号量);

xTicksToWait:阻塞等待任务通知值的最大时间;

返回值:

0:接收失败

非0:接收成功,返回任务通知的通知值

 

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
                    uint32_t ulBitsToClearOnExit,
                    uint32_t *pulNotificationValue,
                    TickType_t xTicksToWait );

 

ulBitsToClearOnEntry:函数执行前清零任务通知值那些位 。

ulBitsToClearOnExit:表示在函数退出前,清零任务通知值那些位,在清 0 前,接收到的任务通知值会先被保存到形参*pulNotificationValue 中。

pulNotificationValue:用于保存接收到的任务通知值。 如果不需要使用,则设置为 NULL 即可 。

xTicksToWait:等待消息通知的最大等待时间。