单片机的精确延时问题,请各位作答!

时间:2022-09-15 19:49:03
最近学习单片机,因为要用到红外解码,所以对单片机的延时要求特别高,找了很久,问了许多人,看了很多代码,到处都是用for循环来进行延时(用c51的c语言),好好想了想,这样作只能在对延时精度要求不高的场合才能随便用用,但要是在有其他中断或其他干扰时这种做法就很不准确,所以我想了两种方法:

1.这种方法最主要了:定时器的初值,也就是对TH1,TL1(T0也是如此)重装初值,这种方法需要先计算(一定要注意波特率和PCON的初始化)。

2.就是用汇编来实现,我先贴一段汇编的延时子程序大家看看:

;1s的延时程序

delay1s: mov r1,#50

    del0:mov r2,#100

    del1:mov r3,$

          djne r2,del1

         djnz r1,del0

  ret

抱歉得很,没有注释,因为我也不太明白这种延时是不是很准确,这是我在电子制作这本杂志上看到的,当时如获至宝抄了下来,不过在网上看了很多汇编程序都是这样,不过要是在C语言上使用还是要内嵌这段汇编代码。

所以我想问问各位要用到精确延时时究竟该怎么做?

32 个解决方案

#1


哦对了,上面汇编代码有点问题。具体是这样的:
delay1s:  mov r1,#50

    del0: mov r2,#100

    del1: mov r3,#100
          djnz r3,$

          djnz r2,del1

          djnz r1,del0

  ret

#2


这种方法应该还是比较精确的
它是用每一条语句运行的时间,时钟每跳一次的时间来算的

#3


谢谢

#4


用循环还是会受中断干扰,用定时器是较可靠的,但软件复杂些

#5


同意楼上的意见,用软件循环延时在关中断的情况下可以实现精确定时,因为每条指令的执行时间是已知的。用定时器来实现一般较为精确,但是涉及到中断和主程序的协调和同步问题,程序结构较为复杂。

#6


用定时器吧...不过我不懂

#7


你的汇编代码主要是应用了DJNZ的2个时钟周期,靠这2个时钟周期定时.在KEIL下C语言也可以实现,具体代码我没有,你可以一边调试(反汇编),一边测试.KEIL是很强大的,它的优化功能做的很好.在"中山单片机"有人专门写了个贴子是关于用c语言写的延时,精确度较高.



调试是一种很好的方法

#8


上述汇编代码其实也是通过循环来延时,只不过汇编指令的时间比较精确。不过不同的晶振频率对应于不同循环计数。用定时器的话,要使用一个定时器,并且禁止定时器中断。如果单片机有多余的定时器的话,可以优先考虑这种方法。

#9


最近看了看书有点明白了上面的汇编延时原理是这样的,一个mov语句是耗时2个周期,例如我经常用的11.0592的晶振,一个单片机周期约1us,那么一个mov语句为2us,djnz r3,$就是在本行不停地作减1,r3为100,那就要在本句做100次执行,明白这两句话的含义上面的延时时间就好算了,具体为:50*(2+2+100*2+(2*100+2)*100)+2=1020200us大约为1s的延时,不知我算得对不对?

#10


精确的话就用定时器啦
我只会C语言的,还没学好汇编的
所以就给个C的吧
void time() interrupt ?(0~4) //中断函数 ?表示中断号,0到4分别为外部中断,定时器中断,串口中断
{TH?=0x??;//重装初值 
 TL?=0x??;
}
main()
{
 TMOD=0x??; //定时器工作模式
 TH0=0x??;  //装初值
 TL0=0x??;
 TR0=1;    //开定时器 使之工作
 EA=1;    //开所有中断
 ET?=1; //开定时器中断
 for(;;){;}//无限循环 等待中断
}

#11


定时器延时精确

#12


同意定时器

#13


定时器是最佳选择

#14


定时器也不是很准确,也在找更好的方法。

#15


本身晶振也是存在误差的。

#16


写好延时函数后,用定时器测试一下 看看具体是多长时间哈

