You are on page 1of 6

Object oriented C++ programming in SIMULINK.

A reengineered simulation architecture for the control algorithm code view.


Sren Top(*), Hans Jrgen Nrgaard(**),Bo Nrregaard Jrgensen(*)
(*)Southern University of Denmark
(**)Msc. control engineer at Danfoss Drives
E-mail:top@ingsdb.sdu.dk

1. Introduction
Abstract
By the use of MATLAB tools Simulink models of control
algorithms can be translated into an equivalent C-program.
This paper treats a different approach of building control
algorithm block components in C++ for use both in Simulink
models and in application code development.
The object oriented reengineering has been applied to a
subset of the present simulation library of control algorithms at
Danfoss Drives, one of the worlds largest producers of
frequency converters. A frequency converter is an electronic
device controlling the rotational speed of an electrical AC
motor. By the use of object orientation, the chosen subset of
control algorithms was transformed from a collection of Cfunctions into a collection of C++ components. Thus by
confining control algorithm state variables and functions to
objects and Simulink specific code to Simulink S-function
wrapper files an entangling of the code has been achieved. Thus
control algorithm clarity has improved.
By utilizing advanced features in C++ the same control code
text could be simulated using a fixed-point calculation library
and a library of calculations with physical units (Volt, Ampere,
Ohm, Radians etc), where improper use of physical units are
caught on compile-time. Furthermore calculations can be
monitored on runtime for overflow, underflow and the degree of
precision when dealing with fixed-point calculations.
As control algorithm blocks are represented as C++ objects,
inter connected control algorithm blocks can be synthesized in a
new class, which in turn becomes a building block of a higher
abstraction level. Such high level building blocks can be handed
out to be used in customers simulations without revealing
Danfoss Drives business secrets, because the blocks are
compiled code comprised in a DLL component (Dynamic
Linked Library component).
The software development process benefits from improved
interfaces between control engineers and software engineers,
faster and shorter development cycles and earlier testing.
Maintenance of the control algorithms becomes easy because
control algorithm code irrelevant details are factored out and
handled safely in other parts of the architecture.

Many embedded systems deals with some sort of


regulation of a physical system. So eventually the start of
application development is often control engineering. In
the realm of control engineering simulation tool plays an
increasingly dominant role in the development of a
regulation system. Simulink also offer code generation
facilities. In our industrial case and in many other big
scale embedded applications, where the regulation code
constitutes a minor part of the overall embedded program
code, an integration of control engineering into the
normal software and hardware engineering is a preferable
approach, we believe. In our industrial setting control
algorithm code part is approximately 20%.
Simulink offers inclusion of legacy code components in
simulations. In this way algebraic equations can be
confined to a so Simulink S-function to be used in the
drawing of a design. This is the approach taken at
Danfoss Drives [1] and in the following sections a
reengineered simulation architecture for the control
algorithm code will be presented.
The outline is as follows: Section 2 gives a short
introduction to the problem domain at Danfoss Drives.
Section 3 explains the future vision. Section 4 describes
the application of strategies. Section 5 treats the impact
on the maintenance and development process. Section 6
gives some conclusions.

2. The
Domain

Frequency

Converter

Problem

In this section we elaborate on the problems identified


in section 1.1.
A frequency converter is a device used to control shaft
speed or torque of a three-phase induction motor to match
the need of a given application. A frequency converter is
often called a drive. Typical applications are speed
control of conveyer belts, torque control of extruders or
control of a fan as shown on Figure 1.

represent substantial know-how of Danfoss Drives, this


vision is of big importance.

4. Strategies Applied

Figure 1 A frequency converter used for controlling


