使用 avr-gcc 在 C++ 中读取意外的全局变量结果(局部变量访问符合预期)

问题描述 投票:0回答:3

在 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
(从数据空间间接加载到寄存器)和
std
(从寄存器间接存储到数据空间)使用寄存器
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

全球版本的 .bss 部分也有

latchingFlag
。 我真的不知道将不同的全局变量行为与局部变量行为归因于什么。 这是 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
编译器标志,循环已从反汇编中消失,但如果声明
latchingFlag
则可以强制再次出现
volatile
,在这种情况下,意外情况对我来说仍然存在。

gcc global-variables avr avr-gcc
3个回答
2
投票

根据您的反汇编程序列表,

latchingFlag
全局变量位于RAM地址0。该地址对应于镜像寄存器
r0
,并且不是全局变量的有效RAM地址。


1
投票

在 EE 聊天中进行几次检查和代码比较后,我注意到我的 avr-gcc (4.7.0) 版本将

latchFlag
的值存储在
0x0100
中,而 Egor Skriptunoff 提到 SRAM 地址
0
在 OP 中组装清单。

查看OP的反汇编(avr-dump版本),我注意到OP的编译器(4.6.2)将

latchFlag
值存储在与我的编译器(版本4.7.0)不同的地址(特别是
0x060
)中,这将
latchFlag
值存储在地址
0x0100

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

当然,如果 4.7.0 解决了该问题,那么升级到更新的版本(如果有)就会有坏处。


1
投票

Egor Skriptunoff的建议几乎完全正确:SRAM变量被映射到错误的内存地址。

latchingFlag
变量并不位于
0x0100
地址(第一个有效的SRAM地址),而是映射到
0x060
,与
WDTCSR
寄存器重叠。这可以在如下所示的拆卸线中看到:

lds r24, 0x0060

此行应该从 SRAM 加载

latchingFlag
的值,我们可以看到使用位置
0x060
而不是
0x100

问题与 binutils 中的 bug 相关,满足两个条件:

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

当这两个条件都满足时,

.data
部分将被删除。当缺少
.data
部分时,SRAM 变量从地址
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

开始
© www.soinside.com 2019 - 2024. All rights reserved.