传统的数码管显示程序为:
#define DUAN P1
#define WEI P0
void delayms(uchar x)
{
uchar y=120;
while(x--)
while(y--);
}
WEI=0; //消影 共阳,共阴为 WEI=0xff
DUAN=code[value_duan1]; //送段选数据
WEI=value_wei1; //送位选数据,确定第几个数码管点亮
Delayms(5); //延时5ms使其显示稳定
WEI=0; //消影 共阳,共阴为 WEI=0xff
DUAN=code[value_duan2]; //送段选数据
WEI= value_wei2; //送位选数据,确定第几个数码管点亮
Delayms(5); //延时5ms使其显示稳定
当延时后依次再送下一个数据,再延时······
这里我想再次说一下关于延时的问题。一般教科书或者说目前绝大多数能看到的数码管处理程序资料大多都是按照上面的方式处理的。我想问一下,这里延时5ms的意义何在?可否不延时?答案是可以,但显示结果就是最后一个被点亮的数码管会比较亮,其余的都比较暗。至于原因很简单,点亮最后一个数码管后,单片机CPU还要跑其他程序,然后再次跑到数码管显示处理函数时再依次点亮第一第二个数码管。显然这样最后一个被点亮的数码管点亮的时间远远比其他的数码管时间要长,自然这一个特别亮,其余的很暗淡。然而加上5ms延时的话呢?由于单片机速度还算比较快(一般一条指令1us),5ms相当于5000条指令。5000条指令什么概念呢?怎么说呢,若程序里不用“delay”这样的空指令的话,一般一个大型项目就差不多了。一般而言,比如AD测温、adclass=0&app_id=0&c=news&cf=1001&ch=0&di=128&fv=17&is_app=0&jk=85d214d39ac24f75&k=%B5%E7%D7%D3%CA%B1%D6%D3&k0=%B5%E7%D7%D3%CA%B1%D6%D3&kdi0=0&luki=8&n=10&p=baidu&q=98059059_cpr&rb=0&rs=1&seller_id=1&sid=754fc29ad314d285&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1831118&u=http%3A%2F%2Fwww%2E51hei%2Ecom%2Fmcu%2F2120%2Ehtml&urlid=0" id="16_nwl" mpid="16" target="_blank">电子时钟等这样小项目实际有用的指令绝对不会达到好几千的样子。所以说5ms对于单片机来说是个相当长的时间。
再回到延时5ms后显示较为稳定的问题上。因为点亮最后一个数码管延时5ms后单片机在跑其他的指令时相对要不了多久(前提是其余地方没有“delay”空指令),所以最后一个数码管比其他数码管多亮的时间就不太明显了,这样自然显示也会比较均匀一些。但是事实上,最后一个被点亮的数码管还是稍微比别的数码管亮一些。注意上面所说的是单片机跑其他指令用的时间不长的情况。如果,程序比较大,模块很多,单片机要处理的事情很多呢?比方说一个float型数据的除法(不知道读者有没有在单片机上试过)所耗费的指令数可能你都想象不到(尤其是对于内部不含硬件除法器单片机,其除法指令转换成其他的运算)。我当时做AD测电阻时用的sonix芯片(没有除法器),开始程序里面一个float型除法,整个程序当时有1.5k的样子,数码管显示,老是一个亮,其余的暗淡,而且很不稳定。后来查找原因,就是因为那条float型除法的问题,竟然占了大几ms的时间。当删除那条指令后,整个程序只有700多字节,也就是说就这么一条指令就直接让程序大小翻倍了!查看编译器翻译成的汇编指令占了相当大一部分,而且还有很多CALL指令。所以说,8位单片机 确实不适合做除法以及float运算。
扯远了,回到数码管问题。综上面所述,delayms(5)的方式是不可靠的。而且最为关键的时delay指令是毫无意义的,就是让单片机啥也不做,在那里死等。试想一下,单片机还要处理其他事情,光在这里死等岂不是太浪费了吗?就好比人一样我可以在吃饭的同时听音乐,而不是先吃饭,饭吃完了再专门听音乐。单片机也是一样,要的是效率,而不是在那里死等。
那么这种方式不好,该怎么办呢?要不要延时?延时肯定是需要的,不然显示不均匀,但不是这种方式。正确的方式是定时器定时5ms,5ms到后刷新一次数码管,这样一来单片机不会在这里死等,二来数码管显示时间绝对均匀。对于定时器,一般的单片机至少有一个,而且它作为单片机的独立模块,根本不影响cpu工作。以3个数码管为例,其程序代码如下
#define DUAN P1 //宏定义段选
#define WEI P0 //宏定义位选
void timer(); //定时器处理函数,用定时器定时5ms
{
if(定时标志位置一)
{
定时标志清零;
If(T_5ms) //若T_5ms大于0,每5ms减1
T_5ms--;
}
}
if(T_5ms==2)
{
WEI=0; //消影 共阳,共阴为 WEI=0xff
DUAN=code[value_duan1];
WEI= value_wei1;
}
if(T_5ms==1)
{
WEI=0;
DUAN=code[value_duan1];
WEI= value_wei2;
}
if(T_5ms==0)
{
T_5ms=3; //计时寄存器重新赋值
WEI=0;
DUAN=code[value1];
WEI=value2;
}
这样CPU不用在这里死等,每次程序跑到这里时,只需做个判断就好了,5ms到后就进去点亮数码管,否则就不进去,显示效果绝对均匀。同理,诸如键盘扫描程序,延时消抖,都可以采用这种方式,而不用delay。
也许读者可能发现了问题,对,还是有点问题。假若程序在其他地方耗费时间大于5ms的话,那么这里的定时5ms就失去了作用。确实是这样的。实际上对于好程序来说,主循环绝对控制在1ms一下(CPU跑1Mhz,即1条指令1us)。也许你会问,有些模块不可能1ms以内就完成啊。是的,有些时候某些模块确实需要很久才能完成。这就涉及到程序分时分段处理的问题。好比人做事情一样,我这件事今天做不完,但可以明天再做,而且同时今天我也要吃饭、睡觉做其他的事情,而不是说这件事没做完,别的什么也不干了。单片机也是如此。不管在大的程序,都是分时分段处理的,不然CPU会崩溃的,CPU不可能同时把所有的都一起处理了,而是这段时间处理一点,下段时间再处理一点。这样在总的时间上是一样的,CPU完成的事情却翻倍了。说道这里程序主循环控制在1ms绝对不成问题了。在试想一下,在程序里面到处delay是不是很可怕?比如按键,消抖可以delay 10ms,如果是长按键呢?难道要delay 3s或更长?所以说不管从功能上还是程序结构上,delay是绝对不可取的。对于delay,几个us(相当于几个nop指令)还是允许的。
说到这里,程序分时分段思想已经很明白了。虽然说一个大型项目,包括很多模块,比如按键、AD采样、数码管显示(或lcd显示)、PWM输出、UART、IIC通讯等等,但是并不是说每个模块都在同一时间完成。比如按键按一下大概几百毫秒的样子,长按好几秒的样子,主程序不是一直在这里等,而是一遍又一遍的循环扫描,当扫描的按键键值变化而且连续在100ms以内没有变化,那么确认此次为短按键按下。每次在扫描键盘时大概耗费几十us,然后接着以同样的方式扫描其他模块。这样主循环把所有的模块都扫一遍顶多也就几百us的样子。这样说来,一次按键按下事件,程序已经把它分成了大概几百次来完成,即为分时处理。而不是以delay的方式死等这一次事件完成。其他模块都遵循这个道理。
告别delay,主循环while(1)周期做到小于1ms,那么就告别了学校教育从而进入实际应用!为什么学校所教的不是这种思想呢?因为在学校和实践严重脱节,没有考虑过真正的项目。所学的都是一些独立子程序模块。由于子程序结构简单,程序较小,delay无所谓。而把各个模块都加在一起,可能就会出毛病。实际情况要考虑的还多的很,比如功耗、成本等等。在学校只管能搞出结果就不错了,哪管这个芯片多少钱,这个电容多少钱?对于真正的项目应用,实现功能只算很小很小的一部分。由于学校所学和实际脱节,所以这也是当今大学生难找工作的原因之一,这也是当今中国教育的缺陷!当然成为单片机高手,还有很多路要走,了解这些只是一个门槛而已。