In the object-oriented software community, an architectural pattern defines a fundamental structural organization or schema for a software system. It provides a set of predefined subsystems, specifies their responsibilities, and includes rules and guidelines for organizing the relationships between them.
In embedded systems, there are a number of fundamental patterns that once recognized make the process of developing code for an application much easier: periodic task patterns, state machine patterns, and coding patterns. Periodic Task Pattern. Periodic tasks deal with things that your system needs to do on a regular basis.
Suppose, for example, that your system sometimes needs to blink an LED on and off once per second to indicate some error condition to the user. You could no doubt piggyback this onto some task in your system, but you could also write the simple code shown in Figure 9:
|Figure 9 A Periodic Task|
A few points about this pattern:
(1) Using RTOSSleep as shown here may not be particularly accurate, depending upon your RTOS and depending upon the rest of your system. Most RTOSes offer more sophisticated ways, such as a callback from a timer service, to get accurate timings if they are important for your periodic task.
(2) This periodic task wakes up and uses some CPU time, even when the LED is not blinking. If this is a problem in your system, the code in Figure 10 below deals with that (at the penalty of some slight complication to your code). Note that most RTOSes give you a way to get a message into a queue at some future time (although you’ll probably have to write a callback function such as the one shown in Figure 8 in Part 2).
|Figure 10 A Periodic Task the Runs Only When it Needs to.|
State Machine Pattern
State machine tasks are a very simple way to implement state machines in a real-time environment. State machines keep track of the state of the outside world in one or more “state variables” and perform actions and change their states in response to events that arrive from the outside world. In an RTOS environment the basic pattern for a state machine task is the code here in Figure 11, below:
|Figure 11 State Machine Task|
Whenever something happens that affects this state machine, the ISR or task that notices the event writes a message to vStateMachineTask’s message queue indicating what has happened. The vStateMachineTask code uses this message as an event to drive the state machine forward. Even though the real world events that drive the state machine may happen so quickly that the code can’t process one before the next has occurred, vStateMachineTask’s queue will serialize those events so that the code can handle them in an orderly way.
State machines are common in mechanical control systems. The state variables of such a state machine contain the condition of the mechanical hardware. Events are sent to the task when a sensor triggers or a stepper motor has completed a certain number of steps or a given amount of time has passed. When it receives one of these events, the state machine code changes its internal state variables to reflect the new state of the hardware and does whatever is needed to start the next necessary hardware action.
In the sections above, we have discussed various patterns that can guide you to reasonable ways of dividing the work of your system into separate tasks under an RTOS. In this last section, we discuss a pattern for coding tasks.
Once we have decided how to break a system up into a set of tasks, we use the coding pattern discussed below to help us avoid many of the common pitfalls of multi-threaded programming. While a pre-emptive RTOS can help your system meet its deadlines, it also introduces, by its very nature, a whole raft of potential problems that are brought about because one thread of execution can lose control of the processor to another thread of execution essentially at any time.
All RTOSes supply tools, such as mutexes and queues, to help solve these problems, but you must use these tools flawlessly or your system will have bugs that appear only intermittently and that are very difficult to analyze and find. The coding pattern helps systematize the use of these tools in order to minimize problems of this nature.
The coding pattern is a generic pattern that can be used as a starting point for tasks that don’t fit the task patterns already discussed. It combines and generalizes some of the concepts introduced earlier. The coding pattern provides a task that makes efficient use of CPU time.
The coding pattern also eliminates re-reentrancy problems through the use of a simple, mechanical coding strategy. And, in the spirit of object oriented programming, the coding pattern encapsulates all of the resources that are used by a task and provides an API for controlling the task. Specifically, here are the salient characteristics of the coding pattern:
(1) The task is either blocked waiting for a message telling it to do something or it is busy doing something. It is never blocked waiting for some particular message or event. It never spins in a loop waiting for something to finish. It never polls variables figuring out if there is something to do. The task is either blocked, or it is busy.
(2) The coding pattern systematizes the use of the mutex to serialize access to the task’s data. Every API routine—i.e., every routine that is called by a thread of execution that is different than the task’s thread of execution—that asynchronously accesses the task’s data has a preamble and postamble that takes and releases the mutex that protects that data. Likewise, there is a preamble and postamble to take and release the mutex in each and every case statement for the messages that the task processes from its message queue.
(3) Any asynchronous event that must be processed by the task, such as an interrupt, a timer callback, or a request from another thread to start some process managed by the task, is handled by having a dedicated routine that turns the event into a message that is sent to the task’s queue. In this way, all asynchronous events are serialized in the task’s queue where they are handled one at a time by the task’s thread in the order in which they were received.
(4) The mutex and the data structures it protects are always encapsulated in one module. The data may be accessed in various task contexts using a mutex for protection, but access to the data and the use of the mutex are encapsulated. This gives you more control over how long the mutex is held, since all the code that holds it is in one module. It also helps prevent the data sharing bugs that arise when some piece of code somewhere in your system modifies the shared data but fails to use the mutex properly.
(5) A task’s queue is encapsulated in one module. A queue handle is never a global variable, since that would allow anybody working on any code in your system to write any miscellaneous collection of bytes into the queue. Instead, all messages are written to a task’s queue and read from the queue in just the one place.
The pattern that embodies these characteristics is shown here in Figure 12:
|Figure 12 Generic Task Code Pattern|
All of the patterns that we have discussed here apply to a broad variety of applications. To use them, you must see the characteristics in your systems that match the characteristics of the patterns.
These patterns are not exclusive of one another; sometimes a little mixing and matching is called for as you put together a system. However, we have found that thinking along the lines described here gets us a long ways towards building reliable systems that meet their deadlines and that we can code reasonably quickly.
Michael Grischy is one of thefounders of Octave Software Group,a software development consulting firm. David Simon, also a founder,has recently retired from Octave Software.
1) “Design Patterns for Tasks in Real-Time Systems,” Class ETP-241, Spring 2005
2) “Patterns and Software: Essential Concepts and Terminology,” by Brad Appletonhttp://www.cmcrossroads.com/bradapp/docs/patterns-intro.html
3) “Design Patterns: Elements of Reusable Object-Oriented Software,” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides
4) “Pattern-Oriented Software Architecture: A System of Patterns,” by Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, and Michael Stal