Too many embedded developers believe that UML is all about using big tools and that the UML concepts, such as the advanced form of state machines (UML statecharts), are too heavy for most embedded designs, let alone small microcontrollers. In this article I describe a method and software for implementing UML statecharts in C, small enough to fit a low-end 8-bit microcontroller. More specifically, I present a nontrivial UML statechart example that runs on the USB Toolstick from Silicon Laboratories with room to spare. The USB Toolstick (Figure 1) is a complete evaluation board for the C8051F300 microcontroller (256 bytes of RAM, 8KB of Flash, 11 pins).
The Toolstick is available online from the Silicon Labs web site for just $10.99. Actually, you can even start without the Toolstick, because I provide a Toolstick software emulation that uses the same state machine code, but runs on a Windows PC.
Why Bother?
The legitimate question is "Why even bother using advanced UML statecharts in low-end 8-bitters?" After all, these parts are so small that no big programs can fit into them anyway, let alone designs requiring UML statecharts.
Well, a lot can go wrong in 8KB, or even 4KB of code. All these small microcontrollers are archetypal event-driven systems with the main job of constantly reacting to events. In general, the reaction to an event depends both on the event type and, more importantly, on the current execution context. For example, if you press a button of an electronic watch, the watch probably reacts differently when it is in the timekeeping mode, compared to the same button pressed in the setting mode.
From the programming point of view, the dependency on the context often leads to convoluted, deeply nested if-else
spaghetti code. The "spaghetti" results from capturing the various bits and pieces of the relevant event history (the context) in multitude of variables and flags, then setting, clearing, and testing these variables in complex expressions scattered throughout the code until it's really hard to tell which path through the code will be taken in response to a given event.
Finite State Machines (FSMs) offer a better alternative because they make the reactions to events explicitly dependent on the execution context, represented as "state". By recognizing the importance of the context upfront, FSMs become powerful spaghetti reducers that drastically cut the number of execution paths through the code, simplify the conditions tested at each branching point, and simplify transitions between different modes of operation; see my article "Back to Basics" for more information. In doing this, state machines eliminate many variables and flags used to store the context, and replace most of them with a single "state variable". Therefore the resulting application typically requires less RAM than the original spaghetti, which is a big deal for a small 8-bitter.
But I'm probably not saying anything new. The classical automata theory has been around since dirt. Nonetheless, it is also widely known that the traditional FSMs have a nasty tendency called "state and transition explosion". The number of states and transitions necessary to represent a system tends to grow faster than the complexity of the system itself because the traditional FSM formalism imposes repetitions; see my article "Deja Vu". For example, a FSM model of a simple 4-operation calculator might have some 15 states. Every one of these states needs to handle the "C" (CANCEL
) event, because users must be able to cancel computations and start over at any stage. In traditional FSMs, you have to repeat the essentially identical CANCEL
transition some 15 times. Similarly, you have to repeat at least a few times the "CE" (CANCEL_ENTRY
) transition, the "=" (EQUALS
) transition, and many others. Needless to say, the resulting code is not just bloated, but is also full of impossible to maintain repetitions, all of which renders the whole formalism barely usable. The complexity level at which FSMs start to "explode" is quite low. Apparently, traditional state machines are already in trouble to handle the complexity of a 4-operation calculator, a small home appliance, or even a more advanced digital watch--all application areas for low-end microcontrollers.
To become truly usable, the classical automata theory needs a mechanism for sharing and reusing transitions across many states. The formalism of statecharts, invented originally by David Harel and adapted subsequently into virtually all modern methodologies, including the UML, adds exactly such a mechanism. By allowing hierarchical nesting of states, statecharts provide an efficient way of sharing behavior, so that the complexity of a statechart no longer explodes but grows linearly with the complexity of the modeled system. Obviously, formalism like this is a blessing to embedded developers because it makes the state machine approach truly applicable to real-life problems; see "Deja Vu".