the speed of a fan
Modern frequency converters contain digital signal
processors (DSPs) for executing the control algorithms
that maintain the desired motor speed.
These algorithms are developed and tested by
simulation. A library of simulation components is created
to facilitate the simulation of different motor control
algorithms. The top-level components are divided into
groups of motor models, control models, and load
models. The lower level models components implement
different aspects of the top level components such as the
electrical and mechanical parts of an induction motor.
The models are created in Matlab/Simulink using
Simulink blocks, Matlab scripts and coded in the C
programming language. Simulink is a graphical
simulation tool build on top of a Matlab calculation
engine.
The simulation models are constructed in the following
way: A model comprises
models or basic building
blocks defined as Simulink S-functions. A S-function is
user written code (in this case written in C or m-scripts
(matlabs own interpreted language)) put in a wrapper file
template, which in turn is compiled into a windows DLL
component and used by Simulink in performing
simulations.

3. The Vision.
The vision is to retain the same control algorithm source
code through out the entire development process. This
source code should be invariant to changes in surrounding
tools and environments and even invariant to change of
object-oriented language. The same control algorithm
code should be present in both the simulation
environment and in the runtime environment. The
necessary adjustments should not afflict the original
control algorithm code, but should be done in other parts
of the resulting program. This could be termed as white
box compliance as opposed to the normal black box
compliance assured by testing. As the control algorithms

The first strategy was introduction of object orientation.


The strategies of the code file sandwich hen follows. It
comprises 3 levels (not layers) of files. Subject
orientation as the upper part of the code sandwich,
algorithm control block code in the middle and wrapper
techniques as the lower part. Use of the code file
sandwich strategy is depicted in the figure 2 below. The
last strategy introduced is synthesis of interconnected
basic blocks into bigger building blocks

Setup.h Variation point types are defined In this include file.


-------------------------------------------------------------------------------------------------------------------------------------Volt , Ampere, Ohm = unit types or just floats types ? Numbers = fix point numbers or floats ?

RL.h
#includes Setup.h
-------------------------------------Limiter function

Uf.h
#includes Setup.h
----------------------------------------------UF Controller

wrapper.cpp This file adapts the control algorithm to a certain environment.


-------------------------------------------------------------------------------------------------------------------------------------#includes RL.h
#includes UF.h

Figure 2 The sandwich code file structure

4.1. Object orientation and decoupling from


simulation tool
In section 2 the old simulation code architecture was
described. Simulink when dealing with legacy code act
as a framework based on the Hollywood principle do
not call us we call you. This means that pieces of legacy
code placed in DLLs are dynamically linked into a
simulation model and called during simulation by the
Simulink framework. The functions in the Simulink C
legacy code wrapper file is call back functions used by
Simulink during simulation. Some functions are used to
initialize state variables and parameter variables. Some
are input functions used for updating state variables in a
simulation step. Others are output functions used for
calculating outputs. And finally an activate function and a
destructor function is also defined. Several other callback
functions are available, but are not used in this context.
Instead of writing the blocks of the control algorithm in
the above-mentioned call back functions, we build an
object comprising the control algorithm block
functionality and the control state variables. Then the call
back functions will call the methods in this objects
interface. In this way the control algorithm code is

factored out, and what is left behind in the Simulink Sfunction wrapper file is Simulink specific code.

before

control specific code

after

Figure 3 Out factoring

simulink wrapper code

Example: Listing 1 (at the end of the article) shows an


example of such out-factored control code and listing 2 is
an excerpt from the according S-function wrapper file
showing how the output function is called.
This gives the following immediate benefits:
-Simulation tool independence:
All the major simulation tools in the market are capable
of handling C++ legacy code. Thus if all simulation
blocks were implemented as C++ components, then these
components could be utilized in different simulation
tools. What should be done in this transformation is
writing specific component wrappers for each simulation
tool.
- Control algorithm clarity.
Now the control algorithm blocks are readable to control
engineers without experience in programming Simulink
S-functions. Both the calculation formulas and the
relevant control states for a control code algorithm
building block are gathered in a class. As a rough
estimate only 1/5 of the Simulink C S-function source
code lines originated directly from the control code
algorithm - the rest being Simulink specific code.
- Division of labor
When Simulink specific code has been factored out,
division of labor amongst control engineers will be
facilitated, not all of them will have to know all about
Simulink S-functions. Division of labor between control
engineers and software engineers is improved as well,
because these C++ components are readable for both
parties.

