在单片机的Helloworld程序中(让LED实现闪烁)的过程中,常常用到了延时,在这个延时过程中CPU相当于什么事情都没干,当然这只是个实现电平翻转的简单任务。但是一旦任务增多后CPU的算力就大打折扣,因为在延迟的过程中,CPU这个“劳动者”在等待时间消息,几乎不再工作,这就需要一种方法去最大化“压榨”CPU的算力,使得CPU全时间满负荷运行。
【资料图】
嵌入式设备一般分为以下几种:
裸机系统、轮询系统、前后台系统、多任务系统。轮询系统就是一个主程序中不断运行无限循环里边的程序内容;前后台系统就是在轮询系统中加入了中断,中断事件的处理是在中断函数进行处理的,中断是前台,主程序是后台;多任务系统是事件的中断处理在主函数,中断函数负责传递中断标志位。
不同系统模型的对比如下图:
常见的操作系统如下:常用的 RTOS有国外的 FreeRTOS、μC/OS、RTX 和国内的 FreeRTOS、Huawei LiteOS和 AliOS-Things 等,其中尤以国外开源且免费的FreeRTOS 的市场占有率最高。操作系统其实就是把硬件资源虚拟化,把单核处理虚拟成“多核”提高“并发”感。这种思想非常重要,类似的有虚拟机,还有当下大量应用于物联网的Docker技术。
对于单核ARM单片机来说,要达到两个进程任务的几乎同步态,那是不可能的,所以对于多任务系统,要限制每个任务的运行时间,所以就需要分时切片处理,如下图,运行两个任务进程,每个任务进程的运行时间所占据的时间片一样的,只要时间片划分足够小,例如在毫秒级别上进行任务切换可以满足大部分系统的稳定运行需求。时间片上运行过程如下图 运行结果如下下图:
TWO
在操作系统,每一个要执行的任务,也就是一段程序的运行过程被称为一个 进程。进程包含着动态的概念,它是一个程序的运行过程,而不是一个静态的程序。进程体现在程序中形式实际上就是一段循环执行的代码,使用操作系统的任务创建函数创建了这个进程之后,操作系统就自动找到这段代码并执行。一段程序执行时,一般划分成三个阶段,开始执行--->执行中--->执行完成。这也恰好对应了进程的工作状态: 就绪态(Ready)---> 运行态(Running---> 终止态(Blocked)。如果在一个进程执行的过程中,调用了将进程挂起的功能函数,或者是进程执行时有更高优先级的 任务就绪了,则进程会进入 挂起状态(Suspended)。容易把阻塞态与挂起态相混,我的理解是处于阻塞态的时候进程在等待一个信号条件满足后继续运行,该信号条件可以是中断可以是信号量等等。而处于挂起态的时候该程序根本没运行,就像一个人被挂起,那他根本不可能完成任何任务。
由于使用时间切片引起任务进程的暂停容易导致正在处理的数据丢失,所以操作系统需要给每个任务进程分配一个内存,存储上个时间片尚未处理的任务数据。
同时任务进程的工作并不是完全独立的而是互相交替访问的,各个进程的通信也需要容易导致对某个变量的的访问极易出现冲突,所以需要一个信息传输的介质- 存储队列。该消息队列既能写入数据也能读取数据,消息队列对象一般以全局变量进行声明。消息缓存空间采用先进先出FIFO方式进行存储,实现效果如下图。
有限的硬件资源可能会被多个进程同时访问,并且有的进程对被控制实体的作用是相反的,极短时间内互斥任务进程的切换极易使得被控对象产生震荡,所以需要对互斥进程做一个规约,这个规约的实现就是信号量,FreeRTOS中可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。也可以用作进程顺序执行或者同步运行的保证。
具体来说信号量是一个非负整数,由临界资源释放,当信号量为0时,剩余任务进程失去资源访问的机会,只有当另一个任务停止访问临界资源时,才会释放出信号量。采用信号量的实现过程如下图所示。
单片机开发中用的最多就是二值信号量了,可以理解为一个位0和1,也可以理解为true或false如下图为二值信号量的实现过程。访问的任务进程只能有一个,所以就能避免访问资源引起的竞争与及规避控制效果的互斥。
在一个复杂控制系统中一个控制量常常需要多种事件的出现才能触发,事件的发生与消失用1和0来表示。多事件的状态可以通过FreeRTOS中的事件库进行事件状态的统一管理,FreeRTOS中一个事件组为32位,可通过配置文件修改其长度。
以32位为例其中高九位不可用,一个位代表一个事件,例如0x00000092就表示,因为0x00001188转换为二进制0b 0000 0000 0000 0000 0000 0000 1001 0010,然后通过查询事件句柄查询对应事件的发生与否。
THREE
要看懂FreeRTOS源码了解其原理,那么必须要学会c语言中常用的数据结构--- 链表结构,链表的源文件在list.c文件中,一般分为单向链表和双向链表,单向链表有头尾之分,双向链表则构成了一个环,分别指针分别指向上个链表节点地址和下个链表节点地址。
链表的节点成员以及成员作用如下图所示:
可以看出链表本身就不包含大量数据的存储,链表是一个动态的数据结构,将不连续的离散的硬件地址通过链表映射形成虚拟的连续存储地址,面试中也常常问到链表和数组的区别,数组是开辟了一个连续的存储地址,位置固定。
用最基础的部分的理解就到此结束,当然考虑到不同任务进程的优先级以及不同任务执行的时间长短,和kernel等事件的触发中断,因此FreeRTOS有着更为复杂的调度机制,比如本文开头提到的时间片轮转法,在考虑到任务优先级的时候,该算法根本不能保证高优先级的进程任务的执行,而且不同任务在排队列表中顺序各有差异。排队靠前的低优先级任务和排队较后的高优先级任务怎么取舍等一系列的问题。当然可以明确的是本系统的调度是由“ 先来先服务(FCFS)”调度算法,“ 优先级”调度算法,“ 时间片轮转”调度算法三种算法共同作用的。而且还可以通过设置宏configUSE_PREEMPTION来定义是采用抢占式还是合作式操作系统(说太多了不解释了.....)。
其实把用起来就知道操作系统的一个重要工作就是执行各个进程的状态切换,因为实际上单片机每次只能运行一个进程,而操作系统通过适当的管理,让每一个进程都可以得到及时的响应,让多个进程呈现出一种同时运行的“并发”感,和一根信号上传输多种信号的思想是一致的都是分时处理,但是速度肯定不及两根信号线分别传输两种信号。
现在的嵌入式入门门槛已经很低,原有的STM32固件库官方不在更新,使用CubeMX生成的HAL库正在逐渐替代原有的固件库,大大降低开发门槛提高开发效率。当然熟悉板子上的常用的寄存器会让你得心应手。
项目中关于RTOS问题的记录:
(1)FreeRTOS一定要配置好系统时钟,给予一个单独的硬件时钟源作为系统时钟。否则时间片的长度为无限大..程序只运行第一次进入的进程任务。
(2)队列的大小要设置得当,否则容易内存溢出,数据后移乱码等情况。
(3)中断中对事件组的操作一律使用....FromISR。
(4)任务进程中的延迟要使用操作系统中提供的延迟函数。
(5)应用操作系统是让CPU算力最大化,所以尽量使用官方提供的外设,GPIO口的通信尽量不要选择模拟协议自行编程,因为中间需要加入非操作系统的微秒级延迟,降低了CPU的使用效率。
(6)在通信队列操作中,读取完毕应将通信队列清空。
(7)单片机硬件系统会产生各式各样的中断,而FreeRTOS可以帮助你去对管理中断,由于STM32的内核中断优先级过高,所以规定FreeRTOS能使用的中断级别大于4.
标签: