Search code examples
cembeddedavravr-gcc

Program behaves differently at different optimisation levels


I have written a simple program to familiarise myself with a new MCU. However, my program does not run correctly at optimisation level -O3.

I am using Microchip Studio (Version 7.0.2594) (formerly Atmel Studio); compiling with avr-gcc 5.4.0; and the microcontroller is an ATmega32M1.

At all compilation levels except -O3, LEDs attached to output PB5 and PD6 both blink with a period of 8 seconds. This is the intended behaviour. However, at optimisation level -O3, only the LED attached to output PB5 blinks, while output PD6 remains low.

Is this an obvious error on my behalf, or is it some kind of compiler bug? And, in the unlikely event it is a compiler bug, how can I deal with it?

My code is reproduced below:

#include <avr/io.h>
#include <avr/interrupt.h>

static uint64_t execution_time;

////////////////////////////////////////////////////////////////////////////////
// Setup functions

static inline void SetClock(void) {
    //Set clock to external crystal highest speed (8MHz):  
    //(These instructions must be completed in 4 clock cycles or less)
    CLKPR = 0x80;   //set CLKPCE, clear all else.
    CLKPR = 0x00;   //Clear CLKPCE, set CLKPS[3:0] to 0. (Smallest prescale - fastest speed.)
}

static inline void SetTimer1(void) {
    //set up timer/counter 1 (CTC mode, TOP = OCR1A)
    TCCR1A = 0;             //WGM0 & WGM1 = 0
    TCCR1B = 0x0D;          //set WGM[3:0] = 0,1,0,0 (CTC with OCR1A as TOP); CS1[2:0] = 1,0,1 (internal timer, prescale 1024)
    OCR1A = 0x7A12;         //TOP = 31250; 4000.0 ms - This means an accurate count can be kept as well as the faster count.
    TIMSK1 = 1<<OCIE1A;     //Interrupt on Timer 1 reaching OCR1A.
    //The Timer will run at 7.8125 ticks per ms.
}

void InitOutputs(void) {
    DDRB |= 0x7 << DDB5;    //SPARE6, 7, 10
    DDRC |= 0x3 << DDC6;    //SPARE8, 9
    DDRD |= 0x7 << DDD5 | 0x3 << DDD0;  //SPARE11, 12, 13 && RELAY H & L
}

void Setup(void) {
    //Set up the various parts of the hardware.
    SetClock();
    
    SetTimer1();
    
    InitOutputs();

    sei();
}

////////////////////////////////////////////////////////////////////////////////
// Functional... functions

void TestISR(void) {
    static uint64_t last_time;
    if (execution_time > last_time) {
        PIND = 1 << PORTD6;
        last_time = execution_time;
    }
}

int main(void) {
    Setup();
    
    while (1) {
        TestISR();
    }
}

ISR(TIMER1_COMPA_vect) {
    execution_time += 4000;
    PINB = 1 << PORTB5;
}


Solution

  • Your variable needs to be volatile

    static volatile uint64_t execution_time;
    

    otherwise when you enable optimizations reads from it will be optimized out as the compiler does not see any program path which may modify this variable (ISRs are not called from your code)

    On the other hand as your operations are not atomic they are not thread-safe. On tiny uC like atmega I suggest disabling interrupts when you access this variable in the main program.)

    void TestISR(void) {
        static uint64_t last_time;
        uint64_t execution_time_copy;
        cli();
        execution_time_copy = execution_time;
        sei();
        if (execution_time_copy > last_time) {
            PIND = 1 << PORTD6;
            last_time = execution_time_copy;
        }
    }
    

    or is it some kind of compiler bug

    The compiler would be rather useless if it would fail compiling such a trivial program. Assume: it is always your program