FreeRTOS
- FreeRTOS
- 一丶FreeRTOS基础部分
- 二丶裸机开发 与 FreeRTOS
- 三丶快速移植FreeRTOS
- 四丶任务的创建与删除
- 五丶动手实操进行任务的创建
- 六丶任务调度
- 七丶任务的状态 4种
- 八丶任务小实验
- 九丶任务 入口函数 绑定 任务创建函数
- 十丶队列
- 传递信息的方法
- 队列基本知识
- 十一丶队列API 3种
- 十二丶队列API 实操
- 十三丶信号量semaphore
- 十四丶二值型信号量
- 十五丶计数型信号量
- 十六丶互斥型信号量Mutex 不用于中断
- 十七丶互斥型信号量任务实操
- 十八丶互斥型信号存在意义
- 十九丶事件标志组
FreeRTOS
FreeRTOS资料源码直接去官网下载。
1.学习FreeRTOS需要有C语言基础和STM32基础。
2. FreeRTOS操作系统,轻理论,主要是重动手实操。即多写代码,多做项目。
一丶FreeRTOS基础部分
a.为什么要学习FreeRTOS?
1.单片机裸机开发钱少,FreeRTOS更加有钱途。
2.有FreeRTOS基础学习LIinux帮助十分大。因为操作系统是相通的,知识点是相似的。
b.为什么选择FreeRTOS?
1.FreeRTOS是免费的。
2.WIFI,蓝牙等模块芯片产商以FreeRTOS作为操作系统天
3.简单且小巧,该系统文件数量少
c.FreeRTOS介绍?
Free:免费
RTOS(Real time operating system):;实时操作系统
1.FreeRTOS不是实际上的实时操作系统,而是分时复用的。
1.RTOS是一类操作系统,不是一个。uc/OS,FreeRTOS,RT-Thread这些都属于RTOS。
2.轻量级操作系统,代码量很少的操作系统。但功能有:任务管理,时间管理,信号量,消息队列,内存管理,记录功能,软件定时器器,协程等
3.RTOS主要占据RAM系统资源,小RAM单片机上运行。
4.FreeRTOS是免费的,可移植的,可裁剪的
二丶裸机开发 与 FreeRTOS
游戏和女朋友不可同时兼得。但可以玩1秒游戏,陪女朋友1秒,但把人累死。
而CPU是无情战斗机器,两个或者多个任务间快速切换,CPU从而实现两者兼顾。
分时复用,实现多任务
以前老电影:一帧图片一帧图片的放,但大脑里是连冠的电影。
CPU而言,每个任务都是独立运行的,互不影响,切换频率很快,像同时运行一样。
原理
1.FreeRTOS实现多任务的原理:该系统不是实时操作系统而是分时复用的。即把时间分成很多时间片,轮流去执行每一个任务。
把时间分成一片一片的,即时间片。由于切换频率很快,导致认为是同时运行。
2. 即一句话理解:很短时间内,同时实现多个任务,但实际上是分时复用的,切换频率高,察觉不到罢了。
3
三丶快速移植FreeRTOS
1.作为新手只需要掌握快速移植FreeRTOS到单片机上面,工作需要的话,才去学习手动移植。
移植FreeRTOS移植到STM32单片机上的方法有两种。
方法一:手动移植,特别的复杂 手动移植,参考博文FreeRTOS移植到STM32
方法二:使用CubeMX快速移植
CubeMX快速移植步骤
CubeMX快速移植FreeRTOS具体步骤:
1.选芯片
2.选串口可修改,同时注意修改时基,选任意TIM即可
3.以前时钟配置不变
4.点击Wliddleware,选FreeRTOS,选不同版本的FreeRTOS移值即可
- 工程名字,命名为muban
CubeMX快速移植时注意打开串口
已经创建muban工程,遗漏打开串口用于logol打印。
注意打开串口时,
串口打开步骤,
1.uart.c里面插入fputc代码,注意需要修改成相应定时器
2.注意勾选魔术棒里MI库
3.注意加头文件,注意验证串口是否打开成功
插入代码,如下
int fputc(int ch ,FILE*f)
{
unsigned char temp[1] = {ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
验证代码,如下
printf("FreeRTOS Finished!\n\r");
检测是否移植成功
1.分析main函数,可以看到多一个FreeRTOS中间层程序
2.检查FreeRTOS移植到STM32单片机是否成功
一些常见问题
1.Timebase Source不能选SysTick.
裸机开发是不带操作系统的,裸机时钟源头是SysTick .而FreeRTOS占用SysTick生成1ms定时,用于任务调度。故需要其他总线提供时钟源
所以说不能共用SysTick,FreeRTOS需要一个SysTick用于定时,需要一个其他总线用于时钟源
2.FreeRTOS版本不同对应其内核版本不同
大多数情况下的大多数需求只需要选择CMISIS—V1即可CMSIS-RTOS version 2.00或者1.02
但对于嵌入式系统而言,内核版本越高不是越好。只需满足我们的需求即可。
FreeRTOS系统详细配置选项卡
通常移植内核只需改动三个地方即可,其他不需要改动。
具体配置
1.Timers and Semapheres定时器和信号量
创建各种各样的定时器 和 信号量
2.Mutexes互斥量
3.Everts事件
4.FreeRTOS Heap Usage 堆栈相关的
5.Config parameters 内核相关配置
内核支持裁剪,想要就Enable不想要就disable
6.include paraments
任务相关的函数的启用或者禁用
7.Advanced settings高级设置
8.Uer Constans 用户定义的宏
9.Tacks and Queues任务和队列,以后去了解
内核配置翻译
参考博文
通常移植内核只需改动三个地方即可,其他不需要改动
内核配置说明 ,详细学习查看博文:FreeRTOS高级篇6—FreeRTOS信号量分析
四丶任务的创建与删除
- 操作系统FreeRTOS已经移植到STM32上。任务就相当于LINUX里的线程或者进程。
- FreeRTOS提任务创建与删除相关函数。这些函数是系统函数。任务创建与删除一般有三个。
一个动态创建任务
一个静态创建任务
最后是 删除任务
任务的含义
什么是任务
1. 任务相当于Linux里的进程或者线程。即创建一个任务,就需要在内存里开辟一个空间。任务通常包含while(1)循环或者for循环,除非调用任务删除函数,否则该任务一直执行。
2.任务列举
玩游戏,陪女朋友等;电脑里打开的笔记本/浏览器等都是一个任务。
FreeRTOS提供任务创建相关函数
一般使用动态创建任务。 动态创建任务的堆栈是由系统分配的。而 静态创建的任务的堆栈是由用户户自己传递的
FreeRTOS提供任务删除相关函数
删除函数原型
若传参数NULL,表示删除任务自身,删除正在运行的任务
工作中一般是创建任务,让任务一直跑,==而删除任务较少,==删除任务只需把拿到任务句柄即可。
xTaskCreat 函数解读
三方面:函数返回值 函数名称 函数体
先是函数体,然后是返回值解读
函数体
1.任务函数指针。
每一个任务对应一个函数通过函数的指针与 创建函数相联系
2.任务名字
一般用于调试,默认最大长度16
3.堆栈的大小指定。(任务创建会在堆栈内开辟空间)
4.==传递给任务函数的参数,一般不用,写NULL。
5.任务的优先级(数字越大,优先级越大,与STM32相反)
6.任务的句柄。想要删除 ==调用该任务拿到句柄即可
返回值
函数返回值,
只需关注两个即可。创建成功或者创建失败。
官方案例解读
1.void vTaskCode
- void vTaskCode是任务所对应的函数 里面有死循环while或者for
2.该函数里面的参数
2.该函数里面的参数
函数指针 vTackCode
函数名称 NAME1
该任务分配堆栈占据的大小 STACK_SIZE(一般采用宏定义)
任务优先级
任务句柄 也需要定义
返回值是否等于pdPAS.
3.最后看返回值是否等于pdPAS.,表示任务创建成功,然后把其删除
五丶动手实操进行任务的创建
- 上面章节是讲的任务基本概念以及任务的相关函数
下面章节进行动手实操,创建两个任务,了解一下任务的逻辑
创建任务步骤
-
1.拷贝一份模板,命名为task_tast
-
- CubeMx里点击FreeRTOS中间层,选择最后一个选项卡Tasts and Queues。
默认情况下,创建一个任务。双击可以修改
- CubeMx里点击FreeRTOS中间层,选择最后一个选项卡Tasts and Queues。
-
3.完成第一个任务创建,ADD键添加第二个任务即再创建一个进程或者说一个任务
注意这里优先级是越来越大。往下越来越大。
优先级从上往下越来越高
验证任务的实验现象
实验现象,
利用FreeRTOS点灯,实现两个灯点亮且一个闪的快,另一个闪的慢
PB8 PB9对应两个灯,先配置输出GPIO口。
分析代码
1.创建任务函数是将 入口函数 与 任务绑定在一起任务的参数通过osThreadDef_t保存传给创建任务函数。
2.在入口函数里实现业务逻辑
在入口函数里实现业务逻辑,翻转点灯且延时点灯
main.c
首先看main.c
在main()里
1.对FreeRTOS进行初始化MX_FREERTOS_Init()
2.对内核进行调度,此后freeRTOS开始跑起来OnKernelStart()
3.while(1)业务逻辑里面代码不在执行
freertos.c
然后看freertos.c
1.两个入口函数
2.相应入口函数的实现
3.入口函数与任务绑定
创建任务函数/创建任务函数,CubeMX对创建函数封装成osThreadCreate(osThread(taskLED1),NULL)但其内部是一样的:xTaskCreate()
4. xTaskCreate(里面的参数)保存在入口函数的(const onThreadDef_t thread_def ,void * argument)里
5.在入口函数里实现业务逻辑,翻转点灯且延时点灯
六丶任务调度
上一章节已经使用freeRtos创建两个任务,直观了解了任务的含义。下面是讲解细节:任务调度的过程。工作中,任务调度一般只用时间片调度。
工作中:用时间片任务调度
任务调度的过程,简单理解一下这个过程但是工作中,一般只使用时间片任务调度模式:任务优先级一样,轮流执行这些任务
注意,当正在1ms调度时,突然出现了阻塞,未达到1ms,剩下时间片不在使用,直接进入下一个时间片,依次轮流进行。
任务调度
任务调度
对于内核而言,多个任务应该运行哪个,即运行的前后顺序就是接下来的的任务调度
被封装的任务调度器
任务调度规则 3条
任务调度和 任务调度规则,对于任务调度规则只需掌握抢占式调度和时间片调度任务轮转调度)即可。
FreeRTOS里任务调度的规则主要有两条,第三条已经不更新了
第一条,抢占式调度。系统永远执行最高优先级的任务
(女朋友 和 游戏,女朋友优先级高,先执行)
==第二条,时间片调度。==同优先级下,任务轮转调度
(多个朋友,陪朋友A玩1s,陪朋友B玩1s。)工作常用
第三条,协程式调度,但官方已经不更新了。主要用于小容量芯片,用得不多。因为以前芯片性能容量小,都是用的协程式的,现在芯片性能十分强大
第一条:抢占式任务调度
第一条 抢占式任务调度
优先级高的先执行
任务1先出现,随后任务2出现,即此时任务1被任务2打断。
任务3出现,任务2被打断。而当任务3阻塞时,任务2又被调用。
阻塞
阻塞,可以是一个Delay()函数。阻塞当前任务进行,跳转下一任务。
就绪态
注意被抢占的任务处于就绪态。任务1被任务2抢占,此时任务1处于就绪态。
第二条: 时间片调度
第二条 时间片调度运行规则
同等优先级下,将时间分成时间片,依次轮流去执行。1个时间片默认是1个嘀嗒中断周期,即1ms.
先和兄弟A打1ms篮球,再和兄弟2打1ms,再和兄弟3打1ms篮球,依次按照顺序执行
若兄弟3打0.5ms,兄弟3拉肚子,不跟兄弟3打,剩下0.5ms不管了。直接跳到下一个兄弟打篮球。
阻塞注意事项
注意,当正在1ms调度时,突然出现了阻塞,未达到1ms,剩下时间片不在使用,直接进入下一个时间片,依次轮流进行。
任务调度的过程,简单理解一下这个过程但是工作中,一般只使用时间片任务调度模式:任务优先级一样,轮流执行这些任务
第三条:协程式调度
第三条,协程式调度,但官方已经不更新了。主要用于小容量芯片,用得不多。因为以前芯片性能容量小,都是用的协程式的,现在芯片性能十分强大
七丶任务的状态 4种
FreeRTOS中任务有四种状态。
运行态 就绪态 阻塞态 挂起态
运行态
第一,运行态。(汽车上路,在公路上开)
任务处于运行状态就称之为运行态。同一个时间,只有一个任务处于运行态。即此时CPU的使用权被当前这个任务占用。
就绪态
第二,就绪态。
任务能够运行,但当前没有运行的任务,因为有同优先级或者更高优先级的任务正在运行。
必须经过就绪态的任务,才能到达运行态。
阻塞态
第三,阻塞态。(汽车运行过程中,遇到了红灯)
任务跑着跑着碰到了事件,转到了阻塞态。
或者任务里有延时函数也会进入阻塞态。
或者遇到等待信号量,消息队列,事件标志而处于阻塞态
挂起态
第四,挂起态。
不是阻塞态 而是暂停作用 。挂起后该任务不被执行(被内核遗忘),其他任务可被调度执行。
注意,无论任务处于就绪态,还是运行态,还是阻塞态。都可以通过vTaskSuspend()这个函数将任务挂起,使任务处于挂起态。
任务的挂起与恢复函数
vTaskSuspend()是将任务挂起。
xTaskResume()是将挂起态的任务恢复。
四态转化过程
转化一
创建一个任务,该任务处于就绪态。当满足运行条件,处于运行态。若此时该任务被抢占,那么重新处于就绪态。
转化二
当任务处于运行状态,里面有阻塞条件(小延时等)会进入阻塞态。但是此时没办法再次回到运行态。
阻塞态不能回到运行态。
转化三
运行态可以进入阻塞态。而在阻塞态可以进行分支,进入就绪态或者调用vTasksupend函数进入挂起态。
阻塞态向下有2条支路。
总结:只有 就绪态能到 运行态
也就是说,任务要想运行,必须先回到就绪态
八丶任务小实验
上一章讲了任务四种状态。基于四种状态做任务小实验。
实验需求:
taskled1实现灯1闪烁间隔500ms
taskled2实现灯2闪烁间隔1000ms
taskkey1:任务1存在就删除,若不存在就创建
taskkey2:任务2正常运行就挂起,挂起就恢复为正常运行
具体步骤
1.添加按键1和按键2。
2.配置按键GPIO同时打开串口打印logol
3.frewRTOS里选项卡创建任务,且注意任务优先级
注意优先级,设置成osPriontyNormal状态
4.在串口.c文件里,添加putc()函数,注意头文件以及勾选MI库,可以打印一句换进行验证
5.在main.c文件中的入口函数里实现业务逻辑
main.c文件中的入口函数里实现业务逻辑
按键消抖
一般按键使用中断,这里用的是轮询法。
同时注意按键防抖是指防噪声:瞬间的低电平。方法:先检查到低电平,再里面延时20ms再重新检测低电平状态,仍然为低电平那么说明按键被真正的按下了
防止按键刷屏,仅让按键扫描一次
因为单片机检测速度很快,人的1秒,在1s内单片机每30ms扫描一次。
我们按键被按下一次,单片机读取到的是被按下n次,即会导致刷屏。可以利用按键被按下是低电平这一条件while卡法,防止刷屏。
也就是说,1s内按下,即使单片机扫描速度很快,也只是会扫描一次。
句柄
创建任务函数会返回一个任务的句柄。通过这个句柄可以知道该任务是否存在或者不存在。
删除任务,手动句柄置NULL
需要手动把句柄置为空,防止该句柄被其他地方调用,导致各种奇怪事情发生。
业务实现
任务1,按键1实现任务1存在或者任务1删除
任务2,按键2实现任务2正常运行或者任务2挂起状态
创建进程与删除进程实现
1.通过任务句柄判断,若进程不存在,那么就创建一个进程。
2.注意删除任务时,把任务句柄放入,同时调用删除函数后要把任务句柄置为Null。
任务正常运行或者任务挂起状态实现
在挂起状态与运行状态时,需要用一个标志位,标志位使用静态变量(在函数局部内)。判断flag的状态,挂起或者正常运行状态。
状态标志变量
标志位使用静态变量(在函数局部内)。注意状态标志变量,注意任务句柄,注意标志位重新复位
九丶任务 入口函数 绑定 任务创建函数
四者之间联系
任务句柄 由创建任务函数返回值得到。
入口函数里for里实现任务的业务逻辑。
任务 与 入口函数 通过创建任务函数绑定,任务的各种参数放到 创建任务函数的第一个变量里。
串口printf要勾选MI库
注意使用ptintf串口打印一定要勾选魔术棒里MI库,否则运行到printf会导致程序卡死在这里,导致预期的实验现象没办法看到。
十丶队列
队列
1.什么是队列。
队列又称为消息队列,是一种任务间通信的数据结构。起传递信息的作用。
队列在任务与任务之间,中断和任务之间传递信息。
消息队列用于传递信息。
队列又称消息队列,流水线,管道。当队列满员时,即消息数据无法入队,此时消息需要等待(队列阻塞)。不等,超时返回,死等。
传递信息的方法
方法一: 全局变量(不使用)
2.为什么不用全局变量?
“兔子—狐狸—树懒”模型来理解。树懒处理数据速度慢,对于树懒而言,狐狸的数据会对兔子数据产生干扰。
“兔子—狐狸—树懒”模型
消息队列理解成一个流水线。兔子和狐狸产生的数据都通过消息队列(流水线),树懒可以一个一个的依次去进行数据处理。
消息队列(流水线),不会存在那种全局变量互相干扰的弊端
任务1(兔子)修改全局变量a,树懒会处理变量a(但树懒处理数据速度慢,此时任务2(狐狸)再次修改全局变量a)。
因为树懒处理数据速度慢,导致第一次并不是处理的兔子的数据。(也就是说,狐狸对兔子的数据产生了干扰)
方法二 :消息队列(流水线)
消息队列,也可以理解成流水线,或者管道。
消息队列理解成一个流水线。兔子和狐狸产生的数据都通过消息队列(流水线),树懒可以一个一个的依次去进行数据处理。
队列基本知识
创建队列时,需要指定内容
创建队列时,需要指定队列长度和队列项目大小。
队列项目和队列长度
队列基本知识
1.队列项目:流水线上每一个产品
2.队列长度:流水线上最大容纳产品个数
创建队列时,需要指定队列长度和队列项目大小
队列的特点
数据入队方式(FIFO或者LIFO)
3.1数据入队方式(FIFO或者LIFO)
先进先出FIFO 的数据存储缓冲机制。FIFO是先进先出英文缩写。也可以配置成LIFO后进先出。
数据传递方式(实值传递或者指针传递)
3.2 数据传递方式(实值传递或者指针传递)
采用实际值拷贝传递。而在传递较大数据时,一般采用传递指针(即实际数据的地址)
可被多任务访问(被多任务或者中断访问)
3.3 多任务访问
队列相当于一个管道,不属于任务一个具体的任务。任何任务或者中断都可以使用消息队列管道或者流水线)
出队丶入队阻塞(即无法入队时的等待时间)
队列无法入队时,消息等待的时间称为阻塞,若超时则返回或者依然死等。
消息队列称流水线或者管道,当流水线处于满员时,下一个消息必须等待,可以设置一个阻塞(也就是消息的等待时间),存在两种情况,一种死等,另一种超时返回不等待。
十一丶队列API 3种
队列相关API函数 3种
1.创建队列
2.写队列
3.读队列
创建队列函数,得到队列句柄
创建队列有两种方式,一种动态创建,一种是静态创建。主要使用动态创建的方式。
需指定项目大小和队列长度
创建队列 动态法,注意创建消息队列时,需要注意确定队列项目大小和 队列长度这两方面。
理解成流水线上的产品容纳总个数 以及 流水线上单个产品占据的字节大小
函数分析
函数分析,
参数是产品容纳总数 以及 单个产品占据字节数
返回值是队列句柄。创建成功,返回队列句柄,若内存无法分配,返回NULL
写队列函数 与 读队列函数对比
不同
写队列函数存在头部和尾部插入数据。对于写队列的头部写入用的不多而读队列只存在头部读取数据。
写队列函数与读队列函数都存在普通函数部分 和 中断函数部分。
而在写队列普通部分又可细分为三种,a部分头部写入(不常用),b部分尾部写入,c部分覆写队列消息(只用于队列长度是1)
相同
读队列函数与 写队列函数参数类型和返回值一样的。
写队列函数
写队列分为两部分,
第一部分普通写队列,第二部分是中断中写队列fromisr(isr是中断服务程序)。
第一部分又可分为3部分a和b,c.
a部分是往队列头部写数据,b部分是往队列尾巴(不常用)写数据, c部分覆写队列消息(只用于队列长度为1的情况)。
第一部分普通写队列
a部分 头部,常用
b部分 尾部,不用
b部分是往队列头部写入消息,用的不多
c部分 覆写
c部分覆写队列消息(只用于队列长度为1的情况)。
兔子写一个数据,树懒未读取,而此时狐狸又写一个数据,狐狸的数据直接把兔子数据覆盖掉
第二部分是中断中写队列fromisr
写队列函数举列
读队列函数
读队列函数,
分为两部分。
第一部分,正常读队列。
第二部分 ,中断读队列。
正常情况下,读出一个数据时,就把原本队列里的该数据删除。
函数 xQueueReceive()从头部读消息 并删除消息
== peek情况下,不删除队列里数据。==
函数xQueuePeek()
第一部分普通写队列
一个是读出来删除消息,一个是读出来不删除消息。
读出来删除消息
读出来不删除消息peek
第二部分中断读队列
十二丶队列API 实操
上一章节是队列理论部分,下一章节是队列实操部分
上一章讲了消息队列的理论部分,这一章进行消息队列的实操。
消息队列实操需求:
先创建一个队列,然后创建两个任务。按下KEY1往队列里发数据 ,按下KEY2从队列读取数据。
创建任务
1.打开mubanCubeMX,配置按键
2.创建一个任务和队列,即创建2个任务和队列
3.==任务一:创建发送数据 ==,任务二:创建接收数据
创建队列
上面已经创建了2个任务。下面进行创建队列。
创建队列需指定两个信息
队列几个信息
Queue Size:16该队列数据只能放16个。(容纳的队列项目总数),即调用16次写入队列数据函数。
item Size:uint 16_t 2个字节一个队列项目占据的字节数
4.创建一个队列
十三丶信号量semaphore
十四丶二值型信号量
十五丶计数型信号量
回顾二值信号量
- 回顾二值信号量,盒子里装着是资源的数量。因为资源数量要么是0 要么是1,所以是二值信号量。
计数型信号量
二值型信号量的基础上学习计数型信号量。 计数型信号量,盒子里装着多个小球。但本质上也是队列。
停车位-指示牌模型
计数型信号量的经典案例:停车位
3个停车位,信号量指示牌就显示3。当有一辆车开进来,占用一个资源(占用一个停车位)。
当有车来临时,看一下指示牌(信号量盒子)如果不是数字0,那么表示资源仍未被占用完,即此时车主可以停车。若等于0 ,车主只能等待或者直接走开。
信号量 == 资源指示灯
信号量是 相当于资源指示灯,传递资源的状态。对于计数型信号量而言,车开走计数值加1;车进来,计数值减1。
计数型信号量相关API
与二值信信号量函数API相比,差别在于创建信号量函数。且计数型信号量多一个获取计数型信号量数值函数。
计数型信号量和二值信号量在获取与释放信号量两个方面对应的函数一样的。
创建信号量
不用CubeMx封装的os,而用自带的创建计数型函数。
1.创建计数型信号量
参数解释
第一个参数,信号量容纳最大数(容纳小球的最大数目)
第二个参数,创建计数型信号量时,默认给分配的小球个数
(一般系统给16,但我们模式修改成0)
获取信号量计数值函数
计数型信号量实操
需求:按照KEY1释放信号量,按下KEY2获取信号量。 通过按键对计数型信号量的函数的简单映射 可以通过实验现象看出。
1.利用CubeMX进行实操
打开计数型信号量需要配置
config parameters里的USE_COUNTING 使能打开
2.打开Timers and semaphers 定时器和信号量
注意提前打开使能计数型信号量
必须提前在config parameters里面使能USE_COUNTONG,否则无法Add添加计数型信号量
3.创建计数型信号量的卡片解释
3.创建计数型信号量的卡片解释
第一个是 计数型信号量名称
第二个是 最大计数可达到多少 可以修改成16或者20等
第三个是 动态还是静态
4.Project项目代码分析
对于创建计数型信号量,一般不用CubeMx封装的os(3是初始的最大值是3),而是使用自带的。
二值信号与计数型信号量总结
都是队列
1.本质都是队列,二值信号量可以做存在1个小球 ,计数型信号量可存在多个小球
创建函数不同
与二值信信号量函数API相比,差别在于创建信号量函数。且计数型信号量多一个获取计数型信号量数值函数。
计数型信号量和二值信号量在获取与释放信号量两个方面对应的函数一样的。
十六丶互斥型信号量Mutex 不用于中断
互斥量的本质是二值信号量。但从功能上看,二值型信号量是用于同步,而互斥型信号量用于资源保护。
同步
同步的意思就是 博主录完视频用户才能观看。
资源保护
1.资源保护
情景一:小弟在使用厕所资源时,老大,其他人无法使用WC该资源(任何人无法打断小弟)
情景二:工作中,文件很重要,每时每刻只允许一个人去写,只允许一个人同时对文件修改(文件更安全一点)
优先级翻转
2.优先级翻转 互斥型信号量用于翻转(翻转就是导致高优先级的最后执行)
现象:中优先级和低优先级得到了执行,而高优先级的反而没有得到执行。
情景一
优先级翻转情景:
1.优先级低的先就绪,故T—L先运行,同时获取了一个信号量
2.与此同时高优先级T-H醒了,因为优先级最好会抢占低优先级的任务,但是但是,其没有信号量(信号量在低优先级手里)所以高优先级的进入阻塞态。
3.低优先级继续执行,占用资源。之后,优先级中等的任务醒了,(不需要获取这个信号量,中等优先级任务会打断任务L,比如中任务醒过来只打印一句printf.)(低优级被打断,中等优先级任务去执行)
4.中等任务运行完之后,低优先级继续运行,直到低优先级把信号量释放掉
5.信号量被释放掉,高优先级拿到信号量,高优先级任务开始运行
总结
高优先级因为没有拿到互斥量而处于阻塞态 。导致了低优级先执行的场景。
低优先级释放信号量,从而导致高优先级解除阻塞态,从而高优先级任务开始运行。此时中优先级依然可打断低优先级。为此,引入优先级继承(互斥型信号量里的)
情景二
老大 老二 小弟 老天(点谁谁开始动起来)
涉及到资源保护的问题:
1.老天点小弟,小弟上WC。(WC对应这么一个资源)
2.老天点老大,老大上WC,但厕所被占着,老大去睡觉。
3.老天点老二,老二去干别的事情,找女朋友。不需要这个WC资源
4.老天点小弟,小弟依然去WC,随后小弟出来WC此时WC资源不被占用
5.老天点老大,老大看到厕所没人,去上了厕所
老大是高优先级,必须是优先上厕所。为了解决这个问题,引入了互斥型信号量。
总结
老大是高优先级,必须是优先上厕所。(也就是将小弟优先级调到与老大一致,此时其他人老二无法打断小弟,老大只需等待小弟即可)为了解决这个问题,引入了互斥型信号量。
解决优先级翻转,引入互斥型信号量
互斥型信号量 用于资源保护,且存在优先级翻转情况。
二值型信号量 用于同步。
互斥型信号量无法解决优先级翻转带来的影响。
为了解决优先级翻转情况,引入了互斥型信号量(优先级继承概念)
CPU同时只允许同时做一件事情
问题:
1.小弟有信号量,老大无法打断小弟。老二不去争夺WC,去找女朋友,(会打断小弟上厕所,因为老天资源CPU同时只允许同时做一件事情)
2.想要达到效果:老大在等小弟上厕所WC同时,其他的人不能做任何其他事情。
老大在等小弟出WC时,不允许老二去找女票。(老天是CPU,同时只能允许做一间事情)。但是优先级继承也不能完全解决优先级翻转的问题(也就是说老二很可能又跑去找女票了)
老二为什么不能打断小弟呢?
老大在等小弟时,其他人没办法打断小弟的。
老二为什么不能打断小弟呢?优先级继承概念。小弟的优先级被提到根老大优先级低位一样,且小弟手里还有信号量,故此时老大等小弟,小弟不能被老二打断。
只是缓解优先级翻转
优先级:无论任何时候,高优先级的先执行=
2.小弟实际上的优先级比老大低,但依然小弟先执行。橙色线无法的优先级接别无法得到解决
互斥型信号量
优先级继承
只能缓解中优先级抢占任务
只能解决中优先不抢占高优先级
不能解决低优先级比高优先级先执行(因为互斥型信号量的存在)
解决
缓解
互斥型信号量的缓解与解决 (重要)
老大是高优先级,必须是优先上厕所。(也就是将小弟优先级调到与老大一致,此时其他人老二无法打断小弟,老大只需等待小弟即可)为了解决这个问题,引入了互斥型信号量。
只能解决中优先不抢占高优先级
不能解决低优先级比高优先级先执行(因为互斥型信号量的存在)
互斥型信号量API
第四部分 互斥量相关API函数
1.除了创建函数,其他的释放和获取信号量是一样的。
2.注意互斥量信号量不能用于中断服务函数里面
二值型信号量与互斥型信号量对比
互斥型信号量的本质是二值信号量。但从功能上看,二值型信号量是用于同步,而互斥型信号量用于资源保护。
最大区别:后者可缓解优先级翻转
互斥型信号量与二值型信号量的区别,
最大区别在于 互斥型信号量可以解决优先级反转现象
十七丶互斥型信号量任务实操
简单实操:
两个小任务
任务一:演示一下优先级翻转的问题。
任务二:使用互斥量优化互斥量翻转的问题。
任务一:优先级翻转
1.创建任务
2.创建SemaphoreCreateBinary二值型信号量
第一步,创建3个任务
1.CubeMX打开Freertos.
进入Tasks and Queues
任务创建3个:低优先级 中优先级 高优先级
第一个任务TASK-H 设置成高优先级
第二个任务 正常优先级
第三个任务 TASK-L
第二步,创建二值型信号量
2.创建二值型信号量(因为发生优先级翻转是在二值信号量时)
在Timers and Semapheres里创建一个二值信号量
分析任务一的Project
1.创建了一个二值信号量
1’创建了一个二值信号量
2.创建任务对应的入口函数
3.入口函数里实现业务逻辑
3.在低优先级 中优先级 高优先级任务的入口函数里实现 具体的业务逻辑
获取信号量(信号量句柄,阻塞时间),阻塞时间设置0或者最大值都可以,但是为了进入阻塞状态.且防止跑掉,故设置最大时间port_MAX_Drlay
3.1低中断优先级任务
3.2高中断优先级任务
3.3中间优先级任务(不占用厕所,只占用CPU资源。高与低任务都要抢占厕所)
4.通过串口观察Project现象 中优先级会打断低优先级
中优先级任务会打断高优先级任务,低优先级任务占用这个资源。
上面是映射出 优先级翻转的现象.
5.信号量与资源
上厕所前,将信号量拿到自己手里,上完厕所需要将信号量归还给人家。
注意:打印上完厕所语句,一定要在归还信号量之前,否则会被高优先级任务打断
任务二:优化优先级翻转
通过互斥量来解决优先级翻转的现象
二值信号量有 优先级翻转的现象,为了缓解优先级翻转,我们采用互斥型信号量(即需要先删除二值型信号量,利用互斥量解决这个问题)
删除二值型信号量 Timers and semaphers
删除二值型信号量
创建互斥型信号量 mutex
找到卡片Mutex,创建互斥量。
分析任务二的 Project_mutex
1.创建互斥型信号量函数 初有小球
CubeMX封装的函数
二值型信号量 盒子无小球
互斥型信号量 盒子有小球
创建互斥型信号量时,盒子里已经装了一个小球(信号量了)
十八丶互斥型信号存在意义
互斥型信号量实验验证
验证了 低优先级在占用资源过程中,中优先级无法打断低优先级的任务。小弟安心的上厕所,也就是说该任务不会收到外界的干扰
带有信号量的低优先级任务 不被中优先级任务打断。
互斥量存在的意义是 保证带有信号量的低优先级任务 不被中优先级任务打断。保证任务不受到外界的干扰
程序执行的顺序==依然是按照 优先级的顺序进行的。==高优先级 中优先级 低优先级,加了互斥型信号量的意义在于 当低优先级任务运行时,因为互斥型信号量的存在 不被高优先级或者中优先级任务打断。
深入理解互斥量的优先级继承的优点
用于资源的保护,厕所的保护。借助互斥型信号量的优先级继承特点可带有信号量的低任务 不被 不带信号量中任务打断,用于资源的保护。
存在的意义:不可能每个任务都上一把锁,就是弄一个信号量。
守规则的任务(带信号量)与不守规则的中优先级任务 对CPU同一个资源的竞争。
十九丶事件标志组
事件标志组 是 事件标志位的集合。每一个事件标志位代表一个事件的状态。时间标志组一般16bit或者32bit。但最大可存在24个事件标志位,因为高八位用于控制。
事件标志位
C51单片机 事件标志状态变量 flag .
事件标志组
事件标志位 与C51中的全局变量flag类似,含义:表明某个事件是否发生。即在if或者while语句里面去判断某个变量是否改变。
FreeRTOS里 事件标志是按一位一位说的,即一位表示一个事件的状态。但是需要注意通常,高8位不算。
以前全局变量flag的类型是char int型,而在freeRTOS里的事件标志位是按照1位1位来的。,且高8不算
一组事件标志位的集合就是事件标志组。简单理解:事件标志组就是一个整数。
事件标志组的本质是16位或者32位无符号的数据类型EventBits_t.
configUSE_16_BIT_TICKS宏定义16位/32位
16位还是32位由宏configUSE_16_BIT_TICKS决定。 等于0代表是 32位。
最多可存储24个事件状态。
,:
==虽然是32bit,但是只能最多存储24个事件标志!==而不用高8位,因为里面放的是存储事件标志组的控制信息。
只关注后面24位。
,:
最多可存储24个事件状态。事件标志组 就是一个16bit或者32bit的整数。事件标志组 是 事件标志位的集合。
事件标志组API函数
1.创建一个事件标志组
1.创建一个事件标志组
只需调用空参数的函数即可,成功返回对应句柄,失败返回NULL。
2.设置事件标志位
2.设置事件标志位
设置对应位的具体数据前要先拿到标志组的句柄。
3.清除事件标志位
3.清除事件标志位
4.等待事件标志位
参数一:标志组句柄
参数二:要查看的那位是否置1
参数三:等到那位条件满足,就置0。未等到,就置1。
参数四:同时为1才执行后面的业务逻辑。有一个为1,就执行后面的业务逻辑
参数四解释
检测到某位置0就不执行下面业务逻辑
事件标志组实操
事件标志组实操
实验需求:
创建一个事件标志组和两个任务
任务一 是 按键按下 把对应的数据标志组中的标志位置1.
任务二是 检测事件组的对应几个事件位 是否同时为1
1.创建任务和配置GPIO
1.创建两个任务 任务一 和 任务二
2.因为检测按键 配置GPIO口为INPUT,按键设置为INPUT
2. 创建事件标志组函数
利用CubeMX配置
3.事件标志组在Events,勾选Events V2
调成V2就可以直接用
利用手动写代码实现
在代码里手动写创建事件标志组的函数,不利用CubeMX生成。
3.手动实现创建标志组函数
.创建一个事件标志组句柄
1.先定义一个句柄
在freertos_Init里创建一个事件标志组
4.在任务一和任务二入口函数里面实现业务逻辑
任务一
任务一 不断检测按键,检测到按键被按下,就把标志组中对应的具体某个标志位置1。
24个位都可做为标志位,任意用一位,我们选的是最低那位。
任务二
任务二检测 任务事件组的对应标志位是否都置1。
检测事件标志位用到是等待事件标志函数
任务二就是等待事件标志函数的应用
参数
,:
参数二 是等待一个或多个事件的按位值(也就是置1后的16进制表示)
,:
参数三 是满足检测条件后,是否清零(默认情况下,一般清零,因为不清零一直保持有效状态)
,:
pdTURE表示的含义是 满足条件,就将原本的标志位清零
,:
参数四 条件一:条件满足一个置1就满足 (逻辑或)条件二:条件满足全部置1才满足(逻辑与)
,:
参数五 是阻塞时间。死等,只有检测条件满足才退出该语句,向下执行语句,死等portMAX_DELAY
返回值 由2进制得到,16进制显示
需要判断他的状态,返回值需要定义一个变量
成功,返回 等待到的事件标志位,就是原本的二机制转写成10进制
到等到什么就填什么,即什么的二进制对应的十进制
注意点
多个事件的对应标志位,置1后的按位值
最后一位置1指令,不是位置!
是等待一个或多个事件的对应标志位置1后的按位值(也就是置1后的16进制表示)
最倒数第二位 置1指令,不是位置
等待事件标志函数的返回值
二进制得到 16进制显示
参数四的逻辑或
二机制10 代表16进制 ox2
二机制01 代表16进制 ox1
参数四的逻辑与