原创 GCC延时函数

2010-1-21 16:41 3631 3 5 分类: MCU/ 嵌入式

http://blog.sina.com.cn/s/blog_5ddb672b0100egnh.html<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />


51中我们的延时函数都是自己编写的,无论是在汇编中还是在C言语中。虽然有模板,有时还是有点烦。呵呵。不过在应用avr 单片机的时候我们就有福了。因为avr-gcc 提供给我们很方便的delay 延时函数, 只有在源文件包含:


#include <util/delay.h>就可以使用了。这个头文件定义了两个级别的延时函数分别是:
void  _delay_us(double_us);      //
微秒级
void  _delay_ms(double_ms);    //
毫秒级


 


不过不可以高兴的太早,因为要在你的avr-gcc中正确使用它们是有条件的,下面我将慢慢道来。



这个参数和
Makefile中的F_CPU值有关,Makefile所定义的的F_CPU变量的值会传递给编译器。你如果用AVR_studio 4.1X来编辑和调试,用内嵌AVR-GCC的进行编译,并且让AVR_studio帮你自动生成Makefile的话,那你可以在:Project->Configuration Options-> Gerneral->Frequency写下你的F_CPU的值,F_CPU这个值表示你的AVR单片机的工作频率。单位是Hz ,不是MHZ,不要写错。如<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />7.3728MF_CPU=7372800
你会发现在"delay.h" 头文件中有这个样的一个定义如下:
#ifndef F_CPU
#warning "F_CPU not defined for <util/delay.h>"
#define F_CPU 1000000UL    // 1MHz
#endif


 


这是为了在你没有定义F_CPU这个变量(包括空),或是AVR_studio Frequency没有给值的时候,提供一个默认的 1MHz频率值。让编译器编译时不至于出错。


 


下面是这两个函数的实体:


void _delay_us(double_us)        //  微秒
{


uint8_t _ticks;


double _tmp=((F_CPU)/3e6)*_us;   // 3e6 是因为调用的_delay_loop_1()是三条指令的


if(_tmp<1.0)


_ticks=1;


else


if(_tmp>255)


_ticks=0;


else


_ticks=(uint8_t)_tmp;


_delay_loop_1(_ticks);


}


void _delay_ms(double _ms)       // 毫秒


{


uint16_t _ticks;


double _tmp=((F_CPU)/4e3)*_ms;  // 4e3 是因为调用的_delay_loop_2()是四条指令的


if(_tmp<1.0)


_ticks=1;


else


if (_tmp>65535)


_ticks=0;


else


_ticks=(uint16_t)_tmp;


_delay_loop_2(_ticks);


}


 


你会发现他们都分别调用了  _delay_loop_1(); _delay_loop_2(); 这两个函数
而这两个函数又如下所示:


void  _delay_loop_1(uint8_t _count)


{


_asm_ volatile (
       "1: dec %0" "\n\t"
        "brne 1b"
        : "=r" (_count)
       : "0" (_count)


);


}


 


从其函数注释里面可以了解到,该函数用来延迟3个晶振时钟周期,不包括程序调用和退出该函数所花费的时间。该函数的形参_count是一个8位的变量,由此,我们就可以根据系统采用的晶振频率算出该函数最大的延迟时间了:


1MHz时:  MAX_DELAY_TIME=(1/1000000)*3*256=0.000768S=768uS
8MHz
时:
  MAX_DELAY_TIME=(1/8000000)*3*256=0.000096S=96uS
............
F_CPU     MAX_DELAY_TIME = (1/F_CPU)*3*256


依此类推。


void  _delay_loop_2(uint16_t _count)


{


_asm_ volatile (
  "1: sbiw %0,1" "\n\t"
  "brne 1b"
  : "=w" (_count)
  : "0" (_count)
);


}


 


该函数延时4个晶振周期,形参是一个16位的变量,同样我们也可以算出该函数最大的延迟时间:
1MHz
时:  MAX_DELAY_TIME=(1/1000000)*4*65535=0.26214S=262.1mS
8MHz
时:
  MAX_DELAY_TIME=(1/8000000)*4*65535=0.03277S=32.8 mS
............
F_CPU     MAX_DELAY_TIME=(1/F_CPU)*4*65535


依此类推。


 


重要提示:_delay_loop_1(0)_delay_loop_1(256)延时是一样的!!


同理,_delay_loop_2(0)_delay_loop_2(65536)延时也是一样的!!这些函数的延时都是最长的延时。


 


这两个函数都是avr-gcc inline汇编格式写的,具体的语法规则我就不多说了。可以参考avr-libc。不过这两个函数很简单,很容易明白。一个是字节递减,一个是字递减。如果你认真看上面几个函数,你就会发现要正确使用它们是有如下条件的:


        1. 首先,你要正确定义你的 F_CPU 的值,也就是你的AVR单片机实际的频率。否则延时不准。(延时只在数字上不准确,具体可以计算)


        2. 你在编译时一定要打开优化,MakefileOPT不要选0 ,如果是AVR_studio不要选O0


        3. 你在使用这两个delay()时,传递给两个函数的实参要使用常量,不要使用变量。


        4. 设置的时间参数_ms_us是有范围的,不要超过范围。_ms1-[262.14ms/(F_CPU/1e6)]_us1-[768us/(F_CPU/1e6)][...]表取整数部分(此处结论错误?)