4.2. Subject orientation the upper part of


the code file sandwich
This strategy is about moving architectural variation
points to a namespace, where meaning of named entities
(such as types) can be defined in accordance with a

particular subject. This is a very simple example of


subject oriented programming. In this case the meaning of
arithmetic operators and variable types is defined in an
include file (which is used as a namespace here it works
fine with older C++ compilers).
The types in control algorithm block classes are defined
as physical units (such as Volt, Ampere, Ohm etc.). Thus
a variable in a control algorithm block class is defined as
a Volt type or as an Ampere type or as another physical
unit type. See listing 3.
In the floating-point version of the setup.h include file a
typedef declaration defined the unit types as the type
double. This is just as it was in old days.
In the fixed-point version of the setup.h include file the
unit types was defined to be the fixed_point class. See
listing 4. This class defines its own arithmetic operator
functions. In this way the same control algorithm block
class code text can perform floating-point calculations, if
the first version of the setup include file is used, and
fixed-point calculations, if the second version of the setup
include file is used. Calculations in simulations are
normally done in floating point variables, whereas
calculations done in the embedded program in the
frequency converter are fixed point calculations. The
benefit is that fixed-point calculation inaccuracies in the
run-time target can be studied in simulations.
In the physical unit version of the setup.h include file
the unit types are implemented as instances of the Unit
class template. See listing 5. In this way physical unit
errors in calculations are caught on compile time!
Addition of two Volt variables is OK, but addition of a
Volt variable to an Ampere variable generates a compile
time error. Multiplication of an Ampere variable and a
Volt variable gives you a Watt variable, a Volt variable
divided with an Ampere variable gives you an Ohm
variable and so on. The implementation in the setup.h
include-file follows the principles given in [3].
The Simulink framework can perform unit check too. In
listing 6 RadPerSec is defined as a signal type and an
output port and an input port is defined to be RadPerSec.
Before simulation starts Simulink performs type
matching checks of outputs connected to inputs.

4.3. Wrapping the lower part of the sandwich


code file structure
Simulation environments and execution environments
have different characteristics. Thus the control algorithm
block classes has to be adapted (wrapped) in order to suit
the different environments This could be done by sub
classing in the case of the run-time environment. In the
case of the Simulink simulation tool environment the
wrapping is done by declaring a control algorithm block
object in the Simulink S-function wrapper file for a

Simulink block. A company internal standard wrapper


file has been developed.

4.4. Synthesis of control algorithm blocks

in Danfoss Drives simulations. In this way some of the


laboratory application tests can be moved up front to the
realm of simulations.
.
Before

A group of interconnected control algorithm block


objects could be defined in a new class, which now
becomes a larger building block.
Before
Limiter object

UF controller object

Control
Engineering
Simulation

Software
Engineering

Hardware
Engineering

Customer

Hardware
Engineering

Customer

After
Limited UF controller object

After
dll-files

Figure 4 Synthesis of control algorithm blocks

This gives a lot of new possibilities. Once the simulation


blocks are built as objects a whole range of component
composition techniques from the realm of software
emerge. As a first little example: we could have defined
the limited UF controller object from figure 4 as an object
from a subclass of class UF controller object instead.
Thus an inheritance hierarchy of controller classes can be
established.
Entire control loops could be represented in such a class.
Existing software components could be tested and
verified in Simulink simulations upfront in the system
development process. Thus a decision of reusing legacy
code can be investigated upfront.

5. Process of development and maintenance.


