Atmega328P中的奇怪延迟行为

时间:2018-10-17 21:33:55

标签: 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) {
        pin_enable(P2);
        delay_us(1);
        pin_disable(P2);
        delay_us(1);
    }
}

使用示波器,我可以知道该引脚保持1.120us高和1.120us低,其中1为参数。将参数增加到6,示波器显示6.120us。但是,有了7,它就保持了9 us。与10,大约14我们。

我知道循环会带来开销,但是为什么在1到6 us之间没有开销(或者为什么开销不会改变)?

OBS:我正在使用Arduino UNO(16 MHz)

1 个答案:

答案 0 :(得分:2)

对于小参数,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

上面显示的程序集可能与编译器的操作有所不同,因为编译器的输出随版本和标志的不同而有很大差异,但清单应合理地接近。 对于示例,我假定了优化级别为-O2的gcc-avr 4.6.4。 Try it out