51单片机delay延时函数计算
通过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,NEXT→DJNZ 31H,NEXT→DJNZ 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的周期
别忘了代码里还有PUSH、POP、MOV,这些也要算进去:
PUSH 30H、PUSH 31H、PUSH 32H:3 次压栈,每次 2 个周期,共 3×2=6 个;MOV 30H、MOV 31H、MOV 32H:3 次赋值,每次 12个周期,共3×2=6 个;POP 32H、POP 31H、POP 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个时钟周期。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)