2

在为 ATmega328 编译 avr-gcc 4.6.2 中的以下代码时,我得到了意外的全局变量读取结果:

#include <avr/io.h>
#include <util/delay.h>

#define LED_PORT            PORTD
#define LED_BIT             7
#define LED_DDR             DDRD

uint8_t latchingFlag;

int main() {
    LED_DDR = 0xFF;
    for (;;) {
        latchingFlag=1;
        if (latchingFlag==0) {
            LED_PORT ^= 1<<LED_BIT; // Toggle the LED
            _delay_ms(100);         // Delay
            latchingFlag = 1;
        }
    }
}

这是整个代码。我希望 LED 切换永远不会执行,因为latchingFlag设置为1,但是 LED 会持续闪烁。如果latchingFlag声明为本地main()程序按预期执行:LED 永远不会闪烁。

反汇编的代码没有显示我可以看到的任何陷阱,这是使用全局变量对版本的主循环进行反汇编(延迟例程调用被注释掉;相同的行为)

  59                .L4:
  27:main.cpp      ****     for (;;) {
  60                    .loc 1 27 0
  61 0026 0000              nop
  62                .L3:
  28:main.cpp      ****         latchingFlag=1;
  63                    .loc 1 28 0
  64 0028 81E0              ldi r24,lo8(1)
  65 002a 8093 0000         sts latchingFlag,r24
  29:main.cpp      ****         if (latchingFlag==0) {
  66                    .loc 1 29 0
  67 002e 8091 0000         lds r24,latchingFlag
  68 0032 8823              tst r24
  69 0034 01F4              brne .L4
  30:main.cpp      ****             LED_PORT ^= 1<<LED_BIT; // Toggle the LED
  70                    .loc 1 30 0
  71 0036 8BE2              ldi r24,lo8(43)
  72 0038 90E0              ldi r25,hi8(43)
  73 003a 2BE2              ldi r18,lo8(43)
  74 003c 30E0              ldi r19,hi8(43)
  75 003e F901              movw r30,r18
  76 0040 3081              ld r19,Z
  77 0042 20E8              ldi r18,lo8(-128)
  78 0044 2327              eor r18,r19
  79 0046 FC01              movw r30,r24
  80 0048 2083              st Z,r18
  31:main.cpp      ****             latchingFlag = 1;
  81                    .loc 1 31 0
  82 004a 81E0              ldi r24,lo8(1)
  83 004c 8093 0000         sts latchingFlag,r24
  27:main.cpp      ****     for (;;) {
  84                    .loc 1 27 0
  85 0050 00C0              rjmp .L4

第 71-80 行负责端口访问:根据数据表,PORTD地址0x2B为十进制43(参见第 71-74 行)。

变量的局部/全局声明之间的唯一区别latchingFlag是如何latchingFlag访问:全局变量版本使用sts(直接存储到数据空间)和lds(直接从数据空间加载)访问latchingFlag,而局部变量版本使用ldd(从数据间接加载) Space to Register)和std(Store Indirect From Register to Data Space)使用寄存器Y作为地址寄存器(可以用作堆栈指针,通过avr-gcc AFAIK)。以下是反汇编的相关行:

  63 002c 8983              std Y+1,r24

  65 002e 8981              ldd r24,Y+1

  81 004a 8983              std Y+1,r24

全球版本也有latchingFlag.bss 部分。我真的不是将不同的全局变量和局部变量行为归因于什么。这是 avr-gcc 命令行(注意-O0):

/usr/local/avr/bin/avr-gcc \
    -I. -g -mmcu=atmega328p -O0 \
    -fpack-struct \                                                 
    -fshort-enums \                                         
    -funsigned-bitfields \                                        
    -funsigned-char \                                                 
    -D CLOCK_SRC=8000000UL \
    -D CLOCK_PRESCALE=8UL \
    -D F_CPU="(CLOCK_SRC/CLOCK_PRESCALE)" \
    -Wall \
    -ffunction-sections \
    -fdata-sections \
    -fno-exceptions \
    -Wa,-ahlms=obj/main.lst \
    -Wno-uninitialized \
    -c main.cpp -o obj/main.o

使用编译器标志,循环从反汇编中消失,但如果声明了,-Os则可以强制再次出现,在这种情况下,意外对我来说仍然存在。latchingFlagvolatile

4

3 回答 3

2

根据您的反汇编程序列表,latchingFlag全局变量位于 RAM 地址 0。该地址对应于镜像寄存器r0,不是全局变量的有效 RAM 地址。

于 2013-04-16T20:03:30.317 回答
1

在 EE 聊天中进行了几次检查和代码比较后,我注意到我的 avr-gcc (4.7.0) 版本存储了latchFlagin的值0x0100,而Egor Skriptunoff提到 SRAM 地址0在 OP 的汇编列表中。

查看 OP 的反汇编(avr-dump 版本),我注意到 OP 的编译器(4.6.2)将值存储在与我的编译器(版本 4.7.0)latchFlag不同的地址(特别是)中,后者将值存储在 address 。0x060latchFlag0x0100

我的建议是将 avr-gcc 版本至少更新到 4.7.0 版。4.7.0 的优点是能够再次将生成的代码与我的发现进行比较,而不是最新和最好的。

当然,如果 4.7.0 解决了这个问题,那么升级到更新的版本(如果有的话)是有害的。

于 2013-04-16T20:58:13.237 回答
1

Egor Skriptunoff的建议几乎完全正确:SRAM 变量映射到错误的内存地址。latchingFlag变量不在地址,这0x0100是第一个有效的 SRAM 地址,但映射到,与寄存器0x060重叠。WDTCSR这可以在如下的反汇编行中看到:

lds r24, 0x0060

这条线应该是latchingFlag从 SRAM 中加载的值,我们可以看到0x060使用的是 location 而不是0x100.

问题必须与满足两个条件的 binutils 中的错误有关:

  • --gc-sections使用标志(编译器选项:)调用链接-Wl,--gc-sections以节省代码空间
  • 您的 SRAM 变量均未初始化(即初始化为非零值)

当这两个条件都满足时,该.data部分将被删除。当该.data部分丢失时,SRAM 变量从 address 开始,0x060而不是0x100.

一种解决方案是重新安装binutils:当前版本已修复此错误。另一种解决方案是编辑您的链接器脚本:在 Ubuntu 上,这可能位于/usr/lib/ldscripts. 对于 ATmega168/328 需要编辑的脚本是avr5.x,但是你真的应该把它们都编辑好,否则你可能会在其他 AVR 平台上遇到这个错误。需要进行的更改如下:

   .data   : AT (ADDR (.text) + SIZEOF (.text))
   {
      PROVIDE (__data_start = .) ;
-     *(.data)
+     KEEP(*(.data))

所以*(.data)KEEP(*(.data)). 这确保了该.data部分不会被丢弃,因此 SRAM 变量地址从0x0100

于 2013-04-18T15:43:30.530 回答