Electronic – Interrupt handling in microcontrollers and FSM example

embeddedinterruptsmicrocontrollerstate-machines

Initial question

I have a general question about the handling of interrupts in microcontrollers. I am using the MSP430, but I think the question may be extended to other uCs.
I would like to know whether is or not a good practice to enable/disable interrupts frequently along the code.
I mean, if I have a portion of code that is not going to be sensitive to interrupts (or even worse, must not listen to interrupts, for any reason), is it better to:

  • Disable the interrupts before and then re-enable them after the critical section.
  • Put a flag inside the respective ISR and (instead of disabling the interrupt), set the flag to false before the critical section and resetting it to true, just after. To prevent the code of the ISR from being executed.
  • Neither of the two, so suggestions are welcome!

Update: interrupts and state charts

I will provide a specific situation. Let us assume we want to implement a state chart, which is composed by 4 blocks:

  1. Transitions/Effect.
  2. Exit conditions.
  3. Entry activity.
  4. Do activity.

This is what a professor taught us at university. Probably, not the best way of doing it is by following this scheme:

while(true) {

  /* Transitions/Effects */
  //----------------------------------------------------------------------
  next_state = current_state;

  switch (current_state)
  {
    case STATE_A:
      if(EVENT1) {next_state = STATE_C}
      if(d == THRESHOLD) {next_state = STATE_D; a++}
      break;
    case STATE_B:
      // transitions and effects
      break;
    (...)
  }

  /* Exit activity -> only performed if I leave the state for a new one */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(current_state)
    {
      case STATE_A:
        // Exit activity of STATE_A
        break;
      case STATE_B:
        // Exit activity of STATE_B
        break;
        (...)
    }
  }

  /* Entry activity -> only performed the 1st time I enter a state */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(next_state)
    {
      case STATE_A:
        // Entry activity of STATE_A
        break;
      case STATE_B:
        // Entry activity of STATE_B
        break;
      (...)
    }
  }

  current_state = next_state;

  /* Do activity */
  //----------------------------------------------------------------------
  switch (current_state)
  {
    case STATE_A:
      // Do activity of STATE_A
      break;
    case STATE_B:
      // Do activity of STATE_B
      break;
    (...)
  }
}

Let us also assume that from, say STATE_A, I want to be sensitive to a interrupt coming from a set of buttons (with debouce system, etc. etc.). When someone presses one of these buttons, an interrupt is generated and the flag related to the input port is copied into a variable buttonPressed. If the debounce is set to 200 ms in some way (watchdog timer, timer, counter, …) we are sure that buttonPressed cannot be updated with a new value before 200 ms.
This is what I am asking you (and myself 🙂 of course)

Do I need to enable interrupt in the DO activity of STATE_A and disable before leaving?

/* Do activity */
//-------------------------------------
switch (current_state)
{
  case STATE_A:
    // Do activity of STATE_A
    Enable_ButtonsInterrupt(); // and clear flags before it
    // Do fancy stuff and ...
    // ... wait until a button is pressed (e.g. LPM3 of the MSP430)
    // Here I have my buttonPressed flag ready!
    Disable_ButtonsInterrupt();
    break;
  case STATE_B:
    // Do activity of STATE_B
    break;
  (...)
}

In a way that I am sure that the next time I execxute block 1 (transition/effects) at the next iteration I am sure that the conditions checked along the transitions are not coming from a subsequent interrupt that has overwritten the previous value of buttonPressed that I need (although it is impossible that this happens because 250 ms must elapse).

Best Answer

The first tactic is to architect the overall firmware so that it's OK for interrupts to occur at any time. Having to turn off interrupts so that the foreground code can execute a atomic sequence should be done sparingly. There is often a architectural way around it.

However, the machine is there to serve you, not the other way around. General rules of thumb are only to keep bad programmers from writing really bad code. It is far better to understand exactly how the machine works and then architect a good way to harness those capabilities to perform the desired task.

Keep in mind that unless you really are tight on cycles or memory locations (can certainly happen), that otherwise you want to optimize for clarity and maintainability of the code. For example, if you have a 16 bit machine that updates a 32 bit counter in a clock tick interrupt, you need to make sure that when the foreground code reads the counter that the two halves of it are consistent. One way is to shut off interrupts, read the two words, and then turn interrupts back on. If interrupt latency isn't critical, then that's perfectly acceptable.

In the case where you must have low interrupt latentcy, you can, for example, read the high word, read the low word, read the high word again and repeat if it changed. This slows down the foreground code a little, but adds no interrupt latency at all. There are various little tricks. Another might be to set a flag in the interrupt routine that indicates the counter must be incremented, then do that in the main event loop in the foreground code. That works fine if the counter interrupt rate is slow enough so that the event loop will do the increment before the flag is set again.

Or, instead of a flag use a one-word counter. The foreground code keeps a separate variable that contains the last counter it has updated the system to. It does a unsigned subtract of the live counter minus the saved value to determine how many ticks at a time it has to handle. This allows the foreground code to miss up to 2N-1 events at a time, where N is the number of bits in a native word the ALU can handle atomically.

Each method has its own set of advantages and disadvantages. There is no single right answer. Again, understand how the machine works, then you won't need rules of thumb.