Experience Embedded

Professionelle Schulungen, Beratung und Projektunterstützung

Grafische objektorientierte C-Programme mit Simulink

Autor: Dr. Hartmut Schorrig, vishia

Beitrag - Embedded Software Engineering Kongress 2017

Im Beitrag wird die Anwendung der grafischen Programmierung in einer Matlab-Simulink-Umgebung gezeigt. Dabei werden Kern-Module in sogenannten S-Functions genutzt, die in C programmiert sind. Der Vorteil dieser Herangehensweise ist einerseits, dass in C hardwarenahe Schichten oder Betriebssystemanbindungen direkt formuliert werden können. Andererseits finden sich die Aufrufe der Kern-Module in dem aus Simulink generierten Code wieder, was die Navigation im Code auch für eine langzeitliche Pflege erleichtert. Dem bisher weitgehend in der zeilenorientierten Programmierung arbeitetenden Entwickler sollen die Vorteile der grafischen Programmierung und die einfache Einbindung bestehender Codes nahegebracht werden.

Objektorientierung in Simulink

In Simulink ist traditionell zwar eine stark modulare Arbeitsweise verbreitet, nicht aber die objektorientierte. Die Objektorientierung wird mit C in Verbindung gebracht und zieht sich damit in die Simulink-Modelle hinein, bis hindurch auf den generier­ten Code der Implementierungsplattform. Objektorientierung bedeutet, die Daten in den Mittelpunkt der Betrachtung zu stellen. Auf die Daten arbeiten Operationen (Methoden). Für die Simulink-Ebene gibt es dafür S-Functions als Object-FB und als Operation-FB. (FB= Function Block) Der Object-FB enthält die Daten und liefert diese an zugehörige Function-FB über ein Handle. Das Handle ist im 32-Bit-Zielsystem direkt die Speicheradresse, im Simulink ein Index.

Das Bild (siehe Abbildung 1, PDF) zeigt ein Simulink-Modell. Die Blöcke sind intern S-Functions in C, hier aber teils schon in Simulink als For-Each-Subsystem für die Verarbeitung mehrerer paralleler Werte als Vektor ausgeführt. Die übergeordnete Datenorganisation braucht also schon nicht mehr in C ausprogrammiert werden, sondern wird von der automatischen Codegenerierung besorgt. Die Zusammenfassung von Daten im Bild rechts ähnlich einem Simulink-Bus ist ebenfalls eine S-Function in C. Hier können zusätzlich Mechanismen wie Wechselpuffer oder Mutex realisiert werden. Im Bild farblich gestaltet sind die Abtastzeitzuordnungen. Bestimmte Berechnungen werden im Echtzeitsystem beispielsweise in einem schnellen Interrupt ausgeführt, andere laufen in Threads. Simulink erzeugt in der Codergenerierung für jede Abtastzeit eine eigene Routine, die sich entsprechend einbinden lässt.

Vererbung und Abstraktion

Zur Objektorientierung gehört die Möglichkeit, abgeleitete Klassen mit abstrakten Operationen zu verbinden. Bei der Verbindung der Blöcke in Simulink über zunächst einheitliche getypte Handle (uint32) wird beim Start des Modells die Typrichtigkeit geprüft. Im generierten Code für das Zielsystem ist dann diese Typprüfung nicht mehr enthalten, es wird vorausgesetzt, dass das Modell bereits auf Simulink-Ebene getestet wurde. Bei der Typprüfung wird die Bereitstellung eines Handles eines abgeleiteten Object-FB zu einem Basisklassen-Operation-FB zugelassen. Der Operation-FB arbeitet damit zunächst nur mit den Basisdaten. Mehrfachvererbung ist nicht vorgesehen, ähnlich wie in Java. Die späte Bindung von Operationen zur Laufzeit kann in C mit FunctionPointer ebenfalls realisiert werden. Damit ruft ein Operation-FB einer Basisklasse zur Laufzeit die Operation, wie sie in der abgeleiteten Klasse vorgesehen ist, auf. In C++ sind dies die bekannten virtual Methoden.  Für die Simulink-Ebene ist dies nutzbringend, wenn beispielsweise in einem Modul Daten abgeholt werden sollen, die in einem anderen Modul unabhängig möglicherweise eben mit einem abgeleitem Object-FB, bereitgestellt werden.

Ein Vergleich mit UML

Die UML ist ein geeignetes Mittel, um Softwarestrukturen darzustellen und auch die Rahmen der C-Codes automatisch zu generieren. Simulink geht in der Codegenerierung weiter. Es ist stärker auf Funktionalität als auf Design orientiert. Mit diesem Simulink-Konzept wird eine Klasse in ihrer Instanziierung als Object-FB dargestellt. Aggregationen sind in der Pfeilrichtung umgekehrt, nicht auf die genutzte Klasse gerichtet, sondern als Bereitstellung der Aggregation in Richtung der nutzenden Klasse. Operationen sind hier eigene Kästchen, in der Abfolge des Aufrufes gezeichnet. Die Operationen werden hier also nicht angelegt, sondern aufgerufen. Die Anlage erfolgt in C, das möglicherweise mit einem UML-Tool generiert wurde. Daten sind gekapselt im Object-FB und damit private

Generierung der S-Function Wrapper und tlc-Files für Simulink

Simulink bietet Hausmittel an, um C-Codes in Modelle einzubetten. Allerdings ist dies eher auf größere Einheiten orientiert, es ist pro S-Function etwas Aufwand notwendig. Wenn es, wie hier gezeigt, sehr viele Kern-C-Module gibt, dann ist dies nicht mehr optimal. Es wurde daher vom Verfasser ein Generator entwickelt, der aus Informationen in den C-Headerfiles automatisch die für Simulink notwendigen Aufruf-Wrapper der S-Function und die sogenannten tlc-Files für die Codegene­rierung des Simulink erzeugt. Bestehende C-Quellen lassen sich leicht für dieses System ergänzen.

