说来汗颜,本人虽然从事硬件研发,但连电子工程师起码最应该懂的C51编程都没弄过。随着生活和工作压力的增大,心里也就开始浮躁了,以至于静不下心来搞技术了,后果就是我相当长一段时间都没有任何进步。最近想想自己微薄的工资和渺茫的未来,我决定还是从新开始,从51单片机编程开始,一步一个脚印,把该走的路都走了,做技术的本就应该脚踏实地,认认真真做出点东西。好了不多说了,下面开始讨论技术了。
以前从没听说过前后台系统,所以也没有用过,刚学C51编程哪会还是用教科书的程序,能够实现相应的功能,比如亮个LED灯,显示各数码管之类的,用的延迟都是Delay10ms()之类的,也没有觉得不妥。后来再往上查找资料时无意间看到前后台系统。在Google上搜索了一番后,如获至宝,那种感觉就像让我发现单片机编程的新大陆,回头再看教科书上程序,感觉就是功能性的演示,实际中什么用也没有。下面是我查找的关于前后台系统的解释的。
前后台系统 (Foreground/Background System)
这种系统可称为前后台系统或超循环系统(Super-Loops)。应用程序是一个无限的循环,循环中调用相应的函数完成相应的操作,这部分可以看成后台行为(background)。中断服务程序处理异步事件,这部分可以看成前台行为(foreground)。后台也可以叫做任务级。前台也叫中断级。时间相关性很强的关键操作(Critical operation)一定是靠中断服务来保证的。因为中断服务提供的信息一直要等到后台程序走到该处理这个信息这一步时才能得到处理,这种系统在处理信息的及时性上,比实际可以做到的要差。这个指标称作任务级响应时间。最坏情况下的任务级响应时间取决于整个循环的执行时间。因为循环的执行时间不是常数,程序经过某一特定部分的准确时间也是不能确定的。进而,如果程序修改了,循环的时序也会受到影响。
其实第一眼看到前后台系统,我脑海里不知怎么地就想起来初中学过的华罗庚的统筹方法。原文已经忘的干干静静了,只是清楚里面的道理。统筹方法告诉我们要合理分配时间,这样可以避免不必要等待,造成时间的浪费和效率的下降,这个在公司的项目管理中尤为有用。下面给出统筹法的连接
华罗庚的统筹方法——小学对我影响最深的课文
http://baike.baidu.com/view/293640.htm
在介绍前后台系统前,我们还是从最简单的程序开始,抛砖引玉。还是从最简单的LED灯闪烁开始,要求LED0以1Hz闪烁。
这里给出教科书上最经典的闪烁LED程序。
void main(void) { While(1) { LED0 = ON; DelayMs(500); LED0 = OFF; DelayMs(500); } }
把程序下载到单片机后,上电后看见LED0按预期目标闪烁,没什么问题。但是实际中你看哪个单片机系统只是用来闪烁个LED灯来着,那也太浪费了吧!总要加点功能吧!比如要求LED0以1Hz闪烁,LED1以2Hz闪烁,怎么办?好办啊,我改下程序就是了:
void main(void) { While(1) { LED1 = ON; DelayMs(250); LED1 = OFF; DelayMs(250); LED0=ON; LED1 = ON; DelayMs(250); LED1 = OFF; DelayMs(250); LED0=OFF; } }
如果再加些需求,要求LED0以1Hz闪烁,LED1以2Hz闪烁,LED2以3Hz闪烁,LED4以50Hz闪烁,再加上检测每10ms检测一次按键,每20ms动态刷新一次数码管。怎么办?你还有信心改程序么?反正我是已经头大了,还是想想问题出在哪了!想来想去都觉得问题是出在延时DelayMs()上了。为了说明问题,我这里做个估算,假设一个12MHz的51单片机,执行一个单周期指令需要1us。那么10ms差不多就可以执行10000条指令了。10000条指令,这是个什么概念啊?10ms对人来说可能都感觉不到,可对于单片机来说,哪可是相当长的时间啊。如果这段时间让CPU无意义的死等。什么事也不干。这可比人死等在火炉边等水烧开要严重的多。如果人要都像单片机这种等法,那就是等死啊。总之一句话,死等=等死。
思路决定出路,所以要换个思路来解决问题了。思考一下,要是不用DelayMs()的话,那CPU怎么样才知道每隔多久改变一次LED状态啊?这里又要联系到华罗庚的统筹方法了,这里面最重要的因素是什么?我觉得应该的时间,既然是时间那就离不开钟表。那么51单片机里面有没有钟表么?很幸运,还真有一个类似的,就是我们常说的定时器Timer。关于定时器网上描述一大堆,这里就不做详细说明了。简单的说定时器就像个闹铃,只要设置好每次闹铃的间隔时间,比如1ms的话,当启动定时器后,每隔1ms闹铃就会响一次(产生一次中断)。那么从现在开始设置闹铃,闹铃响10下,单片机就知道10ms到了;响了1000下,就知道经过了1s……。这样的话单片机就可以像人一样利用统筹法来工作了,比如LED0以1Hz闪烁的话(周期为1s,半个周期就是500ms),那么单片机可以在闹铃每响500下后,才去改变LED0 的状态。单片机在这段时间可以用来干其他事,完全不需要死等500ms。
好了,说了一大堆,脑袋有点晕了,上程序:
#include <reg52.h> sbit LED0 = P1^0; sbit LED1 = P1^1; sbit LED2 = P1^2; //定义软件计数器,用于对每个事件进行计数 unsigned int LED0_Counter; unsigned int LED1_Counter; unsigned int LED2_Counter; void Init(void) { //设置软件计数器 LED0_Counter=500; LED1_Counter=250; LED2_Counter=125; //设置定时器T0 TMOD&=0xf0; TMOD|=0x01; TH0=(65536-1000)/256; //定时1ms(晶振12MHz) TL0=(65536-1000)%256; ET0=1; //开定时器中断 TR0=1; //开定时器 EA=1; //开总中断 } void main(void) { Init(); while(1){ if(LED0_Counter==0){ //判断时间是否到,如果到了则执行LED0Event(),否则绕行 LED0_Counter=500;//LED0Event(),每500*1ms执行一次 LED0Event(); } if(LED1_Counter==0){ LED1_Counter=250; LED1Event(); //LED1Event(),每250*1ms执行一次 } if(LED1_Counter==0){ LED2_Counter==125; LED2Event(); //LED2Event(),每125*1ms执行一次 } } } void Timer0_isr(void) interrupt 1 { TH0=(65536-1000)/256; //定时1ms TL0=(65536-1000)%256; //每次中断,所有软件定时器减一,如果定时器为0,表明其相应事件执行的时间到了 if(LED0_Counter) LED0_Counter--; if(LED1_Counter) LED1_Counter--; if(LED2_Counter) LED2_Counter--; }
这里最关键的是定义了三个全局变量 LED0_Counter、LED1_Counter和LED2_Counter,相当于分别对LED0、LED1、LED2定义了一个软件计数器。软件定时器采用倒计时方式,只在Timer0中断中进行减一操作(相当于在每次闹铃的时候减一) ,只要减为0的话,就执行相应操作。