#17


每条语句执行的时间是一定的   你把 tr1  th1 重新装初始值     delay函数的延迟时间 一般情况还是很精确的在毫秒级内

#18


用定时器还是比较好的

#19


其实红外协议根本不用那么精确的延时,可以在一段延时之后判断i/o口的方法做。
我在3.8M定时器时钟下都能正常解码。

#20


还是用定时器中断形式比较好

#21


还没有开始学习汇编

#22


定时器,软件延时都可以吧,关键看你怎么写

#23


定时器的确是比较准确的,但是如果用到的比较多的中断程序的话要精确的定时是有点复杂的。lz用到的红外协议其实要求的延时直接用for就行啦,我自己本身用过的ds18b20这个单总线传输的温度传感器就是用for的延时的。

#24


红外遥控根本就不用精确定时,只要有效控制好范围就ok了!以nec为例,例如你要解头码的9ms,你可以使用0.14ms的定时中断,在里边计数,只要最后的结果在8-10ms中就ok了!

#25


掐秒表

#26


  这要看你是用多少兆晶振的,下面的代码是我写的,晶振是12MHZ的,延时程序如下:
  ;//////////////延时程序1/////////////////////////////
;//实现的功能:延时1004us
delay1:
mov  r6,#3
d0: mov  r7,#165
djnz r7,$
djnz r6,d0
ret

#27


我这儿有一种方法,来和大家共享一下
C语言的精确延时



以下所有测试若作为子函数,则调用时还要加上调用的2us和返回的2us,再加上赋值的时间。
long 4us(赋值)+2us(清零),int 2us,char 1us,3for 则不用.
即作为子函数调用全部要加的时间为long +10us,int +6us,char +5us,3for +0us.
*/
#include <at89x52.h>

void delay(void)
{
unsigned long i;
i=135;
while(--i);
}

void main(void)
{
/*unsigned long i;
i=113;
while(--i);
//此种形式定时时间为i*480/M us。最大误差不会超过1us,此1us主要是由某些特殊晶振不能被整除引起.
//i最大值为4294967295.M=12MHz下,Tmax=171798691800us约=17万秒
//大概生成代码是74
*/
/*unsigned int i;
i=113;
while(--i);
//此种形式定时时间为i*96/M us。最大误差不会超过1us,此1us主要是由某些特殊晶振不能被整除引起.
//i最大值为65535.M=12MHz下,Tmax=524280us约=0.5秒(500ms)
//大概生成代码是32
*/
/*unsigned char i;
i=150;
while(--i);
//此种形式定时时间为i*24/M us。最大误差不会超过1us,此1us主要是由某些特殊晶振不能被整除引起.
//i最大值为255.M=12MHz下,Tmax=510us约=0.5ms(500us)
//大概生成代码是23
*/
/*unsigned char i,j,k;
for(i=10;i>0;i--)
for(j=10;j>0;j--)
for(k=10;k>0;k--)
;
//此种形式定时时间为(((2*i+3)*j+3)*k+1)*12/M us。最大误差不会超过1us,
//此1us主要是由某些特殊晶振不能被整除引起.
//一层循环n:R5*2 DJNZ 2us
// 二层循环m:R6*(n+3) DJNZ 2us + R5賦值 1us = 3us
// 三层循环: R7*(m+3) DJNZ 2us + R6賦值 1us = 3us
//循环外:R7赋值 1us
//i最大值为255.M=12MHz下,Tmax=33.358591s
//大概生成代码是31。不过计算挺麻烦,要用软件计算,而且不是每1us都能算得到。
*/
delay();//子函数调用才用此行
P1_1=0;
while(1);

}

#28


这要看你是用多少兆晶振的,下面的代码是我写的,晶振是12MHZ的,延时程序如下:
  ;//////////////延时程序1/////////////////////////////
;//实现的功能:延时1004us
delay1:
mov r6,#3
d0: mov r7,#165
djnz r7,$
djnz r6,d0
ret

