FreeRTOS学习笔记(4、事件组、任务通知)

前言

这是第四弹,由于CSDN长度的限制,所以把FreeRTOS学习分为几部分来发,这是第四部分


主要包括事件组、任务通知

第一弹:FreeRTOS学习笔记(1、FreeRTOS初识、任务的创建以及任务状态理论、调度算法等)
第二弹: FreeRTOS学习笔记(2、同步与互斥通信、队列、队列集的使用)
第三弹: FreeRTOS学习笔记(3、信号量、互斥量的使用)

事件组 event group

可以通过队列传送数据

可以通过信号量,来传递状态信息

还可以使用互斥量来实现临界资源的互斥访问(独占)

但是上述都没办法解决事件组,事件组包含多个事件
FreeRTOS学习笔记(4、事件组、任务通知)_第1张图片
事件组,右边可以等

  • 若干个事件中的某个事件
  • 某个事件
  • 若干个事件中的所有事件

image.png

从学习此知识点开始,使用Source Ingsight编辑代码,不再使用VScode

事件组的创建函数
FreeRTOS学习笔记(4、事件组、任务通知)_第2张图片

事件组的结构体
FreeRTOS学习笔记(4、事件组、任务通知)_第3张图片

EventBits_t 代表一个整数,每一位bit代表一件事件
当生产者,生产完后就可以设置这个EventGroup的某一位,表示这个事件我做完了
生产者1和生产者2所做的事情不一样,所设置的位也不一样

如果事件组里没有东西,那么消费者任务就会等待
这些任务存放在List_t xTasksWaitingForBits;链表中

使用事件组

  1. 创建事件组

FreeRTOS学习笔记(4、事件组、任务通知)_第4张图片

  1. 左边生产者set bits 设置事件的某个位

能够设置哪个事件,就去设置哪个位
FreeRTOS学习笔记(4、事件组、任务通知)_第5张图片

  1. 右边消费者 wait bits 等待事件的某个位

FreeRTOS学习笔记(4、事件组、任务通知)_第6张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第7张图片

FreeRTOS学习笔记(4、事件组、任务通知)_第8张图片

  1. 同步点

FreeRTOS学习笔记(4、事件组、任务通知)_第9张图片
假如有taskA、taskB、taskC

taskA做完某些事情后,设置bit0,等待三个任务的bit都设置为1后才可以做下一步
taskB同
taskC同
这三个task都可以调用这个函数表示完成了某件事情
三个bit位都被设置完后,这三个task都可以从这个函数里退出来

这就是同步点,可以使用这个函数来实现多个task,函数退出之后将会设置三个bit,清零

FreeRTOS学习笔记(4、事件组、任务通知)_第10张图片

注意

事件组只起通知作用,要想把数据保存起来,就要另外使用其他方法保存数据

比如队列保存数据

keil里面的代码标准

keil默认使用的C语言标准是C89,必须这样子做
使用C99,变量的定义可以定义在任何地方
可以在Keil里使用C99标准

图片写错辽 是–c99

FreeRTOS学习笔记(4、事件组、任务通知)_第11张图片

事件组的基本使用

1、创建事件组

事件组只能起通知作用,保存数据要采用其他的方法,这里采用队列
FreeRTOS学习笔记(4、事件组、任务通知)_第12张图片
注意,使用事件组时,需要定义宏后,才能使用
FreeRTOS学习笔记(4、事件组、任务通知)_第13张图片

#define configSUPPORT_DYNAMIC_ALLOCATION 1 /* 使用事件组头文件*/

2、设置事件

计算完成后,向队列中写入数据,并且设置事件组的bit0位
FreeRTOS学习笔记(4、事件组、任务通知)_第14张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第15张图片

3、等待事件

等待事件组的bit0位|bit1位后,将数据从队列中读取出来
FreeRTOS学习笔记(4、事件组、任务通知)_第16张图片

可以看到,计算结束后,设置事件组的bit0|bit1位,等待事件组的两位设置位1后,然后就从队列中将数据读取出来
FreeRTOS学习笔记(4、事件组、任务通知)_第17张图片

事件组的使用-同步点

FreeRTOS学习笔记(4、事件组、任务通知)_第18张图片
同步函数,有三个功能

  • 设置事件,表示自己完成了某个或者某些事件
  • 等待事件,和别的task同步
  • 成功返回后,清除等待的事件

同步函数退出后

  • 成功退出的话,会清除事件
  • 成功退出的话,等待哪些事件,就会清除哪些事件

image.png

1、创建事件组

FreeRTOS学习笔记(4、事件组、任务通知)_第19张图片

2、等待同步

