在avr-gcc 4.6.2中为ATmega328编译以下代码时,我得到了意外的全局变量读取结果:
#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
(直接存储到数据空间)和{{1 (直接从数据空间加载)访问lds
,而本地变量版本使用latchingFlag
(从数据空间加载间接注册)和ldd
(将寄存器间接存储到数据)空格)使用寄存器std
作为地址寄存器(可以用作堆栈指针,由avr-gcc AFAIK提供)。以下是反汇编的相关行:
Y
.bss部分中的全局版本也有 63 002c 8983 std Y+1,r24
65 002e 8981 ldd r24,Y+1
81 004a 8983 std Y+1,r24
。我真的不知道将不同的全局变量和局部变量行为归因于什么。这是avr-gcc命令行(注意latchingFlag
):
-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
,则可以强制再次出现,在这种情况下意外持续存在。
答案 0 :(得分:2)
根据您的反汇编程序列表,latchingFlag
全局变量位于RAM地址0.此地址对应于镜像寄存器r0
,并且不是全局变量的有效RAM地址。
答案 1 :(得分:1)
在EE聊天中进行了几次检查和代码比较后,我注意到我的avr-gcc版本(4.7.0)将latchFlag
的值存储在0x0100
中,而Egor Skriptunoff提到了SRAM addres 0
在OP的汇编列表中。
看看OP的反汇编(avr-dump版本),我注意到OP的编译器(4.6.2)将latchFlag
值存储在与我的编译器不同的地址(特别是0x060
)中(版本) 4.7.0),将latchFlag
值存储在地址0x0100
。
我的建议是将avr-gcc版本更新到至少版本4.7.0。 4.7.0的优势而不是最新和最大可用的是再次比较生成的代码的能力我的发现。
当然,如果4.7.0解决了这个问题,那么升级到更新版本(如果有的话)会有害。
答案 2 :(得分:1)
Egor Skriptunoff建议几乎完全正确:SRAM变量映射到错误的内存地址。 latchingFlag
变量不在0x0100
地址,这是第一个有效的SRAM地址,但映射到0x060
,与WDTCSR
寄存器重叠。这可以在反汇编行中看到,如下所示:
lds r24, 0x0060
该行应该从SRAM加载latchingFlag
的值,我们可以看到使用位置0x060
而不是0x100
。
问题必须与bug in the binutils满足两个条件:
--gc-sections
标志调用链接器(编译器选项:-Wl,--gc-sections
)to save code space 当满足这两个条件时,.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
开始