winfuture 发表于 2010-1-2 11:28:46

C51精确延时,绝对精确!(原创)

有些特殊的应用会用到比较精确的延时(比如DS18B20等),而C不像汇编,延时精准度不好算。本人经过反复调试,对照KEIL编译后的汇编源文件,得出了以下几条精确延时的语句(绝对精确!本人已通过实际测试),今天贴上来,希望对需要的朋友有所帮助。

sbit LED=P1^0;      // 定义一个管脚(延时测试用)
        unsigned int i = 3;   // 注意i,j的数据类型,
        unsigned char j = 3;    // 不同的数据类型延时有很大不同
        //-----------------各种精确延时语句-----------------------------------
        while( (i--)!=1 );      // 延时10*i个机器周期
        i = 10; while( --i );   // 延时8*i+2个机器周期
        i = 10; while( i-- );   // 延时(i+1)*9+2个机器周期
        j = 5;while( --j );   // 延时2*j+1个机器周期
        j = 5;while( j-- );   // 延时(j+1)*6+1个机器周期

        i = 5;
        while( --i )            // 延时i*10+2个机器周期,在i*10+2个机器周期
                if( LED==0 )break; // 内检测到LED管脚为低电平时跳出延时

        i = 5;
        while( LED )            // 每隔10个机器周期检测一次LED管脚状态,当LED               
                if( (--i)==0 ) break;// 为低时或者到了10*i+2个机器周期时跳出延时
        //--------------------------------------------------------------------

例如18b20的复位函数(12M晶振):
//***********************************************************************
// 函数功能:18B20复位
// 入口参数:无
// 出口参数:unsigned char x: 0:成功 1:失败
//***********************************************************************
unsigned char ow_reset(void)
{
        unsigned char x=0;// 12M晶振 1个机器周期为1us
        DQ = 1;                                  // DQ复位
        j = 10;while(--j);// 稍做延时(延时10*2+1=21个机器周期,21us)
        DQ = 0;                                  // 单片机将DQ拉低
        j = 85;while(j--);// 精确延时(大于480us) 85*6+1=511us
        DQ = 1;                                  // 拉高总线
        j = 10;while(j--);// 精确延时10*6+1=61us
        x = DQ;                                  // 稍做延时后,
        return x;                          // 如果x=0则初始化成功 x=1则初始化失败
        j = 25;while(j--);// 精确延时25*6+1=151us
}
//*********************************************************************************
再如红外解码程序:
(先说传统红外解码的弊端:
程序中用了while(IR_IO);while(!IR_IO);这样的死循环,如果管脚一直处于一种状态,就会一直执行while,造成“死机”现象。当然这种情况很少,但我们也的考虑到。而用以下程序则不会,在规定的时间内没有正确的电平信号就会返回主程序,这样就不会出现“死机”了)
//***************************外部中断0*******************************
void int0(void) interrupt 0
{
        unsigned char i,j;
        unsigned int count = 800;
//--------------8.5ms低电平引导码-------------------------------------
        while( --count )       
                if( IR_IO==1 )      return;   // 在小于8ms内出现高电平,返回
        count = 100;                     // 延时1ms
        while( !IR_IO )                  // 等待高电平
                if( (--count)==0 )return;   // 在9ms内未出现高电平,返回
//-------------4.5ms高电平引导码------------------------------------
        count = 410;                     // 延时4.1ms
        while( --count )               // ...
                if( IR_IO==0 )      return;   // 在4.1ms内出现低电平,返回
        count = 50;                      // 延时0.5ms
        while( IR_IO )                   // 等待低电平
                if( (--count)==0 )return;   // 在4.7ms内未出现低电平,返回
//-----------------------------------------------------------------
//------------4个数据码------------------------------------
        for( j=0;j<4;j++ )
        {
                for( i=0;i<8;i++ )
                {
                        IR_data <<= 1;                // 装入数据
                        count = 60;                      // 延时0.6ms
                        while( !IR_IO )                  // 等待高电平
                                if( (--count)==0 )   return;// 在0.6ms内未出现高电平,返回
                        count = 40;                      // 低电平结束,继续
                        while( --count )               // 延时0.4ms
                                if( IR_IO==0 )       return;// 在0.4ms内出现低电平,返回
                        count = 100;                     // 延时1.4ms
                        while( IR_IO )                   // 检测IO状态
                                if( (--count)==0 )            // 等待1.4ms到来
                                {                           // 在1.4ms内都是高电平
                                        IR_data |= 1;         // 两个单位高电平,为数据1
                                        break;                     // 跳出循环
                                }
                        count = 20;                      // 延时0.2ms
                        while( IR_IO )                   // 等待低电平跳出
                                if( (--count)==0 ) return;    // 0.2ms内未出现低电平,返回
                }
        }
//-------------------------------------------------------------------
        flag_IR = 1;                           // 置位红外接收成功标志
}