#29


这要看你是用多少兆晶振的,下面的代码是我写的,晶振是12MHZ的,延时程序如下:
  ;//////////////延时程序1/////////////////////////////
;//实现的功能:延时1004us
delay1:
mov r6,#3
d0: mov r7,#165
djnz r7,$
djnz r6,d0
ret

#30


直接用增强型单片机,使用自动重载定时器功能,也可以精确定时

#31




  用定时器啊,不用重装初值的那组,很精确   


#32


用定时器工作在方式2(不用重装初值)即M1M0=10,定时的时间由你自己设定
采用汇编的循环也可能会有中断的影响

#1


哦对了,上面汇编代码有点问题。具体是这样的:
delay1s:  mov r1,#50

    del0: mov r2,#100

    del1: mov r3,#100
          djnz r3,$

          djnz r2,del1

          djnz r1,del0

  ret

#2


这种方法应该还是比较精确的
它是用每一条语句运行的时间,时钟每跳一次的时间来算的

#3


谢谢

#4


用循环还是会受中断干扰,用定时器是较可靠的,但软件复杂些

#5


同意楼上的意见,用软件循环延时在关中断的情况下可以实现精确定时,因为每条指令的执行时间是已知的。用定时器来实现一般较为精确,但是涉及到中断和主程序的协调和同步问题,程序结构较为复杂。

#6


用定时器吧...不过我不懂

#7


你的汇编代码主要是应用了DJNZ的2个时钟周期,靠这2个时钟周期定时.在KEIL下C语言也可以实现,具体代码我没有,你可以一边调试(反汇编),一边测试.KEIL是很强大的,它的优化功能做的很好.在"中山单片机"有人专门写了个贴子是关于用c语言写的延时,精确度较高.



调试是一种很好的方法

#8


上述汇编代码其实也是通过循环来延时,只不过汇编指令的时间比较精确。不过不同的晶振频率对应于不同循环计数。用定时器的话,要使用一个定时器,并且禁止定时器中断。如果单片机有多余的定时器的话,可以优先考虑这种方法。

#9


最近看了看书有点明白了上面的汇编延时原理是这样的,一个mov语句是耗时2个周期,例如我经常用的11.0592的晶振,一个单片机周期约1us,那么一个mov语句为2us,djnz r3,$就是在本行不停地作减1,r3为100,那就要在本句做100次执行,明白这两句话的含义上面的延时时间就好算了,具体为:50*(2+2+100*2+(2*100+2)*100)+2=1020200us大约为1s的延时,不知我算得对不对?

#10


精确的话就用定时器啦
我只会C语言的,还没学好汇编的
所以就给个C的吧
void time() interrupt ?(0~4) //中断函数 ?表示中断号,0到4分别为外部中断,定时器中断,串口中断
{TH?=0x??;//重装初值 
 TL?=0x??;
}
main()
{
 TMOD=0x??; //定时器工作模式
 TH0=0x??;  //装初值
 TL0=0x??;
 TR0=1;    //开定时器 使之工作
 EA=1;    //开所有中断
 ET?=1; //开定时器中断
 for(;;){;}//无限循环 等待中断
}

#11


定时器延时精确

#12


同意定时器

#13


定时器是最佳选择

#14


定时器也不是很准确,也在找更好的方法。

#15


本身晶振也是存在误差的。

#16


写好延时函数后,用定时器测试一下 看看具体是多长时间哈

#17


每条语句执行的时间是一定的   你把 tr1  th1 重新装初始值     delay函数的延迟时间 一般情况还是很精确的在毫秒级内

#18


用定时器还是比较好的

#19


其实红外协议根本不用那么精确的延时,可以在一段延时之后判断i/o口的方法做。
我在3.8M定时器时钟下都能正常解码。

#20


还是用定时器中断形式比较好

#21


还没有开始学习汇编

#22


定时器,软件延时都可以吧,关键看你怎么写

#23


