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; // 置位红外接收成功标志
} 留个爪印! mark mark 我顶一下 while( (i--)!=1 ),自减生成的指令会少一点,看生成的汇编指令就知道,和数据0比较,生成的指令会少一点。 回复【5楼】steel 型钢
-----------------------------------------------------------------------
这我知道,关键是while( (i--)!=1 );这一句刚好是延时10个机器周期,如果用12M的晶振的话,对于10us倍数的延时时就非常方便了!
比如需要延时500us:i = 50; while( (i--)!=1 );执行完后就是500us了(当然有几个us的误差,但误差小于5us) 如果flash够大就直接用nop吧 打击下
这样的程序在项目里基本都是费物,浪费CPU资源
用定时器做吧 回复【8楼】wenxusun 番薯猪猪
-----------------------------------------------------------------------
话不能说得太绝对,定时器和软件延时各有各的用处,关键看你用在哪里,用来做什么了。
如果需要1个5us的延时,你用定时器?恐怕杀鸡用牛刀了哈?
哈哈,我只是说明这个东西各有各的用处哈! 用你的程序做做30分钟定时不超一秒试试,估计要调到你想哭。
不是说你的程序完全没用,只是不准确,换了不同频率的晶振又要很长时间调整,很麻烦。
做做I2C、164等等的时钟还可以,而且延时不要过长,最好不超过ms吧,几个us就直接用nop上了。 路过顶帖! 回复【10楼】wenxusun 番薯猪猪
-----------------------------------------------------------------------
这个我不反对,你不是也说“做做I2C、164等等的时钟还可以”吗,这个本来就是用在几ms以下延时的,有谁会笨到用这个做几十分钟的延时?呵呵,还是刚才那句:定时器和软件延时各有各的用,不能一棒子打死了。关键看用在什么地方了
关于nop延时:
如果我要用一个800us的延时,你写800个nop?不现实吧?
用循环加nop?你又能算清楚到底延时800us该用多少次循环?(当然能算出,但会花一定时间,那还不如直接用以上延时了,都已经算好了的)
那我的延时就是用在以上的情况下的
呵呵,实话实说。 只是在你的板子上,你的芯片,你认为的精确。 回复【13楼】yemingxp
-----------------------------------------------------------------------
这不是某一块班子上的问题,而是keil看编译后的汇编源码并经过实际检验得出的结论 留个脚印,搞不好哪天会用上.... 呵呵我的延时都不是很 准确 呵呵,路过 感谢 分享 这个还是看反汇编的计算一下就是了,优化等级生成的汇编也不一样,或者自己嵌入几句DJNZ不受优化影响 回复【18楼】myfaith2
-----------------------------------------------------------------------
楼上说的有理,之前我也考虑过。
上面的程序是在keil默认条件下编译的,当然设置过优化等级后就只能具体看生成的汇编了 留下了,谢谢 注意"中断": 调用前关确保没有中断会发生, 或关所有可能的中断
否则,精确延时基本是瞎扯! Learn mark 【21楼】 liulh
积分:57
派别:
等级:------
来自:
注意"中断": 调用前关确保没有中断会发生, 或关所有可能的中断
否则,精确延时基本是瞎扯!
我一直这么认为。 hehe mark 留个爪印! mark mark mark 如果楼主用到1T类~4T类的mcu,那么上述都是不准确的。
要准确,还要乘以延时用到asm指令/12的周期。计算很麻烦。
要多少延时,我最后都是用存储示波器卡一下,调整得到的。 留个记号,哈哈 我觉得一般情况下软件延时,不需要很高的精度 行不行啊? 在keil仿真试了下,楼主的时间挺准的。很实用的程序 看起来好复杂,没怎么看懂~继续学习! 常觉得定时器不够用的关注下。多谢LZ! mark^ mark 谢谢楼主 留个爪子 谢谢分享!学习了。, C51延时不好整,谢谢楼主 mark 学习 mark 谢谢LZ!
记号,备查。 mark 学习学习了。。。 mark 留个脚印 不错,谢谢楼主了。 不知道有没有必要这么去算。 mark 留个爪子 标记一下,呵呵 短时间的用延时,长时间的用定时,
短时间,主要是指对于IO操作等,
偶的程序里,时间超过50uS,就用定时器。 很好。 支持原创. 正好在用红外的东东. 我越看越感觉很多地方是自相矛盾的 回复【1楼】smhh
留个爪印!
-----------------------------------------------------------------------
是的是的
我也上点 用弟上方软件生成的代码 单片机小精灵
就是用这个软件生成的 先mark 学习了。。。 我的方法同31楼 存储示波器 也是个方法 软件防真下看就知道走了多少周期了
我只用过S51和AVR的,
S51有两个C代指令可以优化DJNZ Rx,rel 就是生成跟汇编一样的。
楼主的j = 5;while( j-- ); 这种和for(i=5;i>0;i--);
AVR的就不一样了,
用++的话生成的代码会比用--的少一条。 mark... mark.... 用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,这样的误差已经不算误差了,用汇编语言编写还达不到这个程度。
现在你不再为延时不精确担忧了,参照表中的数据写延时,想延时多少就能延时多少。
再次重申:此表格是根据我的延时模块编写,这也是大多数朋友都习惯用的一个模块,如果你用其他模块或是改动了我的模块,延时都不准的,不信大家可以试试!!! 试了下,上面的程序用的晶振是24MHz。
for(a=0;a<?;a++)执行一次循环需要3个周期
如果用 while( j-- ); 或for(i=5;i>0;i--);这两种形式,执行一次只要两个周期,可以做更精确的延时。 感谢大家的分享,根据自己的情况采用。 Mark,以后可能会用到,不同的方法不同,用于做不同的事情,并不是都对,也不是都错. mark 写的不错! mark,, 直接在C中嵌入汇编延时不就行了嘛 mark mark 先mark下先 thank you 则个帖子太好了 标志一下。。。 记号一下下,以后再来学习 对于STC 1T的延时,又要怎么计算? 精确 感觉用汇编才能做到 只能说感谢编译器和编写编译器的人 做个记号吧 mark mark mark mark mark mark c语言正在慢慢的取代汇编,只会汇编的人也学点c吧,因为汇编的优势越来越少了,不能死抱着它不放呀 学习了! 不错,确实很准,不过暂时用不上~ mark!! 这种死等廷时要想用“绝对精准”这个词,你必须列出如下几点:
1、晶振频率。
2、使用哪种编译环境。
3、优化级别。
从你的程序中只见你满足了第一点,说是“绝对精准”只能是蒙人,这种绝对靠的是运气。
就拿优化级别试一下吧,-1级和-8编译出来的程序你反汇编看一下试试,有时相差海了去了。
更不用提编译器不同编译出来的结果了.....
当然,廷时也不一定非要用定时器,如果要定1US,非得把定时器累死。
要精确廷时,时间稍长的用定时器,短廷时用循环,量体裁衣,乱用肯定要占资源,如果要短精确廷时,一定要反汇编计算一下到底运行了几个周期,占多少时间,否则不要说什么绝对精准,那是掩耳盗铃。