FreeRTOS学习笔记(4、事件组、任务通知)_第20张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第21张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第22张图片
当三个bit都被设置为1之后,就可以同步继续,打印等待同步之后的内容
FreeRTOS学习笔记(4、事件组、任务通知)_第23张图片

任务通知 task notification

任务通知的基础知识

使用队列、信号量、事件组等,我们都需要事先创建对应的结构体,对方通过中间的结构体进行通信

而使用任务通知,任务结构体TCB,中就包含了内部对象,可以直接接收别人发送过来的通知

使用任务通知时,只能通知指定的task,所以是多对1的关系

FreeRTOS学习笔记(4、事件组、任务通知)_第24张图片

TCB结构体中包含了内部对象

一个是uint8_t 类型的,表示通知的状态
一个是uint32_t类型的,表示通知的值

image.png

一个TCB结构体代表一个task,别的task可以去通知它,可以往TCB结构体中放入值,通知它,进而放一个通知值

发送task往TCB结构体中放入值的时候,要么成功,要么失败,都不会进入到阻塞状态

目标task可以等待,无数据时,可以阻塞等待
有数据时,即刻返回

在TCB结构体中只有一个通知状态,并没有List列表存放阻塞的task
并不像队列一样让其存放在链表中,从而阻塞等待

当发送task将通知值放入TCB结构体中时,将会改变任务通知的状态,并且将目标task唤醒

所以是多对1的关系
FreeRTOS学习笔记(4、事件组、任务通知)_第25张图片

任务通知状态的取值

image.png
ucNotifyState 通知状态的取值为

  • taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
  • taskWAITING_NOTIFICATION:任务在等待通知
  • taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为pending(有数据了,待处理)
  1. 一开始创建taskA时,任务通知的状态的初始值为taskNOT_WAITING_NOTIFICATION,taskA没有在等待通知
  2. taskA想要等待通知的话,taskB可以调用ulTaskNotifyTake函数或 xTaskNotifyWait函数,将会进入taskWAITING_NOTIFICATION状态,taskA在等待通知
  3. taskB可以调用xTaskNotify或xTaskNotifyGive函数来通知taskA

此时taskA的任务状态为taskNOTIFICATION_RECEIVED,表示接收到了数据,待处理
同时还会将taskA,从阻塞状态变为就绪状态,taskA开始运行

任务通知的两类函数

FreeRTOS学习笔记(4、事件组、任务通知)_第26张图片

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 );

任务通知的优缺点

  1. 传递数据/发送事件时,更快
  2. 更节省内存,因为无需创建通信对象
  3. 不能使用任务通知给ISR发送数据/发送事件,ISR不属于任务
  4. 接收方只有一个任务
  5. 无法缓冲多个数据,任务通知只能保存1个数据
  6. 无法向多个任务进行广播
  7. 发送方无法阻塞

任务通知的使用-轻量级信号量

FreeRTOS学习笔记(4、事件组、任务通知)_第27张图片

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait);

FreeRTOS学习笔记(4、事件组、任务通知)_第28张图片

xClearCountOnExit,函数返回前是否清零

  • pdTRUE,将通知值清零
  • pdFALSE,如果通知之大于0,就把通知值减1

ulTaskNotifyTake()函数的返回值有两种情况

  • 大于0,在超时之前,通知值被增加了,返回的是通知值
  • 等于0,一直没有其他task增加通知量,超时返回

信号量和使用任务通知实现信号量的辨析

信号量
  1. 创建信号量
  2. taskA完成事件后,give信号量,让信号量计数值+1
  3. taskB等待信号量,take信号量,让信号量计数值-1
  4. taskA可以give N 次,taskB可以take N次

FreeRTOS学习笔记(4、事件组、任务通知)_第29张图片

任务通知实现信号量
  1. taskA发送任务通知给taskB
  2. taskB任务通知值++
  3. taskA类似于give操作,taskB类似于take操作
  4. taskB take几次,取决于xClearCountOnExit
    1. pdTRUE,退出时将通知值清零
    2. pdFALSE,退出时如果通知之大于0,就把通知值减1

xClearCountOnExit采用pdFALSE,时,和一般的信号量是一样的

FreeRTOS学习笔记(4、事件组、任务通知)_第30张图片

如果采用pdTRUE,只能take一次,因为在take后就将任务通知值-1了
FreeRTOS学习笔记(4、事件组、任务通知)_第31张图片

1/通知其他任务

使用xTaskNotifyGive函数通知其他任务,使其他任务,通知值+1
FreeRTOS学习笔记(4、事件组、任务通知)_第32张图片

2/等待任务通知

