Grafische objektorientierte C-Programmierung mit Simulink
Mit einem grafischen Ansatz können in C hardwarenahe Schichten oder Betriebssystemanbindungen direkt formuliert werden. Zudem erlauben grafische Entwicklungstools für C-Programme eine einfache Navigation des Codes. Dieser Artikel beschreibt die Anwendung der grafischen Programmierung in einer Matlab-Simulink-Umgebung.
Anbieter zum Thema

Viele C-Programmierer arbeiten immer noch in zeilenbasierten Editoren. Dabei haben Entwicklungstools wie Simulink schon längst die möglichkeit zu einer grafischen Entwicklung in der Programmiersprache implementiert. Dieser Ansatz mag für den "traditionellen" Softwareentwickler gewöhnungsbedürftig sein, er bietet aber auch einige Vorteile.
Im hier vorliegenden Beispiel soll die Anwendung der grafischen Programmierung in einer Matlab-Simulink-Umgebung demonstriert werden. 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.
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 generierten Code der Implementierungsplattform. Objektorientierung bedeutet, die Daten in den Mittelpunkt der Betrachtung 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.
Bild 1 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 wie hier gezeigt es 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 wird 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 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 werden 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.
Der Autor:
* Dr. Hartmut Schorrig arbeitet seit über zwei Jahrzehnten für die Siemens AG. In den Jahren zuvor wurden in verschiedenen Forschungsinstituten und in der Wirtschaft Erfahrungen gesammelt, anfänglich in den 80-ger Jahren mit der Entwicklung eines Industrie-PCs „MC80“, damals selbstverständlich noch in Assembler. Schon mit dem Studium „Technische Kybernetik und Automatisierungstechnik“ an der TH Ilmenau wurde der Blick auf den Zusammenhang von Elektronik, Regelungstechnik und Software gerichtet. Für das hier beschriebene System sind weitere Informationen über www.vishia.org/smlk abrufbar.
:quality(80)/images.vogel.de/vogelonline/bdb/1266600/1266608/original.jpg)
Modellierung
Objektorientierung und modellbasierte Werkzeuge
:quality(80)/images.vogel.de/vogelonline/bdb/1266600/1266600/original.jpg)
Codegenerierung – was man damit (nicht) machen kann
(ID:45365876)