smhh 发表于 2010-1-2 11:52:32

留个爪印!

llsenyue 发表于 2010-1-2 12:07:02

mark

Yan.hong.yu 发表于 2010-1-2 12:11:58

mark

lrzxc 发表于 2010-1-2 12:13:59

我顶一下

steel 发表于 2010-1-2 12:21:01

while( (i--)!=1 ),自减生成的指令会少一点,看生成的汇编指令就知道,和数据0比较,生成的指令会少一点。

winfuture 发表于 2010-1-2 12:36:24

回复【5楼】steel 型钢
-----------------------------------------------------------------------

这我知道,关键是while( (i--)!=1 );这一句刚好是延时10个机器周期,如果用12M的晶振的话,对于10us倍数的延时时就非常方便了!
比如需要延时500us:i = 50; while( (i--)!=1 );执行完后就是500us了(当然有几个us的误差,但误差小于5us)

mc56f8037 发表于 2010-1-2 12:51:59

如果flash够大就直接用nop吧

wenxusun 发表于 2010-1-2 12:59:20

打击下
这样的程序在项目里基本都是费物,浪费CPU资源
用定时器做吧

winfuture 发表于 2010-1-2 13:08:24

回复【8楼】wenxusun 番薯猪猪
-----------------------------------------------------------------------

话不能说得太绝对,定时器和软件延时各有各的用处,关键看你用在哪里,用来做什么了。
如果需要1个5us的延时,你用定时器?恐怕杀鸡用牛刀了哈?

哈哈,我只是说明这个东西各有各的用处哈!

wenxusun 发表于 2010-1-2 13:32:06

用你的程序做做30分钟定时不超一秒试试,估计要调到你想哭。
不是说你的程序完全没用,只是不准确,换了不同频率的晶振又要很长时间调整,很麻烦。
做做I2C、164等等的时钟还可以,而且延时不要过长,最好不超过ms吧,几个us就直接用nop上了。

lv998127 发表于 2010-1-2 13:37:19

路过顶帖!

winfuture 发表于 2010-1-2 13:44:57

回复【10楼】wenxusun 番薯猪猪
-----------------------------------------------------------------------

这个我不反对,你不是也说“做做I2C、164等等的时钟还可以”吗,这个本来就是用在几ms以下延时的,有谁会笨到用这个做几十分钟的延时?呵呵,还是刚才那句:定时器和软件延时各有各的用,不能一棒子打死了。关键看用在什么地方了

关于nop延时:
如果我要用一个800us的延时,你写800个nop?不现实吧?
用循环加nop?你又能算清楚到底延时800us该用多少次循环?(当然能算出,但会花一定时间,那还不如直接用以上延时了,都已经算好了的)
那我的延时就是用在以上的情况下的

呵呵,实话实说。

yemingxp 发表于 2010-1-2 13:46:13

只是在你的板子上,你的芯片,你认为的精确。

winfuture 发表于 2010-1-2 13:53:08

回复【13楼】yemingxp
-----------------------------------------------------------------------

这不是某一块班子上的问题,而是keil看编译后的汇编源码并经过实际检验得出的结论