The use of simulations as means of verification gives
improvements to the process of development. This
process is divided into 3 stages. First the control
engineers build the control algorithm model, then
software engineers implement it in the embedded
environment and finally hardware engineers perform the
final integration tests in laboratory. If errors are
discovered in the later stages, then an extra (costly) long
(involving 2 or 3 stages) iteration can take place see the
figure 5 below.
Regarding 3 stages iterations: In Simulink a
windows DLL component could be made for an object of
a control loop class built by synthesis of low level
components as explained in section 4.3. As this DLL
component is compiled code, it could be handed over to a
customers simulation model without revealing any of
Danfoss Drives secrets (Danfoss Drives holds a number
of patents for its control algorithms). Likewise the
costumer could, without revealing her secrets, in return
hand over application simulation components to be used

Control
Enginering
Simulation

Software
Engineering

Application
Knowledge

Figure 5 Stages of development

6. Conclusions
The vision about retaining the same control algorithm
code throughout the development process has shown to
be viable. Control engineers easily adapt to object
orientation and the clear division between control
algorithm code and simulation code has been appreciated.
A simulation tool independent representation of the
control algorithms has been achieved. The new
opportunities for checking inaccuracies of fixed-point
calculations and for unit compile-time check are being
explored.
The combined use of object orientation, subject
orientation and wrapping techniques has resulted in an
code architecture with a clear division of stable and
variable parts suitable for the future development and
maintenance of control applications at Danfoss Drives.

7. References
[1] http://www.danfoss.com/drives.
[2] The book: Applied Software Architecture by
Christine Hoffmeister, Robert Nord, Dilip Soni.
Addison-Wesley,1999
[3] Applied Template Metaprogramming in Siunits: the
Library of Unit-Based Computation, Walther E. Brown,
august-2001

Listing 1

Listing 4

#include "math.h"

const long scale_factor=256*256;// 2^16


const long long twoto32=(long long)256*256*256*256;// 2^32

class UF_control
{ public:
UF_control(double ts,double un,double rs,
double isNom,double fsNom)
: Usy(0), theta(0), theta_old(0), Ts(ts), Ws(0)
{
ufgain = 1.06*1.5*(un/sqrt((double)3)-rs*isNom)*
sqrt((double)2)/(fsNom*2*pi);
ufoffset = rs*isNom*sqrt((double)2)*1.5;
}
void update(double ref)
{
theta_old = theta;
theta = ref * Ts + theta_old;
Usy = ufgain * ref + ufoffset;
Ws = (theta - theta_old) / Ts ;
}
// this function returns the length of the voltage vector
double getUsy(){ return Usy; }
//this function returns the angle of the voltage vector
double getTheta(){ return theta; }
//this function returns the angle speed of the voltage vector
double getWs(){ return Ws; }
private:
double Ts,Usy,ufgain, ufoffset, theta, theta_old,Ws;
};

Listing 2
#define MDL_UPDATE /* Change to #undef to remove
function */
#if defined(MDL_UPDATE)
static void mdlUpdate(SimStruct *S, int_T tid)
{// retrieve input pointer vector
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S, 0);
double ref=*uPtrs[0] ; // reference input signal is retrieved
// retrieve pointer to C++ object from Simulinks pointer work
//vector
UF_control *UF_control_ptr = (UF_control *) ssGetPWork(S)[0];
// call of the function update in the C++ object
UF_control_ptr->update( ref );
} /* End of mdlUpdate */
#endif

