4
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ +1 for a nicely worded question but it's a bit borderline on seeking opinions so if you can improve it you might get more responses? \$\endgroup\$ – Andy aka Feb 11 '18 at 17:22
  • \$\begingroup\$ Care to edit? I literally don't know how to make it less opinion, it's kind of inherently a design question. \$\endgroup\$ – Leroy105 Feb 11 '18 at 17:25
  • \$\begingroup\$ Each state machine has code that determines the next state, right? When you enter a state that has a timeout, you snapshot the system timer and store it. Then when deciding on the next state, you get the current time, subtract the snapshot, and compare with the timeout value. Doesn't seem super complicated. I suppose it could use up a bit of storage that might be scarce. If you want to salvage the state machine ethos, you can dedicate a state to storing the current time for purposes of calculating the elapsed time later. \$\endgroup\$ – mkeith Feb 11 '18 at 17:42
  • 1
    \$\begingroup\$ If the timer is an unsigned 16-bit, then subtracting across the overflow boundary will still give the correct elapsed time. \$\endgroup\$ – mkeith Feb 11 '18 at 17:55
  • 1
    \$\begingroup\$ "It would be horrifying for someone new to pick up this system and implement some new functionality."-> Implement your State machine using a Tool Like IBM Rational Rhapsody. It generates code based on your state machine diagram and hence is parallely documented as well \$\endgroup\$ – Akshay Immanuel D Feb 20 '18 at 3:36
2
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Lundin, I totally agree. I was lying in bed envisioning multiple state machines last night thinking about this. If the LED had a separate state machine, that took states from another machine. In your opinion, if you take that route, do you reduce the complexity enough that it's worth it? In my if/else routine, I am getting to the point where there is 6 flags if most IFs to control for logic control of states edge cases. I totally agree the LED is fundamentally seperate from everything else, but the flags get littered everywhere to keep the ship afloat. \$\endgroup\$ – Leroy105 Feb 12 '18 at 16:27
  • \$\begingroup\$ So, when you say benchmark each state, basically you mean each state finishes and has to kick the dog before the timer runs out? That is a great idea. In my case, I think my watchdog can't run as fast as I'd need it, but in other cases would work great. \$\endgroup\$ – Leroy105 Feb 12 '18 at 16:35
  • 1
    \$\begingroup\$ @Leroy105 When I had to deal with messy code bases in the form of "flaghetti" in the past, I used exactly this design to untangle them. I have one particular example where a program suffered for all manner if intermittent bugs, but when the flags were replaced with state machines all bugs just disappeared, even though I hadn't touched the actual application logic. So yes I know from experience that this is a sound way to go. \$\endgroup\$ – Lundin Feb 12 '18 at 16:47
  • 1
    \$\begingroup\$ @Leroy105 As for the benchmarking, you'll need to ensure that some state isn't going haywire and ruining all real-time performance. The above is kind of a crude form of a RTOS, if implemented correctly. You can also clock each state from the caller code and log them - that's something I use in more mission-critical code, but then also together with some manner of watchdog. Also notably, kicking the wdog from inside an ISR is not a brilliant idea. So if this is all one big ISR, perhaps look for an alternative design. \$\endgroup\$ – Lundin Feb 12 '18 at 16:51
  • \$\begingroup\$ "flaghetti" -- bingo. \$\endgroup\$ – Leroy105 Feb 12 '18 at 16:59
2
\$\begingroup\$

If you want super low tech "multitask" your timer interrupt can look like this:

timer_isr()
{
    process1();
    process2();
    process3();
}

So, if your timer fires, say, 100x per second then every time each process() function is called. These functions are FSMs which implement your different "multitasked" tasks. If they are all independent, then it's easy.

Now, if your tasks are dependent, you can do something like:

timer_isr()
{
    check_buttons();
    blink_red();
    blink_blue();
}

In this case the tasks will communicate via ugly global variables (we aint gonna do message boxes and FIFOs on a 8-bit are we). For example check_buttons() would debounce, etc, and set some flags and/or directly influence the state of the other two FSMs which blink the LEDs.

