我们使用单片机去做一些任务的时候,通常把程序写成顺序结构,基本可以解决大部分的设计要求了。而且这种结构便于理解,而且程序易构成模块化,在各个模块中调用实现更复杂的任务。
然而顺序结构的写法,有时候避免不了沉重冗长的时间等待。例如键盘扫描,你就给我弄了一个delay_20ms()函数,而在这延时的过程,其实 MCU可以做很多事情的,这不白白的浪费掉这段时间吗?其实,delay的这段时间用数码管显示代替,也就是在等待的过程,我们可以做一下显示。但仅此而已?
之前,我在做3寸大数码钟的时候就遇到过一个时间要求苛刻的问题,我采用了17个数码管,分成两组来动态显示。为了不闪烁,那么刷新频率起码大于 50Hz。而单片机还有其他任务,比如说读DS1302实时时钟,串口收发数据,按键扫描,读DS18B20等等,而其中最要命的是读取DS18B20温度传感器的数据,大家都知道其中等待温度转换的时间,基本要达到900ms了,这样一来,数码管就会闪烁得很厉害了。
所以,我网上找了一些资料学习。大家都采用“时间片轮询”算法的程序架构来写,这样既保证了实时,也充分利用了任务等待的时间。
下面简单来看看,关于时间片轮调的程序思想,而按照这种思路,可以衍生出很多程序结构。
假定,单片机要执行的任务有task_1(); task_2(); task_3(); ……task_n(); 各个任务对时间要求不同。
下面是我对时间片轮调的相关认识。
系统基准时间片:
我们采用定时器中断来产生系统的基准时间片,也叫系统的基准节拍,例如每4ms中断一次。这可以形象的比喻成脉搏心跳。
任务(事件)的轮调:
每一次心跳,我们就给任务执行的时间标志计数。当标志计数到了,就执行该任务函数!
事件的要求:
1.每一个事件的执行时间不允许超过一个时间片。
2.事件中不使用较长的delay();函数,可以使用定时延时等待,但永远必须遵守第一条要求。
3.执行时间较长的任务,或者较为复杂的任务,可以分割到多个时间片内执行。
实时性任务要求:
对于实时性要求较高的任务。比如串口收发事件,可以考虑放在主循环调用,或者再定时中断中调用。
参数传递要求:
各个任务函数之间参数传递,建议使用全局变量。任务中的内部函数,可以使用局部变量。
程序结构:
分析一下上面的程序结构,使用了一个定时器产生系统时钟滴答,然后时钟滴答到了,就更新时间标志,然后统一用一个事件函数来根据时间标志分时的执行各个任务函数。
但任务执行完后,时间标志被重置,并重新计数。那么这个任务函数就相当于被调度在了任务队列的末尾了!(感觉是不是有点任务调度管理的意思了?)
当然,各个任务函数调用的时间不同,就造成了任务执行频率的不同。这也是时间片的大小商定,以及时间片分布的问题,这需要从实际的任务考虑,并取得一个最佳的时间片,以及合理的安排各个任务函数的关系。
另外一种时间片轮调程序结构
其实,原理大致相同。执行机制不同罢了,各种程序结构有它优缺点,有最适合使用的地方。
下面,简单了解。
程序结构:
对于时间片轮询法的程序结构,无疑有比顺序结构程序更多的优点,但任务函数有时候被拆分成多段,不方便理解程序整体思路。