2.4 延时时间计算实例

在单片机的实时控制系统中,常常需要用到延时操作,所以,延时子程序往往是编写单片机程序中不可缺少的一部分。延时方法有硬件延时和软件延时,硬件延时将在后面有关章节中介绍,本节将介绍软件延时方法。

所谓软件延时,就是让计算机重复执行一些无具体任务的程序,利用执行程序的时间来达到延时的目的。

2.4.1 机器周期和指令周期

单片机读、写操作都需要消耗一定的时间,机器周期是指单片机完成一个基本操作所用的时间,如读操作、写操作等。当石英晶体为12MHz时,1个机器周期为1μs。

指令周期是指单片机执行一条指令所需要的时间,一个指令周期通常含有1~4个机器周期,其中常用的DJNZ指令周期为两个机器周期,即执行DJNZ指令需要2μs;MOV指令周期为一个机器周期,即执行MOV指令需要1μs。

2.4.2 单重循环短暂延时

短暂的时间延时可采取简单的单重循环结构来实现,例如,下面程序为延时540μs的短暂延时子程序,程序中采取了单重循环。

540μs延时子程序:

此子程序中,由第3、4 行代码构成单重循环结构,其中,DJNZ指令为控制转移指令,该指令每执行一次,寄存器R值减1,只要R值减1后不为0,就会转移到第3行标号DE1处去执行。

每循环一次需要的时间为3μs,由于R值为180,所以要循环180次,循环花费的时间为540μs。该延时子程序总的延时时间还要包括执行MOV指令时间1μs和执行RET返回指令时间2μs,但由于这个时间比循环时间要短很多,所以,延时时间的长短主要是由循环次数来控制。

2.4.3 多重循环较长时间延时

如果需要较长时间的延时,则需采用多重循环结构。例如下面程序为1s延时子程序,程序中采取了多重循环。

1s延时子程序:

此子程序采用了3重循环结构,先运行第5行代码操作,每次减1,减到0为止;再运行第6行代码对R6进行减操作,每次减1,减1后不为0,则转移到标号DLY2处运行,此时将R7赋值为100,并再对R7进行减法内循环。

当R6减为0时,程序运行到第7行,开始外循环,R5减1不为0时转移到标号DLY1处运行。为了计算上的简便,可以忽略赋值语句的时间,只计算“DJNZ R7, $”语句的执行时间,该指令执行一次需2μs,执行的次数为R5、R6和R7值的乘积,即:

总延时 = 2μs×R7×R6×R5

=2μs×100×100×50 = 1000 000μs

=1s

2.4.4 延时程序改进

从上节程序可以看出,只要改变寄存器R5、R6和R7的值,就可以获得不同延时时间。在上例中假设R6和R7值不变,只改变R5值(R5取值范围为1~255),可获得不同的延时。由于忽略了赋值语句的执行时间,实际延时时间要比计算的时间略大一些。

为了使一个延时子程序能产生不同的延时,可以将2.4.3节中的程序改进为以下的形式。其中R7、R6值不变,通过改进R5值,即改变外循环次数来改变延时时间,延时子程序的延时时间为0.02s×R5。

使用时,在调用DELAY延时子程序之前,要根据对延时时间的要求,先对R5赋值,假如延时为0.5s,需将R5值赋值为25;延时为1s,需将R5值为50,R5最大值为255,这是因为R0~R7都是8位寄存器,最大存放数据为二进制数11111111,即255,在使用时注意不要超出其有效范围。改进后的延时程序使用形式如下:

    MOV        R5, #50
    ACALL      DELAY_20ms×R5

延时时间为0.02s×R5的延时程序如下:

    1  DELAY_20ms×R5:
    2
    3     DLY1: MOV      R6, #100
    4     DLY2: MOV      R7, #100
    5              DJNZ  R7, $
    6              DJNZ  R6, DLY2
    7              DJNZ  R5, DLY1
    8              RET

2.5 用取表方式实现灯移动

功能说明:单片机端口接8只LED,编程时利用取表的方法,使端口P1的LED先一次点亮1只跳跃左移,点亮顺序为P1.0、P1.2、P1.4、P1.6;接着一次点亮2只,从左向右移动2次;一次点亮3只,左右移3次;然后4只灯P1.0、P1.2、P1.4、P1.6与P1.1、P1.3、P1.5、P1.7交互点亮4次;最后8只灯闪烁6次,不断循环。

2.5.1 程序设计

1.流程图

程序设计流程如图2-3所示。

图2-3 程序流程

2.程序