In Headerfiles werden Strukturen und Funktionsprototypendeklarationen dazu mit Kommentaren ergänzt, die Annotationen enthalten. Es gelten bestimme Bezeichnungsregeln der Argumente in den Prototypendeklarationen. Folgendes Beispiel veranschaulicht dies:

/**Internal data of a OrthogonalOscillator.

 * @simulink no-bus

 */

typedef struct OrthOsc2_FB_t

{

  ObjectJc obj;            //:The base structure

  Param_OrthOsc2_FB* par;  //:Reference to parameter

  Angle_abwmf_FB* anglep;  //:Reference to angle, null is ok

  float . . .  //internal data

} OrthOsc2_FB;

 

/**The constructor. @simulink ctor */

OrthOsc2_FB* ctor_OrthOsc2_FB(ObjectJc* othiz, int32 identObj,

 float k1, float Tstep);

 

/**Prepares the instance data.

 * @param par aggregation to the parameter.

 * @param angle aggregation to angle source.

 * @simulink init

 */

char const* init_OrthOsc2_FB(OrthOsc2_FB* thiz, float

  Param_OrthOsc2_FB* par, Angle_FB* angle, float k2_param);

 

/**Step routine.

 * @param xAdiff Difference ...

 * @param yaz_y variable to store the a-Output.

 * @param ab_Y variable to store the orthogonal output..

 * @simulink Object-FB, accel-tlc

 */

void step_OrthOsc2_FB(OrthOsc2_FB* thiz, float xAdiff,

 float* yaz_y, float_complex* ab_y);

 

Die struct-Definition kann als Simulink-Bus generiert werden, hier aber nicht (no-bus). Der Constructor wird immer in der Start-Phase der S-Funktion durchlaufen. Die Allokierung des Speicherplatzes wird mit malloc automatisch generiert, kann aber auch im Generierscript angepasst werden. Das Argument Tstep bestimmt die Abtastzeit. Ansonsten richtet sich die Abtastzeit einer S-Funktion auch nach den Signalquellen. Alle Argumente des ctor sind in Simulink non-tunable-Parameter der S-Funktion.  

Die init-Routine (@simulink init) wird auf eine spezielle Abtastzeit gelegt und nur initial beim Startup der Run-Phase gerufen. Sie ist für die Aggregationen des FB mit anderen FB zuständig, die nichtnumerische Zeigertypen sind entweder Handle oder Busse (Endung _bus). Diese Routine kann auch Argumente mit der Endung _param als non-tunable-Parameter oder numerische Inputs als Konstant-Vorgaben übernehmen.

Die Routinen außer dem ctor können einen Text als Fehlermeldung zurückgeben, der im Simulink als Fehler angezeigt wird und zum Abbruch der Simulation führt.

Die mit @simulink=Object-FB oder ...Operation-FB gekennzeichnete C-Routine bestimmt die S-Funktion. Numerische Skalar-Argumente oder Zeiger sind Input oder Output, auch als Vektoren. Outputs müssen Zeiger sein und auf _y oder _ybus enden.   Argumente mit der Endung _param sind für die S-Funktione tunable-Parameter. In der Codegenerierung können solche Parameter in einer extra Struktur zusammengefasst werden und sind dann auch im Zielsystem von außen änderbar.

Für die eine oder andere ungeänderte Anwender-Funktion wird man den kleinen Aufwand eines im Header formulierten Wrappers (inline) spendieren müssen. Wichtig ist, dass Daten in struct zusammengefasst werden, dies ist der Schlüssel zur Formulierung der Objektorientierung in C.

Beobachtungsmittel in Simulationslauf und für das Zielsystem: Inspector

Simulink gestattet die Beobachtung aller Signale in den Modellen mit verschiedenen Mitteln wie Scopes, Data-Logging oder der direkten online-Anzeige "gelbe Kästchen". Das gilt für die C-implementierten Daten selbstverständlich nicht, wenn sie nicht nach außen geführt sind. Um dort Abhilfe zu schaffen, können die Daten mit einem Inspector über Socketkommunikation abgerufen oder auch im Modell symbolisch auf Ausgänge gelegt werden. Die Basis dazu bietet ein Datenzugriff in C-Strukturen über einen Reflection-Mechanismus. Für den Reflection-Zugriff werden aus den struct in Headerfiles const-Daten in C generiert, die Name, Typ und Position der Daten in den Strukturen enthalten. Ein Service-Inspc-Dienst führt dann den Zugriff aus. Dieses System steht für Simulink bereit, lässt sich aber auch auf dem Zielsystem für den Zugriff auf alle oder relevante Daten einsetzen.

 

Beitrag als PDF downloaden


Implementierung - unsere Trainings & Coachings

Wollen Sie sich auf den aktuellen Stand der Technik bringen?

Dann informieren Sie sich hier zu Schulungen/ Seminaren/ Trainings/ Workshops und individuellen Coachings von MircoConsult zum Thema Implementierung /Embedded- und Echtzeit-Softwareentwicklung.

 

Training & Coaching zu den weiteren Themen unseren Portfolios finden Sie hier.


Implementierung - Fachwissen

Wertvolles Fachwissen zum Thema Implementierung/ Embedded- und Echtzeit-Softwareentwicklung steht hier für Sie zum kostenfreien Download bereit.

Zu den Fachinformationen

 
Fachwissen zu weiteren Themen unseren Portfolios finden Sie hier.