AVR 中 delay 函数的调用注意事项!delay_ns delay_ms

时间:2022-07-19 23:45:08

早就知道AVR的编译器有自带的延时子函数(或者说是头文件),但一直没时间一探究竟,今天终于揭开了其内幕。

AVR编译器众多,可谓是百家齐鸣,本人独尊WinAVR.
说明:编译器版本WinAVR-20080610
先说winAVR的_Delay.h_肯定是在Include文件夹下了,进去一看果然有,可打开一看,其曰:“This file has been moved to <util/delay.h>."
在util文件夹中找到delay头文件如下:
--------------------------------------------------------------------------------------------------------------------------------------------
void
_delay_us(double __us)
{
uint8_t __ticks;
double __tmp = ((F_CPU) / 3e6) * __us; //3e6=3000000
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);
}

-----------------------------------------------------------------------------------------------------------------------------------------------
_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);
}
1、分析程序发现上面两个子函数,分别using _delay_loop_1() and using_delay_loop2()
2、还有一点,用此头文件时,必须设置主频和优化项,否则会出现如下提示:
#ifndef F_CPU
/* prevent compiler error by supplying a default */
# warning "F_CPU not defined for <util/delay.h>"
# define F_CPU 1000000UL
#endif

#ifndef __OPTIMIZE__
# warning "Compiler optimizations disabled; functions from <util/delay.h> won't work as designed"
#endif
3、通过查找发现_Delay_loop1()和_Delay_loop2()在文件delay_basic.h中,如下:
/** \ingroup util_delay_basic

Delay loop using an 8-bit counter \c __count, so up to 256 iterations are possible. (The value 256 would have to be passedas 0.) The loop executes three CPU cycles per iteration, not including the overhead the compiler needs to setup the counter register.

Thus, at a CPU speed of 1 MHz, delays of up to 768 microseconds can be achieved.
*/
上面翻译如下:
循环变量为8位,所以可达256(其值256和0等同),每次循环好执行3个CPU时钟,不包括程序调用和退出该函数所花费的时间。
如此,当CPU为1MHZ时,最大延时为768us。( 3us*256)
void _delay_loop_1(uint8_t __count)
{
__asm__ volatile (
"1: dec %0" "\n\t"
"brne 1b" a a
: "=r" (__count)
: "0" (__count)
);
}

/** \ingroup util_delay_basic

Delay loop using a 16-bit counter \c __count, so up to 65536 iterations are possible. (The value 65536 would have to be passed as 0.) The loop executes four CPU cycles per iteration, not including the overhead the compiler requires to setup the counter register pair.

Thus, at a CPU speed of 1 MHz, delays of up to about 262.1 milliseconds can be achieved.
*/
上面翻译如下:
循环变量为16位,所以可达65536(其值65536和0等同),每次循环好执行4个CPU时钟,不包括程序调用和退出该函数所花费的时间。
如此,当CPU为1MHZ时,最大延时大约为262.1us。( 4us*65536)
void_delay_loop_2(uint16_t __count)
{
__asm__ volatile (
"1: sbiw %0,1" "\n\t"
"brne 1b"
: "=w" (__count)
: "0" (__count)
);
}
4、有了上面的基础就不难得出
#include <util/delay_basic.h> // 头文件

// _delay_loop_1(XX); // 8-bit count, 3 cycles/loop
// _delay_loop_2(XXXX); // 16-bit count, 4 cycles/loop

#include <util/delay.h> // 头文件

_delay_loop_1(uint8_t __count)
1MHz时: MAX_DELAY_TIME = (1/1000000)*3*256 = 0.000768 S = 768 uS
8MHz时: MAX_DELAY_TIME = (1/8000000)*3*256 = 0.000096 S = 96 uS
............
F_CPU MAX_DELAY_TIME = (1/F_CPU)*3*256
依此类推。

_delay_loop_2(uint16_t __count)
1MHz时: MAX_DELAY_TIME = (1/1000000)*4*65535 = 0.26214 S = 262.1 mS
8MHz时: MAX_DELAY_TIME = (1/8000000)*4*65535 = 0.03277 S = 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)延时也是一样的!!这些函数的延时都是最长的延时。

