首页 AVR单片机 8051单片机 PIC单片机 msp430应用 motorola单片机 C166单片机 开关电源 电源设计 电源技术应用 电磁兼容
ARM芯片开发 DSP开发技术 PowerPC处理器 RABBIT单片机 嵌入式系统设计 工业控制 控制电路 PLC编程 机器人技术 电机原理
vhdl语言学习 asic芯片技术 fpga培训 cpld编程 verilog hdl Vxworks开发 wince开发教程 嵌入式linux uclinux开发 ucos入门
电路设计 数字电路设计 模拟电路设计 音频电路设计 模拟滤波器设计 电子技术基础 电子名词解析

首页->微处理器->单片机8051->正文

51单片机延时程序

作者:  来源:51mcu 

上篇:两片hc595级联控制8个数码管加8个按键
下篇:8051单片机内部结构分析

 

应用单片机的时候,经常会遇到需要短时间延时的情况。需要的延时时间很短,一般都是几十到几百微妙(us)。有时候还需要很高的精度,比如用单片机驱动 DS18B20的时候,误差容许的范围在十几us以内,不然很容易出错。这种情况下,用计时器往往有点小题大做。而在极端的情况下,计时器甚至已经全部派上了别的用途。这时就需要我们另想别的办法了。
以前用汇编语言写单片机程序的时候,这个问题还是相对容易解决的。比如用的是12MHz晶振的51,打算延时20us,只要用下面的代码,就可以满足一般的需要:
mov r0, #09h
loop: djnz r0, loop
51 单片机的指令周期是晶振频率的1/12,也就是1us一个周期。mov r0, #09h需要2个极其周期,djnz也需要2个极其周期。那么存在r0里的数就是(20-2)/2。用这种方法,可以非常方便的实现256us以下时间的延时。如果需要更长时间,可以使用两层嵌套。而且精度可以达到2us,一般来说,这已经足够了。
现在,应用更广泛的毫无疑问是Keil的 C编译器。相对汇编来说,C固然有很多优点,比如程序易维护,便于理解,适合大的项目。但缺点(我觉得这是C的唯一一个缺点了)就是实时性没有保证,无法预测代码执行的指令周期。因而在实时性要求高的场合,还需要汇编和C的联合应用。但是是不是这样一个延时程序,也需要用汇编来实现呢?为了找到这个答案,我做了一个实验。
用C语言实现延时程序,首先想到的就是C常用的循环语句。下面这段代码是我经常在网上看到的:
void delay2(unsigned char i)
{
for(; i != 0; i--);
}
到底这段代码能达到多高的精度呢?为了直接衡量这段代码的效果,我把 Keil C 根据这段代码产生的汇编代码找了出来:
; FUNCTION _delay2 (BEGIN)
; SOURCE LINE # 18
;---- Variable i assigned to Register R7 ----
; SOURCE LINE # 19
; SOURCE LINE # 20
0000 ?C0007:
0000 EF MOV A,R7
0001 6003 JZ ?C0010
0003 1F DEC R7
0004 80FA SJMP ?C0007
; SOURCE LINE # 21
0006 ?C0010:
0006 22 RET
; FUNCTION _delay2 (END)
真是不看不知道~~~一看才知道这个延时程序是多么的不准点~~~光看主要的那四条语句,就需要6个机器周期。也就是说,它的精度顶多也就是6us而已,这还没算上一条 lcall 和一条 ret。如果我们把调用函数时赋的i值根延时长度列一个表的话,就是:
i delay time/us
0 6
1 12
2 18
...
因为函数的调用需要2个时钟周期的lcall,所以delay time比从函数代码的执行时间多2。顺便提一下,有的朋友写的是这样的代码:
void delay2(unsigned char i)
{
unsigned char a;
for(a = i; a != 0; a--);
}
可能有人认为这会生成更长的汇编代码来,但是事实证明:
; FUNCTION _delay2 (BEGIN)
; SOURCE LINE # 18
;---- Variable i assigned to Register R7 ----
; SOURCE LINE # 19
; SOURCE LINE # 21
;---- Variable a assigned to Register R7 ----
0000 ?C0007:
0000 EF MOV A,R7
0001 6003 JZ ?C0010
0003 1F DEC R7
0004 80FA SJMP ?C0007
; SOURCE LINE # 22
0006 ?C0010:
0006 22 RET
; FUNCTION _delay2 (END)
其生成的代码是一样的。不过这的确不是什么好的习惯。因为这里实在没有必要再引入多余的变量。我们继续讨论正题。有的朋友为了得当更长的延时,甚至用了这样的代码:
void delay2(unsigned long i)
{
for(; i != 0; i--);
}
这段代码产生的汇编代码是什么样子的?其实不用想也知道它是如何恐怖的$#^%&%$......让我们看一看:
; FUNCTION _delay2 (BEGIN)
; SOURCE LINE # 18
0000 8F00 R MOV i+03H,R7
0002 8E00 R MOV i+02H,R6
0004 8D00 R MOV i+01H,R5
0006 8C00 R MOV i,R4
; SOURCE LINE # 19
; SOURCE LINE # 20
0008 ?C0007:
0008 E4 CLR A
0009 FF MOV R7,A
000A FE MOV R6,A
000B FD MOV R5,A
000C FC MOV R4,A
000D AB00 R MOV R3,i+03H
000F AA00 R MOV R2,i+02H
0011 A900 R MOV R1,i+01H
0013 A800 R MOV R0,i
0015 C3 CLR C
0016 120000 E LCALL ?C?ULCMP
0019 601A JZ ?C0010
001B E500 R MOV A,i+03H
001D 24FF ADD A,#0FFH
001F F500 R MOV i+03H,A
0021 E500 R MOV A,i+02H
0023 34FF ADDC A,#0FFH
0025 F500 R MOV i+02H,A
0027 E500 R MOV A,i+01H
0029 34FF ADDC A,#0FFH
002B F500 R MOV i+01H,A
002D E500 R MOV A,i
002F 34FF ADDC A,#0FFH
0031 F500 R MOV i,A
0033 80D3 SJMP ?C0007
; SOURCE LINE # 21
0035 ?C0010:
0035 22 RET
; FUNCTION _delay2 (END)
呵呵,这倒是的确可以延迟很长时间~~~但是毫无精度可言了。
那么,用C到底能不能实现精确的延时呢?我把代码稍微改了一下:
void delay1(unsigned char i)
{
while(i--);
}
因为根据经验,越简洁的C代码往往也能得出越简洁的机器代码。那这样结果如何呢?把它生成的汇编代码拿出来看一看就知道了。满怀希望的我按下了“Build target”键,结果打击是巨大的:
; FUNCTION _delay1 (BEGIN)
; SOURCE LINE # 13
;---- Variable i assigned to Register R7 ----
; SOURCE LINE # 14
0000 ?C0004:
; SOURCE LINE # 15
0000 AE07 MOV R6,AR7
0002 1F DEC R7
0003 EE MOV A,R6
0004 70FA JNZ ?C0004
; SOURCE LINE # 16
0006 ?C0006:
0006 22 RET
; FUNCTION _delay1 (END)
虽说生成的代码跟用for语句是不大一样,不过我可以毫无疑问的说,这两种方法的效率是一样的。似乎到此为止了,因为我实在想不出来源程序还有什么简化的余地。看来我就要得出来这个结论了:“如果需要us级的延时精度,需要时用汇编语言。”但是真的是这样吗?我还是不甘心。因为我不相信大名鼎鼎的 Keil C 编译器居然连 djnz 都不会用???因为实际上程序体里只需要一句 loop: djnz r7, loop。近乎绝望之际(往往人在这种情况下确可以爆发出来,哦呵呵呵~~~),我随手改了一下:
void delay1(unsigned char i)
{
while(--i);
}
心不在焉的编译,看源码:
; FUNCTION _delay1 (BEGIN)
; SOURCE LINE # 13
;---- Variable i assigned to Register R7 ----
; SOURCE LINE # 14
0000 ?C0004:
; SOURCE LINE # 15
0000 DFFE DJNZ R7,?C0004
; SOURCE LINE # 16
0002 ?C0006:
0002 22 RET
; FUNCTION _delay1 (END)
天~~~奇迹出现了......我想这个程序应该已经可以满足一般情况下的需要了。如果列个表格的话:
i delay time/us
1 5
2 7
3 9
...
计算延时时间时,已经算上了调用函数的lcall语句所花的2个时钟周期的时间。

相关资料:
c语言编程的难点单片机的C语言应用程序设计
关于单片机硬件抗干扰学用8051单片机的常见问题
微处理器硬件电路设计的几个基本问题51系列单片机I/O口上拉电阻使用点滴
关于C51的中断编程嵌入式系统综述
MCS-51单片机实用子程序库(7)MCS-51单片机实用子程序库(6)
MCS-51单片机实用子程序库(5)MCS-51单片机实用子程序库(4)
MCS-51单片机实用子程序库(3)MCS-51单片机实用子程序库(2)
MCS-51单片机实用子程序库(1)单片机基础知识


网站地图 | 联系我们 | 免责声明 | 招聘信息 | 友情链接
Copyright © 2005 - 2006 单片机设计网