Electronic – Microcontroller Sleep Race Condition

clow-powermicrocontrollersleep

Given a microcontroller that is running the following code:

volatile bool has_flag = false;

void interrupt(void) //called when an interrupt is received
{
    clear_interrupt_flag(); //clear interrupt flag
    has_flag = true; //signal that we have an interrupt to process
}

int main()
{
    while(1)
    {
        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

Suppose the if(has_flag) condition evaluates to false and we are about to execute the sleep instruction. Right before we execute the sleep instruction, we receive an interrupt. After we leave the interrupt, we execute the sleep instruction.

This execution sequence is not desirable because:

  • The microcontroller went to sleep instead of waking up and calling process().
  • The microcontroller may never wake up if no interrupt are thereafter received.
  • The call to process() is postponed until the next interrupt.

How can the code be written to prevent this race condition from occurring?

Edit

Some microcontrollers, such at the ATMega, have a Sleep enable bit which prevents this condition from occurring (thank you Kvegaoro for pointing this out). JRoberts offers an example implementation that exemplifies this behavior.

Other micros, such at PIC18s, do not have this bit, and the problem still occurs. However, these micros are designed such that interrupts can still wake up the core irrespective of whether the global interrupt enable bit is set (thank you supercat for pointing this out). For such architectures, the solution is to disable global interrupts right before going to sleep. If an interrupt fires right before executing the sleep instruction, the interrupt handler will not be executed, the core will wake up, and once global interrupts are re-enabled, the interrupt handler will be executed. In pseudo-code, the implementation would look like this:

int main()
{
    while(1)
    {
        //clear global interrupt enable bit.
        //if the flag tested below is not set, then we enter
        //sleep with the global interrupt bit cleared, which is
        //the intended behavior.
        disable_global_interrupts();

        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            enable_global_interrupts();  //set global interrupt enable bit.

            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

Best Answer

There is usually some kind of hardware support for this case. E.g., the AVRs' sei instruction to enable interrupts defers enabling until the following instruction is complete. With it one can do:

forever,
   interrupts off;
   if has_flag,
      interrupts on;
      process interrupt;
   else,
      interrupts-on-and-sleep;    # won't be interrupted
   end
end

The interrupt that would have been missed in the example would in this case be held off until the processor completes its sleep sequence.