12

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.

TRISAbits
  • 1,348
  • 1
  • 13
  • 25
  • Is this practical or theoretical question? – AndrejaKo Mar 20 '13 at 21:06
  • in theory you use a timer that wakes you up once every (enter acceptable value)ms and then goes back to sleep if nothing needs doing. – Grady Player Mar 20 '13 at 21:10
  • You could clear_interrupt_flag() right before sleeping, but if you can handle the latency Grady's answer is probably best. – kenny Mar 20 '13 at 21:26
  • 1
    I would do `interrupt_flag` as an `int`, and increment it every time there is interrupt. Then change the `if(has_flag)` to `while (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? – angelatlarge Mar 20 '13 at 21:30
  • 1
    well it depends on what micro you are running.. If it was an ATmega328 you could possibly disable the sleep mode on the interrupt, so if the race condition you describe happens then the sleep function would be overridden, loop back again and you would processes the interrupt with a small latency. But also using a timer to wake up at a interval equal or less to your maximum latency would be a great solution too – Kvegaoro Mar 20 '13 at 21:33
  • @angelatlarge: I've considered using a count, but again the same problem occurs when the count reaches zero. Processing inside the interrupt is an option as long as process() is relatively small or there are no interrupt latency requirements. If the interrupt needs to execute as fast as possible, this is also not an option. – TRISAbits Mar 20 '13 at 21:35
  • @kenny: I'm not sure how this would work. The micro would still go to sleep, which is the problem. – TRISAbits Mar 20 '13 at 21:52
  • @AndrejaKo: This is a practical question that I've encountered in the field but never found a clean solution to. – TRISAbits Mar 20 '13 at 21:53
  • @Kvegaoro: Aha! The solution you provide DOES work on an ATmega328. Not all micro's provide this sleep enable bit however (e.g. pic18). I wonder if there would be a way to emulate the behavior on a PIC18 (or have some external hardware support to make this happen). – TRISAbits Mar 20 '13 at 22:11
  • 1
    @TRISAbits: On the PIC 18x, the approach I've described in my answer works just fine (it's my normal design when using that part). – supercat Mar 20 '13 at 22:46
  • See also: https://www.state-machine.com/doc/Samek0710.pdf – Dvd848 Mar 24 '23 at 13:46

3 Answers3

10

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.

JRobert
  • 3,162
  • 13
  • 27
  • Great answer! The algorithm you provide actually works really well on an AVR. Thanks for the suggestion. – TRISAbits Mar 21 '13 at 15:48
3

On many microcontrollers, in addition to being able to enable or disable particular interrupt causes (typically within an interrupt controller module), there is a master flag within the CPU core that determines whether interrupt requests will be accepted. Many microcontrollers will exit sleep mode if an interrupt request reaches the core, whether or not the core is willing to actually accept it.

On such a design, a simple approach to achieving reliable sleep behavior is to have the main loop check clear a flag and then check whether it knows any reason the processor should be awake. Any interrupt that occurs during that time which might affect any of those reasons should set the flag. If the main loop didn't find any cause to stay awake, and if the flag isn't set, the main loop should disable interrupts and check the flag again [perhaps after a couple NOP instructions if it's possible that an interrupt which becomes pending during a disable-interrupt instruction might be processed after the operand fetch associated with the following instruction has already been performed]. If the flag still isn't set, then go to sleep.

Under this scenario, an interrupt which occurs before the main loop disables interrupts will set the flag before the final test. An interrupt which becomes pending too late to be serviced before the sleep instruction will prevent the processor from going to sleep. Both situations are just fine.

Sleep-on-exit is sometimes a good model to use, but not all applications really "fit" it. For example, a device with an energy-efficient LCD might be most readily programmed with code that looks like:

void select_view_user(int default_user)
{
  int current_user;
  int ch;
  current_user = default_user;
  do
  {
    lcd_printf(0, "User %d");
    lcd_printf(1, ...whatever... );
    get_key();
    if (key_hit(KEY_UP)) {current_user = (current_user + 1) % MAX_USERS};
    if (key_hit(KEY_DOWN)) {current_user = (current_user + MAX_USERS-1) % MAX_USERS};
    if (key_hit(KEY_ENTER)) view_user(current_user);
  } while(!key_hit(KEY_EXIT | KEY_TIMEOUT));
}

If no buttons are pushed, and nothing else is going on, there's no reason the system shouldn't go do sleep during the execution of the get_key method. While it may be possible to have keys trigger an interrupt, and manage all user-interface interaction via state machine, code like the above is often the most logical way to handle highly-modal user-interface flows typical of small microcontrollers.

supercat
  • 45,939
  • 2
  • 84
  • 143
  • Thanks supercat for the great answer. Disabling interrupts and then going to sleep is a fantastic solution provided the core will awake from any interrupt sources irrespective of whether the global interrupt bit is set/clear. I took a look at the PIC18 interrupt hardware schematic, and this solution would work. – TRISAbits Mar 21 '13 at 15:57
1

Program the micro to wake on interrupt.

The specific details will vary depending on the micro you are using.

Then modify the main() routine:

int main()
{
    while(1)
    {
        sleep();
        process(); //process the interrupt
    }
}
jwygralak67
  • 699
  • 3
  • 9
  • 2
    Wake-on-interrupt architecture is assumed in the question. I don't think your answer solves the question/problem. – angelatlarge Mar 20 '13 at 21:32
  • @angelatlarge Point accepted. I've added a code example that I think helps. – jwygralak67 Mar 20 '13 at 21:51
  • @jwygralak67: Thanks for the suggestion, but the code you provide merely moves the problem into the process() routine, which now has to test whether the interrupt occurred before executing the process() body. – TRISAbits Mar 20 '13 at 21:58
  • 2
    If the interrupt hadn't occurred, why are we awake? – JRobert Mar 20 '13 at 22:13
  • 1
    @JRobert: We could be awake from a previous interrupt, complete the process() routine, and when we finish the if(has_flag) test and right before the sleep, we get another interrupt, which causes the issue I've described in the question. – TRISAbits Mar 20 '13 at 22:20
  • I think the point is that when you enter process(), you have come out of sleep(), so an interrupt must have occurred very recently. (And no other can occur until you re-enable ints with eint or reti). For many cases, that is good enough. With multiple int sources and no temporal relation between them, it's more complex of course. –  Mar 21 '13 at 11:11
  • @BrianDrummond: I see. Yes, if the system has a single interrupt source, then this solution works just fine. If you have multiple interrupt sources, this solution becomes a bit more complex. – TRISAbits Mar 21 '13 at 16:38
  • ... has a single interrupt source and you can always process it before it can recur. – JRobert Mar 21 '13 at 19:28