FreeRTOS学习笔记(4、事件组、任务通知)_第33张图片

FreeRTOS学习笔记(4、事件组、任务通知)_第34张图片

两者区别

任务通知不需要创建结构体,直接使用TCB结构体中的任务通知值和任务状态即可

如果任务通知想向信号量一样使用

那么xClearCountOnExit参数的选择需要选择pdFALASE,否则选择pdTRUE的话,任务通知,give++,任务接收时只能接收一次

任务通知的使用-轻量级队列

队列和任务通知实现队列的区别

1、队列

task可以向队列中写入数据,也可以向队列中接收数据,在写入数据时可以等待,在接收数据时也要可以等待

并且在队列的创建时,可以指定队列的长度和大小(字符串、结构体、整数等)

2、任务通知

而对于任务通知
TCB结构体中的任务通知值和任务状态,

任务通知值只能够存放一个数据,且是32位的

发送通知task,有两种情况,要么覆盖,要么不覆盖数据

覆盖指的是,发送第一个数据,存放至通知值中,发送第二个数据时,覆盖第一次的数据,此时,通知值中是第二次的数据
不覆盖的话,存入数据时将会不成功

接收任务通知,可以读取任务通知值

3、区别

任务通知不需要创建结构体
FreeRTOS学习笔记(4、事件组、任务通知)_第35张图片

同时

发送数据

BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction);

第一个参数是任务句柄

第二个参数就是存入到任务通知值的值

第三个参数是选择实现 覆盖Or 不覆盖

FreeRTOS学习笔记(4、事件组、任务通知)_第36张图片

接收数据

BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t *pulNotificationValue,TickType_t xTicksToWait);
  • 队列的长度和大小可以指定
  • 任务通知只有1个数据,通知值,数据是32位的
  • 队列,向队列写数据或者读数据时,可以阻塞
  • 任务通知,写队列时不可以阻塞
  • 队列,如果队列的长度是1,可以选择覆盖队列
  • 任务通知,可以覆盖也可以不覆盖

这个轻量级的队列的长度只有1

队列实现

task1向队列中写入了十个数据,task2就可以从队列中取出这十个数据
FreeRTOS学习笔记(4、事件组、任务通知)_第37张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第38张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第39张图片

任务通知实现

1、通知值不覆盖

FreeRTOS学习笔记(4、事件组、任务通知)_第40张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第41张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第42张图片

2、通知值覆盖

FreeRTOS学习笔记(4、事件组、任务通知)_第43张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第44张图片

这里注意了,任务通知,任务通知写入多次,但是等待任务通知只能读一次
但是邮箱,一旦邮箱中有数据,可以多次读,都会成功

任务通知的使用-轻量级事件组

可以通过设置xTaskNotify()函数的eNotifyAction参数,设置为eSetBits时,即可实现轻量级事件组

事件组和任务通知实现事件组

事件组

任务完成后,可以设置事件组中的某一位
等待事件组的某一位或某几位或者所有位

假如,taskA,完成事件后,设置bit0,taskB完成事件后,设置bit1
这是等待事件时,有两种情况

  • 等待bit0或bit1
  • 等待bit0和bit1

当等待的事件为或的时候,每设置一位,就会被唤醒一次,比如设置了bit0,将会被唤醒一次,设置了bit1,将会被唤醒第二次
当等待的事件为和的时候,只有当两个事件位都被设置,才会被唤醒
FreeRTOS学习笔记(4、事件组、任务通知)_第45张图片

任务通知实现事件组

通过设置xTaskNotify()函数的eNotifyAction参数,设置为eSetBits时,即可实现轻量级事件组

此时ulvalue将会等于val = val | ulValue

此时一旦taskA,调用**xTaskNotify()**函数,就会唤醒目标任务taskB


但是事件组,设置了某些位后,要等待这些位满足条件才能被唤醒

发送方可以设置事件,但是接收方并不能等待指定的事件,不能等待若干个事件中的任意一个或多个
一旦有事件,总会唤醒task

事件组实现

FreeRTOS学习笔记(4、事件组、任务通知)_第46张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第47张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第48张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第49张图片
可以看到事件组,task3在等待两位都被设置为1之后,才被唤醒,进入运行状态

任务通知实现事件组

任务通知实现事件组并不能实现,等待某些事件或者某个事件

每被通知一次,将会被唤醒一次

FreeRTOS学习笔记(4、事件组、任务通知)_第50张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第51张图片
FreeRTOS学习笔记(4、事件组、任务通知)_第52张图片

你可能感兴趣的:(FreeRTOS,学习,笔记,stm32,学习分享,FreeRTOS)