damoplus 发表于 2010-1-2 13:53:47

留个脚印,搞不好哪天会用上....

elecfun 发表于 2010-1-2 14:07:02

呵呵我的延时都不是很 准确

19871212 发表于 2010-1-2 14:38:40

呵呵,路过 感谢 分享

myfaith2 发表于 2010-1-2 15:57:10

这个还是看反汇编的计算一下就是了,优化等级生成的汇编也不一样,或者自己嵌入几句DJNZ不受优化影响

winfuture 发表于 2010-1-2 16:11:18

回复【18楼】myfaith2
-----------------------------------------------------------------------

楼上说的有理,之前我也考虑过。
上面的程序是在keil默认条件下编译的,当然设置过优化等级后就只能具体看生成的汇编了

lea2005 发表于 2010-1-2 16:13:54

留下了,谢谢

liulh 发表于 2010-1-2 17:15:00

注意"中断": 调用前关确保没有中断会发生, 或关所有可能的中断
否则,精确延时基本是瞎扯!

QQ373466062 发表于 2010-1-2 17:48:53

Learn

fuanzwg 发表于 2010-1-2 18:01:57

mark

guolun 发表于 2010-1-4 08:40:03

【21楼】 liulh
积分:57
派别:
等级:------
来自:
注意"中断": 调用前关确保没有中断会发生, 或关所有可能的中断
否则,精确延时基本是瞎扯!


我一直这么认为。

guozs1984 发表于 2010-1-4 18:48:02

hehe

kele2009 发表于 2010-1-4 18:57:22

mark

lxbeiming 发表于 2010-1-5 11:08:38

留个爪印!

liumaojun_cn 发表于 2010-1-5 13:01:18

mark

almasy 发表于 2010-1-5 13:28:47

mark

hengxin01 发表于 2010-1-6 13:09:24

mark

wkman 发表于 2010-1-6 13:40:22

如果楼主用到1T类~4T类的mcu,那么上述都是不准确的。
要准确,还要乘以延时用到asm指令/12的周期。计算很麻烦。
要多少延时,我最后都是用存储示波器卡一下,调整得到的。

skydog 发表于 2010-1-10 14:02:50

留个记号,哈哈

cat_li 发表于 2010-1-10 14:09:57

我觉得一般情况下软件延时,不需要很高的精度

kyughanum 发表于 2010-1-10 14:48:52

行不行啊?

hq0402 发表于 2010-1-30 03:20:49

在keil仿真试了下,楼主的时间挺准的。很实用的程序

wanzd527 发表于 2010-1-30 08:59:53

看起来好复杂,没怎么看懂~继续学习!

Roader 发表于 2010-1-30 09:26:28

常觉得定时器不够用的关注下。多谢LZ!

zszhere 发表于 2010-1-30 10:29:14

mark^

gxy508 发表于 2010-1-30 10:47:59

mark

wxdl 发表于 2010-1-30 22:44:49

谢谢楼主

litteworm 发表于 2010-1-31 00:02:34

留个爪子

zh2010 发表于 2010-1-31 12:30:23

谢谢分享!学习了。,

wuzhonghua273 发表于 2010-2-16 03:48:40

C51延时不好整,谢谢楼主

benjiang 发表于 2010-2-16 09:36:31

mark

ggyyll8683 发表于 2010-2-16 09:37:46

学习

freud 发表于 2010-2-18 11:21:23

mark

arbol 发表于 2010-2-18 14:05:38

谢谢LZ!
记号,备查。

sbsdys 发表于 2010-2-18 16:44:01

mark

mrjj 发表于 2010-2-18 17:05:32

学习学习了。。。

gxy508 发表于 2010-2-18 17:23:56

mark

smallp 发表于 2010-2-18 21:44:35

留个脚印

AIHHLI 发表于 2010-3-5 13:40:21

不错,谢谢楼主了。

xiaog0219 发表于 2010-3-5 21:56:40

不知道有没有必要这么去算。

