I am getting unexpected global variable read results when compiling the following code in avr-gcc 4.6.2 for 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;
}
}
}
This is the entire code. I would expect the LED toggling to never execute, seeing as latchingFlag
is set to 1
, however the LED blinks continuously. If latchingFlag
is declared local to main()
the program executes as expected: the LED never blinks.
The disassembled code doesn't reveal any gotchas that I can see, here's the disassembly of the main loop of the version using the global variable (with the delay routine call commented out; same behavior)
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
The lines 71-80 are responsible for port access: according to the datasheet, PORTD
is at address 0x2B
, which is decimal 43
(cf. lines 71-74).
The only difference between local/global declaration of the latchingFlag
variable is how latchingFlag
is accessed: the global variable version uses sts
(store direct to data space) and lds
(load direct from data space) to access latchingFlag
, whereas the local variable version uses ldd
(Load Indirect from Data Space to Register) and std
(Store Indirect From Register to Data Space) using register Y
as the address register (which can be used as a stack pointer, by avr-gcc AFAIK). Here are the relevant lines from the disassembly:
63 002c 8983 std Y+1,r24
65 002e 8981 ldd r24,Y+1
81 004a 8983 std Y+1,r24
The global version also has latchingFlag
in the .bss section. I am really not what to attribute the different global vs. local variable behavior to. Here's the avr-gcc command-line (notice -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
With -Os
compiler flags the loop is gone from the disassembly, but can be forced to be there again if latchingFlag
is declared volatile
, in which case the unexpected persists for me.
-Werror -O0
to the command line the compiler throws an error:functions from <util/delay.h> won't work as designed" [-Werror=cpp]
. When I change it to-Werror -Os
the error disappears. – jippie-O
!=0
. The unexpected behavior occurs regardless of whether delay functions are used or not and the behavior described above is the same whetherutil/delay.h
is#include
d or not. – angelatlarge_delay_ms(100)
lines in your disassembler listing? – Egor Skriptunoff