Title: C to C++ Migration for Embedded Systems
1C to C Migration for Embedded Systems
- A Step by Step tutorial with Pros and Cons
by Dirk Braun
2Contents
- General considerations
- What is OO? (very briefly, very quick)
- Why is OO in embedded systems a special subject?
- Tutorial Convert an example C module into a C
class in 6 simple steps
- Measurements comparison of code-size and speed
Not dealt with Inheritance and advanced OO.
3General Considerations
- What is meant by Object Orientation?
Code is in classes, Objects are instances of
classes. Analogy Alarm clock. The
generalization of the alarm clock is the
class. Real alarm clocks are instances of the
idea (the class). These would be called the
objects or class instances. Uses for
SW-projects Code for all objects of the same
class can be identical and exist only
once. Benefit save code size from the second
instance The instances differ by their states.
I.e. Each object has its own private copy of
memory that completely describes its
state. Benefit object instances are
independant of each other objects can be
created at run time
4General Considerations
- Why is OO in embedded systems a special subject?
- Because assumption about available memory differ.
Embedded Reality
OO
Dynamic object- creation at runtime (usually)
- Limited memory
- Coding standards
- Because many developers havent learned it during
their professional training. OO trained from mid
90s, many developers learned electronics rather
than computing science. Combined studies emerging
now.
5General Considerations
Why is OO linked to dynamic memory allocation?
Compile Time
Run-Time
Object 1 with var
Class Code Data Structure
Object 2 with var
Object 3 with var
- Dynamic memory allocation has to be treated with
care because - We cannot easily test if the physically available
memory will survive the worst case. - Memory can get fragmented and thereby seem to get
used up. - Programmed Memory holes are not very easy to
detect and may appear only after the system has
been running for days or weeks.
- When using object oriented programming
- Each object has its own variable (of class
type). - When the number of required objects is unknown
at compile-time, the objects have to be
instantiated at run-time and allocated
dyamically. - With limited RAM I avoid permament dynamic
object creation (i.e. Dynamic memory allocation)
at run-time but I think its OK during startup or
reconfiguration.
6Tutorial Convert a C-Timer Module to C in 6
simple steps
- 0. Get to know the initial C-Project
- Switch from C to C compiler (no change of
source code) - A simple class with Constructor and
Initialization functions. What is the
this-pointer? How to do static object memory
allocation. - Convert all functions (except the ISR) to class
member functions. - Convert the Interrupt-Service-Function to a class
member-function. - Turn all global module-variables into protected
member variables. - Create multiple class-instances that represent
separate HW-entities.
7Step 0 The initial C-Project
- Standard on Chip HW-Timer
- Provides SW-Timers for other modules
- Wake up at intervals, count ticks, check if any
SW-timer needs being called. - Similar to the work of a cyclic task scheduler
- Additional features time base readjusts itself
automatically to the greatest possible
granularity to satisfy all SW timer cycles.
8Working-Principle of Timer Module
App Module A
Timer Module
InitA
1
Timer Registration
TimerCallbackA
a
HW-Timer Event
Timer ISR works like a scheduler
x
2
App Module B
InitB
b
TimerCallbackB
- SW-Timers register their on cyclic functions (1
2) - Timer-Interrupts get counted (x)
- SW-Timer-Callbacks get called at the right times
(a b)
9Use debug the Timer Demo Project
1. Initialize Register SW-Timer-callbacks
2. Set breakpoints inside the callbacks.
int main (void) ... TimerInit() // erster
TestTimer TimerCreate(25, On1stTimer3)
TimerCreate(30, On2ndTimer3) TimerCreate(35,
On3rdTimer3) ... while (TRUE) / Loop
forever /
3. Observe the times at which the breakpoints are
approached (watch the timing repoted by the
simulator).
10Step 1 Switch from C to C compiler
- Rename timer.c to timer.cpp. This causes the
µVision IDE to use the c compiler. - Errors show stricter type checking -gt do type
casts - New linker errors Undefined symbol TimerCreate
- The reason for this is name decoration that is
used in C. Further examination on next page.
11Decorated Names
- Searching object files reveals hello.o wants
TimerCreate but timer.o exports
- _Z11TimerCreatejPFvvE
- _Z11TimerDeleteiPFvvE
- _Z15TimerIntDisablev
- _Z11TimerTx_ISRv
- _Z10TimerStartv
- _Z9TimerInitv
- int TimerCreate(WORD,TimerCallbackPtr)
- BOOL TimerDelete(int, TimerCallbackPtr)
- void TimerIntDisable(void)
- void TimerTx_ISR (void) __irq
- void TimerStart(void)
- void TimerInit(void)
Reason for name decoration C permits identical
function names that differ only by their
parameter lists. Name decoration codes the
parameter lists into the exported function
names. Different compilers very like decorate in
different ways. So using a C library compiled
wih a different compiler very likely wont
work. -gt To us this means all code module that
use C functions have to be compiled with a C
Compiler, too. -gt Rename hello.c to hello.cpp
12Call C-functions from CPP works
- Now the linker reports undefinded symbols
ienable and idisable. These functions are
implemented in a module (utilities) that is still
compiled by the C compiler (and shall remain so). - The C compiler assumed these are also C
functions and hence tells the linker to look for
decorated function names. We have to tell the C
compiler that these are C functions by use of
extern C.
At the beginning and end of utilities.h insert
the statements shown on the right. These
conditionals ensure that the same file can freely
be included by C and C modules.
ifdef __cplusplus extern "C" endif ... ifdef
__cplusplus // extern "C" endif
Finally the project should compile. Debug and
check that it still works.
13Step 2 Create a simple class classes,
code-reuse, object allocation
From Step 2 to step 6 use mixture of C and C
using only a single instance (object) of the new
timer class. Add a very simple class to the
class.
14Step 2 Create a simple class classes,
code-reuse, object allocation
1. Add a class declaration to the header
class Timer protected public Timer(BYTE
timerNo) void Init()
void TimerInit() becomes TimerTimer(BYTE
timerNo) Add new (empty function) function void
TimerInit()
2. Change implementation in timer.cpp The static
initialization creates the timer before main gets
called. The intialization (for static object
instantiation) is split into two parts. The
constructor that gets called automatically and
the self-made Init-function.
Timer myTimer Timer(0)
3. Declare a timer object statically in hello.c
(similar to a global variable). To avoid dynamic
memory allocation I used a static declaration.
Space for the class gets reserved at compile time.
4. Compile and check for errors.
15Step 3 Convert all Functions to class methods
(except ISRs)
- class Timer
-
- protected
- WORD Gcd(WORD a, WORD b)
- void IntEnable(void)
- void IntDisable(void)
- void Start()
- void Stop()
- void CalculateInternals(void)
- public
- Timer(BYTE timerNo)
- void Init()
- int Create(WORD ms_interval,
- TimerCallbackPtr pCallback)
- BOOL Delete(int timerNo,
- TimerCallbackPtr pCallback)
- Turn all forward declared functions (used in .c
file only) into protected member functions
(except ISR). - Turn all the remaining publicly declared
functions (those in the header) into public
members. - Change the implementation of these functions by
prefixing the functions with Timer. - Change all calls to these functions from outside
of the class (i.e. in hello.c) to class-method
calls. - Compile and check for errors.
void TimerIntEnable(void) becomes void
TimerIntEnable(void)
TimerCreate(25, On1stTimer3)
becomes myTimer.Create(25, On1stTimer3)
16Step 3 Convert all Functions to class methods
(except ISRs)
Detecting the this pointer
- We have to get a better understanding of what
happens in the background. - Set a breakpoint at the first call to
myTimer.Create and let the debugger run up to
there. - Open the disassembly view.
- See how 3 parameters (R0, R1 R2) in the call to
TimerCreate(). - Close the disassembly view and step into the
function. - Add two variables myTimer (the address of a
global variable) and this to the watch window.
The addresses conicide (also with the content of
R0 used for parameter passing into the
function).
17Step 3 Convert all Functions to class methods
(except ISRs)
The purpose of the this pointer
As mentioned earlier OO is about code reuse. Code
exists only once, but we can have many instances
of objects. Every method call gets a hidden
first parameter a pointer to the object the
method shall be applied to. The object then is a
variable containing the objects state. In our
case at this stage of conversion the class
does not have any properties (i.e. variables)
yet, and hence this points to en empty
structure. This knowledge is important in order
to understand the next step.
18Step 4 Turn the ISR into a class member
We just learned that every class method receives
a hidden this pointer. So if we want to change
the Interrupt Service Routine (ISR) into a class
method were going to run into trouble. An ISR is
just an interrupt vector. The interrupt-controller
wont be kind enough to provide a suitable this
pointer. In C a method can be static. This
simply means that the function doesnt receive a
this pointer and will not be able to know which
object its working on. For now were dealing
with one object only, so we should get along.
Lets just convert it and see how far we get.
19Step 4 Turn the ISR into a class member
- Remove the forward declaration of the ISR in
timer.cpp - In timer.h declare
- static void Tx_ISR (void) __irq
- in the protected section of the class
declaration. - 3. Change the function name in the .cpp file
accordingly - void TimerTx_ISR (void) __irq
- Then adjust the interrupt vector to point to the
new function - SET_VIC_VECT_ADDR(TIMER_ILVL, Tx_ISR)
- in the constructor.
- 5. Compiler, Debug, Check.
- Dont worry if you do not quite understand why
this works. Step 5 makes it clear.
20Step 5 The objects get a state global variable
become protected members
- Cut paste all globally declared variables in
timer.c to the protected section in timer.h. - Try to compile.
- Get loads of errors, but all from inside the ISR
!? - In the previous step the ISR refered to global
variables. These have just been moved into the
class (or in C-talk into a structure). All the
other class functions reference into this
structure by use of the hidden this pointer. The
ISR doesnt have one.
21Step 5 The objects get a state global variable
become protected members
Idea how to provide a this pointer for the
static ISR
- Timer pTimer0 // somehow globally stored
- void TimerT0_ISR (void) __irq
-
- pTimer0-gtTx_ISR()
-
- void TimerTx_ISR (void)
- ... // as before
The ISR uses a global pointer to call into a
method of a real object instance. This method
contains the original ISR code.
22Step 5 The objects get a state global variable
become protected members
Use a global class-pointer to call back into the
class
- Declare a global pointer variable pTimer0 of
type Timer - In the constructor save the this pointer to the
global pointer. - Create a new protected method (Tx_ISR) that does
what the ISR did so far. - From the real static ISR call the new Tx_Isr with
the new global class-pointer. - Compile, debug and check for errors.
- Set a breakpoint in T0_ISR and see how the code
calls back into the class.
- Timer pTimer0
- TimerTimer(BYTE timerNo)
-
- ...
- switch (timerNo)
-
- case 0
- pTimer0 this
- break
- default
- // todo show error
- break
-
- ...
-
- void TimerT0_ISR (void) __irq
-
- pTimer0-gtTx_ISR()
-
23Step 6 - Multiple Object Instances
- An ordinary class would be finished now and could
be instantiated many times. - This class is special because it is linked to
HW-resources, namely Timer-Peripherals. As a
consequence each object instance needs its own - set of pointers to registers
- Interrupt Service Routine
protected ... WORD m_timerChannel
volatile unsigned long m_pTxMR0 volatile
unsigned long m_pTxTCR volatile unsigned
long m_pTxIR ... static void T1_ISR (void)
__irq
- Create pointer vars to timer registers
- Create a second ISR for Timer 1
24Step 6 - Multiple Object Instances
TimerTimer(BYTE timerNo) ... switch
(timerNo) case 0 pTimer0 this
T0MCR 3 // Interrupt and Reset on MR0
T0TCR 0 // Timer0 Enable m_pTxMR0
T0MR0 // set pointers to SFRs m_pTxTCR
T0TCR m_pTxIR T0IR m_timerChannel
TIMER0_CHANNEL SET_VIC_VECT_ADDR(TIMER0_ILVL,
T0_ISR) SET_VIC_VECT_CNTL(TIMER0_ILVL,
m_timerChannel) break case 1 pTimer1
this T1MCR 3 // Interrupt and Reset on
MR1 T1TCR 0 // Timer1 Enable m_pTxMR0
T1MR0 // set pointers to SFRs m_pTxTCR
T1TCR m_pTxIR T1IR m_timerChannel
TIMER1_CHANNEL SET_VIC_VECT_ADDR(TIMER1_ILVL,
T1_ISR) SET_VIC_VECT_CNTL(TIMER1_ILVL,
m_timerChannel) break ...
- In timer.cpp create global pointer to timer 1.
- Store global object pointer to timer 1 in
constructor. - Store register pointers in constructor.
Timer pTimer1
25Step 6 - Multiple Object Instances
- Access registers via these pointers. (Changes
shown for one example) - Create ISR for Timer1 peripheral.
- Instantiate use the second HW-timer object in
hello.c (note no of HW-Timer passed in
constructor) - Compile and check for errors.
void TimerStop() m_pTxTCR 0 // Timer X
Disable void TimerT1_ISR (void) __irq
pTimer1-gtTx_ISR()
Timer myOtherTimer Timer(1) // static
allocation of timer object int main (void)
... myOtherTimer.Init() ... myOtherTimer.
Create(35, On3rdTimer3) // cycle time 35
ms ...
26Measurements
Comparison of two systems A code of step 0
(C-only) B code of step 0 duplicated
(C-only) C equal to step 6 (OO)
- Interpreting measurements
- Code size
- C much less than B. Expected.
- C slightly more than A. May be assigned to added
indirection and storing of pointers etc. - RAM
- B and C very similar. Expected.
- Performance
- C greater than A and B. Expected. Attributed to
indirection and additional function call.
27Summary
- Pros
- Smaller Code for more than one object
- Automatic code maintenance as code exists only
once.
- Cons
- Small performance overhead
- Automatic code maintenance as code exists only
once.
- Uses
- Bus couplers
- Time synchronous machines e.g. multi axis
control
Contact Dirk Braun Email dbraun_at_cleversoftware.de
28Thanksfor yourattention!