让我们考虑一下这个为atmega32编译的简单C代码:

_delay_ms(1000);

它被翻译成这个程序集:
00000039  SER R18       Set Register
0000003A  LDI R24,0x69      Load immediate
0000003B  LDI R25,0x18      Load immediate
0000003C  SUBI R18,0x01     Subtract immediate
0000003D  SBCI R24,0x00     Subtract immediate with carry
0000003E  SBCI R25,0x00     Subtract immediate with carry
0000003F  BRNE PC-0x03      Branch if not equal
00000040  RJMP PC+0x0001        Relative jump
00000041  NOP       No operation

当这一行:
_delay_ms(500);

编译为:
00000039  SER R18       Set Register
0000003A  LDI R24,0x34      Load immediate
0000003B  LDI R25,0x0C      Load immediate
0000003C  SUBI R18,0x01     Subtract immediate
0000003D  SBCI R24,0x00     Subtract immediate with carry
0000003E  SBCI R25,0x00     Subtract immediate with carry
0000003F  BRNE PC-0x03      Branch if not equal
00000040  RJMP PC+0x0001        Relative jump
00000041  NOP       No operation

有人能解释生成的程序集背后的逻辑吗?内建函数的开发人员如何保证__builtin_avr_delay_cycles函数的精确周期延迟?
编辑:显然没有在文件顶部提到#define F_CPU 8000000UL

最佳答案

可在此处访问AVR指令集手册:
http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf
让我们看看_delay_ms(1000);的代码。
开始时的ser、ldi和ldi指令需要3个周期,并将24位计数器设置为0x1869FF。
然后我们有一个循环。此循环的每次迭代都将24位计数器减少1,并且一旦计数器达到零,循环就终止。所以循环将有0x1869FF迭代。
循环的大多数迭代都需要5个周期,因为brne在分支时需要两个周期。在循环的最后一次迭代中,brne不分支,因此最后一次迭代只需要4个循环。
循环后的两条指令总共需要3个周期。
把所有的东西加起来,我们看到循环的总数是:
3+0x1869FF*5-1+3=8000000个
由于您的CPU速度是8mhz,因此产生的延迟是1秒。
编译器实现者的诀窍是选择循环要执行多少次迭代,循环之后要执行多少rjp,循环之后要执行多少nop。由于在循环中添加迭代在程序空间方面是免费的,所以您需要尽可能多的循环迭代。由于一个rjmp占用的空间少于两个nop,因此您希望在循环之后有尽可能多的rjmp。然后你可能需要一个nop之后,使周期计数正确。
对于较短的延迟,编译器可能使用8位或16位计数器。对于非常短的延迟,它可能只使用rjmp和nop。对于很长的延迟,它可能使用超过24位的计数器。
在某些情况下,可以通过在循环中添加一些nop来节省程序空间,因为这可能会使更小的循环计数器成为可能,或者让您在结束时删除多条指令。因此,一个真正聪明的实现将考虑到这一点。

关于c - __builtin_avr_delay_cycles实现说明,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54972524/

10-11 22:43