汇编语言编写的用取表方式实现灯移动源程序代码如下:

    01:     START:      MOV             DPTR,  #TABLE        ; 存表
    02:     LOOP:       CLR             A                    ; A清0
    03:                 MOVC            A, @A + DPTR         ; 取表代码
    04:                 CJNE            A, #01H, LOOP1       ; 不是01H,循环
    05:                 JMP             START                ; 重新设定
    06:     LOOP1:      MOV             P1, A                ; 将A值送入P1输出
    07:                 ACALL           DELAY                ; 调用延时子程序
    08:                 INC             DPTR                 ; 数据指针加1
    09:                 JMP             LOOP                 ; 跳转到LOOP处
    10:     DELAY:      MOV             R5, #25              ; 延时0.5s
    11:     DLY1:       MOV             R6, #100
    12:     DLY2:       MOV             R7, #100
    13:                 DJNZ            R7, $
    14:                 DJNZ            R6, DLY2
    15:                 DJNZ            R5, DLY1
    16:                 RET                                  ; 延时子程序返回
    17:  ; 单灯向左跳移1次控制码
    18:     TABLE:      DB              0FEH,0FBH,0EFH,0BFH
    19:  ; 2灯向右移动2次控制码
    20:                 DB              3FH,9FH,0CFH,0E7H
    21:                 DB              0F3H,0F9H,0FCH
    22:                 DB              3FH,9FH,0CFH,0E7H
    23:                 DB              0F3H,0F9H,0FCH
    24:  ; 3灯左右移动3次控制码
    25:                 DB              0F8H,1FH,0F8H,1FH
    26:  ; 4灯交互移动4次控制码
    27:                 DB              0AAH,55H,0AAH,55H
    28:  ; 8灯闪烁6次控制码
    29:                 DB              00H,0FFH,00H,0FFH
    30:                 DB              00H,0FFH,00H,0FFH
    31:                 DB              00H,0FFH,00H,0FFH
    32:                 DB              01H                       ; 结束码
    33:                 END                                       ; 程序结束

2.5.2 代码详解

1.标号说明

START:起始程序的进入点。

LOOP:循环操作的进入点。

LOOP1:输出循环的进入点。

DELAY:延时子程序的进入点。

DEL1:延时重复递减判断的进入点1。

DEL2:延时重复递减判断的进入点2。

TABLE:代码表的进入点。

2.寄存器使用分配情况

程序中A、P、R5、R6、R7、DPTR为寄存器,其中A、P、R5、R6、R7寄存器的作用与前节相同。

DPTR是新用到的寄存器,也是唯一的16位专用寄存器,它是由两个8位寄存器DPH和DPL拼装而成,其中DPH为DPTR的高8位,DPL为DPTR的低8位。它既可作为一个16位寄存器来使用,也可以作为两个独立的8位寄存器(DPH和DPL)来使用。

我们把代码表存入DPTR,确切地说是将代码表的首地址TABLE送到DPTR中作基地址,A作为变址寄存器,基址寄存器和变址寄存器的内容相加(@A + DPTR)形成16位地址,该地址既是操作数的地址,也是存入地址指针(也叫数据指针)。知道了地址指针,按照地址指针所指,自然就能取到代码表中的控制码了。

3.程序分析解释

本程序利用取表方式实现亮灯的移动。

利用取表的方式将控制码建成一个表,利用“MOV DPTR, #TABLE”语句存表,再利用“MOVC A, @A + DPTR”语句从表中读取出控制码,这种方法可方便地处理一些复杂的控制动作,下面重点介绍这一部分。

第01~09行代码的作用是存表、读取表并输出,是程序的主要部分。

01:存入TABLE表,用来控制灯的一组组十六进制数控制码建成一个表,取表名为TABLE。将TABLE表存放的地址存入特殊功能寄存器DPTR中,以便查找。

所谓存表是指存入表的首地址,即表的地址指针或数据指针。地址指针指向第1行第1位控制码的位置。总之,地址指针指向哪个位置就到哪个位置读取控制码。

02:将寄存器A中的内容清除为0。

03:查表读取控制码。“MOVC A, @A + DPTR”是一条很重要的查表语句,它将A + DPTR指定的表地址的数据送入寄存器A中。当程序第一次运行时,地址指针指向的位置是表中首位放置的控制码0FEH,将0FEH取出并存入寄存器A中。

04:判断读取出的控制码是否为结束码01H,如果不是则跳转到LOOP1 处。CJNE指令的功能是比较两个操作数,若不相等就跳转,只有相等时才继续向下运行。01H是结束标志码,放在表中的最后位置。

如果寄存器A中的数不是01H,即不相等,说明表还没有查完,程序跳转到LOOP1处继续查表读取出控制码并输出。

若寄存器A中的数是01H,即两个操作数相等了,说明表已经查完一遍,程序执行下一条语句。

05:程序跳转到标号START处,即跳转到程序开始处,重新设定显示值。

06:将取来的控制码送入P1并输出。

07:调用0.5s延时子程序。

08:数据指针加1,指向下一个操作码。INC指令的功能是使寄存器A中的数据加1,即地址指针向下移动一个位置,指向第二个控制码,为下一次取码做准备。

09:跳转到LOOP处,重新开始,不断循环。

10~16:0.5s延时子程序。

第18~32行代码为控制码编制表,表名为TABLE。

