1. 概要

延时在嵌入式系统中,无处不在。延时的作用是等待固定的时间后,执行某个操作。
如,控制LED闪烁,如果延时很小或者没有延时,人眼无法看到闪烁的效果。

2. 原理

CPU在延时处理中,执行指定的时间。在时间未到之前,不能返回或被打断。

3. 实现方式

实现方法有两种:硬件延时,软件延时。

3.1. 软件延时

许多情况下,系统的定时器资源有限,或者用作其他用途,这时可以选择使用软件延时的方法。软件延时,通常使用循环体实现。

3.1.1. 汇编延时

汇编语言延时特点是准确,当是不灵活。

在软件中,每执行一条指令都是要消耗时间的。那么,一条指令的执行时间是确定的吗?对于时钟固定的系统,答案是肯定的。时钟是嵌入式系统的“小心脏”。以51单片机12MHz晶振为例,12MHz的意思是单片机1s内可以执行(12M/12)=1M个机器周期,51单片机规定每个机器周期需要12个时钟震荡周期。换句话说,每个机器周期的时间=1/1M=1um。不同的汇编指令,消耗的时间是机器周期的整数倍。

指令 功能 机器周期数 时间(um)

MOV     数据传送    1   1
NOP     空操作 1   1
DJNZ    循环移位    2   2
LCALL   调用      2   2
RET     返回  2   2

因此,使用汇编语句可以实现很精确的延时。汇编实例分析,50ms延时子程序

DEL :    MOV R7, #200    ①
DEL1:    MOV R6, #125    ②
DEL2:    DJNZ R6, DEL2   ③
         DJNZ R7, DEL1   ④
         RET             ⑤

①:MOV R7, #200 ,单周期指令,执行一次,时间1us
②:MOV R6, #125,单周期指令,由于④,循环执行200次,时间200us
③:DJNZ R6, DEL2,双周期指令,由于②和④,循环执行125*200次,时间2*125*200us
④:DJNZ R7, DEL1,双周期指令,由于①,循环执行200次,时间2*200us
⑤:RET,双周期指令,只执行一次,时间2us

总时间=①+②+③+④+⑤=1+200+2*125*200+2*200+2=50603≈50ms

3.1.2. C 语言延时

C 语言实现的延时程序就相对灵活一些。
C 语言最终编译为汇编语言,就可以知道程序的执行时间了。

可以在C文件中使用NOP()语句实现。(假设:晶振12MHz,一个机器周期1us.)
例1:

void delay_10us(void)
{
    _NOP_();
    _NOP_();
    _NOP_();
    _NOP_();
    _NOP_();
    _NOP_();
}

分析:上面函数中使用了6个NOP()语句,每句执行时间为1us;调用delay_10us函数时,执行LCALL指令(2个机器周期,2us),函数退出时执行RET指令(2个机器周期,2us),执行该函数共消耗10us时间。

例2:

void delay(unsigned char n)
{
    while(--n);
}

如果调用delay(1),keil C对应的汇编代码为

C:0x0005 7F01 MOV R7,#0x01
C:0x0007 111D ACALL delay1(C:001D)
C:0x001D DFFE DJNZ R7,delay1(C:001D)
C:0x001F 22 RET

分析:传递参数1,函数调用2,函数返回2,此三个为固定开销,还有DJNZ的2个
实际延时= 1+2+2+n*2

例3:

void delay_500_ms(void)
{
    unsigned char i, j, k;
    for(i =  15; i > 0; i--)
        for(j = 202; j > 0; j--)
            for(k = 81; k > 0; k--);
}

对应的汇编代码

DEL:  MOV R7, #15
DEL1: MOV R6, #202
DEL2: MOV R5, #81①
      DJNZ R5, $①
      DJNZ R6, DEL2
      DJNZ R7, DEL1
      RET

分析:
一层循环n:R5*2 = 81*2 = 162us DJNZ 2us
二层循环m:R6*(n+3) = 202*165 = 33330us DJNZ 2us + R5赋值 1us = 3us
三层循环: R7*(m+3) = 15*33333 = 499995us DJNZ 2us + R6赋值 1us = 3us
循环外: 5us 子程序调用 2us + 子程序返回 2us + R7赋值 1us = 5us
延时总时间 = 三层循环 + 循环外 = 499995+5 = 500000us =500ms
计算公式:延时时间=[(2*R5+3)*R6+3]*R7+5

3.1.3. 软件延时步骤

  1. 确定系统时钟
  2. 计算指令周期
  3. C语言,分析对应的汇编代码
  4. 根据指令消耗的机器周期数,计算延时时间

3.2. 硬件延时

硬件延时就是使用定时器了。

对于晶振12MHz,一个机器周期1us的51单片机而言,最长的延时时间为216=65536us,若定时器采用工作方式2,可以实现精确延时。步骤,配置定时器工作方式,设置计数值,启动定时器,等待溢出,停止计数器。

void delay_1ms(void)
{
    TMOD = 0x01;      /*定时器0工作在模式1下(16位计数器)*/
    TH0 = 0xfd;       /*(65536-1000)/256*/
    TL0 = 0x65;       /*(65536-1000)%256*/

    TR0 = 1;          /*启动定时器*/
    while(TF0 == 0);  /*计数器溢出后TF0由0变1*/
    TR0 = 0;          /*停止定时器*/
}

65.536ms内的延时程序

void delay_us(int n)
{
    TMOD = 0x01;
    TH0 = (65536-n)/0xff;
    TL0 = (65536-n)%0xff;
    TR0 = 1;
    while(TF0 == 0);
    TR0 = 0;
}

3.2.1 硬件延时步骤

  1. 选择定时器
  2. 配置定时器工作方式
  3. 计算计数值,设置相关寄存器
  4. 启动定时器
  5. 等待溢出
  6. 停止定时器

参考

单片机延时方法总结
51单片机Keil C延时程序的简单研究