Dr. Arne JachensDr. Arne Jachens

Simulink

C-Programm Simulink Interface

Da sich komplexere Berechnungen nur mühselig in Simulink darstellen lassen, kann man so genannte Mex-Funktionen einbauen. Diese können in Form eines m-Scriptes geschrieben werden - deutlich schneller rechnen jedoch C-Funktionen.
Einfache C-Funktionen können wiederum mit dem Mex-Function-Builder gebastelt werden, wenn man jedoch etwas weniger triviale Funktionalitäten benötigt, kommt man wohl nicht umhin, sich das Interface zwischen Simulink und der C-Funktion selbst zu schreiben. Wie das funktioniert ist natürlich in der Matlab-Hilfe erklärt *sigh* Vielleicht hilft zur Veranschaulichung aber dieses komplette Beispiel.

  1. Mexfile als .dll kompilieren:
    mex C_Programm_Simulink_Interface.c C_Programm.c
  2. Simulink starten
  3. Aus User-Defined FunctionsS-Function ins Arbeitsfenster ziehen.
  4. Doppelklick auf das Symbol liefert die Function Block-Parameters. Hier den Namen C_Programm_Simulink_Interface in S-function name eintragen, in S-function parameters den Wert 10 für each und unter S-function modules alle Sourcecode-Datein, hier also "C_Programm". Anschließend sollte der Block 2 Ein- und 2 Ausgänge haben.
  5. Als Bezeichnung (unterhalb des Blockes) kann C_Programm eingegeben werden.
  6. Durch Klicken mit der rechten Maustaste auf den Block, kann man unter Mask S-Function die Ein- und Ausgänge benennen:
    port_label("input",1,"Vin")
    port_label("input",2,"dt")
     
    port_label("output",1,"Vout")
    port_label("output",2,"error")
  7. In SimulationConfiguration Parameters Type auf Fixed-step setzen und in Fixed-step size dt eintragen.
  8. In Matlab dt=0.1 eingeben und Simulation starten.
Simulink Beispiel

C_Programm_Simulink_Interface.c

/* Mit dem Befehl
     mex C_Programm_Simulink_Interface.c C_Programm.c 
  eingegeben in Matlab, lässt sich eine .dll erzeugen, die sich 
  als Mex-Funktion in Matlab einbinden lässt.
*/ 
 
/* Hier MUSS unnuetzerweise der (Datei-) Name wiederholt werden. */ 
#define S_FUNCTION_NAME C_Programm_Simulink_Interface
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
#include "C_Programm_globals.h"
FILE   *out1;
 
