含延时程序的灯光显示
经过前一阶段的学习我们终于编写了一个可以控制灯光变化的程序。在实际应用中,既可以用这个程序控制发光二极管, 也可以控制继电器,还可以控制可控硅……。只是对于继电器、 可控硅等需要用较大功率推动的器件,不能用单片机直接带动,要在输出接上三极管或集成电路,以扩大推动能力。
仿照前面的讲解,可以根据自己需要改变灯光显示的顺序,也可以改变显示的花样。但是,前面的程序有一个缺点,就是勘浠灰淮蜗允荆?托枰?匆淮渭?獭?/span>
在实际工作中是否每运行一步都需要用按一次按键呢?如果真这样还不如直接用人工控制呢。其实,刚才的手动操作是为调试程序检查实验效果设计的,用手动操作,每次前进一步,如果运行的结果和设计的不同就可以停下来,检查原因。在计算机调试中称为“单步运行”。对应的由程序自己运行称之为“连续运行”。
如果在程序输入完成后,将K1(注有S-E的标记)拨向“E”一方,不是按STEP键,而是按一次EXE键,程序就自己运行了,在主机板显示“厂”字,其原意是显示英文“r”,(run 是运行的意思)但是形状相差比较大。
这时,再观察实验板的LED情况如何呢?并没有出现想象中的轮流显示,而是全部都亮了,只是亮度较刚才低了一点,这又是怎么回事呢?在手动实验中,每次变换都由人工操作,过程很慢,可以清楚的观察到灯光转换现象。由计算机直接运行,速度比人工快的多,虽然灯光是轮流显示的,由于眼睛的滞后效应观察起来灯光连成了一片。但是每个灯只有十分之一的时间在发光 (想想为什么?)所以就看起来暗了。
为了观察到轮流发光的效果,就必须在每次转换之后,让计算机停顿一段时间,可是计算机的运行是无法停止的,于是就在每次变换灯光之后加入一段程序,让计算机白白消耗一段时间再运行下一步。这段程序称做“延时程序”。延时程序在计算机程序中应用很普遍。
为了简洁,只给出两组灯光的变换程序,读者可以根据原理自己扩展。
程序二,延时灯光变换
8000 1 0RG 800H
8000 7590AA 2 LOOP:MOV P1,#0AAH;灯光1
8003 7FFF 3 MOV R7,#OFFH;预置R7为255
8005 7EFF 4 WAIT1:MOV R6,#OFFH;预置R6为255
8007 00 5 WAIT1: NOP ;空操作
8008 DEFD 6 DJNZ R6,WAIT2;R6减1,不为零返回DELA2处
800A DFF9 7 DJNZ R7,WAIT1;R7减1,不为零返回DELA1处
800C 759055 8 MOV P1, #055H;灯光2
800F 7FFF 9 MOV R7, #0FFH;预置R7为255
8011 7EFF 10 WAIT3: MOV R6,#0FFH;预置R6为255
8013 00 11 WAIT4: NOP;空操作
8014 DEFD 12 DJNZ R6,WAIT4;R6减1,不为零返回DELA2处
8016 DFF9 13 DJNZ R7,WAIT3 1R7减1,不为零返回DELA1处
8018 0100 14 AJMP LOOP
15 END
和程序一比较,这个程序中使用了R6、R7两个寄存器。在单片机中可以同时使用8个寄存器,分别叫R0、R1……R7。这么多寄存器做什么用呢?在一个复杂的程序中,要进行许多运算,运算的初始值和中间结果都可以用寄存器保存。如果寄存器太少运算就不方便。在我们这个程序中就使用了两个寄存器,至于使用哪一个都是一样。
下面对程序二的改进部分做一说明: 第3、4行将R7、R6寄存器预先送入0FFH (等于十进制 的255)。 然后执行一条空操作(NOP)指令,这条指令除了消耗一段时间(两个微秒)以外什么也不做。 DJNZ R6, WAIT2指令的功能是:将R6减1,并判断是否到0,如果没有到0,就返回WAIT2,再执行NOP(空操作指令)……直到循环了255次,R6减到0,执行下条指令。这条指令也消耗2微秒的时间。
后续DJNZ R7,WAIT3的功能类同上条,当R7减1不等于0时,返回到WAIT3,将R6又一次设定为0FFH,R6又执行一遍递减。如此这般总计循环了65025次(255x255)。在Dp-851K机器上每次循环消耗4微秒,总计约0.3秒,也就是说,每变换一次灯光就停顿0.3秒,由于时间加长,就可以清楚的观察到灯光变换了。改变R6、R7的设定值就可以改变延时时间。
把程序二的机器码输入到单片机中,实验一下连续运行方式,是否如上面分析的?可是,如果再用单步方式一步步地走一遍,即便一秒钟按2下键盘,完成一个循环也需要18个小时,而计算机仅用0.3秒,这就是计算机和人的区别!
程序二中,两个延时程序十分相似:第3-7行和第9-13行。如果增加灯光变换指令,还要在其后面加上延时程序,这样整个程序就显得很烦琐。能否将这些程序简化呢?完全可以,这需要采用于程序形式。
将程序中多处需要使用的程序编制为一个独立的部分称之为“子程序”。子程序的第一条加一个标号,作为子程序的名称, 最后一条一定是返回指令RET。在使用于程序的地方加入一条调用指令CALL XXXX即可。这里的XXXX是子程序的名称,也就是子程序的入口地址。上例可改编如下:
程序三:用延时子程序的灯光变换
8000 1 0RG 800H
8000 7590AA 2 LOOP: MOV P1,#0AAH;灯光1
8003 110C 3 ACALL WAIT ;调用延时子程序
8005 759055 4 MOV P1,#055H ;灯光2
8008 110C 5 ACALL WAIT ;调用延时子程序
800A 0100 6 AJMP L00P
800C 7FFF 8 WAIT:MOV R7, #0FFH;预置R7为255
800E 7EFF 9 WAIT1 : MOV R6, #0FFH;预置R6为25
8010 00 10 WAIT2: NOP ;空操作
8011 DEFD 11 DJNZ R6,WAIT2;R6减1,不为零返回DELA2处
8013 DFF9 12 DJNZ R7,WAIT1;R7减1,不为零返DELA1处
13 END
在这个程序中,编写了一个叫“WAIT”的延时子程序。延时程序的内容和前面程序二的3-7行相同,但是主程序简洁多了,子程序独立后也很清晰。使用于程序还有一些优点:将已经调试成功的模块编成子程序,以后可以直接使用,不必重新编写。在进行大型程序调试时,将一个大程序分解为若干个子程序,可以使程序流程清晰,也便于分别调试加快进度。使用子程序需要注意几个问题:
1·每个子程序必须有一个唯一的名字,便于主程序调用时指定。
2.子程序的最后一条必须是返回指令(RET)。
3·子程序中使用的寄存器不能和主程序冲突。在上例中,子程序使用了R6、R7,在主程序中就不能在调用子程序前、后用R6、R7存储数据。比如下面的程序就会出现错误。
MOV R6,#05H
MOV R7,#06H
ACALL WAIT
MOV A,R6
ADD A,R7
……
WAIT:MOV R7,#0FFH;预置R7为255
WAIT1:MOV R6,#0FFH;预置R6为255
WAIT2:NOP;空操作
DJNZ R6,WAIT2;R6减1,不为零返回DELA2处
DJNZ R7,WAIT1;R7减1,不为零返回DELAI处
END
程序的原意是在R6、R7中保存了数据,在延时之后求R6和R7的和。但是,在子程序WAIT中R6、R7已经被使用(最后实际值都为0)。后面的加法运算结果不是预计的0BH(二进制05H+06H=0BH)而是0。如果主程序和子程序使用的寄存器产生了冲突怎么办?在后面我们将讲到,可以用堆栈的方法来解决。