wlhj521334 发表于 2010-3-5 22:06:13

mark

ymnymn 发表于 2010-3-5 22:10:52

留个爪子

skydog 发表于 2010-3-5 22:16:19

标记一下,呵呵

QuakeGod 发表于 2010-3-6 03:36:40

短时间的用延时,长时间的用定时,
短时间,主要是指对于IO操作等,
偶的程序里,时间超过50uS,就用定时器。

yujian12365 发表于 2010-3-6 23:49:15

很好。

qiushui 发表于 2010-3-8 19:40:43

支持原创. 正好在用红外的东东.

kwer 发表于 2010-4-26 13:56:52

我越看越感觉很多地方是自相矛盾的

jrcsh 发表于 2010-4-26 18:27:43

回复【1楼】smhh
留个爪印!
-----------------------------------------------------------------------

是的是的



我也上点 用弟上方软件生成的代码

jrcsh 发表于 2010-4-26 18:44:01

单片机小精灵

就是用这个软件生成的

langzijue 发表于 2010-4-26 19:49:25

先mark

kunpeng032 发表于 2010-4-26 20:20:59

学习了。。。

wsql 发表于 2010-4-26 20:43:28

我的方法同31楼

jrcsh 发表于 2010-4-27 02:07:35

存储示波器 也是个方法

hsztc 发表于 2010-4-27 02:52:56

软件防真下看就知道走了多少周期了

我只用过S51和AVR的,

S51有两个C代指令可以优化DJNZ Rx,rel 就是生成跟汇编一样的。

楼主的j = 5;while( j-- ); 这种和for(i=5;i>0;i--);

AVR的就不一样了,

用++的话生成的代码会比用--的少一条。

caiyue3577 发表于 2010-4-27 15:47:20

mark...

jadejoyang 发表于 2010-4-30 19:23:52

mark....

zhuyi2576 发表于 2010-5-3 16:57:48

用C语言写出来程序非常的简练,它是一种模块化的语言,一种比汇编更高级的语言,但是就是这样一种语言也还是有它不足之处:它的延时很不好控制,我们常常很难知道一段延时程序它的精确延时到底是多少,这和汇编延时程序没法比。但有时后写程序又不得不要用到比较精确的延时,虽然说可以用混合编程的方式解决,但这种方式不是每个人都能掌握,且写起来也麻烦。所以,通过测试我给大家提供一个延时子程序模块,并以此给一个出我们经常用到的延时的数据表格。(注意:表格中的数据只适合我的延时模块,对其他的延时程序不适用,切忌!!!!!!!!别到时候延时不对来骂我)

      延时模块:其中问号代表要填的数,要延时多少,到表格中去找数据,然后填上就OK!切忌3条FOR语句不能颠倒顺序

void Delay()
{
unsigned char a,b,c;
for(a=0;a<?;a++)
for(b=0;b<?;b++)
for(c=0;c<?;c++);
}

                                                      数据表如下
