28
C to C++ Migration for Embedded Systems A Step by Step tutorial with Pro’s and Con’s by Dirk Braun

C to C++ Migration for Embedded Systems A Step by Step tutorial with Pro’s and Con’s by Dirk Braun

Embed Size (px)

Citation preview

C to C++ Migration for Embedded Systems

A Step by Step tutorial with Pro’s and Con’s

by Dirk Braun

Contents

2. Tutorial: Convert an example C module into a C++ class in 6 simple steps

1. General considerations• What is OO? (very briefly, very quick)• Why is OO in embedded systems a special subject?

3. Measurements & comparison of code-size and speed

Not dealt with: Inheritance and advanced OO.

General 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

General Considerations

Why is OO in embedded systems a special subject?

Dynamic object- creation atruntime (usually)

• Limited memory• Coding standards

• Because assumption about available memory differ.

• Because many developers haven’t learned it during their professional training. OO trained from mid 90s, many developers learned electronics rather than computing science. Combined studies emerging now.

OOEmbedded Reality

Class:Code + Data Structure

Object 1 with var

Object 2 with var

Object 3 with var

Compile Time Run-Time

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 it‘s OK during startup or reconfiguration.

General Considerations

Why is OO linked to dynamic memory allocation?

Dynamic memory allocation has to be treated with care because:1. We cannot easily test if the physically available memory will survive the worst case.2. Memory can get fragmented and thereby seem to get used up.3. Programmed Memory holes are not very easy to detect and may appear only after

the system has been running for days or weeks.

Tutorial: Convert a C-Timer Module to C++ in 6 simple steps

0. Get to know the initial C-Project1. Switch from C to C++ compiler (no change of

source code) 2. A simple class with Constructor and Initialization

functions. What is the “this-pointer”? How to do static object memory allocation.

3. Convert all functions (except the ISR) to class member functions.

4. Convert the Interrupt-Service-Function to a class member-function.

5. Turn all global module-variables into protected member variables.

6. Create multiple class-instances that represent separate HW-entities.

Step 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.

Working-Principle of Timer ModuleApp Module A

Timer Module

HW-Timer Event Timer ISRworks like a scheduler

TimerCallbackA

Timer Registration

InitA1

App Module B

TimerCallbackB

InitB

2

x

a

b

• 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)

Use & debug the Timer Demo Project

int main (void) {

...

TimerInit();

// erster TestTimer

TimerCreate(25, On1stTimer3);

TimerCreate(30, On2ndTimer3);

TimerCreate(35, On3rdTimer3);

...

while (TRUE)

{ /* Loop forever */

;

}

}

1. Initialize & Register SW-Timer-callbacks

2. Set breakpoints inside the callbacks.

3. Observe the times at which the breakpoints are approached (watch the timing repoted by the simulator).

Step 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 -> 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.

Decorated NamesSearching 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 won’t work.

-> To us this means: all code module that use C++ functions have to be compiled with a C++ Compiler, too. -> Rename “hello.c” to “hello.cpp”

Call C-functions from CPP worksNow 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.

Step 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.

Step 2 Create a simple class – classes, code-reuse, object allocation

void TimerInit()becomesTimer::Timer(BYTE timerNo)

Add new (empty function) functionvoid Timer::Init()

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.

class Timer{protected:public:

Timer(BYTE timerNo);void Init();

};

1. Add a class declaration to the header

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.

Step 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);

};

1. Turn all forward declared functions (used in .c file only) into protected member functions (except ISR).

2. Turn all the remaining publicly declared functions (those in the header) into public members.

3. Change the implementation of these functions by prefixing the functions with „Timer::“.

4. Change all calls to these functions from outside of the class (i.e. in hello.c) to class-method calls.

5. Compile and check for errors.

void TimerIntEnable(void)

becomesvoid Timer::IntEnable(void)

TimerCreate(25, On1stTimer3);

becomesmyTimer.Create(25, On1stTimer3);

