此篇文章为自己学习FreeRTOS过程中,写下的笔记,学习的是
韦东山老师的FreeRTOS快速入门
,感觉还行,自己对FreeRTOS也有了一个深刻的理解,并且在学习完课程后,成功把FreeRTOS移植到Stm32F103C8T6上
,期间还是踩了很多坑的
FreeRTOS主要包括任务的创建,全部都在围绕
同步和互斥通信
,主要包括队列、信号量、互斥量、事件组、任务通知、定时器、中断管理
等
和裸机比起来,操作系统还是很高效的,对于刚开始,可能需要浅浅理解一下
目前也只是对FreeRTOS有了一个整体的认识,还是有很多不足
下面是对FreeRTOS,记下的笔记,总字数约23000字
,在此分享,希望对大家有帮助
第一弹
:FreeRTOS学习笔记(1、FreeRTOS初识、任务的创建以及任务状态理论、调度算法等)
第二弹
: FreeRTOS学习笔记(2、同步与互斥通信、队列、队列集的使用)
第三弹
: FreeRTOS学习笔记(3、信号量、互斥量的使用)
第四弹
: FreeRTOS学习笔记(4、事件组、任务通知)
第五弹
: FreeRTOS学习笔记(5、定时器、中断管理、调试与优化)
所有学习工程
oufen / FreeRTOS学习
都在我的Gitee工程当中,大家可以参考学习
实时操作系统,简称为RTOS,是指当外界时间或数据产生时,能够接收并以足够快的速度做出相应处理,其处理的结果又能在规定时间内来控制生产过程或对处理系统快速做出相应
调度一切可以利用的资源完成实时任务,并且控制所有实时性任务协调一致的操作系统
提供及时响应和高可靠性是其主要特点
大体上,实时操作系统要求:
由于RTOS需要占用一定的系统资源(尤其是ARM资源),只有uc/os-II、embos、salvo、FreeRtos能够在小RAM单片机上运行
FreeRtos是完全免费的操作系统,具有源码公开,可移植,可裁剪,调度灵活的特点,可以方便的移植到单片机上运行
FreeRtos是一个迷你的实时操作系统内核,作为一个轻量的操作系统,功能包括:任务管理,时间管理,消息队列,内存管理,记录功能,软件定时器,协程等,可以基本满足较小系统的需要
可以一边喂饭,一边打字,交替执行
如果发生了某些紧急的事情,将会立马停下所有工作,去灭火,这就是程序的实时性
优先级高的事情可以先处理,优先级一样的东西可以交叉处理
cpu的类别,架构,CPU的内部结构,深入理解RTOS,就要深入了解CPu的架构
双架构和双系统
RTOS的意思是:Real-time operating system,实时操作系统。
我们使用的Windows也是操作系统,被称为通用操作系统。使用Windows时,我们经常碰到程序卡死、停顿的现象,日常生活中这可以忍受。
但是在电梯系统中,你按住开门键时如果没有即刻反应,即使只是慢个1秒,也会夹住人。
在专用的电子设备中,“实时性”很重要。
我们只需要多任务,并不需要文件系统等等,所以FreeRTOS应用最为广泛
而RT_Thread,生态非常完善,从底层的文件系统,网络协议到上层的各种组件都非常丰富
如果只是使用别人移植好的RTOS来写程序,当然不需要了解CPU架构。
甚至编写驱动程序时,也不需要了解CPU架构:因为我们操作的是CPU之外的设备,不是操作CPU。
task.c和list.c
task.c 里面是任务的相关函数,包括任务操作
list.c 里面是列表的操作
关于Keil仿真中无法执行的问题,
keil 5的软件仿真遇到的问题:error 65: access violation at 0x40021000 : no 'read' permission
的解决办法
**Dialog DLL**
**改为 ****DARMSTM.DLL**
**Parameter**
**改为 **** -pSTM32F103C8(此项根据具体型号而定)**
三个任务交替执行
优先级相同的任务,后创建的先执行
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改
volatile用来避免被优化掉
RTOS是多任务系统
优先级相同的情况下要后面的任务要等前面的任务释放串口1才能去使用
多任务在我们的感觉中是同时运行的,但是任务之间是交叉执行的
在FreeRTOS中,任务就是一个函数
void ATaskFunction( void *pvParameters );
创建一个任务函数
在main函数中进行创建task
更高优先级的或是后面创建的task先执行
在FreeRTOS中定义了两个基本的数据类型
TickType_t 和 BaseType_t
中断次数累加 tick_count 的数据类型为TickType_t,有可能是16位也有可能是32位,根据处理器来进行选择
BaseType_t 也是基于效率来考虑的,对于32位处理器,BaseType_t 就是32位的
编程时就可以返回这个BaseType_t,是最高效的返回值类型
通常作为简单返回值的类型,还有逻辑值,比如pdTRUE/pdFALSE
根据以上规则来命名
前缀表示类型,后一部分表示含义
比如
const char * const pcName;
这里的pc 就是 指向char类型的指针,Name是这个变量名的名字
就比如这个函数,返回值类型位BaseType_t ,在Task中定义,Create就代表者这个函数的含义![image.png](https://img-blog.csdnimg.cn/img_convert/20f61d228e675fb53e1b0f6610afaab0.png#averageHue=#f3f2f2&clientId=u23120ea5-c164-4&from=paste&height=198&id=ud2c43987&originHeight=248&originWidth=1381&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=42501&status=done&style=none&taskId=u587c2826-8919-4f08-87ae-204fac2e99f&title=&width=1104.8)
prvSetupHardware();
static void prvSetupHardware(void)
这里prv就代表这是一个私有函数,即static函数
任务控制块结构体
对于每一个task,都有一个TCB结构体,TCB_t,这个叫做任务控制块
动态分配,就是使用xTaskCreate,创建任务时,函数内部会使用malloc,从动态内存,即堆中分配出结构体
也可以使用其他函数,使用其他函数时,可以事先分配好这个结构体
一个任务可以简单任务就是一个函数,函数中可能存在各种调用,各种局部变量
局部变量,函数的调用关系存放在栈中,每个任务的栈都不一样,否则栈会互相冲突,乱套
在任务中指定了栈的大小
handle句柄就指向了TCB_t结构体,handle就是一个任务控制块
创建任务所返回的handle就是指向TCB_t的指针,写程序时一般使用句柄来表示某一个对象,handle是同种结构体类型的另外的一种名称
静态和动态创建task本质就是
创建任务时,TCB任务块是动态分配的,任务堆栈的大小也是动态分配的,即我们手动给的
静态task,TCB结构体需要我们事先分配好,栈也需要我们事先分配好
FreeRTOS的任务优先级是,数值越小,优先级越低
同优先级的task是交叉执行的
高优先级的task,优先执行
如果高优先级的task没有主动放弃执行的话,其他低优先级的task无法正常执行
高优先级的task优先执行,同等优先级task交替执行
这是一种调度机制
创建了一个task,如何使用task
传入一个句柄handle,使用handle来引用这个task,想要删除task,就必须要使用handle来删除
使用vTaskDelete();函数可以删除使用xTaskCreate()函数创建的Task
也可以删除xTaskCreateStatic()函数创建的Task,使用一个变量接收句柄,并且记录这个函数的返回值,使用vTaskDelete()函数删除
在创建Task时,要对栈的大小仔细考虑,防止栈溢出,造成程序崩溃
RTOS多个任务可以同时运行,实际上是多任务交叉执行
实现多任务交替执行的基础是tick中断,滴答中断,周期性的定时器中断
两次中断之间的时间被称为时间片
这个tick大概一次是1ms进行一次
发生中断时,会把中断次数记录下来,发生第一次中断时为1,发生第二次中断时为2,这个称之为tick count ,RTOS的时钟基准
每发生中断时,tick的函数将会被调用,判断是否要切换任务
执行完中断处理函数后,切换到新的task
在FreeRTOS中,每个基本任务的基本时间是1ms,这个基本时间可以由我们自己配置,配置滴答定时器产生中断的周期
这个1000,就代表者是1ms,产生一次中断
FreeRtos的基准tick是1ms
而其他RTOS,可以指定Task执行的tick,比如任务1可以执行10个tick,任务2可以执行5个tick等
正在运行的Task,成为Running状态,即运行状态,即任务3正在运行
任务3正在运行,任务1和任务2 处于Ready状态
我可以随时运行,但是还轮不到我
等待某事完成后,再运行,妈妈喂饭,等孩子吃完后再喂
而孩子一直在吃,就进入了阻塞状态
主动休息,被命令去休息
FreeRTOS中的Task只能有这四种状态之一
就绪(等待高优先级任务结束),运行(当前运行的任务),阻塞(当前任务释放CPU使用权),挂起(任务退出就绪列表)
在阻塞状态中,是等待某些事情,而暂停状态中,存粹的是休息
不是因为等待某些事情而进入阻塞状态
在暂停状态中,不是因为要等待某些事情,而是自己主动休息或者被动休息
如何从暂停状态切换成READY状态呢?
必须由别的Task,调用vTaskResume(),传入句柄,从而回到Ready状态
如何从blocked状态切换成READY状态呢?
当这件事情Event发生后,就可以从Blocked状态切换成READY状态
如何管理各个Task
最简单的就是链表
当发生Tick中断时,将会从链表中查找任务,进行任务的切换
创建三个任务 task1 task2 task3
对于已经进入到暂停状态的task,必须由别人来调用Resume来使其退出暂停状态
task1 调用vTaskSuspend 去命令task3 进入暂停状态
task3 如何转换成Ready状态,再由task1来调用vTaskResume ,使task3进入Ready状态
task2 主动进入阻塞状态 vTaskDelay 等待延时结束,退出阻塞状态,进入Ready状态
task3正在运行running,task1和task2处于就绪Ready状态
task2 延时某段时间,处于阻塞blocked状态
task3被task1命令休息,处于suspended状态
基于tick实现的延时并不精确,在使用延时时
使用pdMS_TO_TICKS把时间转换为tick
vTaskDelay:指的是阻塞的时间
vTaskDelayUntil:指的是任务执行的间隔、周期等
使用这个vTaskDelayUntil函数,可以让任务周期性的执行
task刚开始的时间是t1,
vTaskDelayUntil(t1,▲t); 延时到t1+▲t,即t2
指定了终点,延时的时间是不固定的,通过传入的参数来控制delay的延时时间
t1、t2、t3时刻他们之间的间隔是一样的
延时的时间是固定的,Task延时的时间是固定的
把task1的任务优先级设置为最高
只有他delay的时候,其他任务才可以执行
打印1的时间不同,但是延时的时间是相同的,休眠的时间是一样的,但不能保证每次高电平的时间一样
而vTaskDelayUntil可以解决这个问题,让函数周期执行
tStart 是任务task启动时间,使vTaskDelayUntil将会进入,延时t1+20后,才会退出进入running状态,并且tStart=t2,继续进入下一段,以保证每一次的running后延时的时间是相同的,从而保证任务周期执行
所以
在task1中创建task2,并且在task1中把task2删除掉
如果task创建成功,将会返回pdPass
vTaskDelete
对于task的清理,内存的回收工作是放在空闲任务里的
系统有三个任务 ,空闲task的优先级是0,task1的优先级是1,task2的优先级是2
由于task1和task2的优先级都比task的优先级要高,空闲task没有机会执行,无法进行清理
这样就意味者,在task1中不断创建task2,但是清理工作没办法执行,于是堆将会逐渐消耗光,最终导致创建task2失败
空闲任务(Idle任务)的作用:释放被删除的任务的内存。
对于自杀的任务,由空闲task进行清理
对于被杀的任务,由vTaskDelete这个函数内部清理(凶手调用这个函数,由凶手清理)
在启动任务调度器时,会帮助我们创建空闲task
创建task1,优先级为1,task1运行时,创建task2,task2的优先级是2
task2优先级最高,优先执行,task2打印一句话然后,就自杀了
task2删除后,task1的优先级最高,task1继续执行,调用delay函数进入阻塞状态
阻塞状态下,idle 空闲函数执行,释放task2的内存和TCB块,延时时间到后,task1继续执行
如果不调用delay函数,则idle函数就没办法执行,无法释放task2的内存
task1就在不断的创建任务,不断的消耗内存,最终内存耗尽,创建task失败
可以让空闲task去执行一些低优先级、后台的、需要连续执行的函数
这个如何实现呢,需要修改空闲任务的task函数
但是最好不要修改FreeRTOS的核心文件,所以给我们提供了一个函数,这个函数就叫做钩子函数
使用钩子函数的话,就要定义宏并且实现钩子函数
且钩子函数有限制
实现钩子函数
设置task1的优先级和idle task的优先级相同 ,这样task1 和 idle就可以交替执行了
可以看到程序并未崩溃,因为idle task和task1交替执行,idle task 执行的话,就会去清理工作,内存就不会被耗尽
在单处理系统中,任何时间里只能有一个任务处于运行状态。
优先级高的task优先执行,优先级相同的task交替执行
这是RTOS调度策略中的一种,还有其他调度策略
任务的状态有四种状态
blocked 状态 在等待某些事情发生
suspended 状态 只是存粹的休息
等待某些事情的发生,又可以分为两类
一个任务可以可以因为等待某些事件,而进入阻塞状态
所谓调度算法,就是怎么确定哪个就绪态的任务可以切换为运行状态。
通过配置文件FreeRTOSConfig.h的两个配置项来配置调度算法:configUSE_PREEMPTION、configUSE_TIME_SLICING。
可否抢占?高优先级的任务能否优先执行(配置项:configUSE_PREEMPTION)
让此宏为1,就会让高优先级任务优先执行,抢占策略
配置项:configUSE_PREEMPTION 默认为1
task3的优先级最高,优先执行,delay后阻塞
其他task 抢占cpu资源,进行交替执行
当延时结束,task3由阻塞状态转换为运行状态,再次优先执行
不能抢占就只能协商,
task3优先级最高,优先执行,delay后阻塞
然后task1执行,一直占据CPU资源,这种调度策略就是不支持抢占
修改配置项(:configUSE_PREEMPTION) 为0
如果想使用此种策略,并且不允许抢占的话,每次task中做完一些事情后,马上主动放弃CPU资源
对于同优先级的task,交替执行,这也是一个可以选择的配置项
可以通过此配置项来决定同种优先级的task是否交替执行
将此配置项修改为0 同优先级task无法交替执行,不支持时间片轮转
先到先得CPU资源,谁先执行,就一直占用CPU资源,task1执行了很久很久,应该是task1,idle task,task2,交替轮流执行一个tick,何时放弃的CPU资源,被task3抢占后放弃CPU资源
引起任务调度器进行调度,从而task2执行
默认情况下,都应该去支持时间片轮转
空闲task,任务创建后
如果配置了让步的宏,在while循环里只会循环一次,循环一次后就触发一次调度,主动让出CPU资源
如果未配置的话,将会在while循环里多次循环
下方为空闲task的实现函数
空闲task,在while循环中循环一次后,就让出了CPU资源
将配置项修改为0,设置为不礼让用户task
也抢占CPU资源
总结
是否允许抢占
允许抢占的情况下,是否允许时间片轮转
允许抢占,允许时间片轮转的情况下,空闲task是否让步用户task