Object Oriented C++ Programming in SIMULINK - A Reengineered Simulation Architecture For The Control Algorithm Code View
Object Oriented C++ Programming in SIMULINK - A Reengineered Simulation Architecture For The Control Algorithm Code View
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.
2. The
Domain
Frequency
Converter
Problem
4. Strategies Applied
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
RL.h
#includes Setup.h
-------------------------------------Limiter function
Uf.h
#includes Setup.h
----------------------------------------------UF Controller
factored out, and what is left behind in the Simulink Sfunction wrapper file is Simulink specific code.
before
after
UF controller object
Control
Engineering
Simulation
Software
Engineering
Hardware
Engineering
Customer
Hardware
Engineering
Customer
After
Limited UF controller object
After
dll-files
Control
Enginering
Simulation
Software
Engineering
Application
Knowledge
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"
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;
};
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)); }
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
int_T status;
DTypeId idRadPerSec;
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
};
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;
}