Electronic – Interrupt fails to change variable, how so

atmegaavrcinterruptsmicrocontroller

Problem was virtually undebuggable, the interrupt that was changing a variable would randomly fail to do so, with no apparent pattern; within interrupt code variable would change but outside of it the change is essentially reverted back to previous state. It's a regular variable so it's not double-buffered. Marking it as volatile had no effect (besides making the code run slower). I've already worked around the problem, but phenomena is still there nonetheless. Also, my chip is Atmega644 and language is C.

I have narrower down the problem to a following code: the interrupt was firing during the execution of code that was also writing to that variable. It checked for pin state, and depending on it would either set or clear a bit from that variable. I resolved the issue by putting that code in the interrupt as well, which left interruptable code with not a single place where that variable was written – only read.

The code is substantially long, so I only provide relevant snippets (the rest of the code doesn't even deals with related variables):

#define BIT(x) (1<<(x))
#define BITSET(x,y) ((x)|=(y))
#define BITCLR(x,y) ((x)&=~(y))

//Interrupt snippet:
if ( PINB & BIT(PINB0) )
    BITSET( display, BIT(DSPL_PWR) );
if ( PINB & BIT(PINB1) )
    BITSET( display, BIT(DSPL_ACT) );

//Main code:
if ( PIND & BIT(PIND2) )
    BITSET( display, BIT(DSPL_ACC) );
else
    BITCLR( display, BIT(DSPL_ACC) );

<...>
leds_display = 0x00;
switch ( roll )
{
    case 0:
        leds_display |= display & BIT(DSPL_PWR);
        break;
    //etc.
}
PORTA = leds_display;

However, that is but a crutch-fix. It works that way, but it should've worked the way it was. Changing a variable is atomic operation, so it can not be interrupted mid-action. My guess was that it was due to optimization magic, but then volatile would have changed the behavior. And even then, even without volatile marker, the code may have ignored the change the first time it ran (it's not critical for it to be real-time up to date), but the next iteration would have recognized the change. This leaves me puzzled about nature of the phenomena.

Anyone knows anything about it? Or is it just a compiler bug?

Best Answer

It is highly unlikely that you have discovered a compiler problem.

Plain and simple.....many variable modifications will require the CPU to perform a read-modify-write behavior to the memory location. If your interrupt comes in the middle of that process you will have undefined behavior.

The fix for it in the mainline code is to bracket the variable modification with a sequence like...

disable interrupts

modify variable

re-enable interrupts

Note that most embedded C compilers have specialized macros that you can invoke to perform the disable/re-enable steps. It is often typical that these only take one machine instruction each and so the overhead is very low.

There is sometimes a need to perform a somewhat more robust code protection scheme in cases where the mainline program code may run with or without interrupts enabled from time to time. This case can also come up if a subroutine or library function is called from both the mainline context and the interrupt context - and the called function needs variable code protection. In these cases it is common to have to implement as:

save current interrupt enabled state (most often to stack)

disable interrupts

modify variable

restore current interrupt enable state (most often from stack)