This series of articles is about embedded systems – specifically the software that runs in an embedded system. It is worth starting by making sure that we are all on the same page and have our terminology straight. So, what is an embedded system? When I first wrote a book on this topic – back in 1986 – the word “embedded” did not occur in the title or anywhere in the text. This was simply because the term had not yet been coined. We were very much at a loss to give a handle to the systems we worked on, using terms like “dedicated systems” or “microsystems”, none of which were very satisfactory. A few years later, the word “embedded” started to be used and was rapidly adopted by everyone in the field.
So, back to my question: what is an embedded system? Having had a lot of practice at explaining to friends and family about what I work on, I tend to say something like “any electronic device that contains a microprocessor (CPU) that would not normally be described as a computer”.
An operating system (OS) is always used on a computer; the use of an operating system of some kind on modern embedded systems is common. Although prevalent in high-end (32- and 64-bit mainly) systems, there may be benefits from using a kernel in lower power devices. Operating systems are very much the focus of this series, with lots of detail in the articles to come in future months – firstly about operating systems in general, then taking a look at a specific implementation in depth.
Why Use an Operating System?
Having established that the key topic of this series is operating systems in embedded applications, it is worth just checking why they are employed. There are various explanations, some of which have got as much to do with human nature as they have technical requirements. I am reminded of a story. In one of our offices that I used to visit, there was a kitchen where one could prepare coffee. On the door was a sign which said “Please do not close this door.” Underneath, someone had written “Why not?” To that, someone else had responded: “Cuz.” This is, of course, an abbreviation for “Because”, which, in turn, is short for “Because we are telling you to behave in this way.” This is why an OS is employed on some systems – just because that is what is done: cuz.
Another explanation comes from looking at desktop applications. If you are going to write some software for a PC or a Mac, where do you start? Having got the computer, you switch it on and it starts up in Windows/Linux or macOS and you start programming. The OS is a given and provides useful services. It is very unlikely that you would consider starting from scratch, programming “bare” hardware. So, it is not surprising that an engineer, who has some software experience but is new to embedded software, would expect the “safety blanket” of an OS on their embedded system.
It is worth noting that the key aspect of a desktop OS, that users are aware of, is the user interface (UI). Ask someone what Windows is all about and they will mention windows, menus, dialogs and icons, but are less likely to talk about file systems and inter-program communication and interoperability. This is a fundamental difference between a desktop and an embedded system: an embedded device might not even have a UI – if it does, it may be quite simplistic. This is the first of many key differences that become apparent with a little thought:
An embedded system normally runs a single software application from the moment it is switched on until it is shut down.
Embedded systems have limited resources. Memory space may be large enough, but it is unlikely to be extensible; the CPU is probably just powerful enough, but with no extra capacity to spare.
Many embedded applications are “real time”. We will consider more carefully what this means later in this article.
Embedded software development tools are quite specialized and run on a “host” computer (like a PC), not on the final (“target”) system.
The updating of embedded software in service is challenging. Although with connected devices, possibilities are beginning to emerge; in-field updates are still not the norm (compared with the regular updates and patches applied to desktop software).
As we will see, an embedded OS provides a useful programming model to the developer, which can make its use very attractive.
Before looking at the ways that an embedded software application might be structured, it is worth thinking about the paradigms used on computers to facilitate the execution of programs under the control of an OS. I will do this by considering some of the approaches used in recent years.
Firstly, there is “DOS style” program execution, where programs are executed in sequence, thus:
Here each program is started, utilized, and then terminated. We use Program 1, then Program 2, perhaps then take a break, return to use Program 3, and then come back to using Program 2 again. The second use of Program 2 is from scratch – it does not continue from where it left off earlier (unless this specific application provides that capability itself).
After DOS, life became more complex in many ways, as Windows became the norm. With “Windows style” program execution, multiple programs may be running (apparently) concurrently, thus:
Here multiple programs appear to be running at the same time, with Windows managing that illusion. First we start Program 1, then Program 2 is started as well, then Program 3. Program 2 finishes, leaving Programs 1 and 3 still running. Program 3 finishes leaving just Program 1. Later Program 2 is run again, and Program 1 terminates leaving just Program 2 running. This is quite a realistic scenario for the use of Windows by a typical user. The OS maintains the resources so that all the programs share the CPU in a reasonable way. It also facilitates easy communication between programs (the Clipboard is one example) and controls the user interface.
There are some circumstances, notably with handheld devices, that more flexibility than DOS is required, but, with limited resources, lower overhead than Windows is necessary. This can result in “iOS style” program execution, thus:
In this situation, programs are run in sequence, but the state of each one is normally saved automatically so that it can continue from where it left off. In the diagram we start off with Program 1, then this is paused to allow the use of Program 2, then perhaps the device is turned off for a while. It is restarted straight into Program 3 (Program 2’s state had been automatically saved), and then the user returns to using Program 2 continuing their work from earlier in the day. (I am aware that the iOS app execution model is rather more complex than this, but my description encapsulates the user’s primary perspective.)
Most embedded applications do not really conform to any of these models, as typically the device starts running a program at power on and continues to run only that software indefinitely. The structure of such code needs to be considered carefully.
Embedded Program Models
Desktop systems are all much the same. From an application program’s point of view, every Windows PC is identical. Embedded systems are unique – every one is different from every other one. These differences may simply be technical: the CPU choice, the amount of memory and the range of peripheral devices may all vary. The priorities of the application program may differ: execution speed, power consumption, security or reliability may be the critical factor(s). The differences may also be commercial, affecting the price: the production volume and the choice between custom or standard hardware are examples.
These differences have many implications on the embedded software developer. The choice of development tools (compilers, debuggers and the like) is strongly affected by the choice of CPU, for example. Many factors affect the choice of an OS or even the decision to employ one. The software structure – the program model – needs to be carefully reviewed for every embedded software application.
Depending upon the requirements of the application, embedded software may be structured in a number of ways, each of increasing complexity, thus:
At its simplest, an embedded application may be structured as just a loop, where it goes around repeatedly executing the same sequence of actions. If the application is simple enough to be implemented this way, it is an excellent choice – simple code is reliable and easy to understand. However, this structure is very sensitive to part of the code “hogging” the processor – i.e. some instructions taking too long to run and delaying the execution of other parts of the application. This model is also not very scalable – enhancing the code may be challenging, as the additions may impact the performance of the older code.
If something a little more sophisticated is required, the next step is to consider placing all the non-time-critical code in a main loop and locate the time-sensitive parts in interrupt service routines (ISRs). The ISRs would typically be quite short, performing only vital work and flagging part of the main loop to complete the job when it can. This is a good next step on the complexity ladder and again can be the right choice if the needs of the application are met. The challenge can come with the distribution of work between the main loop and the ISRs (and also between multiple developers).
For the ultimate in flexibility, a means is required to divide an application into a number of individual, somewhat independent programs (which we will call tasks or threads), which are run in an apparently concurrent fashion. Small ISRs are also likely to be included in the system, but will mostly serve to notify tasks or trigger action. To achieve this, an OS – or at least a kernel – is required. The application of multi-threading not only facilitates the flexible distribution of functionality within the software, but eases the apportioning of work across a development team.
What is Real Time?
Earlier I made the comment that many embedded applications are “real time”. It is also common to talk in terms of a real-time operating system (RTOS) in this context instead of simply an OS. So, now would be a good time to define our terms.
Here is one definition of a real-time system:
“A real-time system is one in which the correctness of the computations not only depends upon the logical correctness of the computation, but also upon the time at which the result is produced.
“If the timing constraints of the system are not met, system failure is said to have occurred.”
The most important characteristic of such a system is predictability – or, as it is commonly termed, determinism .
A real time system is not necessarily very fast – “real time” does not always mean “real fast”. It means that any required action will be done in a timely manner. This means fast enough, but it can also mean not too fast (i.e. slow enough).
An RTOS can (if used the right way) provide very precise control over the allocation of CPU time to tasks and, hence, render an application entirely deterministic. But there is one factor that can get in the way of this ideal: interrupts. There are some RTOS products that control interrupts entirely. Their approach is to manage the servicing of the interrupt within the task scheduling scheme. Although this should lead to very predictable behavior, the mechanism tends to be rather complex and imposes a significant overhead.
Most RTOSes simply allow ISRs to “steal” time from the task running at the moment that the interrupt occurs. This, in turn, places the onus on the programmer to keep the ISR code as short as possible. The result is a reasonable approximation of true real time. The only significant complication is catering for RTOS service calls within the ISR. Some calls are quite innocuous, whilst others will cause a context switch on return from interrupt; this situation needs to be specifically accommodated and this is done in various ways by different RTOSes.
In the next article we will start to look at how tasks are defined and how task scheduling works.
Colin Walls has over thirty years experience in the electronics industry, largely dedicated to embedded software. A frequent presenter at conferences and seminars and author of numerous technical articles and two books on embedded software, Colin is an embedded software technologist with Mentor Embedded [the Mentor Graphics Embedded Software Division], and is based in the UK. His regular blog is located at: http://blogs.mentor.com/colinwalls. He may be reached by email at .