We can even use this bleeding edge tech called C++:

timer_isr()
{
    check_buttons();
    red.blink();
    blue.blink();
}

In this case check_buttons() would call "red.setBlinkMode( some value )" when the appropriate button is pressed, for example. "red" and "blue" are global objects. In this case this tiny bit of OO allows you to implement the same algo for both without having to mess with tons of globals, or pointers to structs, etc.

It is a nice thing to handle your buttons in only one place in your code. Especially if the buttons control several things, for example one button to select the LED, and another button to modify the blinking pattern of the selected LED.

The .blink() method would, for example, increment a LED-specific counter until it reaches the blinking period, or adjust a PWM in a time-dependent fashion to make it blink fancily, that kind of stuff.

For example, if your times is called ever millisecond, your blink() method could be:

LED::blink()
{
    if( led_on) {
        if( counter++ > period ) {
            counter=0;
            led_pin = !led_pin;
        }
    } else { led_pin = 0; counter=0; }
}

...something like that. They're all called at the same timer period, so all these small state machines know about time by counting the number of times they are called. In this case the state of the FSM is led_on and counter.

\$\endgroup\$
  • \$\begingroup\$ Yes, I get this but you see how you introduced a bunch of control variables? I've got the if/else mess, but I also have the mountains of control/logic variables mess as well. I'm trying to tidy both piles. ;) \$\endgroup\$ – Leroy105 Feb 11 '18 at 17:33
  • \$\begingroup\$ Try to cut it up into small independent boxes that communicate via "channels" (in your case, simple signal variables), the smaller each box/FSM is, the better. \$\endgroup\$ – peufeu Feb 11 '18 at 18:06
  • 1
    \$\begingroup\$ @Leroy105 If you need to use say 20 flag/signal variables for communication between your modules, then you will HAVE TO use 20 variables for them, either as a bit-masks or as a separate volatile variables or as a separate communication structure or even a separate communication module with several structures inside - it's up to you, depending on the complexity. You just need to isolate your communication variables/flags outside of your main logic. \$\endgroup\$ – GAttuso Feb 12 '18 at 8:21
  • 1
    \$\begingroup\$ Tight coupling between a LED driver and button driver is by no means better design than the original spaghetti. That is not how you do proper OO! Your classes are not autonomous, but know of things they shouldn't know about. That is not a OO design, neither in C nor C++. For a proper design, higher-level code should keep track of both buttons and LEDs. \$\endgroup\$ – Lundin Feb 12 '18 at 15:27
1
\$\begingroup\$

Generally I have found it's best to pick a small time increment (maybe a couple milliseconds down to perhaps 250usec for an 8-bit micro) and use that for most or all of the timing. This is comparable to the granularity in an RTOS.

It's easier if you have a micro with a decent architecture that allows nested interrupts.

\$\endgroup\$
  • \$\begingroup\$ Sphero, do you mean you wake-up the state machine every 250usecs? What about if your LED needs to blink every 1000usecs, would you use a counter in your state machine code than? When LedCounter = 1, LED goes on, etc... I'm trying to eliminate so many blasted counter variables and logic flags. The system currently basically is a 500us timer, and everything runs off of that. \$\endgroup\$ – Leroy105 Feb 11 '18 at 17:25
  • \$\begingroup\$ Yes a counter. Imagine an array of counters, each decremented to zero in the interrupt code (declare them as volatile obviously). \$\endgroup\$ – Spehro Pefhany Feb 11 '18 at 17:28
  • \$\begingroup\$ If I said -- okay we are going to have a SysTick timer ISR now, and then we'll add LedTimer ISR vs. just running counter variables for the LedTimer inside of the SysTick timer-- overall the system complexity has increased with two ISRs being introduced? That's my gut feeling is it is a bad approach. \$\endgroup\$ – Leroy105 Feb 11 '18 at 17:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.