51单片机软件延时分析和计算.docx
《51单片机软件延时分析和计算.docx》由会员分享,可在线阅读,更多相关《51单片机软件延时分析和计算.docx(17页珍藏版)》请在冰豆网上搜索。
51单片机软件延时分析和计算
51单片机软件延时分析
朱铮南
一、单片机的时钟周期和机器周期
时钟周期也叫做振荡周期,是指为单片机提供时钟信号的振荡源的频率的倒数。
CPU可以完成一个独立的操作的最短时段叫做机器周期。
89C51等老一代单片机将振荡频率12分频后作为机器频率,所以机器周期是12个时钟周期的长度。
一般振荡频率为11.0592MHz,机器周期是1.085μs。
现在的高速单片机如STC15系列,工作在1T模式,即振荡频率不再分频,机器周期等于时钟周期。
振荡频率在从11.0592MHz到33.1776MHz的范围内可以选择,如果振荡频率为30MHz,它的机器周期即为0.03333μs。
二、指令占用的机器周期
每条指令执行时所要占用的机器周期不同,下面列出的是软件延时代码中经常用到的指令及这些指令占用的机器周期:
指令
说明
89C51
机器周期
STC15
机器周期
MOVRn,#data
立即数送入寄存器
1
2
MOVA,Rn
寄存器内容送入累加器
1
1
DJNZRn,re1
寄存器值减1,如结果非0就跳到rel指定地址
2
4
LJMPaddr16
跳转到16位地址(调用子函数)
2
4
RET
从子函数返回
2
4
DECRn
寄存器减1
1
2
JNZre1
累加器不等于0就跳转到rel指定地址
2
4
JZre1
累加器等于0就跳转到rel指定地址
2
4
SJMPre1
跳转到rel指定地址
2
3
SETBC
将进位位C置1
1
1
SUBBA,#data
累加器减借位位C减立即数
1
2
JCrel
进位位C为1就跳转到rel指定地址
2
3
软件延时就是利用循环来占用机器周期,达到延时的目的。
三、几种循环结构的比较
为了比较几种循环结构,特意用C语言编写了以下几段主函数和延时子函数,在uVision2里建造可执行文件以后,点击菜单“调试”里的“开始/停止调试”,再点击“反汇编窗口”工具,就可以看由C语言编译成的汇编语言代码。
1.第一种的延时子函数用的是do循环和--i结构,最外层有一层x循环,以便调用时可以指定延时时间的整倍数。
左边是C语言代码,右边是反汇编窗口显示的汇编代码截图。
汇编代码里红色的是对应的C语言语句,黑色的才是汇编代码,只有8行。
底下的两行是主函数,一行是实参赋值,另一行是对子函数的调用。
上面的6行是延时子函数,分别是两行赋值,三行寄存器减1非0跳转,最后一行是返回。
voiddelay(unsignedcharx)
{
unsignedchari,j;
do
{
i=2;
j=240;
do
{
while(--j);
}while(--i);
}while(--x);
}
voidmain()
{
delay
(1);
}
2.第二种和第一种循环结构相同,仅将“--j”、“--i”变成了“j--”、“i--”。
汇编代码变化很大,变成了17行,结构复杂多了。
voiddelay(unsignedcharx)
{
unsignedchari,j;
do
{
i=2;
j=240;
do
{
while(j--);
}while(i--);
}while(x--);
}
voidmain()
{
delay
(1);
}
3.第三种将do循环改成while循环,汇编代码也很复杂,有15行。
voiddelay(unsignedcharx)
{
unsignedchari,j;
while(--x)
{
i=2;
j=240;
while(--i)
{
while(--j);
}
}
}
voidmain()
{
delay
(1);
}
4.第四种是for循环,汇编代码最复杂,有23行之多。
voiddelay(unsignedcharx)
{
unsignedchari,j;
for(;x>0;x--)
i=2;
j=240;
for(;i>0;i--)
{
for(;j>0;j--);
}
}
voidmain()
{
delay
(1);
}
三、详细分析
比较以上四种循环,第一种的汇编代码最为简单,结构最为清晰,极力推荐这种结构。
下面就对这种循环进行详细的分析
先看C语言,x循环是为了调用时能够指定延时的整倍数而添加上去的,分析时可以暂不管它,只要分析i循环以及嵌套的内层j循环就可以了。
特别要注意的是i、j的赋值语句在循环以外,这一点很重要,也很巧妙。
为了方便分析将汇编代码抄写在下面并写出执行一次所占用的机器周期(89C51):
C:
0x000FMOVR6,#0x021T
C:
0x0011MOVR5,#B(0xF0)1T
C:
0x0013DJNZR5,C:
00132T
C:
0x0015DJNZR6,C:
00132T
C:
0x0017DJNZR7,delay(C:
000F)2T
C:
0x0019RET2T
C:
0x001AMOVR7,#0x011T
C:
0x001CLJMPR7,delay(C:
000F)2T
①第1行是将2的十六进制数0x02放入寄存器R6,相当于C语言里给i赋值。
第2行是将240的十六进制数0xF0放入寄存器R5,相当于C语言里给j赋值。
这两行共占用了2T(T是机器周期)。
②第三行是将寄存器R5里的值减1,如果结果不为0即回到第三行再次执行,直到R5里的值等于0,才向下执行第四行。
这一行在这一循环里共执行j=240次,占用机器周期为2T×j。
③第三行执行了j次,第j次执行前寄存器R5里的值为1,第j次执行时减去1等于0,因此不再回到本行而是向下执行第四行,先前寄存器R6里的值是i=2,第四行执行时先减1,结果为1,不等于0,就重新跳转到第三行。
第四行这一次只执行了1次,因此占用机器周期是2T。
④戏剧性的时刻到了。
由于在循环以外赋的值,重新跳转到第三行时寄存器R5里的值已经是0,减去1后溢出变成FF(十进制256),因此第三行这一次循环要执行256次,占用机器周期为2T×256。
⑤第三行执行了256次后重新回到第四行,此时寄存器R5里的值是1,第四行执行时减去1后等于0,因此不再跳转到第三行而是转向执行下面的行。
第四行这一次又执行了1次,占用机器周期为2T。
如果当初放入寄存器R6的值不是2,而是i,显然④⑤要重复i-1次,因此,④⑤占用的机器周期为(2T×256+2T)×(i-1)+2T。
⑥第6行是退出子函数返回主函数,占用机器周期为2T。
⑦第七行是主函数调用延时子函数时的实参赋值,占用机器周期为1T。
第八行是对延时子函数的调用,占用机器周期为2T。
上面⑥⑦两项一共占用机器周期为5T,这是固定不变的,不会随着延时的长短而改变,这应该看成是延时以外额外的机器周期。
综合以上分析,延时总的机器周期是以上各项之和,再考虑到x倍,则:
89C51总周期=x×{2T+2T×j+2T+[(2T×256+2T)×(i-1)+2T]}+5T
=x×{2T×j+2T×i-508T}+5T
STC15总周期=x×{4T+4T×j+4T+[(4T×256+4T)×(i-1)+4T]}+10T
=x×{4T×j+4T×i-1016T}+10T
仔细研究这个公式,可以看出延时的主要贡献在中括号里,j只是起补充的作用。
只要通过计算将大括号内的机器周期设计成一个常用值,比方1ms,如果需要延时100ms,调用时只需要将实参赋值成100就可以了,对89C51单片机来讲仅存在有5个机器周期的固定误差。
由于i、j和x最大只能为256,当x=1时最大延时对89C51单片机来讲是131588个机器周期,对STC15单片机来讲是263176个机器周期。
四、长延时代码
如果要进一步延长延时,可以再增加一层循环,这样延时大约可以增大256倍。
当x=1时最大延时对89C51单片机来讲是33686021个机器周期,对STC15单片机来讲是67372042个机器周期。
C语言如下:
voiddelay(unsignedcharx)
{
unsignedchari,j;
do
{
i=2;
j=240;
k=156;
do
{
do
{
while(--k);
}while(--j);
}while(--i);
}while(--x);
}
此时的机器周期为:
89C51总周期=x×{3T+2T×k+2T+[(2T×256+2T)×(j-1)+2T]+
[(2T×256+2T)×256×(i-1)+2T]}+5T
=x×{2T×k+514×j+131586T×i-131586T}+5T
STC15总周期=x×{6T+4T×k+4T+[(4T×256+4T)×(j-1)+4T]+
[(4T×256+4T)×256×(i-1)+4T]}+10T
=x×{4T×k+1028×j+263172T×i-263172T}+10T
五、短延时代码
如果延时的时间时间比较短,当S=1时,对于89C51单片机来讲小于515个机器周期,对于89C51单片机来讲小于1030个机器周期,可以仅用一层循环。
voiddelay_10us(unsignedcharx)
{
unsignedchari;
do
{
i=3;
while(--i);
}while(--x);
}
此时的机器周期为:
89C51总周期=x×(1T+2T×i+2T)+5T
=x×(2T×i+3T)+5T
STC15总周期=x×(2T+4T×i+4T)+10T
=x×(4T×i+6T)+10T
六、精确的延时代码
根据推导出的公式,就能够精确计算延时的时间,如果算下来还差一两个机器周期,可以在循环外赋值的地方添加一两个“_nop_()”函数,无论是89C51还是STC15,“_nop_()”函数都只占用1个机器周期。
下面这些延时子函数是非常精确的,调用它们的时候仅会有前述的固定误差,对于89C51单片机来讲是5个机器周期,对于STC15单片机来讲是10个机器周期,不会因为参数x的大小而改变。
voiddelay_10us(unsignedcharX)//@89C51
{
unsignedchari;
do
{
_nop_();
i=3;
while(--i);
}while(--X);
}
voiddelay_15us(unsignedcharX)//@89C51
{
unsignedchari;
do
{
i=6;
while(--i);
}while(--X);
}
voiddelay_1ms(unsignedcharX)//@89C51
{
unsignedchari,j;
do
{
i=2;
j=240;
do
{
while(--j);
}while(--i);
}while(--X);
}
voiddelay_10ms(unsignedcharX)//@89C51
{
unsignedchari,j;
do
{
i=20;
j=114;
do
{
while(--j);
}while(--i);
}while(--X);
}
voiddelay_100ms(unsignedcharX)//@89C51
{
unsignedchari,j;
do
{
i=195;
j=139;
do
{
while(--j);
}while(--i);
}while(--X);
}
voiddelay_1s(unsignedcharX)//@89C51
{
unsignedchari,j,k;
do
{
_nop_();
i=8;
j=154;
k=123;
do
{
do
{
while(--k);
}while(--j);
}while(--i);
}while(--X);
}
voiddelay2us(unsignedcharx)//@30.000MHz
{
unsignedchari;
do
{
_nop_();
_nop_();
i=13;
while(--i);
}while(--x);
}
voiddelay10us(unsignedcharx)//@30.000MHz
{
unsignedchari;
do
{
_nop_();
_nop_();
i=73;
while(--i);
}while(--x);
}
voiddelay15us(unsignedcharx)//@30.000MHz
{
unsignedchari;
do
{
i=111;
while(--i);
}while(--x);
}
voiddelay1ms(unsignedcharx)//@30.000MHz
{
unsignedchari,j;
do
{
i=30;
j=44;
do
{
while(--j);
}while(--i);
}while(--x);
}
voiddelay10ms(unsignedcharx)//@30.000MHz
{
unsignedchari,j,k;
do
{
_nop_();
_nop_();
i=2;
j=36;
k=207;
do
{
do
{
while(--k);
}while(--j);
}while(--i);
}while(--x);
}
voiddelay100ms(unsignedcharx)//@30.000MHz
{
unsignedchari,j,k;
do
{
_nop_();
_nop_();
i=12;
j=103;
k=58;
do
{
do
{
while(--k);
}while(--j);
}while(--i);
}while(--x);
}