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
}
}
Edit 2
Different MCUs handle this condition in different ways. Dvd848 pointed to this PDF that goes through how this situation is resolved for a variety of MCU architectures.
interrupt_flag
as anint
, and increment it every time there is interrupt. Then change theif(has_flag)
towhile (interrupts_count)
and then sleep. Nonetheless, the interrupt could occur after you've exited the while loop. If this is a problem, then do the processing in the interrupt itself? \$\endgroup\$