定时器的确是比较准确的,但是如果用到的比较多的中断程序的话要精确的定时是有点复杂的。lz用到的红外协议其实要求的延时直接用for就行啦,我自己本身用过的ds18b20这个单总线传输的温度传感器就是用for的延时的。

#24


红外遥控根本就不用精确定时,只要有效控制好范围就ok了!以nec为例,例如你要解头码的9ms,你可以使用0.14ms的定时中断,在里边计数,只要最后的结果在8-10ms中就ok了!

#25


掐秒表

#26


  这要看你是用多少兆晶振的,下面的代码是我写的,晶振是12MHZ的,延时程序如下:
  ;//////////////延时程序1/////////////////////////////
;//实现的功能:延时1004us
delay1:
mov  r6,#3
d0: mov  r7,#165
djnz r7,$
djnz r6,d0
ret

#27


我这儿有一种方法,来和大家共享一下
C语言的精确延时



以下所有测试若作为子函数,则调用时还要加上调用的2us和返回的2us,再加上赋值的时间。
long 4us(赋值)+2us(清零),int 2us,char 1us,3for 则不用.
即作为子函数调用全部要加的时间为long +10us,int +6us,char +5us,3for +0us.
*/
#include <at89x52.h>

void delay(void)
{
unsigned long i;
i=135;
while(--i);
}

void main(void)
{
/*unsigned long i;
i=113;
while(--i);
//此种形式定时时间为i*480/M us。最大误差不会超过1us,此1us主要是由某些特殊晶振不能被整除引起.
//i最大值为4294967295.M=12MHz下,Tmax=171798691800us约=17万秒
//大概生成代码是74
*/
/*unsigned int i;
i=113;
while(--i);
//此种形式定时时间为i*96/M us。最大误差不会超过1us,此1us主要是由某些特殊晶振不能被整除引起.
//i最大值为65535.M=12MHz下,Tmax=524280us约=0.5秒(500ms)
//大概生成代码是32
*/
/*unsigned char i;
i=150;
while(--i);
//此种形式定时时间为i*24/M us。最大误差不会超过1us,此1us主要是由某些特殊晶振不能被整除引起.
//i最大值为255.M=12MHz下,Tmax=510us约=0.5ms(500us)
//大概生成代码是23
*/
/*unsigned char i,j,k;
for(i=10;i>0;i--)
for(j=10;j>0;j--)
for(k=10;k>0;k--)
;
//此种形式定时时间为(((2*i+3)*j+3)*k+1)*12/M us。最大误差不会超过1us,
//此1us主要是由某些特殊晶振不能被整除引起.
//一层循环n:R5*2 DJNZ 2us
// 二层循环m:R6*(n+3) DJNZ 2us + R5賦值 1us = 3us
// 三层循环: R7*(m+3) DJNZ 2us + R6賦值 1us = 3us
//循环外:R7赋值 1us
//i最大值为255.M=12MHz下,Tmax=33.358591s
//大概生成代码是31。不过计算挺麻烦,要用软件计算,而且不是每1us都能算得到。
*/
delay();//子函数调用才用此行
P1_1=0;
while(1);

}

#28


这要看你是用多少兆晶振的,下面的代码是我写的,晶振是12MHZ的,延时程序如下:
  ;//////////////延时程序1/////////////////////////////
;//实现的功能:延时1004us
delay1:
mov r6,#3
d0: mov r7,#165
djnz r7,$
djnz r6,d0
ret

#29


这要看你是用多少兆晶振的,下面的代码是我写的,晶振是12MHZ的,延时程序如下:
  ;//////////////延时程序1/////////////////////////////
;//实现的功能:延时1004us
delay1:
mov r6,#3
d0: mov r7,#165
djnz r7,$
djnz r6,d0
ret

#30


直接用增强型单片机,使用自动重载定时器功能,也可以精确定时

#31




  用定时器啊,不用重装初值的那组,很精确   


#32


用定时器工作在方式2(不用重装初值)即M1M0=10,定时的时间由你自己设定
采用汇编的循环也可能会有中断的影响