通过51单片机的烧录软件STC-ISP,只需给定系统时钟频率、定时长度及8051指令集,即可轻松生成延时代码。那么这个延时是如何计算出来的?本文可以进行一些解释,如有错误请指正。

如图所示,程序生成的500ms延时代码如下:

#include <REGX52.H>

void Delay500ms() //@12.000MHz

{

    unsigned char i, j, k;//无符号数,范围0-255,为0再自减会变为255

    _nop_();

    i = 4;

    j = 205;

    k = 187;

    do

    {

        do

        {

            while (--k);

        } while (--j);

    } while (--i);

}

为方便理解,展示汇编语言版本:

DELAY500MS: ;@12.000MHz

 PUSH 30H

 PUSH 31H

 PUSH 32H

 MOV 30H,#4

 MOV 31H,#205

 MOV 32H,#180

NEXT:

 DJNZ 32H,NEXT

 DJNZ 31H,NEXT

 DJNZ 30H,NEXT

 POP 32H

 POP 31H

 POP 30H

 RET

一、先明确两个 “时间基准”

在计算延时前,必须先搞懂 8051 单片机的两个核心时间概念,这是所有计算的基础:

1. 机器周期:延时的 “最小时间单位”

1 个机器周期 = 12 个时钟周期。我们用的是 12.000MHz 系统时钟(代码里@12.000MHz就是指这个),所以:时钟周期 = 1/12MHz ≈ 0.083μs机器周期 = 12 × 0.083μs ≈ 1μs

简单说:12MHz 下,1 个机器周期 = 1 微秒,延时时间就是 “总机器周期数 × 1μs”。

2. 指令周期:每个操作的 “耗时”

单片机执行不同指令,消耗的机器周期不同。我们代码里关键指令的耗时如下:

  • NOP(空操作,C 语言里的_nop_()):1 个机器周期;
  • MOV(赋值,如MOV 30H,#4)2个机器周期;(这里操作的是片内RAM,将立即数传入片内RAM,所以是2个机器周期,并非所有MOV指令都是2个,如 MOV R1,#4只需要1个机器周期)
  • DJNZ(减 1 并判断跳转,汇编里的核心循环指令):2 个机器周期;
  • PUSH/POP(压栈 / 出栈):各 2 个机器周期。

二、从 C 代码到汇编

STC-ISP 生成的 C 代码和汇编代码,逻辑是完全对应的,先理清变量和片上RAM的关系:

  • C 语言里的i=4 → 汇编里用30H RAM区,即MOV 30H,#4
  • C 语言里的j=205 → 汇编里用31H RAM区,即MOV 31H,#205
  • C 语言里的k=187 → 汇编里用32H RAM区,但为MOV 32H,#180;具体原因见后文
  • C 语言的三层嵌套循环(do-while) → 汇编里的三层DJNZ循环(DJNZ 32H,NEXTDJNZ 31H,NEXTDJNZ 30H,NEXT)。

三、核心:500ms 延时的 “机器周期叠加” 计算

延时的本质,就是把所有指令消耗的机器周期加起来,再乘以 1μs(12MHz 下)。我们按 “从内层到外层” 的顺序,一步步计算总周期:

1. 第一步:计算内层循环(最里层,DJNZ 32H,NEXT

内层循环由32H(对应 C 语言的k)控制,初始值 180,核心是DJNZ 32H,NEXT

  • 指令逻辑:每次执行 “32H减 1 → 判断是否为 0 → 不为 0 就跳回NEXT继续循环”;
  • 循环次数:从 180 减到 0,共执行 180 次;
  • 每次DJNZ耗 2 个机器周期,所以内层循环总周期:180 × 2 = 360 个机器周期。

2. 第二步:计算中层循环(中间层,DJNZ 31H,NEXT

中层循环由31H(对应 C 语言的j)控制,初始值 205,逻辑是 “每执行完一次内层循环,就判断31H是否为 0,不为 0 就重新执行内层循环”:

  • 首先,31H初始 205,意味着要 “触发 205 次内层循环”;
  • 但注意:第一次触发内层循环时,32H是初始值 187;后续触发时,32H已经减到 0,再次DJNZ会 “溢出”(8 位寄存器 0 减 1 变 255),所以后续内层循环会变成 256 次(从 255 减到 0);
  • 中层总周期拆解:
    • 第 1 次内层循环:360 个周期(前面算的 180×2);
    • 第 2~205 次内层循环(共 204 次):每次是 256×2=512 个周期(256 次DJNZ,每次 2 周期);
    • 再加上中层自身的DJNZ 31H,NEXT:205 次,每次 2 个周期(因为每触发一次内层循环,就要执行一次中层DJNZ);
  • 中层总周期:360 +(204×512) +(205×2)= 360 + 104448 + 410 = 105218 个机器周期。

3. 第三步:计算外层循环(最外层,DJNZ 30H,NEXT

外层循环由30H(对应 C 语言的i)控制,初始值 4,逻辑是 “每执行完一次中层循环,就判断30H是否为 0,不为 0 就重新执行中层循环”:

  • 30H初始 4,意味着要 “触发 4 次中层循环”;
  • 同理,第一次触发中层循环是 “非溢出状态”(31H=205),后续 3 次触发时,31H已减到 0,溢出后变成 255,中层循环会变成 “256 次内层循环 ×256 次中层循环” 的满周期;
  • 外层总周期拆解:
    • 第 1 次中层循环:105218 个周期(前面算的);
    • 第 2~4 次中层循环(共 3 次):每次是(256×512) +(256×2)= 131072 + 512 = 131584 个周期(256 次中层循环,每次含 256 次内层循环);
    • 再加上外层自身的DJNZ 30H,NEXT:4 次,每次 2 个周期;
  • 外层总周期:105218 +(3×131584) +(4×2)= 105218+ 394752 + 8 = 499978 个机器周期。

4. 第四步:加上 压栈/出栈 和NOP的周期

别忘了代码里还有PUSHPOP、MOV,这些也要算进去:

  • PUSH 30HPUSH 31HPUSH 32H:3 次压栈,每次 2 个周期,共 3×2=6 个;
  • MOV 30HMOV 31HMOV 32H:3 次赋值,每次 12个周期,共3×2=6  个;
  • POP 32HPOP 31HPOP 30H:3 次出栈,每次 2 个周期,共 3×2=6 个;
  • RET(函数返回):2 个机器周期;

这些额外周期总和: 6 + 6 + 6 + 2 = 20 个机器周期。

再加上调用此函数的指令周期(2个)

5. 最终:总延时时间

总机器周期数 = 外层循环周期 + 额外周期 = 499978 + 20 +2 = 500000个;总延时时间 = 总机器周期数 × 1μs =500000μs = 500ms。

再举一个简单的例子,大家可以自行计算验证一下

(234x2+256x2+2x2)+4+4+4+2+2=1000us=1ms。

注:适用于标准STC-Y1指令集 12T模式。若为1T,则1个机器周期对应1个时钟周期。

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