Statecharts are, without question, a good choice for the design and implementation of embedded devices. This is because embedded devices usually react to some kind of stimuli that then leads to an action and, eventually, to a change of state. If you're not familiar with statecharts, a number of interesting articles are available.1, 2
Whether you've used statecharts to model your device, a subsystem, or the behavior of one module, you might ask yourself why you don't just generate code from that model. Tools are available that will generate code from your model. Most of them are now based on the Unified Modeling Language (UML). Usually only class diagrams and statecharts are needed for code generation. Class diagrams define the static code structure, whereas statecharts define the dynamic behavior.
The available tools and methods are:
- Hand-coded statecharts without any tool support.
- Hand-coded statecharts using a special framework (no tool needed except the framework code).
- Special state modeling tools targeting the embedded domain and supporting code generation. They also might use some kind of framework the generated code is based on.
- All-round tools especially developed for the embedded domain. A vendor-defined runtime model is typically used for the whole system design, such as active objects communicating via ports. Such tools can be quite powerful, providing functions like statechart simulation or online monitoring, but they come at a price.
Besides these options, a growing number of general-purpose UML tools, such as Enterprise Architect, Magic Draw, UModel, or the lightweight Cadifra UML editor, are also available. In addition to many other features, they all enable the modeling of hierarchical statecharts with the elements defined in UML at a price that's affordable for teams with smaller budgets. And very importantly, they can all export the created models in XML (XMI or other XML formats).
An efficient modeling tool and open access to the model information make modeling and code generation interesting for embedded developers. Because of the specific requirements of embedded C programmers, the sometimes built-in generators (that usually only create function bodies anyway) aren't really usable. So what are the requirements the generator and the generated code must fulfill to be used in the embedded domain? Below are some possibilities, but your requirements might differ depending on your application.
- It can be helpful if generated code is human readable. This lets you to understand the code and simplifies validation and verification.
- Generated code should fit into resource-constrained microcontrollers, like TI's MSP430. No large run-time libraries should be needed.
- The generation process should be configurable. Whereas the state-machine function shouldn't have a return value or any parameters if it realizes an IRQ handler. But parameters might be of interest if the state-machine functions run in the context of a task.
- Generated code shouldn't create problems when using static analysis tools.
- Generated code shouldn't enforce a specific system design but let you select what's best for your problem. Ideally the statechart code can be used everywhere (ISR handler, within a task body, etc.).
- The event communication mechanisms shouldn't be predefined. Depending on your design, a queue, a simple variable, or another RTOS mechanism should be used.
- As UML supports constructs that are hard to realize in a small embedded system, a reasonable subset of UML statechart elements and their mapping to code should be defined.
- It would be useful if the generator can be easily integrated into existing development tools and processes, such as in a Makefile.
A generic tool design includes a parser that can read an XML file and create an internal model representation. This internal representation is then the basis for the code generator which walks over the model, creates the statechart, and writes the C and header files.
Experience has shown that XMI files exported from the various tools are always a bit different. Therefore, it's beneficial to design the parser in a way that it can be flexibly extended (e.g., using the template pattern). XMI has evolved over time and there are different versions. But luckily, most tools already support version 2.1, which is considerably easier to process than the older versions.
The model must support at least the elements and their relations that you want to use in your UML model. The model is probably the most stable part of your design as it represents a state-chart that's based on the UML standard.
The UML tools doesn't perform any checks to prove that your model is legal. Therefore, it's useful to implement a model checker that performs some sanity checks before the code generator starts. Some possible checks are listed here:
- Are initial states defined where required?
- Does every transition own at least an event definition?
- Are all states named?
- Is there more than one state within a hierarchical state? If not, this might indicate an inappropriate design.
If everything is okay, the code generator can start. It might accept many flags to influence the creation process. This could include simple things like the use of spaces or tabs in the generated code or the method of how the machine is realized. There are several ways to implement a state-machine, such as the simple (but easy to understand) switch/case based approach. But you can also realize a table-based machine or generate code for an existing framework.
With the existing UML tools and their capability to export the model into a standardized XMI representation, model-driven software development will gain more acceptance in the future. Generating code from the model is an important step to keep design and code in sync.
Peter Mueller has many years of experience in the embedded software area. He offers a state-machine code generator for embedded C (available at www.sinelabore.com.) and can be reached at firstname.lastname@example.org.