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;

  switch(data>>6) {
  case 0: 
    data = SPDR;
  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;
  case 2:               /* read eeprom */
    EEARL = data & 0x1f;    /* with 0x3f stops working (???) */
    EECR |= (1<<EERE);
    SPDR = EEDR;
    data = 0;
  case 3:           /* read state */
    SPDR = state[data&7];
    data = 0;

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)

  • 3
    \$\begingroup\$ Try reading/comparing the generated machine code. \$\endgroup\$
    – PlasmaHH
    Aug 17, 2016 at 8:54
  • 1
    \$\begingroup\$ Where do you initialize the data variable? Are you relying on static initialization to give you "zero-out"? That's always a bad idea, since microcontrollers often deviate from the C standard there, in order to give faster start-up. \$\endgroup\$
    – Lundin
    Aug 17, 2016 at 9:08
  • 4
    \$\begingroup\$ That being said, the most common reason by far why programs stop working between different levels of optimization, is that somewhere in your program, you have some manner of undefined behavior. That is, you probably have a dormant bug which surfaced when you changed the build. Not necessarily in the code posted. \$\endgroup\$
    – Lundin
    Aug 17, 2016 at 9:13
  • 1
    \$\begingroup\$ @PlasmaHH Thanks for the suggestion, it occurred to me that I can diff the disassembly when I just change one constant from 0x1f to 0x3f as discussed in the question. The differences are vast - practically whole program is different. Seems wrong though - for example cli (disable interrupts) instruction disappeared from the interrupt handler routine \$\endgroup\$
    – student
    Aug 17, 2016 at 9:19
  • 2
    \$\begingroup\$ Doing time taking things (like writing EEPROM ) inside an interrupt handler (recalled each SPI receiving event ) definitevely brings rotten luck. \$\endgroup\$
    – carloc
    Aug 17, 2016 at 9:59

1 Answer 1


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.

  • \$\begingroup\$ There are no warnings with -Wall; Simulavr/debugger might be some use but since I have a version of the software that works in repeatable fashion it is not a problem on the the chip. About the differences due to change of a constant from 0x1f to 0x3f: Diff will mark most of the code as a rewrite. It boils down to extra variable in .bss section and changes in offsets in the program that change addressing literals. It is strange, but the program is structurally different - seems that the optimizer decides to use extra variable to speed thing up with 0x3f constant. \$\endgroup\$
    – student
    Aug 17, 2016 at 12:04
  • 3
    \$\begingroup\$ @student try to produce assembler listings from compiler, i.e. avr-gcc -mmcu=attiny88 -Os -S main.c. That should leave address literals in symbolic form (label_0001 etc.), so producing a diff should be easier. \$\endgroup\$ Aug 17, 2016 at 12:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.