/*****************************************************************************************************
延时时间   a的值      b的值       c的值       延时误差(us)
10us      1         1            1            -0.5      
20us      1         1            8             0
30us      1         1            15         +0.5
40us      2         1            9             0
50us      1         1            28            0
60us      1         1            35         +0.5
70us      1         1            42         +1
80us      1         1            48            0
90us      1         1            55         +0.5
100us       1         1            61         -0.5
200us       1         1            128         0
300us       3         1            63            1.5
400us       2         1            129         0
500us       5         1            63         +0.5
600us       6         1            63            0
700us       7         1            63         -0.5
800us       1         3            175          +0.5
900us       9         1            63         -1.5
1ms         1         3            219          -1.5
2ms         2         3            220          +3
3ms         3         3            220          +3
Xms         X         3            220          +3         
(X的范围为2到255)
   基本上我们平时用到的延时都在这里了,每种延时的误差都很小,最大也不过3us,有的甚至没有误差,已经很精确了,如果想延时1秒钟,你可以连续调用延时250ms的程序4次,总共延时误差12us,这样的误差已经不算误差了,用汇编语言编写还达不到这个程度。

      现在你不再为延时不精确担忧了,参照表中的数据写延时,想延时多少就能延时多少。

      再次重申:此表格是根据我的延时模块编写,这也是大多数朋友都习惯用的一个模块,如果你用其他模块或是改动了我的模块,延时都不准的,不信大家可以试试!!!

hsztc 发表于 2010-5-3 19:41:38

试了下,上面的程序用的晶振是24MHz。

for(a=0;a<?;a++)执行一次循环需要3个周期

如果用 while( j-- ); 或for(i=5;i>0;i--);这两种形式,执行一次只要两个周期,可以做更精确的延时。

496541983 发表于 2010-5-3 19:54:01

感谢大家的分享,根据自己的情况采用。

qufo 发表于 2010-5-3 20:03:35

Mark,以后可能会用到,不同的方法不同,用于做不同的事情,并不是都对,也不是都错.

jadejoyang 发表于 2010-5-4 09:12:05

mark

kouxiangtang 发表于 2010-5-4 09:35:38

写的不错!

jackmo 发表于 2010-5-4 10:55:16

mark,,

cocoasuny 发表于 2010-5-4 11:59:19

直接在C中嵌入汇编延时不就行了嘛

tangwei039 发表于 2010-5-4 12:53:50

mark

liumaojun_cn 发表于 2010-5-4 13:38:55

mark

haiyu 发表于 2010-5-4 19:04:32

先mark下先

to_destiny 发表于 2010-5-4 23:37:54

thank you

gerbee 发表于 2010-5-5 10:07:52

则个帖子太好了

58180698 发表于 2010-5-5 12:39:37

标志一下。。。

tuowai 发表于 2010-5-11 09:59:38

记号一下下,以后再来学习

cmj8992004 发表于 2010-5-11 10:05:49

对于STC 1T的延时,又要怎么计算?

gdmfq 发表于 2010-5-11 10:52:35

精确 感觉用汇编才能做到

dikex 发表于 2010-5-11 10:54:56

只能说感谢编译器和编写编译器的人

crose0106 发表于 2010-5-11 12:14:18

做个记号吧

HuWenjin 发表于 2010-5-11 12:26:52

mark

langren27 发表于 2010-5-13 09:36:52

mark

gaylexia 发表于 2010-5-15 18:05:19

mark

xietuo 发表于 2010-5-16 22:55:40

mark

80162535 发表于 2010-5-18 14:41:37

mark

wmz61755100 发表于 2010-5-23 09:14:14

mark

roamer26 发表于 2010-5-23 10:32:32

c语言正在慢慢的取代汇编,只会汇编的人也学点c吧,因为汇编的优势越来越少了,不能死抱着它不放呀

ding86361953 发表于 2010-5-23 10:57:28

学习了!

boy364100 发表于 2010-6-8 00:58:28

不错,确实很准,不过暂时用不上~

mkzlovedamy 发表于 2010-6-10 21:01:35

mark!!

blueghost 发表于 2010-6-14 23:22:30

这种死等廷时要想用“绝对精准”这个词,你必须列出如下几点:
1、晶振频率。
2、使用哪种编译环境。
3、优化级别。

从你的程序中只见你满足了第一点,说是“绝对精准”只能是蒙人,这种绝对靠的是运气。
就拿优化级别试一下吧,-1级和-8编译出来的程序你反汇编看一下试试,有时相差海了去了。

更不用提编译器不同编译出来的结果了.....

当然,廷时也不一定非要用定时器,如果要定1US,非得把定时器累死。
要精确廷时,时间稍长的用定时器,短廷时用循环,量体裁衣,乱用肯定要占资源,如果要短精确廷时,一定要反汇编计算一下到底运行了几个周期,占多少时间,否则不要说什么绝对精准,那是掩耳盗铃。
页: [1] 2 3
查看完整版本: C51精确延时,绝对精确!(原创)