C51中精确的延时与计算的实现Word文件下载.docx
- 文档编号:19262471
- 上传时间:2023-01-04
- 格式:DOCX
- 页数:14
- 大小:21.41KB
C51中精确的延时与计算的实现Word文件下载.docx
《C51中精确的延时与计算的实现Word文件下载.docx》由会员分享,可在线阅读,更多相关《C51中精确的延时与计算的实现Word文件下载.docx(14页珍藏版)》请在冰豆网上搜索。
其三:
对于要求精确延时时间更长,这时就要采用循环嵌套的方法来实现,因此,循环嵌套的方法常用于达到ms级的延时。
对于循环语句同样可以采用for,do…while,while结构来完成,每个循环体内的变量仍然采用无符号字符变量。
unsignedchari,j
i--)
for(j=255;
j0;
j--);
i=255;
do{j=255;
do{j--}
while(j);
i--;
}
while(i);
while(i)
{j=255;
while(j)
{j--};
i--;
}
这三种方法都是用DJNZ指令嵌套实现循环的,由C51编译器用下面的指令组合来完成的
MOV R7,#0FFH
LOOP2:
MOV R6,#0FFH
LOOP1:
DJNZ R6,LOOP1
DJNZ R7,LOOP2
这些指令的组合在汇编语言中采用DJNZ指令来做延时用,因此它的时间精确计算也是很简单,假上面变量i的初值为m,变量j的初值为n,则总延时时间为:
m×
(n×
T+T),其中T为DJNZ指令执行时间。
同样对于更长时间的延时,可以采用多重循环来完成。
只要在程序设计循环语句时注意以上几个问题。
下面给出有关在C51中延时子程序设计时要注意的问题
1、在C51中进行精确的延时子程序设计时,尽量不要或少在延时子程序中定义局部变量,所有的延时子程序中变量通过有参函数传递。
2、在延时子程序设计时,采用do…while,结构做循环体要比for结构做循环体好。
3、在延时子程序设计时,要进行循环体嵌套时,采用先内循环,再减减比先减减,再内循环要好。
unsignedchardelay(unsignedchari,unsignedcharj,unsignedchark)
{unsignedcharb,c;
b=j;
c=k;
do{
do{
do{k--};
while(k);
k=c;
j--;
};
while(j);
j=b;
while(i);
这精确延时子程序就被C51编译为有下面的指令组合完成
delay延时子程序如下:
MOVR6,05H
MOVR4,03H
C0012:
DJNZR3,C0012
MOVR3,04H
DJNZR5,C0012
MOVR5,06H
DJNZR7,C0012
RET
假设参数变量i的初值为m,参数变量j的初值为n,参数变量k的初值为l,则总延时时间为:
l×
(m×
T+2T)+2T)+3T,其中T为DJNZ和MOV指令执行的时间。
当m=n=l时,精确延时为9T,最短;
当m=n=l=256时,精确延时到16908803T,最长。
采用软件定时的计算方法
利用指令执行周期设定,以下为一段延时程序:
指令周期
MOV1
DJNZ2
NOP1
采用循环方式定时,有程序:
MOVR5,#TIME2;
周期1
LOOP1MOVR6,#TIME1;
1
LOOP2NOP;
NOP;
DJNZR6,LOOP2;
2
DJNZR5,LOOP1;
定时数=(TIME14+2+1)TIM22+4
==============================================================================
[转帖]KeilC51延时程序的两次研究
51单片机KeilC延时程序的简单研究
应用单片机的时候,经常会遇到需要短时间延时的情况。
需要的延时时间很短,一般都是几十到几百(us)。
有时候还需要很高的精度,比如用单片机驱动DS18B20的时候,误差容许的范围在十几us以内,不然很容易出错。
这种情况下,用计时器往往有点小题大做。
而在极端的情况下,计时器甚至已经全部派上了别的用途。
这时就需要我们另想别的办法了。
以前用汇编语言写单片机程序的时候,这个问题还是相对容易解决的。
比如用的是12MHz晶振的51,打算延时20us,只要用下面的代码,就可以满足一般的需要:
movr0,#09hloop
djnzr0,loop
51单片机的指令周期是晶振频率的112,也就是1us一个周期。
movr0,#09h需要2个极其周期,djnz也需要2个极其周期。
那么存在r0里的数就是(20-2)2。
用这种方法,可以非常方便的实现256us以下时间的延时。
如果需要更长时间,可以使用两层嵌套。
而且精度可以达到2us,一般来说,这已经足够了。
现在,应用更广泛的毫无疑问是Keil的C编译器。
相对汇编来说,C固然有很多优点,比如程序易维护,便于理解,适合大的项目。
但缺点(我觉得这是C的唯一一个缺点了)就是实时性没有保证,无法预测代码执行的指令周期。
因而在实时性要求高的场合,还需要汇编和C的联合应用。
但是是不是这样一个延时程序,也需要用汇编来实现呢?
为了找到这个答案,我做了一个实验。
用C语言实现延时程序,首先想到的就是C常用的循环语句。
下面这段代码是我经常在网上看到的:
voiddelay2(unsignedchari)
{for(;
i!
=0;
i--);
到底这段代码能达到多高的精度呢?
为了直接衡量这段代码的效果,我把KeilC根据这段代码产生的汇编代码找了出来:
FUNCTION_delay2(BEGIN)
SOURCELINE#18
----Variable'
i'
assignedtoRegister'
R7'
----
SOURCELINE#19
SOURCELINE#20
0000C0007
0000EFMOVA,R7
00016003JZC0010
00031FDECR7
000480FASJMPC0007
SOURCELINE#21
0006C0010
000622RET
FUNCTION_delay2(END)
真是不看不知道~~~一看才知道这个延时程序是多么的不准点~~~光看主要的那四条语句,就需要6个机器周期。
也就是说,它的精度顶多也就是6us而已,这还没算上一条lcall和一条ret。
如果我们把调用函数时赋的i值根延时长度列一个表的话,就是:
idelaytimeus
06
112
218
...
因为函数的调用需要2个时钟周期的lcall,所以delaytime比从函数代码的执行时间多2。
顺便提一下,有的朋友写的是这样的代码:
{
unsignedchara;
for(a=i;
a!
a--);
可能有人认为这会生成更长的汇编代码来,但是事实证明:
a'
----
SOURCELINE#22
其生成的代码是一样的。
不过这的确不是什么好的习惯。
因为这里实在没有必要再引入多余的变量。
我们继续讨论正题。
有的朋友为了得当更长的延时,甚至用了这样的代码:
voiddelay2(unsignedlongi)
for(;
这段代码产生的汇编代码是什么样子的?
其实不用想也知道它是如何恐怖的$#^%&
%$......让我们看一看:
SOURCELINE#18
00008F00R
MOVi+03H,R7
00028E00R
MOVi+02H,R6
00048D00R
MOVi+01H,R5
00068C00R
MOVi,R4
0008C0007
0008E4CLRA
0009FFMOVR7,A
000AFEMOVR6,A
000BFDMOVR5,A
000CFCMOVR4,A
000DAB00RMOVR3,i+03H
000FAA00RMOVR2,i+02H
0011A900RMOVR1,i+01H
0013A800RMOVR0,i
0015C3CLRC
0016120000ELCALLCULCMP
0019601AJZC0010
001BE500RMOVA,i+03H
001D24FFADDA,#0FFH
001FF500RMOVi+03H,A
0021E500RMOVA,i+02H
002334FFADDCA,#0FFH
0025F500RMOVi+02H,A
0027E500RMOVA,i+01H
002934FFADDCA,#0FFH
002BF500RMOVi+01H,A
002DE500RMOVA,i
002F34FFADDCA,#0FFH
0031F500RMOVi,A
003380D3SJMPC0007
SOURCELINE#21
0035C0010
003522RET
FUNCTION_delay2(END)
呵呵,这倒是的确可以延迟很长时间~~~但是毫无精度可言了。
那么,用C到底能不能实现精确的延时呢?
我把代码稍微改了一下:
voiddelay1(unsignedchari)
while(i--);
因为根据经验,越简洁的C代码往往也能得出越简洁的机器代码。
那这样结果如何呢?
把它生成的汇编代码拿出来看一看就知道了。
满怀希望的我按下了“Buildtarget”键,结果打击是巨大的:
FUNCTION_delay1(BEGIN)
SOURCELINE#13
SOURCELINE#14
0000C0004
SOURCELINE#15
0000AE07MOVR6,AR7
00021FDECR7
0003EEMOVA,R6
000470FAJNZC0004
SOURCELINE#16
0006C0006
FUNCTION_delay1(END)
虽说生成的代码跟用for语句是不大一样,不过我可以毫无疑问的说,这两种方法的效率是一样的。
似乎到此为止了,因为我实在想不出来源程序还有什么简化的余地。
看来我就要得出来这个结论了:
“如果需要us级的延时精度,需要时用汇编语言。
”但是真的是这样吗?
我还是不甘心。
因为我不相信大名鼎鼎的KeilC编译器居然连djnz都不会用?
?
因为实际上程序体里只需要一句loopdjnzr7,loop。
近乎绝望之际(往往人在这种情况下确可以爆发出来,哦呵呵呵~~~),我随手改了一下:
{
while(--i);
心不在焉的编译,看源码:
0000DFFEDJNZR7,C0004
0002C0006
000222RET
天~~~奇迹出现了......我想这个程序应该已经可以满足一般情况下的需要了。
如果列个表格的话:
15
27
39...
计算延时时间时,已经算上了调用函数的lcall语句所花的2个时钟周期的时间。
终于,结果已经明了了。
只要合理的运用,C还是可以达到意想不到的效果。
很多朋友抱怨C效率比汇编差了很多,其实如果对KeilC的编译原理有一个较深入的理解,是可以通过恰当的语法运用,让生成的C代码达到最优化。
即使这看起来不大可能,但还是有一些简单的原则可循的:
1.尽量使用unsigned型的数据结构。
2.尽量使用char型,实在不够用再用int,然后才是long。
3.如果有可能,不要用浮点型。
4.使用简洁的代码,因为按照经验,简洁的C代码往往可以生成简洁的目标代码(虽说不是在所有的情况下都成立)。
5...想不起来了,哦呵呵呵~~~
===============================================================================
C51精确延时程序
C51精确延时程序再抛砖
我看到的地方也是从别的地方转贴,所以我不知道原作者是谁,但相信这么成熟的东西转一下他也不会见意。
看到了个好帖,我在此在它得基础上再抛抛砖!
有个好帖,从精度考虑,它得研究结果是:
{
while(--i);
为最佳方法。
分析:
假设外挂12M(之后都是在这基础上讨论)
我编译了下,传了些参数,并看了汇编代码,观察记录了下面的数据:
delay2(0)延时518us518-2256=6
delay2
(1)延时7us(原帖写“5us”是错的,^_^)
delay2(10)延时25us25-20=5
delay2(20)延时45us45-40=5
delay2(100)延时205us205-200=5
delay2(200)延时405us405-400=5
见上可得可调度为2us,而最大误差为6us。
精度是很高了!
但这个程序的最大延时是为518us显然不
能满足实际需要,因为很多时候需要延迟比较长的时间。
那么,接下来讨论将t分配为两个字节,即uint型的时候,会出现什么情况。
voiddelay8(uintt)
while(--t);
}
delay8(0)延时524551us524551-865536=263
delay8
(1)延时15us
delay8(10)延时85us85-80=5
delay8(100)延时806us806-800=6
delay8(1000)延时8009us8009-8000=9
delay8(10000)延时80045us80045-8000=45
delay8(65535)延时524542us524542-524280=262
如果把这个程序的可调度看为8us,那么最大误差为263us,但这个延时程序还是不能满足要求的,因为延时最大为524.551ms。
那么用ulongt呢?
一定很恐怖,不用看编译后的汇编代码了。
。
那么如何得到比较小的可调度,可调范围大,并占用比较少得RAM呢?
请看下面的程序:
--------------------------------------------------------------------
程序名称:
50us延时
注意事项:
基于1MIPS,AT89系列对应12M晶振,W77、W78系列对应3M晶振
例子提示:
调用delay_50us(20),得到1ms延时
全局变量:
无
返回:
无
voiddelay_50us(uintt)
ucharj;
t0;
t--)
for(j=19;
j--)
;
delay_50us
(1)延时63us63-50=13
delay_50us(10)延时513us503-500=13
delay_50us(100)延时5013us5013-5000=13
delay_50us(1000)延时50022us50022-50000=22
赫赫,延时50ms,误差仅仅22us,作为C语言已经是可以接受了。
再说要求再精确的话,就算是用汇编也得改用定时器了。
50ms延时
调用delay_50ms(20),得到1s延时
voiddelay_50ms(uintt)
uintj;
可以在此加少许延时补偿,以祢补大数值传递时(如delay_50ms(1000))造成的误差,
但付出的代价是造成传递小数值(delay_50ms
(1))造成更大的误差。
因为实际应用更多时候是传递小数值,所以补建议加补偿!
for(j=6245;
delay_50ms
(1)延时5001010us
delay_50ms(10)延时49998317us
delay_50ms(100)延时4999713287us
delay_50ms(1000)延时49970222.978ms
赫赫,延时50s,误差仅仅2.978ms,可以接受!
上面程序没有才用long,也没采用3层以上的循环,而是将延时分拆为两个程序以提高精度。
应该是比较好的做法了。
本文来自电子论坛[url][url]电子工程师之家!
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C51 精确 延时 计算 实现