Electronic – Finite State Machine Handling Timers Gracefully

cmicrocontrollermicroprocessor

I've been working with mostly 8 bit MCUs, where most RTOSes are too much overhead.

Most of the applications I've worked one have just been a periodic interrupt with if/else chains for all the processing logic, and then the MCU goes back to sleep.

This has worked well for a lot of things and has a really minimal overhead. But for one system, I'm getting to the point where there are so many control flags that I'm ready to call my own system "spaghetti". It would be horrifying for someone new to pick up this system and implement some new functionality.

(I have a dual color LED, that must have like 8 different states and timing dependent blinking patterns depending on what state the rest of the system is at. It's a horrifying exercise, for what should be so simple…)

I was looking at maybe doing a finite state machine, and trying to weed out so many control flags.

One conceptual problem I am seeing is the use of timers in a state machine. Currently, I have one hardware timer and then a bunch of variable defined timers counters that increment / deincrement, a control flag variable goes to 0/1, and so we go through the if/else chain.

In my planning stage for a more strict state machine, would you just use more hardware timers and trigger the external interrupts as events to go back to the state machine?

My gut reaction (whether right or not) is using as many external interrupts as possible for the state machine is you 1)introduce all kinds of potential interrupt priority issues that bring their own set of problems, where currently the timing is very deterministic but the control logic is simply confusing and 2) you are using more current running a bunch of timers vs. just handling the timer logic as variables.

I see how you could still increment/deincrement variable timers across your state machine, but isn't that anti-ethical to the state machine pattern?

I'm pretty comfortable with the function pointers vs. switch statement debate on how you code the state machine, or if you want to use a transition table, etc.

I'm specifically wondering how folks have handled the timer management aspect of their state machines in a graceful way.

Best Answer

A common way to do this would be to set a maximum execution time for each state and then benchmark each of them (with 100% code coverage) and ensure that they never exceed the maximum execution time. With some luck you can even use the on-chip watchdog to ensure this, if it can run with low enough timeouts.

Now what you are probably looking for is not one, but several state machines. That is, you can have an universal state machine such as

STATE_MACHINE[state++](); 
if(state == STATES_N) 
{ /* reset state machine */ 
} 

which does nothing but to cycle through the various software modules, giving them each a "time slice". Either you can run through all various hardware drivers in one go and go back to sleep, or you can chose to only run a single one of them. This does of course depend on the real-time requirements.

One such state could be led_execute(), which would be the LED routine keeping track of what is happening on the LEDs right now. This routine is residing inside the LED driver and can in turn keep track of every LED state, so that it looks something like:

typedef enum
{
  LED_OFF,
  LED_RED_LIT, // whatever names make sense
  LED_RED_BLUE_LIT,
  ...
  LED_DONE,
  LED_N
} led_state_t;
...    
static led_state_t led_state = LED_OFF;
...

void led_execute (void)
{
  led_state = LED_STATE_MACHINE[led_state]();
}

If the states depend on external input, then maybe skip the return state part and have the state update through setters/getters only.

This should completely eliminate the need of flags - in particular non-related flags located in the same scope, which can be a nightmare. The most important part here is not to mix up the LED complexity with some other hardware's complexity.

Lets say that you are simultaneously de-bouncing a button. Say that you need to finish de-bouncing before the LEDs can lit - that doesn't mean that the buttons have to know about LEDs or that LEDs have to know about buttons. The caller code should keep track of these things. Meaning you might need some abstraction layer between the outer-most state machine and the drivers themselves. If the LED driver only gets one input "do this!" from the caller, then it couldn't care less about the reasons behind it.