Electronic – Avr-gcc does not compile correctly without optimizations, but works (badly) with -Os

attinyavravr-gcccgcc

I am developing software for attiny88 with avr-gcc toolchain. This is a cheap microcontroller with 8kB program memory, 512B SRAM and 64B non-volatile EEPROM data memory. This is all sufficient for the task it is supposed to handle. I do not have a debugger for this chip.

The device acts as SPI slave and allows the master to read/program the EEPROM and to read the device state (state of some analog inputs and digital outputs). The protocol is simple – first databyte carries the instruction where highest two bits encode required action (00-nothing, 01-write eeprom, 10-read eeprom, 11-read state) and the rest is address. Second byte is always zero, third byte is the value to read or write, fourth byte is always zero.

Problem is that I am getting weird behavior from the compiler. It is difficult to put a finger on what is going on, so I will just give a few examples. Most striking is that without -Os optimization the device does not respond over SPI. This is not for any of the obvious reasons – the program fits the memory and the stack should not run into .bss/.data sections (the program has ~700B, SRAM is mapped between 0x100 and 0x2ff, where .bss_end is at 0x109; the heap is empty; there are no nested function calls, neither nested interrupts).

If I turn on the -Os optimization, then the program responds as intended. Here is the working code to handle the ISR:

unsigned char state[8];
volatile unsigned char data;

ISR(SPI_STC_vect)
{
  switch(data>>6) {
  case 0: 
    data = SPDR;
    break;
  case 1:           /* write eeprom */
    while(EECR & (1<<EEPE));
    EECR = (0<<EEPM1)|(0<<EEPM0);
    EEARL = 0;
    EEDR = SPDR;
    EECR |= (1<<EEMPE);
    EECR |= (1<<EEPE);
    data = 0;
    break;
  case 2:               /* read eeprom */
    EEARL = data & 0x1f;    /* with 0x3f stops working (???) */
    EECR |= (1<<EERE);
    SPDR = EEDR;
    data = 0;
    break;
  case 3:           /* read state */
    SPDR = state[data&7];
    data = 0;
    break;
  }
}

However, the program gets broken when written in semantically different way:

  • If I change the line "EEARL = data & 0x1f;" to "EEARL = data & 0x3f;", which is desirable as it would allow to address whole EEPROM address space, the ERPROM writing/reading stops working (I do not need full 64B, so I left it as is)

  • State reading (case 3) gets broken if I replace the line "SPDR = state[data&7];" with switch-case construct that returns value of PORTD/PORTB register when address is 0 and 1 respectively (the current workaround is to keep state[0] and state[1] synchronized with PORTB/PORTD in the main loop).

Am I missing something important? It seems to me that the compiler messes up, but I haven't found any bug reports for avr-gcc or avr-libc (newlib?) that would fit the bill.

The toolchain was installed from current gcc-avr package in aptitude repositories. The makefile is pretty basic:

all: main.hex main.s

main.elf: main.o
    avr-gcc -g -mmcu=attiny88 -o main.elf main.o

main.s: main.elf
    avr-objdump -d main.elf > main.s

main.hex: main.elf
    avr-objcopy -O ihex  main.elf main.hex

main.o: main.c
    avr-gcc -mmcu=attiny88 -Os -c main.c -Wall

UPDATE: Still the same result with latest binutils, gcc and avr-libc (recompiled from sources)

Best Answer

You'll need to inspect the output of avr-objdump to see what exact instructions were generated for your code. Incidentally, it would be helpful to include your C code in the disassembly via avr-objdump -S main.elf > main.s. I doubt that the whole program becomes different when you replace the 0x1F constant by 0x3F, isolating differences in the listing and analyzing them carefully would be your next step.

Such analysis is about as far as you can get without proper development tools. Getting a debugger or a simulator would save you a lot of effort which is required when you're limited to static listing analysis.

PS: I assume here that you don't get any compiler warnings during compilation. If you have any, fixing those should be your first priority. Modern compilers do a pretty good job at informing you about subtle errors which may be hard to find otherwise.