DUECA/DUSIME
Loading...
Searching...
No Matches
Including code from Simulink (old, until Matlab 2012b)

Many computer-aided control engineering packages have an option to export a model to C or C++ code. Often this option is used to solve a calculation-intensive problem in a quicker way, because the exported model does not suffer from the overhead from the package, but this code can also be ported to other environments. DUECA currently offers the possibility to include Simulink models converted with Real-Time Workshop.

Here is an older description is based on Matlab 2012b, with Simulink 8.3. When in your model, select "Simulation" - "Model Configuration Parameters", then change the following options:

  1. Under "Solver", select "Fixed-step", and "ode4". Set the proper step size and select "SingleTasking" for the tasking mode.

  2. Under "Optimization", "Signals and Parameters", if you uncheck the "inline Parameters" flag, you can modify the parameters of the built code, otherwise these are fixed.

  3. To ensure better compatibility, when your target platform is (as in most cases) a 64 bit Intel compatible, under "Hardware Implementation", select Intel for the vendor, and x86-64 for the device type.

  4. Model Referencing, as far as I can determine, does not work for R2012b code generation with our options, so include any sub models in your present model.

  5. Under "Code generation", select "grt_malloc.tlc" (the generic one, not the one for Visual C++) as your system target file. You can uncheck the "Generate makefile" flag. For the code generation, select either the debugging or execution efficiency objective. Finally select the "Fenerate code only" flag.

  6. Under "Code generation" / "Interface", uncheck MAT-file logging.

The script new-module offers the possibility to prepare a file for inclusion of a Real-Time Workshop generated model. Check with "new-module", without arguments, to see which versions are currently available.

Different versions of real-time workshop code

One thing you need to be aware of is that all RTW code you put into a dueca executable must have the same version. When using RTW code, you should edit the main Makefile (the one in the "application directory") and specify that you are going to use one of the rtw modules with the DCOMPONENTS variables.

Calling Simulink/RTW code version 3

Generate a skeleton for your module by invoking the new-module script:

new-module rtw?? <fill in the rtw version here>

The step size of the model can be adjusted. This code is added by default to the setTimeSpec model. Note that some generated models don't function well when the time step is changed, so I recommend generating the model with the proper step size, and not messing with this.

For calculation of the model response, you first need to set the input vector values. Look in in doCalculation for the template code.

