Atmega328P 中的奇怪延迟行为
Strange delay behavior in Atmega328P
因此,我使用 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)
对于小参数,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
然而,在某些时候,编译器将其策略从 space 消耗展开更改为通过 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
因此,我使用 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)
对于小参数,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
然而,在某些时候,编译器将其策略从 space 消耗展开更改为通过 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