在 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
,在这种情况下,意外情况对我来说仍然存在。
根据您的反汇编程序列表,
latchingFlag
全局变量位于RAM地址0。该地址对应于镜像寄存器r0
,并且不是全局变量的有效RAM地址。
在 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 解决了该问题,那么升级到更新的版本(如果有)就会有坏处。
Egor Skriptunoff的建议几乎完全正确:SRAM变量被映射到错误的内存地址。
latchingFlag
变量并不位于0x0100
地址(第一个有效的SRAM地址),而是映射到0x060
,与WDTCSR
寄存器重叠。这可以在如下所示的拆卸线中看到:
lds r24, 0x0060
此行应该从 SRAM 加载
latchingFlag
的值,我们可以看到使用位置 0x060
而不是 0x100
。
问题与 binutils 中的 bug 相关,满足两个条件:
--gc-sections
标志调用链接器(编译器选项:-Wl,--gc-sections
)以节省代码空间当这两个条件都满足时,
.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
开始