/* 
   Zunaechst muessen alle Ein- und Ausgaenge initialisiert werden.
   Die S-Function kann diskrete oder kontinuierliche Zustaende haben, 
   die hier nicht genutzt werden.
   Dieses Beispiel hat 2 Eingaenge, 1 Parameter und 2 Ausgaenge 
   (siehe auch den Simulinkteil weiter unten).
   Die PortWidth gibt die Zahl der uebertragenen Werte an - statt einem 
   Skalar koennte hier auch ein Vektor uebergeben werden.
   Fuer die Eingaenge MUSS jeweils noch "DirectFeedThrough" auf 1 
   gesetzt werden, damit die Werte an das C_Programm uebergeben werden.
*/ 
static void mdlInitializeSizes(SimStruct *S){
   
  int i;
  const int Anz_Param  = 1;
  const int Anz_Input  = 2;
  const int Anz_Output = 2;
  /* Parameter  Ports */ 
  ssSetNumSFcnParams(S, Anz_Param);
  if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
    return;
  }
  /* Input  Ports */ 
  if (!ssSetNumInputPorts(S, Anz_Input)) return;
  for (i=0;i/* Output Ports */ 
  if (!ssSetNumOutputPorts(S, Anz_Output)) return;
  for (i=0;i/* weitere Default Einstellungen */ 
   
  ssSetOptions(S, 0);
}
 
/*
  Die S-Function kann intern mit einem feineren Zeitraster 
  als Simulink berechnet werden. Hier wird die Zeitschritt-
  weite des aufrufenden Modells verwendent (vererbt).
*/ 
static void mdlInitializeSampleTimes(SimStruct *S){
  ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
  ssSetOffsetTime(S, 0, 0.0);
}
 
/*
  Dieses Beispiel hat einen inneren Zustand, der zu Beginn 
  einmalig als Vektor eingelesen wird. (Da der Zustandsvektor
  global bekannt ist, eruebrigt sich eine explizite Uebergabe.)
*/ 
#define MDL_INITIALIZE_CONDITIONS
static void mdlInitializeConditions(SimStruct *S){
  ReadState();
  out1=fopen("Zeitreihen.plot","w");
}
 
/*
  Hier erfolgt der eigentliche Funktionsaufruf und die 
  Rueckgabewerte werden den Ausgaengen zugewiesen.
*/ 
static void mdlOutputs(SimStruct *S, int_T tid){
  /* Eingaenge */ 
  InputRealPtrsType u1_in = ssGetInputPortRealSignalPtrs(S,0);
  InputRealPtrsType u2_in = ssGetInputPortRealSignalPtrs(S,1);
   
  /* Parameter */ 
  double           *p1_in = mxGetPr(ssGetSFcnParam(S,0));
  /* Ausgaenge */ 
  double           *w_out = ssGetOutputPortRealSignal(S, 0);
  double           *error = ssGetOutputPortRealSignal(S, 1);
  /* weitere Deklarationen */ 
  int    FehlerFlag; // Fehler-Rueckgabewert
  int    each;       // Intervall fuer Ausgabe
  double Vin;        // Eingangswert
  double Vout;       // Ausgangswert
 
  /* Handhabung der Pointer */ 
  each = *p1_in;
  Vin  = *u1_in[0];
  dt   = *u2_in[0];
 
  /* Auch mit Simulink kann man Schleifenzaehler verwenden! */ 
  Counter++;
 
  /* Der Programmaufruf - deswegen treiben wir den Umstand */ 
  FehlerFlag = C_Programm( Vin, dt, &Vout );
  if (FehlerFlag!=0){
    *error = (double)FehlerFlag;
    if (FehlerFlag==1){
      mexWarnMsgTxt("Eine Berechnung ist fehlgeschlagen!");
    }
  }
 
  /* 
     Ausgabe von Zeitreihen.
     Die Ausgaenge sind natuerlich in Simulink sichtbar, aber 
     hier koennen z.B. bestimmte Werte des Zustandsvektors 
     ausgegeben werden.
  */ 
  if (each!=0){
    if ((double)Counter/(double)each==(double)(Counter/each)){
      fprintf(out1,"%6.3f | %6.3f %E n",	
	    (double)Counter*dt, v_old[1], v_old[N-1] );
    }
  }
 
  /* Ausgangswerte als Pointer */ 
  *w_out = v_old[N-1];
  return;
}
 
/*
  Beim letzten Aufruf wird die Ausgabedatei geschlossen und der 
  letzte Zustand auf Platte geschrieben.
*/ 
static void mdlTerminate(SimStruct *S){
  fclose(out1);
  WriteState();
}
#ifdef  MATLAB_MEX_FILE
#include "simulink.c"
#else
#include "cg_sfun.h"
#endif

C_Programm.c

#include  
#include 
#include "C_Programm_globals.h"
 
/* einmaliges Ansprechen der globalen Variablen */ 
volatile const int N=10;
volatile int       Counter, each;
volatile double    dt;
volatile double    v_old[10];
 
/* =================================== */ 
int C_Programm( double Vin, double dt, double *Vout ){
  int    i;
  int    FehlerFlag;
  double v_new[10];
  double kappa;
  kappa=0.01;
 
  FehlerFlag=0;
  if (dt==0.0){
    FehlerFlag=1;
  }else{
    v_new[0] = v_old[0]+kappa*(Vin-v_old[0])/dt;
    for (i=1; iSimulations are like miniskirts, they show a lot 
                           and hide the essentials. n Hubert Kirrman n");
#endif
  *Vout = v_old[N-1];
  return FehlerFlag;
}
 
/* =================================== */ 
int ReadState(void){
  int  i;
  FILE *iop;
  char line[200], dummy1[20], dummy2[20];
  iop = fopen("Zustand.ini","r");
  for (i=0; i/* =================================== */ 
int WriteState(){
  int i;
  FILE *iop;
  iop = fopen("Zustand.out","w");
  for (i=0; i 

C_Programm_globals.h

/*
  Wird das folgende einkommentiert, erzeugt die C-Routine 
  Testausgaben zum Debuggen.
*/ 
 
//#define debug
 
 
/*
  Durch die Deklaration als "volatile extern" stehen diese 
  Variablen allen C-Programmen (auch im Interface) zur 
  Verfuegung.
*/ 
volatile extern const int N;
volatile extern int       Counter, each;
volatile extern double    dt;
volatile extern double    v_old[10];
 
/* Maske fuer die C_Funktion */ 
extern int C_Programm( double Vin, double dt, double *Vout );

Zustand.ini

v00= 1.000000E+001 
v01= 2.000000E+001 
v02= 3.000000E+001 
v03= 4.000000E+001 
v04= 5.000000E+001 
v05= 6.000000E+001 
v06= 7.000000E+001 
v07= 8.000000E+001 
v08= 9.000000E+001 
v09= 1.000000E+002

plot_v.sh

Mit gnuplot lassen sich per Skript binnen kurzer Zeit eine Vielzahl an Grafiken erzeugen in diversen Grafikformaten - unter anderem Vektorgrafiken in eps oder pdf.

 
#!/bin/sh
gnuplot << EOF
set size 0.9,0.9
set xlabel "Time [s]"
set ylabel "v [AU]"
set key left top
set logscale x 10
set xrange [1:2000]
set ytics (0,500,1000)
set mxtics 5
set grid xtics ytics mytics
set style line 1 lt 2 pt 7
set term pdf
set output "plot_v.pdf"
plot "Zeitreihen.plot" u 1:3  ti "v" w points linestyle 1
EOF
/usr/bin/xpdf "plot_v.pdf"