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 中的奇怪延迟行为的主要内容,如果未能解决你的问题,请参考以下文章

atmega328p 单片机里面有自带蜂鸣器吗

SPI 在 atmega328p 上不起作用

使用 FTDI Basic 回读 ATmega328p 程序

Atmel Studio 烧录 Atmega328P(Arduiono)

328p芯片引脚间距

使用 UDRE 和 ATmega328P 的中断驱动 USART