Hello and welcome to the Modern Embedded Systems Programming course.
My name is Miro Samek and in this second lesson about state machines you'll learn about "guard
conditions" as a mechanism of making your state machines more flexible. This concept will be
needed in the upcoming lessons where I'll discuss the promised other variants of state machines.
As usual, let's get started by copying the previous directory for lesson-35 to lesson-36.
Get inside the new lesson-36 directory and double-click on
the BlinkyButton-dot-QM model file to open it with the freeware QM modeling tool.
If you don't have QM, you can download it from state-machine.com
as part of the QP-bundle. To remind you quickly what happened so far,
in the last lesson you've learned the very basics of state machines.
You also designed a simple state machine for your BlinkyButton application.
In a nutshell, a state machine is the most optimal solution to the very common problem,
where a system needs to react to events based not only on the event-type,
but also depending on the history of past events.
For example, in your BlinkyButton application the TIMEOUT event needs
to trigger different reactions, depending on what happened to the green LED previously.
This is very elegantly handled by the "off" and "on" states in your state machine.
In the "off" state, the TIMEOUT event causes turning the LED *on* and a transition to
the "on" state to remember the LED change. In the "on" state, the **same** TIMEOUT event
causes turning the LED *off* and a transition to the "off" state, to remember the LED change.
So, this is how the reaction to an event depends on both: the event-signal, TIMEOUT in this case,
and on the current state of the state machine. However, this is still not quite flexible enough
for many real-life situations. The reason is that the whole structure of states and transitions
in a state machine is fixed at design time. But what if the behavior,--that is, the reactions
to future events--depends on the user input, which is obviously not known until runtime?
This means that for certain transitions you cannot know at
design time which state to transition to. To address this need, the UML state machine
notation includes the so called "choice pseudostates" and "guard conditions".
The whole idea is actually very simple and is a way of combining a decision point
from a flowchart with the transition notation. Specifically, a transition can go into a
"choice pseudostate", depicted as a diamond. Out of this diamond shape, you can have several
outgoing transition segments, each labeled with a "guard condition" depicted in square brackets.
Guard conditions are simply boolean expressions evaluated at runtime.
If a guard condition evaluates to true, the corresponding transition is taken.
Otherwise the transition is considered disabled, meaning that it is not taken.
A special complementary guard depicted as "else" in square brackets evaluates to true
only if all other guards from the same choice pseudostate evaluate to false.
Now, let me show you an application, where such choice pseudostates and
guards would be very useful. So, suppose that you want to use
your TivaC LaunchPad board as a controller for a time bomb, which should work as follows:
After reset, the controller lights up the green-LED
and waits for the user to press the button SW1. When you press the button, the board turns the
green-LED off and starts timing out. It blinks the red-LED on and off 3 times,
after which the bomb explodes, which you emulate by turning all three LED colors on.
In other words, after the reset green light, and after the button press:
blink-pause, blink-pause, blink-pause, boom!
OK, so now let's build a state machine for this behavior.
Again, just as in the previous lesson-35, I will use the QM visual modeling tool
with the previous BlinkyButton model as my starting point for the TimeBomb.
I will save the model as TimeBomb-dot-QM
and I will rename the active object to TimeBomb.
So now, as you remember, the initial transition should turn the green-LED on
and transition to a state, where the time bomb waits for the user to press the button.
Let's name this state "wait4button".
Inside the "wait4button" state, the only event of interest is BUTTON_PRESSED,
so you can get rid of the other events. The BUTTON_PRESSED event needs to perform
the following actions: 1. turn the green-LED off,
2. turn the red-LED on, 3. arm the time event to
expire in one-half second, and 4. transition to the "blink" state,
because as you remember, button-press should be followed by "blink".
In the "blink" state, you can get rid of all events except TIMEOUT that you
just armed on the transition to this state. The TIMEOUT event needs to turn the red-LED off,
arm the time event for another one-half second, and transition to the "pause" state,
because as you remember "blink" is followed by "pause".
In the "pause" state, you also need to handle the just armed TIMEOUT event,
to get ready for another blink. But wait a minute,
where should you go with this transition? I mean, you cannot naively go back to "blink",
because that would create an endless loop of "blink" and "pause".
A brute-force solution would be to add three groups of blink and pause states,
because at this point the time bomb is supposed to blink just three times.
But this is obviously a rather brain-dead solution,
not only because there are so many states that mindlessly repeat essentially the same behavior,
but also because the number of blinks is likely to change.
Indeed, the next feature could very well be that the number of blinks before the time bomb goes
off needs to be adjustable by the user. So, you simply cannot know this number at design time.
So instead, a smarter design would be to add a "blink_counter" to the state machine
that would simply count the number of blinks. Specifically, each time through the pause state,
the action on the TIMEOUT event would only decrement the counter.
Now, you need to apply the new concept for this lesson by adding a choice pseudostate,
which you need to label with a guard condition. The guard is simply a boolean expression:
blink_counter greater than zero. This guard means that you still have some blinking to do,
so you go to the blink state. But you should not forget to
also perform actions to prepare for the blink state, such as turn the red-LED on
and arm the time event for one-half second.
You also need to add the complementary "else" guard, which will go to the new state "boom".
On this transition segment, you also need to perform actions to prepare for that state, such as
turning all LEDs on to emulate the explosion. Of course you still need to add the "boom" state
and point the transition segment to it. Finally, you need to initialize the blink_counter
variable to the total number of blinks you wish to have. You can put it in various
transitions upstream from the "blink" state, for example "wait4button".
Alright, so now you just need to code the state machine. Again, like in the last lesson,
you'll be doing this manually, even though the QM modeling tool could generate the code for you.
But at this point, I haven't yet explained any other,
more optimal state machine implementation strategies, so let's stick to the straightforward,
nested-switch statement technique that you've learned in the last lesson-35.
So, first off, you need to rename BlinkyButton to TimeBomb in your whole project.
After the global replacement, you need to make some manual corrections...
And actually, since this is just a name change, the project should still build cleanly.
Now, inside your TimeBomb active object,
you need to enumerate the states of your state machine. These states are: WAIT4BUTTON,
BLINK, PAUSE, and BOOM. You also need to check the declarations of
the internal private variables. You obviously need the time event, so you keep it.
But instead of the blink-time, you now need the blink_ctr data member.
At this point you can delete the no longer needed initial blink time constant.
But next, moving on to your state machine, you start with the initial transition.
As you remember from the last time, the whole state machine implementation
goes into the dispatch function. So, here, for the initial transition,
you tun the Green-LED on and transition to the WAIT4BUTTON state.
All states of your machine go into the switch statement, which discriminates
based on the me-state variable. The first such state is wait4button.
Here you handle just the BUTTON_PRESSED event,
so you can delete all the others. The actions on BUTTON_PRESSED
turn the Green-LED off, the Red-LED on, arm your time event for one-half second,
and initialize the blink-counter to 3. After this you transition to the blink state.
In the blink state you handle only the TIMEOUT event, so you can delete everything else.
The actions on TIMEOUT are: turn the Red-LED off, arm the Time Event for one half second,
and transition to the pause state.
Now, the pause state is similar to blink, so I just copy and paste the
blink code as my starting point for pause. The first segment of the TIMEOUT transition
only decrements the private me-blink-counter data member.
But now comes the most interesting part of coding the choice point and the guard conditions.
I hope you can see the first branch corresponds to the IF-statement that
simply checks the guard condition. If this guard evaluates to TRUE, you turn
the Red-LED on, arm the time event for one-half second, and transition back to the blink state.
The complementary "else" guard corresponds then simply to the ELSE branch of the original iIF.
Here, you turn all the LEDs on, to emulate the bomb
explosion and you transition to the boom state.
Finally, the boom state handles no events, so it is an empty case statement.
In summary, the choice pseudostate and guard conditions turn out to be simply
IF-s and ELSEs in your C code. Please note that these are exactly the same
IFs and ELSEs that the state machine was designed to eliminate in the first place. Just recall the
"spaghetti code" from the Visual Basic calculator example I showed you in the last lesson-35.
So the moral here is that guard conditions should be used very judiciously. Too many of them,
and you'll find yourself back in square one (spaghetti), where the guards effectively take
over handling of all the relevant conditions in the system. On the other hand, guards are
sometimes unavoidable, because they add the necessary flexibility to the state machines.
Therefore one of the main challenges in becoming an effective state machine designer is to develop
a sense for which parts of the behavior should be captured in states and transitions at design time,
and which parts require runtime flexibility of guard conditions.
Generally, you should challenge yourself to use as few guard conditions as possible.
Alright, so the last order of business for today is to check how your code works on the board.
For this, I'll simply upload the code to the board.
When I press the reset button, the green LED lights up, as expected.
Now, when I press the SW1 button... three, two, one, boom!
OK, so now let's change the blink counter to, say, count 10, and rebuild the code.
Load the code to the board...
and press reset: green light. Good. Now: the button: ten, nine, eight, seven,
six, five, four, three, two, one, boom!
This concludes this lesson about guard conditions. Now, you are truly ready for the next lesson,
where you'll learn about the other variants of so called input-driven or polled state machines
and how they compare to the event-driven state machines that I've been discussing so far.
If you like this channel, please give this video a like and subscribe to stay tuned.
You can also visit state-machine.com/quickstart for the class notes and project file downloads.