Listing 3
#include "math.h"
#include "setup.h"
class UF_control
{
public:
UF_control(Sec ts,V un,Ohm rs,A isNom, RadPerSec fsNom)
:Usy(0),theta(0),theta_old(0),Ws(0), Ts(ts);
{ // same as in listing 1}
void update(RadPerSec ref)
{ // same as in listing 1}
V
getUsy(){ return Usy; }
Rad getTheta(){ return theta; }
RadPerSec getWs(){ return Ws; }
private:
Sec Ts; V Usy,ufoffset; VsecPerRad ufgain;
Rad theta, theta_old; RadPerSec Ws;
};

enum init_operator {sum_fix,dif_fix,mul_fix,div_fix};


class Fix_point_number
{
friend double to_double(const Fix_point_number & fp);
public:
Fix_point_number(){ value=0;}
Fix_point_number(double d){ value = d * (double)scale_factor;}
Fix_point_number(long d) { value = d * scale_factor;}
Fix_point_number(int d) { value = d * scale_factor;}
Fix_point_number(init_operator kind_of,const Fix_point_number & a,
const Fix_point_number & b)
{ switch( kind_of )
{case sum_fix: value = a.value + b.value; break;
case dif_fix: value = a.value - b.value; break;
case mul_fix:
value = (long)(( (long long) a.value * (long long) b.value )/
(long long) scale_factor); break;
case div_fix:
value = (long)(((((long long)a.value)*twoto32)/(long long)b.value)/
(twoto32/(long long)scale_factor) ); break;
}
}
double getdouble(){ return (double)value / scale_factor; }
private:
long value;
};
Fix_point_number operator+( Fix_point_number a, Fix_point_number b)
{ return Fix_point_number(sum_fix,a,b); }
Fix_point_number operator-( Fix_point_number a, Fix_point_number b)
{ return Fix_point_number(dif_fix,a,b); }
Fix_point_number operator*( Fix_point_number a, Fix_point_number b)
{ return Fix_point_number(mul_fix,a,b); }
Fix_point_number operator/( Fix_point_number a, Fix_point_number b)
{ return Fix_point_number(div_fix,a,b); }
double to_double(const Fix_point_number & fp)
{ return (double)fp.value / scale_factor; }
double to_double(const double & d){ return d;}
typedef Fix_point_number
typedef Fix_point_number
typedef Fix_point_number
typedef Fix_point_number
typedef Fix_point_number
typedef Fix_point_number
typedef Fix_point_number
typedef Fix_point_number
// und so weiter

Scalar;
V;
A;
Rad;
Sec;
Ohm;
RadPerSec;
VsecPerRad;

Listing 6
Listing 5
enum init_operator {sum_op,dif_op,mul_op,div_op};
template<int u1,int u2, int u3, int u4>
class Unit
{ template<int a1,int a2, int a3, int a4>
friend class Unit;
public:
explicit Unit(){ value=0;}
explicit Unit(double d){ value = d;}
explicit Unit(long d) { value = d ;}
explicit Unit(int d) { value = d ;}
template<int ua1,int ua2, int ua3, int ua4,
int ub1,int ub2, int ub3, int ub4 >
Unit(init_operator kind_of,
Unit<ua1,ua2,ua3,ua4> a, Unit<ub1,ub2,ub3,ub4> b)
{ switch( kind_of )
{case sum_op: value = a.value + b.value; break;
case dif_op: value = a.value - b.value; break;
case mul_op: value = ( a.value * b.value ); break;
case div_op: value = a.value / b.value; } }
double getdouble(){ return value; }
private:
double value; };
template<int u1,int u2,int u3,int u4>
Unit<u1,u2,u3,u4> operator+( Unit<u1,u2,u3,u4> a, Unit<u1,u2,u3,u4> b)
{ return Unit<u1,u2,u3,u4>(sum_op,a,b); }
template<int u1,int u2,int u3,int u4>
Unit<u1,u2,u3,u4> operator-( Unit<u1,u2,u3,u4> a, Unit<u1,u2,u3,u4> b)
{ return Unit<u1,u2,u3,u4>(dif_op,a,b); }
template<int u11,int u12,int u13,int u14, int u21,int u22,int u23,int u24>
Unit<u11+u12,u12+u22,u13+u23,u14+u24>
operator*( Unit<u11,u12,u13,u14> a, Unit<u21,u22,u23,u24> b )
{ return Unit<u11+u12,u12+u22,u13+u23,u14+u24>(mul_op,a,b); }
template<int u11,int u12,int u13,int u14>
Unit<u11,u12,u13,u14> operator*( Unit<u11,u12,u13,u14> a, double b )
{ return Unit<u11,u12,u13,u14>(mul_op,a,Unit<0,0,0,0>(b)); }

// from UF_control S-function wrapper file:


static void mdlInitializeSizes(SimStruct *S)
{

nt_T status;
DTypeId idRadPerSec;
idRadPerSec = ssRegisterDataType(S, "RadPerSec");
if(idRadPerSec == INVALID_DTYPE_ID) {
ssPrintf("INVALID_DTYPE_ID"); return; }
status = ssSetDataTypeSize(S, idRadPerSec, sizeof(RadPerSec) );
if(status == 0) { ssPrintf("status 0");return;}
ssSetInputPortDataType(S, 0, idRadPerSec);

};
#define MDL_UPDATE /* Change to #undef to remove function */
#if defined(MDL_UPDATE)
static void mdlUpdate(SimStruct *S, int_T tid)
{
// retrieve array of input pointers
InputPtrsType u = ssGetInputPortSignalPtrs(S,0);
// retrieve input parameter
RadPerSec speed_reference(*(RadPerSec*)u[0]);
// retrieve pointer to C++ object from Simulinks pointer work vector
UF_control *UF_control_ptr = (UF_control *) ssGetPWork(S)[0];
// call of the function update in the C++ object
UF_control_ptr->update( speed_reference );
} /* End of mdlUpdate */
#endif

-------------------------------------------------------// from rate_limiter S-function wrapper file:


static void mdlInitializeSizes(SimStruct *S)
{

int_T status;
DTypeId idRadPerSec;

template<int u11,int u12,int u13,int u14>


Unit<u11,u12,u13,u14> operator*( double a, Unit<u11,u12,u13,u14> b )
{ return Unit<u11,u12,u13,u14>(mul_op,Unit<0,0,0,0>(a),b); }

idRadPerSec = ssRegisterDataType(S, "RadPerSec");


if(idRadPerSec == INVALID_DTYPE_ID)
{ ssPrintf("INVALID_DTYPE_ID"); return; }

template<int u11,int u12,int u13,int u14, int u21,int u22,int u23,int u24>
Unit<u11-u12,u12-u22,u13-u23,u14-u24>
operator/( Unit<u11,u12,u13,u14> a, Unit<u21,u22,u23,u24> b )
{ return Unit<u11-u12,u12-u22,u13-u23,u14-u24>(div_op,a,b); }
template<int u11,int u12,int u13,int u14>
Unit<-u11,-u12,-u13,-u14>
operator*(double a,Unit<u11,u12,u13,u14> b )
{ return Unit<-u11,-u12,-u13,-u14>(div_op,Unit<0,0,0,0>(a),b); }
template<int u11,int u12,int u13,int u14>
Unit<u11,u12,u13,u14> operator/( Unit<u11,u12,u13,u14> a, double b )
{ return Unit<u11,u12,u13,u14>(div_op,a,Unit<0,0,0,0>(b)); }
typedef Unit<0,0,0,0> Scalar;
typedef Unit<1,0,0,0> V;
typedef Unit<0,1,0,0> A;
typedef Unit<0,0,1,0> Rad;
typedef Unit<0,0,0,1> Sec;
typedef Unit<1,-1,0,0> Ohm;
typedef Unit<0,0,1,-1> RadPerSec;
typedef Unit<1,0,-1,1> VsecPerRad;
// und so weiter

status = ssSetDataTypeSize(S, idRadPerSec, sizeof(RadPerSec) );


if(status == 0) { ssPrintf("INVALID_DTYPE_ID");return;}
ssSetOutputPortDataType(S, 0, idRadPerSec)

};
static void mdlOutputs(SimStruct *S, int_T tid)
{ // retrieve pointer to output port signal
RadPerSec *Outputptr = (RadPerSec *)ssGetOutputPortSignal(S,0);
// retrieve pointer to C++ object from Simulinks pointer work vector
rate_limiter *rate_limiter_ptr = (rate_limiter*) ssGetPWork(S)[0];
// call of the C++ objects output function :
RadPerSec ref=rate_limiter_ptr->get_rate_limited_value() ;
// store it in output signal
(*Outputptr) = ref;
}

You might also like