Introductory guide to software development for reliable embedded systems
We receive many enquiries from people who are looking for information about the development of software for reliable embedded systems.
Some of these enquiries come from people who are about to join our TTa or TTb training courses, from professional programmers who are about to develop their first safety-related embedded system, and from staff and students at universities and colleges around the world.
In response, we’ve created this ‘5-step guide’ for people who want to learn how to program reliable, real-time embedded systems.
Starting from scratch, the material presented here proceeds to a level at which we discuss the development of safety-related embedded systems (up to ‘SIL 2’ level and equivalent).
The page includes links to two free books (PDF format) and to our free introductory training course on “Embedded C”, which is distributed via YouTube®.[This page was last updated: 2017-06-09]
Four categories of modern embedded system
Four different categories of software development for modern embedded systems can be identified:
- Completely ‘bare metal’ designs, that usually involve a number of interrupt services routines and a main program loop. Typically used in introductory textbooks and code examples. Usually used in practice only for very small / simple products.
- Systems based on a conventional ‘RTOS’. Sometimes treated as the ‘default design solution’ and (in our experience) often used inappropriately (e.g. when a Task Scheduler would be more appropriate – see below).
- Systems based on a larger conventional OS, such as Linux.
- Systems based on a ‘Task Scheduler’. Highly deterministic. Easy to model and monitor. Used for a wide range of products (from small to very large). Often used in safety-related designs.
Our focus in this guide will be on designs based on a Task Scheduler: these form an appropriate foundation for many safe and reliable systems in a wide range of sectors.
If you want to obtain a more general understanding of the techniques used to develop other forms of embedded system, we suggest you start with some Google (or equivalent) searches for ‘bare-metal embedded system’, ‘RTOS guide’, and ’embedded Linux’.
1. Learn how to program in C for a “desktop” computer
Let’s start at the beginning.
Before you can start to develop software for embedded systems, you need to learn how to program.
While it is not impossible to begin programming using an embedded board, most people find it much easier to learn how to program on a desktop computer before they start to deal with the complexities of cross-compilation, debugging over a JTAG link, etc.
We therefore recommend that you learn how to program a desktop computer, in C, before you start trying to program an embedded processor.
To explain why we recommend learning C, we make the following observations:
- Computers (such as microcontroller, microprocessor or DSP chips) only accept instructions in “machine code” (“object code”).
- Machine code is, by definition, in the language of the computer, rather than that of the programmer. Interpretation of the code by the programmer is difficult and error prone.
- All software, whether in assembly, C, C++, Java or Ada must ultimately be translated into machine code in order to be executed by the computer.
- There is no point in creating ‘perfect’ source code, if we then make use of a poor translator program (such as an assembler or compiler) and thereby generate executable code that does not operate as we intended.
- When compared to “desktop” processors, embedded processors tend to have limited processor power and very limited memory available: the language used must be efficient.
- To program embedded systems, we need low-level access to the hardware: this means, at least, being able to read from and write to particular memory locations (using ‘pointers’ or an equivalent mechanism).
Of course, not all of the issues involved in language selection are purely technical:
- No software company remains in business for very long if it generates new code, from scratch, for every project. The language used must support the creation of flexible libraries, making it easy to re-use (well-tested) code components in a range of projects. It must also be possible to adapt complete code systems to work with a new or updated processor with minimal difficulty.
- Staff members change and existing personnel have limited memory spans. At the same time, systems evolve and processors are updated. Many embedded systems have a long lifespan. During this time, their code will often have to be maintained. Good code must therefore be easy to understand now, and in five years time (and not just by those who first wrote it).
- The language chosen should be in common use. This will ensure that you can continue to recruit experienced developers who have knowledge of the language. It will also mean that your existing developers will have access to sources of information (such as books, training courses, WWW sites) which give examples of good design and programming practice.
Even this short list immediately raises the paradox of programming language selection. From one point of view, only machine code is safe, since every other language involves a translator, and any code you create is only as safe as the code written by the manufacturers of the translator. On the other hand, real code needs to be maintained and re-used in new projects, possibly on different hardware: few people would argue that machine code is easy to understand, debug or to port.
Inevitably, therefore, we need to make compromises; there is no perfect solution. All we can really say is that we require a language that is efficient, high-level, gives low-level access to hardware, and is well defined. In addition – of course – the language must be available for the platforms we wish to use. Against all of these points, C scores well.
We can summarise C’s features as follows:
- It is a ‘mid-level’ language, with ‘high-level’ features (such as support for functions and modules), and ‘low-level’ features (such as good access to hardware via pointers);
- It is very efficient;
- It is popular and well understood;
- Even desktop developers who have used only Java or C++ can soon understand C syntax;
- Good, well-proven compilers are available for every embedded processor (8-bit to 32-bit or more);
- Experienced staff are available;
- Books, training courses, code samples and WWW sites discussing the use of the language are all widely available.
Overall, C’s strengths for embedded system development greatly outweigh its weakness. It may not be an ideal language for developing embedded systems, but is unlikely that a perfect language will ever be created.
“Can you recommend a book on ‘desktop C’?”
We’ve found “C in a Nutshell” by Peter Prinz and Tony Crawford to be a useful guide to “desktop C”.
2. Learn the basics of “embedded C”
When you are familiar with desktop C, we suggest that you begin to explore embedded systems using a simple processor. For example, the 8051 microcontroller is a popular “starter” processor which is still used in many “real” systems.
If you decide to start with the 8051 microcontroller, “Embedded C” provides a gentle introduction to the programming of embedded systems (using 8051 microcontrollers).
Please note that Embedded C is a self-contained “teach yourself” program: the package includes both a compiler and processor simulator (on CD), which means that you can run all of the examples in the book (and learn a great deal about the programming of embedded systems in C) without buying — or building — any hardware.
Further information is available.
Code examples from “Embedded C”
Copies of the code examples from Embedded C are available for download here (zip file).
Course notes for “Embedded C”
A set of course notes based on “Embedded C” are available here.
These notes are intended for use in a 10-week introductory module on embedded systems.
Teaching slides for “Embedded C”
If you wish to use “Embedded C” in a university or college course, a complete set of Powerpoint slides (10 files, zipped) is available. These would be suitable for use in an introductory course on embedded systems. These files were created by Dr. Jimmy To, a faculty member at the Hong Kong Polytechnic University.
3. Download and read ‘PTTES’
Building on the foundations presented in “Embedded C”, “Patterns for Time-Triggered Embedded Systems” is a 1000-page book which illustrates how you can create a range of reliable embedded systems.
In total, PTTES contains details of more than 70 useful “design patterns”, complete with guidelines to help you apply these techniques in your own projects: full source code for all of the patterns is also included.
Following an agreement with the original publishers and the author, PTTES is now distributed by SafeTTy Systems Ltd.
You can now download the complete book and code examples from our PTTES page.
4. View some of our training videos on YouTube®
You can view a series of four lectures (in total around 7 hours of training) from our TTa training course via our YouTube® channel.
Session 1: An introduction to “Embedded C”
The material in Session 1 provides a foundation for the remainder of the course. We discuss what is meant by the phrases “embedded system” and “deeply embedded system”. We discuss the history of current embedded processors. We introduce the 8051 microcontroller (our hardware target in Session 1 and Session 2). We explain why ‘C’ is the programming language used to develop most embedded systems. We talk about the differences between developing code for “desktop” and “embedded” processors, including key differences in the software architectures used. We discuss task-oriented software engineering. We consider what is meant by real-time embedded systems. We discuss the creation of loop delays and contrast these with hardware delays. We discuss “sandwich” delays. We discuss the need for (and implementation of) timeout mechanisms. We introduce the concept of “balanced code”.
Session 2: Real-time “Embedded C”
In Session 2, we explore real-time constraints in more detail. We consider and discuss concepts such as worst-case execution time (WCET), best-case execution time (BCET), task jitter and CPU loading. We look at interrupt handling and interrupt response time. We introduce time-triggered (TT) system architectures. We introduce – and explore the operation of – a first simple “TT embedded operating system”. We look at the concept of system state and explore ways of implementing state-transition diagrams (“statecharts”).
Session 3: Working with modern hardware platforms
In the first two sessions on this course, we focus on software development, and we work with a simple processor (8051) simulator to test the resulting code. In Session 3, we move into the 21st century. We begin by considering a modern (32-bit) ARM® microcontroller and provide an introduction to some of the challenges and opportunities offered by this new platform. We then discuss key debugging techniques (including the use of JTAG) and the issue of timing analysis. To ensure that your skills are transferrable, we consider other hardware targets, starting with a “Field Programmable Gate Array” (FPGA) platform. FPGAs are already a popular implementation platform for developers of embedded systems (and some people believe that they will be the platform of choice for the majority of new systems within the next few years). We consider “soft” processor running on FPGAs. We also consider x86 (“embedded PC”) hardware. Finally, we introduce a more advanced TT software architecture that can be employed with any of these platforms.
Session 4: Multi-tasking systems
In Session 4, we explore some of the challenges involved in designing and implementing systems in which more than one task must run simultaneously. We look first at ways in which we can approximate multi-tasking systems using a single CPU and a real-time operating system (RTOS) which supports task pre-emption. Our coverage includes a discussion about the challenges of priority inversion (PI), and consideration of some practical solutions to PI problems through the use of “time triggered hybrid” (TTH) and similar system architectures. We then move on to consider some of the challenges involved in creating distributed embedded systems: that is, systems involving multiple processors connected in some form of network using (for example) a bus or star topology. We consider key design issues, including ways in which we can synchronise the timing of tasks that are running on different nodes. Our focus is on systems that employ the popular Controller Area Network (CAN) protocol.
You can find further information about course TTa here.
5. Learn a little more about time-triggered architectures
If you want to create reliable embedded systems, then it pays to learn a little more about time-triggered software (and system) architectures.
Time-triggered (TT) architectures have been used for many years in industries such as aerospace and defence, because they have been found to provide significant benefits (including high system reliability and greatly reduced testing costs). Until recently, use of TT architectures has been less common outside these sectors — but this situation is now changing rapidly, as developers in many different organisations experience the benefits of a time-triggered solution.
We provide an introduction to TT architectures on this page, and make some suggestions for ways in which you can determine whether a TT approach may offer benefits for your own organisation.