copyright : (c) 2016 TUDelft-AE-C&S
copyright : (c) 2022 René van Paassen
license : EUPL-1.2
*/
#define SpacePlane_cxx
// include the definition of the module class
#include "SpacePlane.hxx"
// include additional files needed for your calculation here
#include "StatesOutputs.hxx"
#define D_MOD
#define E_MOD
#include <debug.h>
extern "C" {
// here we include the file with (static) routines for the RTW/Simulink model
#define RTW_MODEL complete_reg
#include "complete.c"
#include "rtw_prototypes.h"
}
// the standard package for DUSIME, including template source
#define DO_INSTANTIATE
#include <dusime.h>
// define conversion factor to degrees
const double RAD_DEG = 180.0 / 3.1415926535897931E+000;
// class/module name
const char* const SpacePlane::classname = "space-plane";
// initial condition/trim table
// this does not work yet, so the table is empty
const IncoTable* SpacePlane::getMyIncoTable()
{
static IncoTable inco_table[] = {
// enter pairs of IncoVariable and VarProbe pointers (i.e.
// objects made with new), in this table.
// For example
// {(new IncoVariable("example", 0.0, 1.0, 0.01))
// ->forMode(FlightPath, Constraint)
// ->forMode(Speed, Control),
// new VarProbe<SpacePlane,double>
// (REF_MEMBER(&SpacePlane::i_example))}
// always close off with:
{ NULL, NULL} };
return inco_table;
}
// parameters to be inserted
const ParameterTable* SpacePlane::getMyParameterTable()
{
static const ParameterTable parameter_table[] = {
{ "set-timing",
new MemberCall<SpacePlane,TimeSpec>
(&SpacePlane::setTimeSpec)},
{ "set-stop-height",
new VarProbe<SpacePlane,double>
(REF_MEMBER(&SpacePlane::z_stop))},
{ "check-timing",
new MemberCall<SpacePlane,vector<int> >
(&SpacePlane::checkTiming)},
// always close off with:
{ NULL, NULL} };
return parameter_table;
}
/** e06 */
// constructor
SpacePlane::SpacePlane(Entity* e, const char* part, const
PrioritySpec& ps) :
/* The following line initialises the SimulationModule base class.
You always pass the pointer to the entity, give the classname and the
part arguments.
If you give a NULL pointer instead of the inco table, you will not be
called for trim condition calculations, which is normal if you for
example implement logging or a display.
If you give 0 for the snapshot state, you will not be called to
fill a snapshot, or to restore your state from a snapshot. Only
applicable if you have no state. */
SimulationModule(e, classname, part, getMyIncoTable(),
NSTATES*sizeof(real_T)),
// initialize the data you need in your simulation
S(NULL),
z_stop(0.0),
// initialize the data you need for the trim calculation
// initialize the channel access tokens
controls(getId(), NameSet(getEntity(), "PrimaryControls", part)),
output(getId(), NameSet(getEntity(), "SpacePlaneY", part)),
state(getId(), NameSet(getEntity(), "SpacePlaneState", part), 21),
// activity initialization
cb1(this, &SpacePlane::doCalculation),
do_calc(getId(), "simulation step", &cb1, ps)
{
// for a simulink model, you need to initialise infinity, - inf and
// Not-a-number.
rt_InitInfAndNaN(sizeof(real_T));
// create a SimuLink/rtw model
S = RTW_MODEL ();
// initialize it
MdlInitializeSizes(S);
MdlInitializeSampleTimes(S);
rt_CreateIntegrationData(S);
const char* ret;
if ((ret = rt_InitTimingEngine(S)) != NULL) {
E_MOD(getId() << ' ' << ret);
}
MdlStart(S);
// set the initial time
ssSetT(S, 0.0);
// and also calculate the initial state
MdlUpdate(S, 0);
/** e00 */
// specify that the control input is the trigger for the calculation
do_calc.setTrigger(controls);
// just a check on the states defined and the model
assert(X_no_states == NSTATES);
assert(Y_no_outputs == NOUTPUTS);
}
/** e01 */
bool SpacePlane::complete()
{
/* All your parameters have been set. You may do extended
initialisation here. Return false if something is wrong. */
return true;
}
/** e02 */
// destructor
SpacePlane::~SpacePlane()
{
// delete the model
MdlTerminate(S);
}
/** e03 */
/** s02 */
// the setTimeSpec function
bool SpacePlane::setTimeSpec(const TimeSpec& ts)
{
// a time span of 0 is not acceptable
if (ts.getValiditySpan() == 0) return false;
// specify the timespec to the activity
do_calc.setTimeSpec(ts);
// do whatever else you need to process this in your model
// hint: ts.getDtInSeconds()
// in this case, the Simulink model has to be told of the time base
ssSetStepSize(S, ts.getDtInSeconds());
// return true if everything is acceptable
return true;
}
/** e04 */
// and the checkTiming function
bool SpacePlane::checkTiming(const vector<int>& i)
{
if (i.size() == 3) {
new TimingCheck(do_calc, i[0], i[1], i[2]);
}
else if (i.size() == 2) {
new TimingCheck(do_calc, i[0], i[1]);
}
else {
return false;
}
return true;
}
/** e05 */
// tell DUECA you are prepared
bool SpacePlane::isPrepared()
{
// assume I am ready if all tokens are valid
return controls.isValid() && output.isValid();
}
// start the module
void SpacePlane::startModule(const TimeSpec &time)
{
do_calc.switchOn(time);
}
// stop the module
void SpacePlane::stopModule(const TimeSpec &time)
{
do_calc.switchOff(time);
}
/** e07 */
// fill a snapshot with state data. You may remove this method (and the
// declaration) if you specified to the SimulationModule that the size of
// state snapshots is zero
void SpacePlane::fillSnapshot(const TimeSpec& ts,
Snapshot& snap, bool from_trim)
{
// The most efficient way of filling a snapshot is with an AmorphStore
// object.
AmorphStore s(snap.accessData(), snap.getDataSize());
if (from_trim) {
// not implemented
}
else {
// this is a snapshot from the running simulation. Dusime takes care
// that no other snapshot is taken at the same time, so you can safely
// pack the data you copied into (or left into) the snapshot state
// variables in here
// use packData(s, snapshot_state_variable1); ...
for (int ii = 0; ii < NSTATES; ii++) {
packData(s, s_x[ii]);
}
}
}
// reload from a snapshot. You may remove this method (and the
// declaration) if you specified to the SimulationModule that the size of
// state snapshots is zero
void SpacePlane::loadSnapshot(const TimeSpec& ts, const Snapshot& snap)
{
// access the data in the snapshot with an AmorphReStore object
AmorphReStore store(snap.data, snap.data_size);
D_MOD(getId() << " loading snapshot");
// copy the state data into the state vector
real_T* X = ssGetX(S);
double t;
for (int ii = 0; ii < NSTATES; ii++ ) {
unPackData(store, t);
X[ii] = t;
}
}
/** s07 */
void SpacePlane::doCalculation(const TimeSpec& ts)
{
// check the state we are supposed to be in
switch (getAndCheckState(t)) {
case SimulationState::HoldCurrent: {
// only repeat the output, don not change the model state
StreamWriter<SpacePlaneY> y(output, ts);
real_T* Y = ssGetY(S);
for (int ii = NOUTPUTS; ii--; ) {
y.data().Y[ii] = Y[ii];
}
// send out the state also
StreamWriter<SpacePlaneState> x(state, ts);
real_T *X = ssGetX(S);
for (int ii = NSTATES; ii--; ) {
x.data().X[ii] = X[ii];
}
}
break;
/** e08a */
case SimulationState::Replay:
case SimulationState::Advance: {
// access the input
StreamReader<PrimaryControls> u(controls, ts);
// copy the input to the simulink input
double* U = ssGetU(S);
#define D_MOD(A)
Debug messages for application module code.
Definition newlog-macros.hxx:565
#define E_MOD(A)
Error messages for application module code.
Definition newlog-macros.hxx:620

Communicating state and parameters between MATLAB/Simulink and DUECA

As from RTWv5.0 and up, DUECA includes the option to generate xml parser scripts that you can use to communicate states and model parameters between DUECA and simulink. When invoking

new-module rtw[x]

You are prompted if you want this functionality.

See also
XmlSnapshot

Note that you need to select the C interfacing option in the real-time workshop code generation for this; the parameters of your model will then be accessible to the DUECA code.

Making sense of what the Simulink code produces

Of course, you do this Simulink stuff for some purpose. The rest of your simulation needs to know what it can do with the output of the Simulink code. My advice is to write a file that defines the states and outputs of the Simulink model with enumerated types. Here is a piece of my StatesOutputs.hxx file:

enum output_vec {
Y_p,
Y_q,
Y_r,
Y_phi,
Y_theta,
Y_psi,
Y_x,
Y_y,
Y_z,
Y_V,
Y_alpha,
Y_beta,
Y_gamma,

Using these enumerated values it is easy to see what type of output you are looking at. There is a check possible on (at least) the number of outputs in the Simulink model and the ones listed in your StatesOutputs.hxx file. As last output (state), define:

Y_gamma_c,
Y_no_outputs
};

The value of Y_no_outputs is equal to the number of outputs in the Simulink model, given by the define "OUTPUTS" in the include file of your model. Use this to check the sanity of your code.

Calling Simulink/RTW code version 4

Generate a skeleton for your module by invoking the new-module script:

new-module rtw4

So, with the advent of Matlab 6.5, also the real-time workshop got a work-over. This makes things so much harder again. The code generated by the new-module script is more or less comparable to the previous code, with frequent substitutions of "ss" to "rtw" or something similar.

What files to keep and include in your project?

Simulink generates a large number of files. Some of these are common to all models, and have been included in the rtw libraries packed with dueca. What files do you keep, and what do you throw away?

Suppose you created a model called MyModel. When generating the code with above options, Simulink creates a directory MyModel_grt_malloc_rtw. From that directory, keep:

  1. MyModel.c
  2. MyModel.c
  3. MyModel_private.h
  4. MyModel_types.h
  5. rtmodel.h
  6. rtwtypes.h

Simulink also generated defines.txt and modelsource.txt. Keep these for reference. If you can, also store the model file (MyModel.slx), any sub-models you might have included alongside the code (i.e., on the repository), and the Matlab file that contains the initial parameters (and state?) for your model, e.g., MyModel_init.m.

If you initialise the parameters and state of the model before doing the code generation, these parameters and state will be built into your model.