_us的最大值应该是768us1M频率下) MAX_VALUE= 256*3/F_CPU s,最小值3个时钟周期MIN_VALUE=1*3/F_CPU s_ms最大值MAX_VALUE=65536*4/F_CPU sMIN_VALUE=1*4/F_CPU s;


 


只有具备了上面的条件你才可以正确使用延时函数_delay_us()_delay_ms()。对于第三个条件,为什么要选用常量,还有第二个条件为什么要打开优化选项。这是为了让编译器在编译的时候就把延时的值计算好,而不是把它编译到程序中,在运行时才进行计算,那样的话,一是会增加代码的长度,还会使你的延时程序的延时时间加长,或是变得不可预料。产生时序的错误。


 


08版本中已经修改,具体函数如下:


void _delay_us(double _us)
{


uint8_t _ticks;


double _tmp=((F_CPU)/3e6)*_us;


if(_tmp<1.0)


_ticks=1;


else


if(_tmp>255)


{


_delay_ms(_us/1000.0);


return;


}


else


_ticks=(uint8_t)_tmp;


_delay_loop_1(_ticks);


}


 


_us过大的时候,就会调用_delay_ms();由上面可以知道8M时候_delay_ms最小可以延时4/8000000=0.5us  1M时,最小延时4/1000000=4us,可以连接上。


void _delay_ms(double _ms)


{


uint16_t _ticks;


double _tmp=((F_CPU)/4e3)*_ms;


if(_tmp<1.0)


_ticks=1;


else


if(_tmp>65535)


{


//_ticks=requested delay in 1/10 ms


_ticks=(uint16_t) (_ms * 10.0);


while(_ticks)


{


// wait 1/10 ms


_delay_loop_2(((F_CPU) / 4e3) / 10);


_ticks --;


}


return;


}


else


_ticks=(uint16_t)_tmp;


_delay_loop_2(_ticks);


}


_ms过大时,只采用_ticks--的方式延时。先延时一个262ms(1M32ms 8M),然后用递减方式。


 


GCC中delay_ms,delay_us延时函数在不同工作频(常用)下的最大值如下:
_delay_ms(double __ms)
The maximal possible delay is 262.14 ms / F_CPU in MHz.
工作频率 最大延时值(ms)
20M 13ms
16M 16ms
12M 21ms
11.0592M 23ms
8M 32ms
7.3728M 35ms
4M 65ms
2M 131ms
1M 262ms
_delay_us(double __us)
The maximal possible delay is 768 us / F_CPU in MHz.
工作频率 最大延时值(us)
20M 38us
16M 48us
12M 64us
11.0592M 69us
8M 96us
7.3728M 104us
4M 192us
2M 384us
1M 768us

*不同的晶振只是最大的延时时间不一样,在F_CPU=4MHz,和F_CPU=8MHz下,要延时10ms都是
_delay_ms(10);
但是4M下可以延时到65ms,8M下可以延时到32ms。
我一般采用方法是:
void DelayMs(uint8_t ms)
{
while(--ms)
{
_delay_ms(1);
}
}

PARTNER CONTENT

文章评论2条评论)

登录后参与讨论

用户1610679 2014-3-24 22:05

不错,阔然开朗!

用户1725473 2014-3-16 09:42

不错,很实用
相关推荐阅读
用户176125 2011-07-16 18:59
常规音频二合一接口定义
一、三段耳机、耳麦插头 二、四段耳机耳麦二合一插头   三、实物图 ...
用户176125 2011-05-23 00:12
自勉
请享受无法回避的痛苦 觉得为时已晚的时候,恰恰是最早的时候 谁也不能随随便便成功,它来自彻底的自我管理和毅力勿将今日之事拖到明日投资未来的人,是忠于现实的人没有艰辛,便无所获...
用户176125 2010-07-12 21:00
关于派生类构造函数调用顺序问题
http://topic.csdn.net/u/20091128/21/8e3cd12c-62b1-4e60-836e-d8d9125d5682.html关于派生类构造函数与基类构造函数的调用顺序问题...
用户176125 2010-06-15 21:07
C语言的两种存储类型
http://blog.chinaunix.net/u/22520/showart_373043.html看谭浩强老师的书后一直以为C语言的存储类型(storage class)有四种(auto/st...
用户176125 2010-06-15 21:06
我对C语言变量的一些认识
http://www.wangchao.net.cn/bbsdetail_795340.html  从本质上讲,变量是程序中用来存放信息的一块空间。“这块空间”一词,还要加两个定语。一是所存放的内容是...
用户176125 2010-06-15 20:49
关于inline函数的使用
一、inline函数(摘自C++ Primer的第三版)    避开函数调用开销:  函数调用需要时间和空间开销,调用函数实际上将程序执行流程转移到被调函数中,被调函数的代码执行完后,再返回到调用的地...
EE直播间
更多
我要评论
2
3
关闭 站长推荐上一条 /3 下一条