重量级函数出场>>>>>>>>>>>>>_delay_us() and _delay_ms() !!!<<<<<<<<<<<<<<<<<

先说_delay_us(double __us),不要以为该函数的形参是double形就为所欲为,随便付值都不会溢出了,其实这个函数的调用是有限制的,不然就会出现延时不对的情况。函 数的注释里说明如下:

The maximal possible delay is 768 us / F_CPU in MHz.
在1MHz时最大延时768us!!!!

也就是说double __us这个值在1M系统时钟时最大只能是768。如果大于768,比如这样调用延时函数_delay_us(780)会怎么样呢??那就会和调用_delay_loop_1(0)一样的效 果了!能延迟多少各位可以算出来。具体在各种系统时钟之下这个值是多少可以通过一个公式算出来:

MAX_VALUE = 256*3000000/F_CPU

同理,分析程序,可以知道_delay_ms(double __ms)函数,在1MHz系统时钟下其最大延时是262.14 ms!在这里也给出该函数的形参的最大值,调用此函数时的实参都不要大于 这个值,大于这个限制值的话就和调用_delay_loop_2(0)同样的延时效果!

MAX_VALUE = 65536*4000/F_CPU (1MHZ时,能输入的最大值为262)

从上面可以看出来,当用延时函数时,若不加注意会出错的(毕竟人们很难经常记住这两个最大值),那还有什么补偿的办法呢?
#include <util/delay_basic.h>

// _delay_loop_2(XXXX); // 16-bit count, 4 cycles/loop
// _delay_loop_1(XX); // 8-bit count, 3 cycles/loop

/*------------------------------------*/
void delay_1ms(void) //1ms延时函数 主频为8MHz
{
_delay_loop_2(2000); // 16-bit count,4 cycles/loop

} // 2000*4/FREQ

//使用不同的晶振,可以自己来计算出()里的值

/*-------------------------------------*/

void delay_nms(unsigned int n) //N ms延时函数
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1ms();
}
/*------------------------------------ -*/


原文见:http://hi.baidu.com/xtuyvzkkkllstue/item/9654ea2f29450bc7ef10f1e2

个人的一些理解,欢迎拍砖:

(1)_delay_us(double __us)调用了子函数 void _delay_loop_1(uint8_t   _count),uint8_t限定了_us的取值范围不能超过255,而_us又决定了_delay_us()能延时多久的问题,具体能延时多久就根据时钟频率了,如上文所说(好像原文有误 _delay_loop_1(uint8_t __count) 这个地方搞成了_delay_loop_2(uint16_t __count) ,本篇已改正):

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

同理,_delay_ms(double _ms)也一样,调用了子函数 void _delay_loop_2(uint16_t   _count),uint16_t限定了_ms的取值范围不能超过65535,而_ms又决定了_delay_ms()能延时多久

 _delay_loop_2(uint16_t __count)
1MHz时: MAX_DELAY_TIME = (1/1000000)*4*65535 = 0.26214 S = 262.1 mS
8MHz时: MAX_DELAY_TIME = (1/8000000)*4*65535 = 0.03277 S = 32.8 mS
............
F_CPU MAX_DELAY_TIME = (1/F_CPU)*4*65535 

(2)AVR自带delay函数延时1us、1ms跟晶振频率没有关系,晶振频率只是决定了参数取值范围,在计数延时时候F_CPU这个值抵消掉了。

(3)上文中的解决办法是自己写了个延时1ms的函数,其实跟自带的是一样,自带的如下:

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 = 0;/* i.e. 65536 */
 else
  __ticks = (uint16_t)__tmp;
  _delay_loop_2(__ticks);
 }

绿色部分暂且不看,红色部分即是_ticks的值,如果晶振是8M带入(F_CPU) / 4e3既得2000,跟上文是一样。就是自带的函数里面不用考虑时钟频率,因为它最终被约分。所以这个1ms延时不必自己写了,我们只需要引用 _delay_ms(1);让它延时1ms,然后再设循环延时nms,这样就摆脱了 _delay_ms(double _ms)中_ms的范围限制。

(4)另外关于延时函数正确使用的条件设置必须注意,见上篇文章: 

基于WINAVR + avr studio 4 使用外部晶振时及延时可能遇到的问题