Step 3 Convert all Functions to class methods (except ISRs)

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 Timer::Create().• 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).

Detecting the this pointer

Step 3 Convert all Functions to class methods (except ISRs)

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.

The purpose of the „this“ pointer

Step 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 we‘re going to run into trouble. An ISR is just an interrupt vector. The interrupt-controller won‘t be kind enough to provide a suitable this pointer.

In C++ a method can be „static“. This simply means that the function doesn‘t receive a „this“ pointer and will not be able to know which object it‘s working on.

For now we‘re dealing with one object only, so we should get along. Let‘s just convert it and see how far we get.

Step 4 Turn the ISR into a class member

1. Remove the forward declaration of the ISR in timer.cpp2. 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 accordinglyvoid Timer::Tx_ISR (void) __irq

4. Then adjust the interrupt vector to point to the new functionSET_VIC_VECT_ADDR(TIMER_ILVL, Tx_ISR)

in the constructor.5. Compiler, Debug, Check.

Don‘t worry if you do not quite understand why this works. Step 5 makes it clear.

Step 5 The objects get a state – global variable become protected members

1. Cut & paste all globally declared variables in timer.c to the protected section in timer.h.

2. Try to compile.3. 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 doesn‘t have one.

Step 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 Timer::T0_ISR (void) __irq

{

pTimer0->Tx_ISR();

}

void Timer::Tx_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.

Timer* pTimer0;

Timer::Timer(BYTE timerNo)

{

...

switch (timerNo)

{

case 0:

pTimer0 = this;

break;

default:

// todo: show error

break;

}

...

}

void Timer::T0_ISR (void) __irq

{

pTimer0->Tx_ISR();

}

void Timer::Tx_ISR (void)

{ ... // as before

1. Declare a global pointer variable „pTimer0“ of type „Timer*“

2. In the constructor save the this pointer to the global pointer.

3. Create a new protected method (Tx_ISR) that does what the ISR did so far.

4. From the real static ISR call the new Tx_Isr with the new global class-pointer.

5. Compile, debug and check for errors.

6. Set a breakpoint in T0_ISR and see how the code calls back into the class.

Step 5 The objects get a state – global variable become protected members

Use a global class-pointer to „call back into the class“

Step 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;

1. Create pointer vars to timer registers

2. Create a second ISR for Timer 1

Step 6 - Multiple Object Instances

Timer::Timer(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;

...

3. In timer.cpp create global pointer to timer 1.

4. Store global object pointer to timer 1 in constructor.

5. Store register pointers in constructor.

Timer* pTimer1;

Step 6 - Multiple Object Instances

void Timer::Stop()

{

*m_pTxTCR = 0; // Timer X Disable

}

void Timer::T1_ISR (void) __irq

{

pTimer1->Tx_ISR();

}

6. Access registers via these pointers. (Changes shown for one example)

7. Create ISR for Timer1 peripheral.

8. Instantiate & use the second HW-timer object in hello.c (note: no of HW-Timer passed in constructor)

9. Compile and check for errors.

Timer myOtherTimer = Timer(1); // static allocation of timer object

int main (void) {

...

myOtherTimer.Init();

...

myOtherTimer.Create(35, On3rdTimer3); // cycle time = 35 ms

...

}

MeasurementsComparison of two systems:

A – code of step 0 (C-only)B – code of step 0 duplicated (C-only)C – equal to step 6 (OO)

  A B C Units

Code size

3548 5008 3772 Bytes

Data size

1544 1792 1772 Bytes

Time in ISR

7.22 6.79 9.12 µsInterpreting 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.

Summary

Pro‘s• Smaller Code for more than

one object• „Automatic“ code

maintenance as code exists only once.

Uses• Bus couplers• Time synchronous machines – e.g. multi axis control

Con‘s• Small performance overhead• „Automatic“ code

maintenance as code exists only once.

ContactDirk BraunEmail [email protected]

Thanksfor your

attention!