Atmega328P 中的奇怪延迟行为
Posted
技术标签:
【中文标题】Atmega328P 中的奇怪延迟行为【英文标题】:Strange delay behavior in Atmega328P 【发布时间】:2018-10-17 21:33:54 【问题描述】:因此,我使用 utils/delay.h 中的标准函数实现了自定义延迟函数。
inline void delay_us(uint16_t time)
while (time > 0)
_delay_us(1);
time--;
在主函数的循环内调用:
#define F_CPU 16000000UL
...
int main()
pin_mode(P2, OUTPUT);
while (1)
pin_enable(P2);
delay_us(1);
pin_disable(P2);
delay_us(1);
使用示波器,我可以看出引脚保持 1.120us 高和 1.120us 低,参数为 1。将参数增加到 6,示波器显示为 6.120us。但是对于 7,它仍然是 9 我们。有10个,大约14个我们。 我知道循环带有开销,但是为什么在 1 到 6 us 之间没有开销(或者为什么开销没有变化)? OBS:我使用的是 Arduino UNO (16 MHz)
【问题讨论】:
可能它正在为time
的小值展开循环。尝试使用变量作为参数而不是常量。
1 us 是 16 个 CPU 时钟周期。所以,实际上,你所有的延迟都是开销而不是“_dethelay...”。由于您将函数设为“内联”,因此编译器只需将其调用(假设参数是常量)替换为内联循环。所以这主要取决于编译器将如何优化该循环(例如展开它等)
@EugeneSh。延迟函数只允许一个整数常量作为参数。
延迟功能的最佳方式是使用计时器。永远记住编译器的优化。
是的,这种抖动是出乎意料的。你可以通过使用控制器的定时器硬件来完全避免这种情况——这对于 PWM 来说很容易。
【参考方案1】:
对于小参数,gcc-avr 将展开 while 循环,有效地将多个 1µs 延迟串在一起:
delay_us(5):
ldi r24,lo8(5)
mov r25,r24
1: dec r25
brne 1b
mov r25,r24
1: dec r25
brne 1b
mov r25,r24
1: dec r25
brne 1b
mov r25,r24
1: dec r25
brne 1b
1: dec r24
brne 1b
然而,在某些时候,编译器将其策略从占用空间的展开更改为实际通过 while 循环进行分支:
delay_us(6):
ldi r24,lo8(6)
ldi r25,hi8(6)
ldi r19,lo8(5)
.L2:
mov r18,r19
1: dec r18
brne 1b
sbiw r24,1
brne .L2
到时候,精心打造的_delay_us()
功能或多或少会败下阵来。与单个 _delay_us(1)
所需的 16 个时钟周期相比,分支开销很大,并且将在每次循环迭代时支付。
您描述的运行时突然增加基本上是您的编译器停止展开循环的点。
将此与直接调用_delay_us(6)
进行比较:
_delay_us(6):
ldi r24,lo8(32)
1: dec r24
brne 1b
上面显示的程序集可能与您的编译器正在执行的程序有些不同,因为编译器输出可能会随版本和标志而显着变化,但列表应该相当接近。
对于示例,我假设 gcc-avr 4.6.4 具有优化级别 -O2
。
Try it out
【讨论】:
以上是关于Atmega328P 中的奇怪延迟行为的主要内容,如果未能解决你的问题,请参考以下文章
使用 FTDI Basic 回读 ATmega328p 程序