C Atmega328P中的奇怪延迟行为

C Atmega328P中的奇怪延迟行为,c,arduino,avr,arduino-uno,atmel,C,Arduino,Avr,Arduino Uno,Atmel,因此,我使用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) {

因此,我使用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到6us之间没有开销(或者为什么开销没有变化)?


OBS:我使用Arduino UNO(16 MHz)

作为小参数,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
届时,精心设计的
\u delay\u us()
函数将或多或少失败。与单个
\u延迟\u us(1)
所需的16个时钟周期相比,分支开销非常大,并且将为每个循环迭代支付

您描述的运行时的突然增加基本上就是编译器停止展开循环的时间点

与直接呼叫
\u delay\u us(6)
相比:

_delay_us(6):
    ldi r24,lo8(32)
    1: dec r24
    brne 1b
上面显示的程序集可能与编译器所做的有所不同,因为编译器的输出可能会因版本和标志而显著不同,但清单应该相当接近。 对于示例,我假设gcc avr 4.6.4具有优化级别
-O2

它可能正在展开循环以获得较小的
时间值。尝试使用变量作为参数,而不是常量。1us是16个CPU时钟周期。所以,事实上,你所有的延迟都是在头顶上,而不是“躺着……”。由于您将函数设置为“inline”,编译器只是将其调用(假设参数为常量)替换为内联循环。所以这主要取决于编译器如何优化循环(例如展开循环等)@EugeneSh。延迟函数只允许整数常量作为参数。延迟函数的最佳方法是使用计时器。永远记住编译器的优化。是的,这种抖动是意想不到的。您可以通过使用控制器的定时器硬件完全避免这种情况-这对于PWM来说很容易。