这是第四弹,由于CSDN长度的限制,所以把FreeRTOS学习分为几部分来发,这是第四部分
主要包括事件组、任务通知
等
第一弹
:FreeRTOS学习笔记(1、FreeRTOS初识、任务的创建以及任务状态理论、调度算法等)
第二弹
: FreeRTOS学习笔记(2、同步与互斥通信、队列、队列集的使用)
第三弹
: FreeRTOS学习笔记(3、信号量、互斥量的使用)
可以通过队列传送数据
可以通过信号量,来传递状态信息
还可以使用互斥量来实现临界资源的互斥访问(独占)
但是上述都没办法解决事件组,事件组包含多个事件
事件组,右边可以等
从学习此知识点开始,使用Source Ingsight编辑代码,不再使用VScode
EventBits_t 代表一个整数,每一位bit代表一件事件
当生产者,生产完后就可以设置这个EventGroup的某一位,表示这个事件我做完了
生产者1和生产者2所做的事情不一样,所设置的位也不一样
如果事件组里没有东西,那么消费者任务就会等待
这些任务存放在List_t xTasksWaitingForBits
;链表中
使用事件组
taskA做完某些事情后,设置bit0,等待三个任务的bit都设置为1后才可以做下一步
taskB同
taskC同
这三个task都可以调用这个函数表示完成了某件事情
三个bit位都被设置完后,这三个task都可以从这个函数里退出来
这就是同步点,可以使用这个函数来实现多个task,函数退出之后将会设置三个bit,清零
注意
事件组只起通知作用,要想把数据保存起来,就要另外使用其他方法保存数据
比如队列保存数据
keil默认使用的C语言标准是C89,必须这样子做
使用C99,变量的定义可以定义在任何地方
可以在Keil里使用C99标准
图片写错辽 是–c99
事件组只能起通知作用,保存数据要采用其他的方法,这里采用队列
注意,使用事件组时,需要定义宏后,才能使用
#define configSUPPORT_DYNAMIC_ALLOCATION 1 /* 使用事件组头文件*/
等待事件组的bit0位|bit1位后,将数据从队列中读取出来
可以看到,计算结束后,设置事件组的bit0|bit1位,等待事件组的两位设置位1后,然后就从队列中将数据读取出来
同步函数退出后
当三个bit都被设置为1之后,就可以同步继续,打印等待同步之后的内容
使用队列、信号量、事件组等,我们都需要事先创建对应的结构体,对方通过中间的结构体进行通信
而使用任务通知,任务结构体TCB,中就包含了内部对象,可以直接接收别人发送过来的通知
使用任务通知时,只能通知指定的task,所以是多对1的关系
TCB结构体中包含了内部对象
一个是uint8_t 类型的,表示通知的状态
一个是uint32_t类型的,表示通知的值
一个TCB结构体代表一个task,别的task可以去通知它,可以往TCB结构体中放入值,通知它,进而放一个通知值
发送task往TCB结构体中放入值的时候,要么成功,要么失败,都不会进入到阻塞状态
目标task可以等待,无数据时,可以阻塞等待
有数据时,即刻返回
在TCB结构体中只有一个通知状态,并没有List列表存放阻塞的task
并不像队列一样让其存放在链表中,从而阻塞等待
当发送task将通知值放入TCB结构体中时,将会改变任务通知的状态,并且将目标task唤醒
taskNOT_WAITING_NOTIFICATION
:任务没有在等待通知taskWAITING_NOTIFICATION
:任务在等待通知taskNOTIFICATION_RECEIVED
:任务接收到了通知,也被称为pending(有数据了,待处理)taskNOT_WAITING_NOTIFICATION
,taskA没有在等待通知等待通知
的话,taskB可以调用ulTaskNotifyTake
函数或 xTaskNotifyWait
函数,将会进入taskWAITING_NOTIFICATION状态
,taskA在等待通知xTaskNotify或xTaskNotifyGive函数
来通知taskA此时taskA的任务状态为taskNOTIFICATION_RECEIVED
,表示接收到了数据,待处理
同时还会将taskA,从阻塞状态变为就绪状态,taskA开始运行
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait);
xClearCountOnExit,函数返回前是否清零
ulTaskNotifyTake()函数的返回值有两种情况
xClearCountOnExit采用pdFALSE,时,和一般的信号量是一样的
如果采用pdTRUE,只能take一次,因为在take后就将任务通知值-1了
使用xTaskNotifyGive
函数通知其他任务,使其他任务,通知值+1
任务通知不需要创建结构体,直接使用TCB结构体中的任务通知值和任务状态即可
如果任务通知想向信号量一样使用
那么xClearCountOnExit
参数的选择需要选择pdFALASE,否则选择pdTRUE的话,任务通知,give++,任务接收时只能接收一次
task可以向队列中写入数据,也可以向队列中接收数据,在写入数据时可以等待,在接收数据时也要可以等待
并且在队列的创建时,可以指定队列的长度和大小(字符串、结构体、整数等)
而对于任务通知
TCB结构体中的任务通知值和任务状态,
任务通知值只能够存放一个数据,且是32位的
发送通知task,有两种情况,要么覆盖,要么不覆盖数据
覆盖指的是,发送第一个数据,存放至通知值中,发送第二个数据时,覆盖第一次的数据,此时,通知值中是第二次的数据
不覆盖的话,存入数据时将会不成功
接收任务通知,可以读取任务通知值
同时
发送数据
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction);
第一个参数是任务句柄
第二个参数就是存入到任务通知值的值
第三个参数是选择实现 覆盖Or 不覆盖
接收数据
BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t *pulNotificationValue,TickType_t xTicksToWait);
这个轻量级的队列的长度只有1
task1向队列中写入了十个数据,task2就可以从队列中取出这十个数据
这里注意了,任务通知,任务通知写入多次,但是等待任务通知只能读一次
但是邮箱,一旦邮箱中有数据,可以多次读,都会成功
可以通过设置xTaskNotify()
函数的eNotifyAction
参数,设置为eSetBits
时,即可实现轻量级事件组
任务完成后,可以设置事件组中的某一位
等待事件组的某一位或某几位或者所有位
假如,taskA,完成事件后,设置bit0,taskB完成事件后,设置bit1
这是等待事件时,有两种情况
当等待的事件为或的时候,每设置一位,就会被唤醒一次,比如设置了bit0,将会被唤醒一次,设置了bit1,将会被唤醒第二次
当等待的事件为和的时候,只有当两个事件位都被设置,才会被唤醒
通过设置xTaskNotify()
函数的eNotifyAction
参数,设置为eSetBits
时,即可实现轻量级事件组
此时ulvalue将会等于val = val | ulValue
此时一旦taskA,调用
**xTaskNotify()**
函数,就会唤醒目标任务taskB
但是事件组,设置了某些位后,要等待这些位满足条件才能被唤醒
发送方可以设置事件,但是接收方并不能等待指定的事件,不能等待若干个事件中的任意一个或多个
一旦有事件,总会唤醒task
可以看到事件组,task3在等待两位都被设置为1之后,才被唤醒,进入运行状态
任务通知实现事件组并不能实现,等待某些事件或者某个事件
每被通知一次,将会被唤醒一次