18:单灯跳跃左移控制码。

20~23:两只灯从左向右移动控制码。

25:3只灯左右移控制码。

27:4只灯交互点亮控制码。

亮灯的移动是由控制码控制的,控制码采用十六进制数,如果将十六进制码换成二进制码就可以清楚地看出灯是怎样被控制的。

29~31:闪烁6次控制码。控制码00H表示8个灯都亮(0表示亮);控制码0FFH表示8个灯都灭(1表示灭)。一亮一灭形成一次闪烁,共6对控制码,闪烁6次。

32:结束控制码。结束码可以是任意的数,存放的位置要在表的最后,标志控制码已经结束。此时,由于第04行语句的作用,当寄存器A中的数是01H,即两个操作数相等,程序将执行下一条语句,即跳转到标号LOOP处,程序重新开始。

如果没有结束码,当取表码结束后,程序运行下一行END指令,使整个程序结束。

此外,制表时要注意,每一行控制码的前边都要加伪指令DB,DB是定义字节的指令。

33:程序结束。

4.边用边学指令

本节使用新的指令有:MOVC、INC、CLR、CJNE和DB。

● MOVC:MOVC和前边经常用到的MOV指令都属于数据传送类指令,基本功能是传送数据,但使用上有所区别。MOVC的功能是将表中查到的一组数据送到寄存器A。常用形式如“MOVC A, @A + DPTR”,意思是将A + DPTR指定的内存地址的数据传送到寄存器A,所以也称查表指令MOVC。

● INC:是算术运算类指令。算术运算类指令包括有加、减、乘和除法指令,其中INC指令的功能是使累加器加1。

● CLR:属于位移类指令。CLR的功能是将累加器A清0。

● CJNE:属于控制转移类指令。CJNE与前边介绍的DJNZ指令都是有条件转移指令,DJNZ的条件是寄存器减1不为0转移,一般是与寄存器配合使用,用在控制已知循环次数的循环结构程序中。而CJNE的条件是对两数进行比较,不相等则转移,多用在分支结构的程序中。

● DB:是伪指令。其功能是从指定的地址单元开始,定义若干个字节作为内存单元的内容。

5.本章用过的指令归类

● 数据传送类指令:MOV、MOVC。

● 算术运算类指令:INC。

● 逻辑运算及位移类指令:RL、RR、CLR。

● 控制转移类指令:JMP、DJNZ、ACALL、RET、CJNE。

● 位操作类指令:CPL。

● 伪指令:END、DB。

2.5.3 模拟仿真

本节模拟仿真时主要注意观察取控制码的过程。

运行程序第1行语句时,把表TABLE的首地址存入到DPTR寄存器。

注意,在特殊功能寄存器窗口中看不到DPTR,而是看到两个独立的8位寄存器DPH和DPL,DPTR寄存器是由DPH和DPL组成的。此时DPL = 1CH,此地址是表的开头第1个控制码的地址,也称地址指针或数据指针,即该指针指向表中的第1个控制码0FEH。

运行程序第2行语句时将寄存器A清0。

运行程序第3行语句时,由于在第3行语句中是将A和DPTR两个寄存器内容相加,此时A为0,相加后仍为原DPTR的地址,此地址指针指向的是第1个控制码0FEH,所以,送入寄存器A的控制码是0FEH。

接下来再运行第6行语句时将控制码0FEH送入P1并输出。

运行到第8 行语句“INC DPTR”时,地址指针加1 即1CH + 01H = 1DH,所以DPL = 1DH,此地址指针指向的是第2个控制码0FBH,依次不断循环。

2.5.4 实例测试

将写入程序的单片机插入实验板接通电源后,会看到单灯向左跳移1次,双灯向右移动2次,3只灯左右移动3次,4只灯交互点亮,8只灯闪烁6次,不断循环。

如果将程序改动一下,把表TABLE中的结束码取消,再在实验板上运行程序,会看到亮灯在进行闪烁后,程序马上停止,不再循环。

这是因为当表查完后,程序要执行下一个语句“END”,END是程序结束指令,所以程序就此结束。

如果想查完一遍表后程序不立即结束,需要在表中最后一行设置一个结束标志码,当程序取到该结束标志码时,通过判断语句(如“CJNE A,#01H,LOOP1”)的作用,使程序不运行结束语句END,而跳转执行其他程序段。

2.5.5 经验总结

利用查表方法编写程序,可以很方便地完成一些复杂的控制功能。

首先通过DB伪指令把控制码制成一个表TABLE,然后再通过查表的指令查表取码。用于查表的指令有两条:

    MOVC  A,@A + DPTR
    MOVC  A,@A + PC

其中使用DPTR作为基地址来查表比较简单,可以通过两步操作来完成。

(1)利用“MOV DPTR,#TABLE”语句,将所查表的首地址存入DPTR数据指针寄存器。

(2)再利用“MOVC A,@A + DPTR”查表指令,就可以到数据指针所指的表格内读取出数据。