Question

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.

Was it helpful?

Solution 3

Egor Skriptunoff suggestion is almost exactly right: the SRAM variable is mapped to the wrong memory address. The latchingFlag variable is not at 0x0100 address, which is the first valid SRAM address, but is mapped to 0x060, overlapping the WDTCSR register. This can be seen in the disassembly lines like the following one:

lds r24, 0x0060

THis line is supposed to load the value of latchingFlag from SRAM, and we can see that location 0x060 is used instead of 0x100.

The problem has to with a bug in the binutils which two conditions are met:

  • The linker is invoked with --gc-sections flag (compiler options: -Wl,--gc-sections) to save code space
  • None of your SRAM variables are initialized (i.e. initialized to non-zero values)

When both of these conditions are met, the .data section gets removed. When the .data section is missing, the SRAM variables start at address 0x060 instead of 0x100.

One solution is to reinstall binutils: the current versions have this bug fixed. Another solution is to edit your linker scripts: on Ubuntu this is probably in /usr/lib/ldscripts. For ATmega168/328 the script that needs to be edited is avr5.x, but you should really edit all them, otherwise you could run into this bug on other AVR platforms. The change that needs to be made is the following one:

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

So replace the line *(.data) with KEEP(*(.data)). This ensures that the .data section is not discarded, and consequently the SRAM variable addresses start at 0x0100

OTHER TIPS

According to your disassembler listing, latchingFlag global variable is located at RAM address 0. This address corresponds to mirrored register r0 and is not a valid RAM address for global variable.

After couple checks and code compares in EE chat I noticed that my version of avr-gcc (4.7.0) stores the value for latchFlag in 0x0100, whereas Egor Skriptunoff mentioned SRAM addres 0 being in OP's assembly listing.

Looking at OP's disassembly (the avr-dump version), I noticed that OP's compiler (4.6.2) stores latchFlag value in a different address (specifically, 0x060) than my compiler (version 4.7.0), which stores latchFlag value at address 0x0100.

My advice is to update the avr-gcc version to at least version 4.7.0. The advantage of 4.7.0 rather than latest and greatest available is the ability to compare the generated code again with my findings.

Of course if 4.7.0 solves the issue, then there is harm in upgrading to a more recent version (if available).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top