A six step process for migrating embedded C into a C++ object-oriented framework
This article discusses one role that object oriented programming can play in embedded systems while maintaining requirements concerning safety and reliability that are often imposed by standards like MISRA.
I will show how good object-oriented design of embedded software can result in many benefits - smaller code and improved ease of source-code maintenance - but with only a slight trade off in terms of performance.
Using an example as the starting point (a standard timer module written in C that services timer units that are provided by nearly every MCU) I will describe a six step process by which to convert C code into object oriented C++ code classes.
Several steps in this process will illustrate a safe path for migrating this source code into an object oriented class. An instance of this class then is a HW- Timer-Peripheral with a much nicer SW-Interface than registers and interrupts. Then it is a matter of simply instantiating the timer class once per HW-Timer available on the particular MCU.
This process can be applied to any situation where more than a single instance of one type of HW-peripheral is to be used in an embedded project. Measurements of performance and code size are provided and topics like OO-in embedded systems and the improved SW-Architecture will be discussed more generally.
The examples we will work through use two timers on an ARM7. They can be completely worked through without any real hardware, using Keil's ARM7 simulator and the Keil Realview Compiler only. Because many other MCUs have at least 2 on-chip-timers these examples can be ported to other platforms, too.
Starting point: the standard
Timer-Module in C
A hardware-timer is a counter/comparator with reset- and interrupt-logic. This timer "wakes up" an interrupt service routine at regular programmed intervals. But many embedded applications need more timers than HW-timers that are available on a given MCU.
We can duplicate the timer, by e.g. letting the HW-timer interrupt at 1 ms intervals. The ISR can then call function A every 3 ms and call another function B every 5 ms. This way we have used one HW-timer to realize several SW-timers, each with it's own time base. This method is fairly common practice.
For reasons out of the scope of this article the project's various timer-functions are registered with the timer-module and will then be called back when their time has come. The timer module (Figure 1 below) is based on a single HW-timer and each registered timer-callback-function has its own time interval. This is very similar to how a cyclic-task-scheduler works.
|Figure 1. Module and process-flow overview. Registration of timer callbacks (1 & 2) and their cyclic execution (x, a and b)|
The time base of the timer module depends on the cycle-times requested by the various application modules. If app. A wants 30 ms and B wants 50 ms, then the HW-timer can realize this using an overall time base of 10 ms. If A wants 9 and B wants 3 ms, then the main time base needs to interrupt every 3 ms.
Raising the interrupt no more often than absolutely necessary saves processor performance. This main-time-base is automatically adjusted (by reprogramming the counter register) when timer-callbacks are registered by means of a "greatest common divider" function.
Listing 1 above and Listing 2 below show excerpts of the interface and implementation which realize this concept of a timer module.
The result is a module that represents a single HW-timer and provides many SW-timers.
Let us now return to the main topic and start converting it into a class. Afterwards we will be able the instantiate a class for each HW-timer, where each provides many SW-timers.
|Listing 2. Implementation of timer module|