Entwicklung und Implementierung einer Schnittstelle zwischen
Transcription
Entwicklung und Implementierung einer Schnittstelle zwischen
Entwicklung und Implementierung einer Schnittstelle zwischen Prozessmodellen in Simulink und in C / C++ implementierten Steuerungen/Regelungen Seminararbeit im Rahmen des Studienganges „Scientific Programming“ Aachen, den 15.12.2011 Verfasser: Daniel Schnitzler Matrikel-Nr.: 836360 Betreuer: Prof. Dr. Andreas Terstegge, FH Aachen Dr.-Ing. Michael Mlynski, EUtech Scientific Engineering GmbH Inhaltsverzeichnis 1. Einleitung...................................................................................................................... 4 2. Verwendete Technologien ........................................................................................... 5 2.1 Dynamic Link Libraries ....................................................................................... 5 2.2 MATLAB/Simulink ............................................................................................. 5 2.2.1 Durchführung einer Simulation ......................................................................6 2.2.2 S-Functions.....................................................................................................7 2.3 3. Regelungstechnik .................................................................................................8 Die Anforderungen ....................................................................................................10 3.1 Anbindung der Regelung in der EshSim ............................................................ 10 3.2 Anbindung der Regelung an einen Evaluierungsprototypen .............................. 11 4. Analyse und Umsetzung ............................................................................................ 11 4.1 Der Energiemanager ........................................................................................... 11 4.1.1 Implementierung einer C/C++ DLL............................................................. 12 4.1.2 Implementierung des Energiemanagers ....................................................... 12 4.2 S-Function ..........................................................................................................13 4.2.1 Verwendung einer C/C++-DLL ...................................................................13 4.2.2 Implementierung der S-Function .................................................................15 4.3 5. Einbettung in C#.................................................................................................16 4.3.1 Einbettung einer unmanaged DLL in C# ..................................................... 17 4.3.2 C++ Strings in C#......................................................................................... 18 4.3.3 Einbettung des Energiemanagers in C# ....................................................... 19 Test .............................................................................................................................. 21 5.1 Das Testmodell ...................................................................................................21 5.2 Die Implementierung der neuen Schnittstelle .................................................... 23 5.3 Laufzeitanalyse ...................................................................................................24 6. Fazit/Ergebnisse .........................................................................................................25 7. Ausblick Bachelorarbeit ............................................................................................ 25 8. Quellenverzeichnis .....................................................................................................26 8.1 Literaturverzeichnis ............................................................................................ 26 8.2 Tabellenverzeichnis ............................................................................................ 26 8.3 Abbildungsverzeichnis ....................................................................................... 27 2 9. Eidesstaatliche Erklärung ......................................................................................... 28 10. Anhang .............................................................................................................. 29 10.1 Energiemanager.h ............................................................................................... 29 10.2 Energiemanager.cpp ........................................................................................... 30 10.3 EM_Aux.h ..........................................................................................................31 10.4 EM_Aux.cpp ......................................................................................................32 10.5 S-Function Energiemanager_SFun.c ..................................................................33 10.6 EnergieManagerWrapper.cs ............................................................................... 38 10.7 Kompilierung unter 64bit mit Visual Studio Express Edition ........................... 39 10.8 Verwenden von unmanaged Code mit Visual Studio Express Edition .............. 40 3 1. Einleitung Das Softwareprodukt EshSim (Energy supply for homes) der Firma EUtech Scientific Engineering GmbH simuliert auf Basis physikalischer Modelle Gebäude mit ihren Energieversorgungsanlagen, insbesondere Heizungsanlagen. Der Anwender kann das Gebäude (Größe, Wärmedämmstandard, Standort, Verbraucherverhalten etc.), die Energieversorungsanlage (Typ, Aufbau, Dimension etc.) und die Randbedingungen (Tarife, Investitionskosten etc.) in der Benutzeroberfläche vorgeben. Auf Basis dieser Informationen liefert die Simulation innerhalb weniger Minuten ein detailliertes Bild über das Betriebsverhalten der Energieversorgungsanlage. So können u.a. Verbrauch, entstandene Kosten, Emissionen etc. für ein Jahr berechnet werden [1]. Bezüglich der Regelung kann der Anwender zwar eine Regelparametrierung vorgeben, eine Änderung der Regellogik ist jedoch nicht möglich. Die EshSim besteht aus zwei Komponenten. Die Benutzeroberfläche (in C# implementiert) und die Simulation (in Simulink implementiert). In der Simulation ist auch die Regellogik implementiert. Änderungen an den Simulationsmodellen können zurzeit nur von der Firma EUtech durchgeführt werden. Der Anwender hat keine Möglichkeit, seine eigene Regellogik zu implementieren. Daraus resultierte die Anforderung, dass der Anwender die Regellogik seiner Energieversorgungsanlage selber implementieren kann. Es soll über eine Schnittstelle möglich sein, diese Regellogik an das Simulationsmodell anzudocken. Darüber hinaus soll es möglich sein, diese Regellogik auch in einen Prototyp einer realen Anlage einzusetzen. Das Ziel dieser Seminararbeit ist daher die Entwicklung und Implementierung einer Schnittstelle zwischen der Regelung des Anwenders und der Simulation. Da die Regelung auch intelligent sein kann, wird nachfolgend von einem Energiemanager gesprochen. Im folgenden Kapitel wird der Begriff „Dynamic Link Library“ und die verwendete Entwicklungsumgebung Simulink näher erläutert. Das Kapitel 3 beschäftigt sich mit den Anforderungen an die Schnittstelle. Die Analyse und Implementierung sind Thema des Kapitels 4; im Kapitel 5 wird der Test der neuen Schnittstelle dargestellt. Abschließend werden ein Fazit und ein Ausblick auf die darauf aufbauende Bachelorarbeit gegeben. 4 2. Verwendete Technologien 2.1 Dynamic Link Libraries Der Begriff Dynamic Link Libraries stammt aus der Microsoft Windows Umgebung. Dynamic Link Libraries (DLLs) sind Bibliotheken (Libraries), die im Gegensatz zu statischen Bibliotheken (*.lib) nicht zur Kompilierzeit, sondern beim Start einer Applikation an diese angebunden werden. Wird bereits ein anderes Programm ausgeführt, das diese DLL ebenfalls verwendet, so braucht das Betriebssystem diese DLL nicht erneut zu laden [2]. Beim Kompilieren des Programmes wird eine „Import Address Table“ (IAT) erstellt. In dieser sind alle benötigten externen Methoden aufgeführt. Wird das erste Programm gestartet, das die DLL verwendet, so wird die IAT ausgelesen und die DLLs werden in den Speicher nachgeladen. Bei jedem Programm, das die DLL verwendet, wird ein Zähler inkrementiert und beim Beenden des Programmes dekrementiert. Dadurch wird sichergestellt, dass die DLL erst aus dem Speicher entladen wird, wenn kein Programm mehr auf die Funktionalität angewiesen ist [3]. Die Vorteile für die Verwendung von DLLs liegen klar auf der Hand [4]: Man spart Arbeitsspeicher, da die DLL nur einmal im Speicher liegt Man kann die Funktionalität mit einer anderen Programmiersprache verwenden, falls diese Programmiersprache DLLs einbinden kann Programmfunktionalität kann beim Anwender leichter ausgetauscht werden Man spart Zeit, da die Funktionalität nur einmal implementiert, kompiliert und getestet werden muss. Die Verwendung von DLLs führt allerdings auch Nachteile mit sich. Wenn verschiedene Programme verschiedene Versionen der DLL verwenden, so kann es zu einem Konflikt kommen, da bereits eine Version der DLL im Speicher liegt, jedoch eine andere Version benötigt wird. 2.2 MATLAB/Simulink Das Softwarepaket MATLAB (MATrix LABoratory) der Firma Mathworks dient zur numerischen Berechnung von naturwissenschaftlich-technischen/mathematischen Problemen unter der Zuhilfenahme von Matrizen. Es wird hauptsächlich zur Modellierung, der Datenerfassung, -analyse und -auswertung verwendet. Auf MATLAB baut Simulink auf. Simulink dient der graphischen Modellierung und der Simulation dynamischer Systeme. Es integriert sich nahtlos in die MATLAB IDE1. Die Idee von Simulink ist es, das gegebene System in Form eines Signalflussplanes darzustellen. Mit Simulink können beispielsweise Prozessschaltbilder in Computermodelle abgebildet werden. Dabei muss das Prozessschaltbild nicht zuerst in eine textuelle Form transformiert werden. 1 Integrierte Entwicklungsumgebung (integrated development environment) 5 Abbildung 2-1 Simulink Library Browser Abbildung 2-1 zeigt den Simulink Library Browser, eine Bibliothek mit bereits vorimplementierten Blöcken. Alle Blöcke sind nach einem einfachen Prinzip aufgebaut. Ein Block kann mehrere Ein- bzw. Ausgänge haben. Die Blöcke werden über Leitungen verbunden. Über diese Leitungen werden die Daten von einem Block in einen anderen Block transportiert. Die Art der Daten (Matrizen, Vektoren oder skalare Werte) ist flexibel handhabbar. Durch diese flexible Behandlung von Daten lässt sich Simulink sehr gut nutzen, um z.B. Regelkreise zu simulieren. Mehrere Blöcke können zu einem Subsystem zusammengefasst werden. Dies entspricht Funktionen in textuellen Programmiersprachen. Dadurch können komplexe Zusammenhänge überschaubarer implementiert werden. Diese Subsysteme können in einem Modell auch wiederverwendet werden [5]. 2.2.1 Durchführung einer Simulation Eine Simulation eines z.B. physikalischen Modells führt immer dazu, dass eine Vielzahl von Gleichungen berechnet werden müssen. Meistens sind dies keine einfachen, linearen Gleichungssysteme, die leicht gelöst werden können. Zudem sind nur die Differentialgleichungen bekannt und nicht die genauen Funktionen. Daher müssen diese numerisch gelöst werden. Dies führt dazu, dass das System in jedem Zeitschritt neu berechnet werden muss. Hierzu gibt es unterschiedliche numerische Verfahren. 6 Abbildung 2-2 Ablauf einer Simulation [6] Die Abbildung 2-2 zeigt den Ablauf einer Simulation. Zunächst wird das gesamte Modell initialisiert. Hierbei wird auch festgelegt, in welcher Reihenfolge die Blöcke im Modell ausgeführt werden. Dann wird der erste Zeitschritt ausgeführt und die Ausgänge jeden Blocks und ihre Ableitungen berechnet. Wird ein diskreter Solver verwendet – das bedeutet, das Modell wird in festgelegten Schrittweiten berechnet – so ist ein Simulationsschritt abgearbeitet. Verwendet der Solver hingegen variable Schrittweiten, also ein adaptives Verfahren zur Lösung der Differentialgleichungen, so muss zur genaueren Bestimmung der optimalen Schrittweite und damit des genaueren Verhaltens des Blockes noch einmal die Ausgangswerte des Blockes, sowie deren Ableitungen bestimmt werden. Dies passiert so lange, bis die Werte als gut angenommen werden können, das heißt, dass sie nicht zu stark vom richtigen Wert abweichen. 2.2.2 S-Functions Wie bereits beschrieben, lässt sich die Simulink Standardbibliothek um individuelle Blöcke erweitern. Dies können zum Beispiel die oben beschriebenen Subsysteme sein. Daneben gibt es aber auch die Möglichkeit, so genannte S-Functions zu verwenden. Der Begriff SFunctions steht für system-functions. Hierbei wird ein Block textuell implementiert. Der Code kann in FORTRAN, C/C++ oder in MATLAB geschrieben werden. In MATLAB programmierte S-Functions können direkt im Modell verwendet werden. S-Functions aus C/C++ bzw. FORTRAN müssen zunächst kompiliert werden und werden dann während der Ausführung des Modells dynamisch verwendet, analog zu den DLLs. 7 S-Functions verwenden eine bestimmte API2, die es erst möglich macht, kompilierte Funktionen zu verwenden. Daher müssen diese Funktionen einem klar definierten Interface entsprechen. Dieses Interface spiegelt den in Abbildung 2-1 angesprochenen Ablauf einer Simulation wider. Für jeden Zwischenschritt eines Simulationsschrittes gibt es eine Funktion in der SFunction (vgl. Tabelle 2-1). Simulation Stage S-Function Routine Initialization mdlInitializeSizes Calculation of next sample hit (variable sample mdlGetTimeOfNextVarHit time block only) Calculation of outputs mdlOutputs Update of discrete states mdlUpdate Calculation of derivatives mdlDerivatives End of simulation tasks mdlTerminate Tabelle 2-1- Methodenübersicht S-Function [6] 2.3 Regelungstechnik Die Regelungstechnik ist ein Teilgebiet der Ingenieurswissenschaften. Aufgabe einer Regelung ist es, eine zu regelnde Größe unabhängig von Störeinflüssen zu halten. Es soll stets ein bestimmten Wert (Sollwert) eingehalten werden [7]. Dazu wird in jedem Zeitschritt der aktuelle Wert bestimmt. Mit der Differenz aus dem aktuellen Wert und dem Sollwert werden Eingriffe auf die Eingangsgrößen des Prozesses ermittelt. Dieses Vorgehen lässt sich gut am Beispiel eines Heizungsthermostat illustrieren (vgl. Abbildung 2-3). Aufgabe der Heizung ist es, die Temperatur in einem Zimmer auf einem bestimmten Temperaturniveau zu halten. Sinkt die Zimmertemperatur, so soll die Heizung heizen. Ist es dagegen zu warm, soll die Heizung abschalten. Damit der Regler weiß, ob der Wärmebedarf des Zimmers steigt oder sinkt, wird zu jedem Zeitpunkt eine Differenz zwischen der eingestellten Wunschtemperatur (Sollwert) und der gemessenen Temperatur (Istwert) berechnet. Der Regler leitet aus der Differenz ab, wie er die Aktoren 3 zu steuern hat. Dabei muss er beachten, dass sich die Erhöhung der Wärmemenge der Heizung nur mit starker Verzögerung auf die Zimmertemperatur auswirkt. 2 Application Programming Interface 3 Aktoren formen Stromsignale in andere physikalische Größen (z.B. die Stellung eines Ventils) um [12]. 8 Abbildung 2-3 Temperaturregelung 9 3. Die Anforderungen In der aktuellen Implementierung der EshSim gibt es zwei Komponenten (vgl. Abbildung 3-1). Der Anwender gibt die Parametrierung der Simulation in einer Benutzeroberfläche ein. Diese ist in C# programmiert. Die Benutzeroberfläche ruft das Simulationsmodell auf. Das Simulationsmodell gibt nach erfolgter Simulation die Ergebnisse an die Benutzeroberfläche zurück. Abbildung 3-1 Aktuelle Implementierung der EshSim In der neuen Version der EshSim wird die Regelung als eigene Komponente implementiert. Die Anbindung des Energiemanagers soll in zwei unterschiedlichen Umgebungen möglich sein: Anbindung der Regelung in eine Simulationsumgebung (EshSim) Anbindung der Regelung an einen Evaluierungsprototypen Diese beiden Verwendungsszenarien des Energiemanagers werden im folgenden Kapitel näher erläutert. 3.1 Anbindung der Regelung in der EshSim Der Energiemanager sollte an das Simulationsmodell der EshSim angebunden werden. Dazu werden S-Functions verwendet. Sowohl die S-Function als auch der Energiemanager werden in C/C++ implementiert. Der Energiemanager wird der Simulation als DLL zur Verfügung gestellt, sodass er vom Anwender durch eigene Regellogik ersetzt werden kann. Abbildung 3-2 Konzept der Schnittstelle in der EshSim Abbildung 3-2 zeigt das neue Konzept bezüglich der Anbindung des Energiemanagers in die EshSim. Der Energiemanager ist nicht mehr Teil der Simulation, sondern wird als unabhängige Komponente von der Simulation aufgerufen. 10 3.2 Anbindung der Regelung an einen Evaluierungsprototypen Neben der Anbindung an die EshSim sollte es auch möglich sein, den Energiemanager als Regelung zur Evaluierung eines Prototypens einer Energieversorgungsanlage zu verwenden. Dazu wird ein Computer verwendet, auf dem eine C#-Applikation läuft. Sie übernimmt die Kommunikation zwischen Energiemanager und der realen Energieversorgungsanlage. Die Kommunikation kann über verschiedene Protokolle stattfinden, z.B. ModbusTCP. Abbildung 3-3 Konzept der Schnittstelle mit einer realen Energieversorgungsanlage Im folgenden Kapitel wird erläutert, welche Schritte notwendig sind, wie der Energiemanager zu implementieren ist, und wie er in einer S-Function bzw. einer C#-Applikation verwendet werden kann. 4. Analyse und Umsetzung 4.1 Der Energiemanager Wie in Kapitel 3 beschrieben, sollte der Energiemanager in C/C++ implementiert werden. Eine wichtige Anforderung war, dass die Trennung von Simulation und Regelung die Rechenzeit nicht beeinträchtigt. Daher wurde die Implementierung in unmanaged Code vorgenommen. In der Windows-Umgebung mit dem Visual Studio als Entwicklungsumgebung für C/C++ stehen 2 Arten von Code zur Verfügung: managed Code unmanaged Code 11 Bei managed Code wird die Instanz der Applikation in einer virtuellen Maschine ausgeführt, die vom .NET-Framework bereitgestellt wird. Der Code wird daher gemanaged (verwaltet). Die Ausführung in einer virtuellen Maschine bietet zwar eine hohe Sicherheit für die Applikation, doch erhöht dies die Ausführungszeit. Daher wurde für den Energiemanager die Variante des unmanaged Codes verwendet. Unmanaged Code, auch als nativer Code bezeichnet, ist die ursprüngliche Version von C/C++-Code, wie er auch im ISO-Standard festgehalten wurde. Dieser C/C++-Code ist unabhängig von der Windows-Umgebung und ist daher portabel. Wie man mit dem Visual Studio Express Edition ein Projekt für unmanaged Code anlegt, sowie die Kompilierung einer Applikation für eine 64bit-Umgebung sind dem Anhang zu entnehmen. 4.1.1 Implementierung einer C/C++ DLL Der Energiemanager sollte als C/C++-DLL implementiert werden. Daher wird zunächst erläutert, wie eine allgemeine Implementierung einer DLL aussieht. Im Quelltext einer DLL muss jede Funktion, die außerhalb der DLL aufgerufen werden kann, als exportierte Funktion explizit deklariert werden: __declspec(dllexport) double MyFunction(int a, int b); Die Bezeichnung __declspec(dllexport) beschreibt zum einen, dass diese Funktion außerhalb der DLL verwendet werden kann. Zum anderen wird über __declspec aber auch dem Compiler mitgeteilt, wie eine Übergabe der Parameter beim Aufruf dieser Funktion stattfinden soll[8]. 4.1.2 Implementierung des Energiemanagers Die Defintion des Energiemanagers sieht wie folgt aus: // returns the current version of the DLL __declspec(dllexport) void GetVersion(short *m, short *s, short *b, short *i); // returns the last date the DLL has changed __declspec(dllexport) void GetDate(short *day, short *month, short *year); // returns the DLL's author __declspec(dllexport) void GetAuthor(char* test, size_t buffer_size); // returns the DLL author's Company __declspec(dllexport) void GetCompany(char* test, size_t buffer_size); // initializes the Energy manager __declspec(dllexport) int EM_Initialize(…); // sets the Configuration of the Energy manager __declspec(dllexport) void EM_SetConfig(…); // executes one step of the controlling 12 __declspec(dllexport) void EM_Execute(…); Die Funktionen GetVersion(…), GetDate(…), GetAuthor(…)und GetCompany(…) liefern jeweils Informationen über die vorliegende Version der DLL. So kann überprüft werden, in welcher Version die DLL vorliegt, und wer die Kontaktperson bezüglich dieser DLL ist. Die Funktionen EM_Initialize(…) überprüft zum einen die Kompatibilität zur aufrufenden Applikation und initialisiert zum anderen Variablen, die zur Speicherung der historischen Daten verwendet werden. In der DLL werden Arrays einer definierten Länge übergeben. Zur Laufzeit muss sichergestellt werden, dass die übergebenen Arrays mindestens die von der DLL vorausgesetzte Länge haben. Sonst kann es bei Zugriffen auf den Speicher zu Fehlern kommen. Dazu wird der Methode EM_Initialize die Länge der zu übergebenden Arrays mitgeteilt. Widersprechen die angegebenen Längen sich, so wird eine Fehlernummer als Integer-Wert zurückgegeben. Anhand der Fehlernummer kann man erkennen, bei welchem Feld die Länge nicht stimmt. Für die Auswertung dieser Fehlernummer ist jedoch der Aufrufer der DLL zuständig. Um die Länge der Felder zumindest zwischen DLL und S-Function von Anfang an konsistent zu halten, wurden Defines verwendet. Defines sind Texterkersetzungen, die vom Präprozessor vor dem eigentlichen Kompilier-Vorgang ausgeführt werden. Diese Definitionen wurden in einer separaten Header-Datei implementiert, die sowohl in der DLL als auch in der S-Function inkludiert ist. Die Funktion EM_SetConfig parametriert die Regelung, indem sie alle Parameter speichert, die sich zur Laufzeit nicht ändern können. (z.B. Minimale und Maximale Temperaturangeben). Dadurch kann die Anzahl der Parameter der Methode EM_Execute stark verringert werden. Der Kern der DLL ist die Funktion EM_Execute. Sie beinhaltet die eigentliche Regelung. Sie berechnet mithilfe der aktuellen Sensorwerte, der Konfiguration und den historischen Daten die auszugebenden Aktorwerte. Dafür sind natürlich aufgrund der Komplexität des Reglers mehrere Unterfunktionen nötig, die von EM_Execute aufgerufen werden. Diese müssen allerdings nicht nach außen sichtbar sein und werden nur von der Funktion EM_Execute verwendet. Eine exemplarische Implementierung der DLL befindet sich im Anhang. 4.2 S-Function Die S-Function (vgl. Abbildung 3-2) ist die Verknüpfung zwischen der Simulation in Simulink und dem Energiemanager in C++. Sie ist in C implementiert. Zunächst wird erläutert, welche Möglichkeiten es gibt, eine C/C++-DLL in einer C/C++-Applikation zu verwenden. Im Kapitel 4.2.2 wird auf die konkrete Implementierung dieser S-Function eingegangen. 4.2.1 Verwendung einer C/C++-DLL Für die Einbindung einer C/C++-DLL in einer C/C++-Applikation gibt es 2 Möglichkeiten: 13 Explizite Bindung Implizite Bindung Diese werden in den folgenden Unterkapiteln näher erläutert. 4.2.1.1 Explizite Bindung Bei der expliziten Bindung wird die DLL erst zur Laufzeit programmatisch geladen. Lediglich die Implementierung der aufrufenden Applikation ändert sich. Der Aufruf sieht folgendermaßen aus[8]: typedef int (MYFUNCTION) (int, int); HINSTANCE hInstance; MYFUNCTION *myfunc; VERIFY(hInstance = ::LoadLibrary(“C:\MyDLL.dll”)); VERIFY(pFunction = (MYFUNCTION*)::GetProgAddress(hInstance, “MyFunction)); int d = (*pFunction)(5, 4); //Aufruf der eigentlichen DLL Das in diesem Beispiel verwendete Makro VERIFY ist ähnlich dem Makro ASSERT. Es ist nicht Teil des Ansi C/C++-Standards. VERIFY liefert einen Fehler, wenn die Applikation im Debug-Modus kompiliert wurde und die Zuweisung für hInstance bzw. pFunction zu Fehlern führte. Im kompilierten Release-Modus wird VERIFY ignoriert. Wie man an diesem Beispiel sieht, ist der Aufwand relativ hoch, bis eine Funktion der DLL verwendet werden kann. Allerdings erhöht sich auch die Flexibilität. Zur Laufzeit kann entschieden werden, welche Implementierung von MyFunction man aus welcher DLL importieren möchte. Bei dieser Lösung müssen jedoch der absolute bzw. relative Pfad und der Name der DLL bekannt sein. 4.2.1.2 Implizite Bindung Bei der impliziten Bindung wird die DLL beim Start der Applikation geladen und kann danach ohne Aufwand verwendet werden. Methoden aus der DLL können genauso verwendet werden, wie Methoden, die innerhalb der Applikation implementiert wurden. So muss eine Applikation, die z.B. die Methode MyFunction(int a, int b) verwenden will, lediglich die Signatur dieser Methode bekannt gemacht werden. Dies geschieht über folgende Funktionssignatur: extern __declspec(dllimport) double MyFunction(int a, int b); Das Schlüsselwort extern weist darauf hin, dass die Funktion außerhalb der Applikation deklariert wurde. Mit __declspec(dllimport) wird beschrieben, dass diese Funktion aus einer DLL importiert wird. Diese Zeilen weisen den Compiler, dass diese Funktionalität von einer anderen Datei zur Verfügung gestellt wird. Erst der Linker löst diese Abhängigkeit auf. Beim Linken der Applikation wird eine *.lib-Datei der DLL zugelinkt. Diese *.lib-Datei hält die Funktionssignaturen bereit. Sie enthält keinerlei Informationen über die Implementierung der Funktionen. 14 Zur Laufzeit wird die DLL dann beim Start der Applikation geladen. Da zum Zeitpunkt des Kompilierens noch kein Pfad zur DLL angegeben werden kann, kann dieser erst beim Start der Applikation gesucht werden. Dazu werden 5 verschiedene Orte durchsucht: 1. Das Verzeichnis, in dem sich die Applikation befindet. 2. Das Verzeichnis, in dem die Applikation ausgeführt wird. 3. Das Windows-Systemverzeichnis 4. Das Windows-Verzeichnis 5. Alle Verzeichnisse, die sich in der Path-Variablen von Windows befinden. Diese 5 Orte werden in der hier angegebenen Reihenfolge durchsucht. Wird die DLL in einem der Orte gefunden, so wird die Suche abgebrochen. Dies kann natürlich zu einem Problem führen. Befindet sich die DLL in einem Verzeichnis, das dem System über die Path-Variable bekannt ist, und man möchte diese DLL einbinden, so wird über alle 4 vorherigen Orte gesucht. Dabei besteht das Risiko, dass eine DLL mit gleichen Namen (vielleicht sogar eine alte Version der DLL) in einem Verzeichnis mit höherer Suchpriorität liegt. Bei Inkompatibilität zwischen der DLL und der Applikation kann dies zu einem Absturz der Applikation führen. Der gängige Weg ist es, daher die DLL neben die Applikation zu legen, da dieser Pfad die Suchpriorität 1 hat. In diesem Konzept konnte auf die Variante der impliziten Bindung zurückgegriffen werden. Für die S-Function ist eine flexible Handhabung der DLL, wie sie die explizie Bindung ermöglicht, nicht notwendig. 4.2.2 Implementierung der S-Function Für die S-Function wurde die Vorlage von Mathworks verwendet. Wie in Kapitel 2.2.1 beschrieben, muss für jeden Schritt innerhalb des Solvers eine Methode in der S-Function vorhanden sein. Wird die Methode nicht benötigt, so bleibt die Funktionsdefinition leer. Da die EshSim ein Modell mit einer festen Schrittweite verwendet, kann der als „Integration (minor time step)“ beschriebene Teil in Abbildung 2-1 außer Acht gelassen werden. Es müssen nur die folgenden Methoden implementiert werden: mdlInitializeSizes(SimStruct *S) mdlStart(SimStruct *S) mdlOutputs(SimStruct *S, int_T tid) Die Funktion mdlInitializeSizes(…) initialisiert die S-Function. Sie beinhaltet die Informationen über Anzahl der Eingänge, sowie Anzahl der Ausgänge des durch die SFunction beschriebenen Blockes. In dieser Funktion wird für jedes Ein- bzw. Ausgangssignal wird der Datentyp und die Anzahl der Elemente festgelegt. In der Funktion mdlStart(…) wird die Funktion EM_Initialize(…) der DLL aufgerufen. Liefert die Funktion EM_Initialize einen Wert ungleich 0 zurück, so ist davon aus15 zugehen, dass die vorliegende Version der DLL nicht kompatibel ist. In diesem Fall wird die Funktion ssGetError(…) aus der S-Function API aufgerufen. Diese beendet die Simulation direkt. Der Anwender wird über die Simulink-internen Fehlerbehandlungen auf den Fehler aufmerksam gemacht. Die Fehlermeldung kann in der S-Function parametriert werden. In jedem Simulationsschritt wird die Funktion mdlOutputs(…) aufgerufen. Diese Methode erhält von Simulink Zeiger auf die Ein- bzw. Ausgangssignale des Blockes und soll die Ausgangswerte des Blockes berechnen. Dazu wird die Funktion EM_Execute des Energiemanagers aufgerufen. Im ersten Zeitschritt wird zusätzlich die Konfiguration des Reglers vorgenommen, indem die Funktion EM_SetConfig aufgerufen wird. Abbildung 4-1 Anbindung der DLL Die Abbildung 4-1 zeigt, Verbindung der Simulation zum Energiemanager. EM_Initialize(…) wird einmalig zum Simulationsstart ausgeführt. EM_SetConfig(…) verwendet ein Teil der Eingänge des Blockes und wird daher in der mdlOutputs(…) der S-Function aufgerufen. Da sich die Konfiguration jedoch nicht während der Simulation ändert, wird diese Methode nur im ersten Zeitschritt aufgerufen. Jedoch wird in jedem Zeitschritt die Methode EM_Execute von der S-Function aufgerufen. Diese berechnet die Ausgangssignale des Blockes. 4.3 Einbettung in C# Um den Energiemanager in C# einzubinden, muss auf die Technik des Platform Invokation Services (P/Invoke) zurückgegriffen werden. P/Invoke ist ein Dienst, über den unmanaged Code in C# eingebunden werden kann. In diesem Kapitel wird zu nächst auf die allgemeine Einbindung einer unmanaged DLL in C# eingegangen und wie die Übergabe von C#-String im Speziellen durchgeführt werden kann. Danach wird die Einbettung des Energiemanagers in eine C#-Applikation behandelt. 16 4.3.1 Einbettung einer unmanaged DLL in C# Abbildung 4-2 Aufruf einer unmanaged DLL aus C# [9] Abbildung 4-2 beschreibt, wie der Aufruf einer unmanaged DLL aus C# aussieht. P/Invoke regelt hierbei den Aufruf der Methode aus der unmanaged DLL. Der Standard marshaling service (Marshaler) übernimmt die Analyse der Funktionsparameter. Es muss nämlich sichergestellt werden, dass ein Wert sowohl in der unmanaged DLL als auch in C# gleich interpretiert wird. Dieser Sachverhalt wird in Kapitel 4.3.2 am Beispiel von Strings näher erläutert. Um eine unmanaged DLL zu verwenden, muss in C# der Namespace System.Runtime.InteropServices eingebunden werden, da dieser das Attribut DLLImport enthält. Das Attribut DLLImport kann parametriert werden (vgl. Tabelle 4-1). Parameter Beschreibung Public bool Aktiviert oder deaktiviert das Best-Fit Mapping Verhalten bei der Konvertierung eines Unicode Zeichens in ein ANSI Zeichen. Der Marshaller sucht nach der besten Übereinstimmung falls das Zeichen nicht eindeutig abgebildet werden kann. Standardmäßig ist dieser Wert auf true gesetzt. BestFitMapping Public CallingConvention Spezifiziert die Aufrufkonvention. Standardmäßig ist das WinAPI, was bei 32-Bit Intel-basierten Plattformen CallingConvention __stdcall entspricht. Public CharSet CharSet Gibt an wie das Marshaling von String-Argumenten der Methode durchgeführt wird und kontrolliert das Name Mangling. 17 Public string EntryPoint Public bool ExactSpelling Public bool PreserveSig Public bool SetLastError Public bool ThrowOnUnmappableChar Gibt den Namen des aufzurufenden DLL-Eintrittspunkts (der Methode) an. Falls kein Argument übergeben wurde wird der Funktionsname verwendet. Kontrolliert ob das DllImportAttribute.CharSet Feld verursacht das die CLR nach einem anderen als dem spezifizierten Namen als Eintrittspunkt sucht. Erlaubt der CLR also, auf Basis des Wissens das die CLR über Namenskonventionen hat, nach übereinstimmenden Methoden mit leicht abweichenden Namen in der DLL zu suchen. Zeigt an ob die Signatur eine direkte Übersetzung des unverwalteten Eintrittspunktes ist. Wenn dieser Parameter auf true gesetzt ist, besteht die Möglichkeit, Marshal-GetLastWin32 Error aufzurufen und dadurch zu prüfen ob beim Aufruf der Methode ein Fehler aufgetreten ist. Aktiviert oder deaktiviert das Auslösen einer Exception bei einem nicht einzuordnenden Unicode Zeichen das in das ANSI Zeichen "?" konvertiert wird. Public Der Name der aufzurufenden DLL. DLLImportAttribut(string dllName) Tabelle 4-1 Parametrierung eines DLLImports [9] Für eine beispielhafte Funktion MyFunction(int a, int b) könnte eine Einbindung in C# wie folgt aussehen: [DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern void MyFunction(int a, int b); 4.3.2 C++ Strings in C# Ruft man aus einer C#-Methode eine Funktion der C/C++-DLL auf, so muss sichergestellt werden, dass die übergebenen Parameter in beiden Funktionen konsistent sind. So muss zum Beispiel bei Integer-Werten klar sein, ob diese in beiden Programmiersprachen als „Little Endian“ oder als „Big Endian“4 interpretiert werden. Bei den meisten Datentypen, insbesondere bei den primitiven Datentypen, übernimmt der Marshaler diese Aufgabe. Bei einigen 4 Little bzw. Big Endian gibt an, in welcher Reihenfolge die Bytes gespeichert werden. Bei Little Endian wird das Byte mit der niedrigsten Stelle zuerst gespeichert; bei Big Endian das Byte mit der größten Stelle [13]. 18 Datentypen, z.B. Objekten ist diese Interpretation nicht ohne eigenen Implementierungsaufwand möglich. String ist kein primitiver Datentyp. Im Grunde sind Strings Felder von Zeichen. In C++ gibt es verschiedene Implementierungen für Strings. Da es in der Programmiersprache C keine Klassen gibt (C implementiert keine Objektorientierung), werden hier Strings als Felder (Arrays) von Zeichen (character) angelegt, auf die ein Zeiger (char *) verweist. Nun stellt sich die Frage, wie man einen String in C# an einer Funktion in C übergeben kann, die keine Strings kennt. Diese Problematik kann über die Klasse StringBuilder in C# gelöst werden. Dazu muss die Funktion in C folgende Definition haben: // returns a C-String void ReturnString( char* mystr, size_t buffer_size) { strcpy_s(mystr, buffer_size, "This is just a test."); } Die Funktion erwartet also einen char* und die Länge des Bereichs, auf den der Zeiger deutet. Der erfahrene C-Entwickler wird bemerkt haben, dass diese Funktion die Methode strcpy_s anstatt der strcpy-Methode verwendet. Das Visual Studio warnt vor der Verwendung von strcpy. Die Methode kann zu Speicherzugriffsfehlern führen, falls der zu kopierende String mehr Zeichen besitzt als das Ziel. Speicherzugriffsfehler können nicht von der Fehlerbehandlung in C# gefangen werden [10]. Die Signatur in C# sieht wie folgt aus: private unsafe static extern void ReturnString(StringBuilder author, UIntPtr length) Die Methode wird wie folgt verwendet: StringBuilder strb = new StringBuilder(255); UIntPtr length = new UIntPtr((uint)strb.Capacity); ReturnString(strb, length); Console.WriteLine(strb.ToString()); Hierbei ist darauf zu achten, dass die Länge des StringBuilders mindestens der Länge des zu übergebenden String entspricht. Ansonsten kommt es zu einer Exception. 4.3.3 Einbettung des Energiemanagers in C# Für die Einbindung des Energiemanagers in C# wurden alle Funktionssignaturen der C#Applikation bekannt gemacht: [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private unsafe static extern void GetVersion(short *m, short *s, short *b, short *i); 19 [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private unsafe static extern void GetDate(short* day, short* month, short* year); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private unsafe static extern void GetCompany(StringBuilder company, UIntPtr length); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private unsafe static extern void GetAuthor(StringBuilder author, UIntPtr length); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public unsafe static extern void EM_Initialize(...); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public unsafe static extern void EM_SetConfig(...); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public unsafe static extern void EM_Execute(...); Die vollständige Implementierung kann dem Anhang entnommen werden. 20 5. Test 5.1 Das Testmodell Abbildung 5-1 Regelkreismodell Abbildung 5-1 zeigt ein einfaches Regelkreismodell mit einem PI-Regler. Der Regler erhält den Sollwert (w), den aktuellen Messwert (x), den Gewichtungsfaktor für den proportionalen Anteil (Kp) und den Gewichtungsfaktor für den integralen Anteil (TI), so wie eine Beschränkung der Stellgröße (y) auf y_max und y_min. Der Regler wirkt auf das Prozessmodell, das in diesem Fall durch eine einfache Differentialgleichung dargestellt werden kann. Im Modell ändert sich dadurch die Regelgröße und der neue Stellwert muss berechnet werden. Das Regelkreismodell aus Abbildung 5-1 könnte z.B. die Regelung eines Heizkörpers sein. In diesem Fall stellt die Regelung das Thermostat der Heizung ein. Das Thermostat kann auf beliebige Werte zwischen 0 und 5 gestellt werden. Der Block „Regler“ in der Abbildung 5-1 ist Subsystem. Seine Implementierung kann der Abbildung 5-2 entnommen werden. 21 Abbildung 5-2 Implementierung eines PI-Reglers Abbildung 5-3 Plot des Regelkreises (Simulink) Das Diagramm in Abbildung 5-3 zeigt die Veränderung des Sollwerts, der Regelgröße und der Stellgröße im Zeitintervall [0, 10]. 22 5.2 Die Implementierung der neuen Schnittstelle In der Abbildung 5-1 wurde das Subsystem „Regler“ durch das in dieser Seminararbeit entwickelte Konzept ersetzt. Abbildung 5-4 Implementierung des Reglers als S-Function Ein Diagramm dieser Simulation lieferte das Resultat in Abbildung 5-5. 23 Abbildung 5-5 Plot des Regelkreises (S-Function) Die Unterschiede zwischen Abbildung 5-3 und Abbildung 5-5 konnten auf unterschiedliche numerische Integratoren zurückgeführt werden. Diese beeinflussen maßgeblich den TIParameter des PI-Reglers. 5.3 Laufzeitanalyse Eine Anforderung an die Schnittstelle war die Laufzeitgeschwindigkeit. Die neue Schnittstelle durfte die Ausführungszeit des Modells nicht erheblich vergrößern. Bei einem Test wurde das Modell sowohl mit der Simulink-Implementierung als auch mit der Implementierung des neuen Konzeptes 1000-mal ausgeführt. Dabei wurde gezeigt, dass die neue Schnittstelle nicht mehr Laufzeit in Anspruch nahm, sondern sogar weniger. Die Zeit konnte durchschnittlich um ca. 40% gesenkt werden. Abbildung 5-6 Zeitvergleich der beiden Implementierungen 24 6. Fazit/Ergebnisse Aufgabe war es, für das Softwareprodukt EshSim eine klar definierte Schnittstelle zwischen der eigentlichen Simulation und der Regelung zu schaffen. Die Schwierigkeit bestand darin, dass die Simulation in einer graphischen Programmiersprache (Simulink) implementiert wurde, die Regelung jedoch in einer textuellen Programmiersprache (C/C++) implementiert werden muss. Diese Seminararbeit legt das Konzept der Schnittstelle dar und beschreibt die technische Ausarbeitung dieser Schnittstelle. Ebenfalls wurde die praktische Umsetzung der Schnittstelle getestet. Änderungen an der Regelung können nun auch durch den Anwender getätigt werden. Des Weiteren kann diese Regelung auch prototypisch an einer realen Energieversorgungsanlage getestet werden. Hier stellt sich der Mehrwert des Produktes EshSim durch diese Seminararbeit da. Zusätzlich zur Konzeption der Schnittstelle konnte in der praktischen Phase der Seminararbeit bereits mit der Reimplementierung des aktuellen Energiemanagers aus dem Simulink-Modell in eine C/C++-DLL begonnen werden. Die Implementierung wird in den folgenden Wochen fertig gestellt, da sie die Ausgangslage der Bachelorarbeit ist. 7. Ausblick Bachelorarbeit Die aktuelle Regellogik besteht aus verschiedenen Reglern, die der Anwender in der Benutzeroberfläche parametrieren kann. Die Parametrierung stellt für viele Anwender eine Herausforderung dar, da sie nicht über die notwendigen reglungstechnischen Grundkenntnisse verfügen. Im Rahmen der Bachelorarbeit soll ein „virtueller Idealregler“ entwickelt und implementiert werden, der die Parametrierung der Regler überflüssig macht. Dieser „virtuelle Idealregler“ stellt zu jedem Zeitpunkt selbstständig das korrekte Betriebsverhalten ein, so wie es ein optimal eingestellter PID-Regler einer echten Anlage ebenfalls machen würde. Für den Anwender entfällt damit die Notwendigkeit, die bisherigen Regler der Simulationsmodelle zu konfigurieren. Der „virtueller Idealregler“ soll in einer C/C++ DLL implementiert werden und über die in der Seminararbeit geschaffene Schnittstelle an das Simulinkmodell angedockt werden. Schwerpunkt der Realisierung ist dabei, ein stabiles Betriebsverhalten des „virtuellen Idealreglers“ unter allen Randbedingungen (unterschiedliche Gebäude, unterschiedliche Anlagen, unterschiedliche Medienflüsse, unterschiedliche Simulationsschrittweite, etc.) zu schaffen. 25 8. 8.1 Quellenverzeichnis Literaturverzeichnis [1] EUtech Scientific Engineering GmbH, Benutzerhandbuch EshSim, Aachen, 2011. [2] „Dynamic Link Library,“ Wikipedia, 16. Oktober 2011. [Online]. Available: http://de.wikipedia.org/wiki/Dynamic_Link_Library. [Zugriff am 15. November 2011]. [3] A. Voss, Vorlessungsskript IT-Systeme, Aachen, 2011. [4] Microsoft, „Vorteile der Verwendung von DLLs,“ Microsoft, [Online]. Available: http://msdn.microsoft.com/de-de/library/dtba4t8b%28v=vs.80%29.aspx. [Zugriff am 5. Dezember 2011]. [5] K. Taubert, Theorie und Numerik von Differentialgleichungen mit MATLAB und SIMULINK, Hamburg: Universität Hamburg, 2008. [6] Mathworks, Developing S-Functions, Natick: Mathworks, Inc., 2011. [7] M. Reuter und Z. Serge, Regelungstechnik für Ingenieure, Vieweg+Teubner Verlag, 2011. [8] D. Kruglinski, Inside Visual C++ 6.0, Microsoft Press, 2005. [9] CodePlanet, „P/Invoke Grundlagen,“ CodePlanet, 5. Mai 2006. [Online]. Available: http://www.codeplanet.eu/tutorials/csharp/6-pinvoke-grundlagen.html. [Zugriff am 7. Dezember 2011]. [10] Microsoft, „strcpy_s, wcscpy_s, _mbscpy_s,“ [Online]. Available: http://msdn.microsoft.com/de-de/library/td1esda9%28v=vs.80%29.aspx. [Zugriff am 13. Dezember 2011]. [11] F. Eller, Visual C# 2005, Addison-Wesley Verlag, 2006. [12] ITWissen, „Aktor,“ [Online]. [Zugriff am 01. Dezember 2011]. [13] ITWissen, „Little-Endian-Format,“ [Online]. Available: http://www.itwissen.info/definition/lexikon/little-endian-Little-Endian-Format.html. [Zugriff am 14. November 2011]. 8.2 Tabellenverzeichnis Tabelle 2-1- Methodenübersicht S-Function.........................................................................8 Tabelle 4-1 Parametrierung eines DLLImports [9] .............................................................. 18 26 8.3 Abbildungsverzeichnis Abbildung 2-1 Simulink Library Browser .............................................................................6 Abbildung 2-2 Ablauf einer Simulation [6] ...........................................................................7 Abbildung 2-3 Temperaturregelung ....................................................................................... 9 Abbildung 3-1 Aktuelle Implementierung der EshSim........................................................ 10 Abbildung 3-2 Konzept der Schnittstelle in der EshSim ..................................................... 10 Abbildung 3-3 Konzept der Schnittstelle mit einer realen Energieversorgungsanlage .......11 Abbildung 4-1 Anbindung der DLL..................................................................................... 16 Abbildung 4-2 Aufruf einer unmanaged DLL aus C# [9] .................................................... 17 Abbildung 5-1 Regelkreismodell ......................................................................................... 21 Abbildung 5-2 Implementierung eines PI-Reglers............................................................... 22 Abbildung 5-3 Plot des Regelkreises (Simulink) .................................................................22 Abbildung 5-4 Implementierung des Reglers als S-Function .............................................. 23 Abbildung 5-5 Plot des Regelkreises (S-Function) .............................................................. 24 Abbildung 5-6 Zeitvergleich der beiden Implementierungen .............................................. 24 27 9. Eidesstaatliche Erklärung Hiermit versichere ich, dass ich die Seminararbeit mit dem Thema Entwicklung und Implementierung einer Schnittstelle zwischen Prozessmodellen in Simulink und in C / C++ implementierten Steuerungen/Regelungen selbstständig verfasst und keine anderen als die angegebenen Quellen und Hilfsmittel benutzt habe, alle Ausführungen, die anderen Schriften wörtlich oder sinngemäß entnommen wurden, kenntlich gemacht sind und die Arbeit in gleicher oder ähnlicher Fassung noch nicht Bestandteil einer Studien- oder Prüfungsleistung war. Name: Aachen, den Unterschrift der Studentin / des Studenten 28 10. Anhang 10.1 Energiemanager.h #ifndef _ENERGIEMANAGER_H_ #define _ENERGIEMANGER_H_ #include <iostream> extern "C" { typedef struct { double Kp; double TI; double y_max; double y_min; } SConfiguration; // returns the current version of the DLL __declspec(dllexport) void GetVersion( short short short short *m, *s, *b, *i); // returns the last date the DLL has changed __declspec(dllexport) void GetDate( short *day, short *month, short *year); // returns the DLL's author __declspec(dllexport) void GetAuthor( char* test, size_t buffer_size); // returns the DLL author's Company __declspec(dllexport) void GetCompany( char* test, size_t buffer_size); // initializes the Energymanager __declspec(dllexport) void EM_Initialize(); // sets the configuration for the Energymanager __declspec(dllexport) void EM_SetConfig( double double double double Kp, TI, y_max, y_min); // executes one step of the controlling __declspec(dllexport) void EM_Execute( double double double double T, w, x, *y); } #endif 29 10.2 Energiemanager.cpp #include "Energiemanager.h" #include "EM_Aux.h" extern "C" { // static PID Members // static configuration SConfiguration config; CIntegral integral; double prev_T; // returns the current version of the DLL void GetVersion( short *m, short *s, short *b, short *i) { *m = 1; *s = 0; *b = 0; *i = 1; } void // returns the last GetDate( short short short date the DLL has changed *day, *month, *year) { *day = 27; *month = 10; *year = 2011; } // returns the DLL's author void GetAuthor( char* author, size_t buffer_size) { strcpy_s(author, buffer_size, "Daniel Schnitzler"); } // returns the DLL author's Company void GetCompany( char* company, size_t buffer_size) { strcpy_s(company, buffer_size, "EUtech Scientific"); } // initializes the Energymanager void EM_Initialize() { integral.Initialize(0); prev_T = 0; } void EM_SetConfig( double double double double Kp, TI, y_max, y_min) { config.Kp = Kp; 30 config.TI = TI; config.y_max = y_max; config.y_min = y_min; } // executes one step of the controlling void EM_Execute( double T, double w, double x, double *y) { double e = w - x; double delta_T = T - prev_T; double integ = integral.Int(e, delta_T); double output = config.Kp*(e + (config.TI * integ)); if (output > config.y_max) { output = config.y_max; } else if (output < config.y_min) { output = config.y_min; } y[0] = output; } } 10.3 EM_Aux.h #ifndef _EM_AUX_H_ #define _EM_AUX_H_ /////////////////////////////////////////////////////////////////////////////// /// /// /// CIntegral /// /// /// /////////////////////////////////////////////////////////////////////////////// class CIntegral { //Constructor and Destructor public: CIntegral(); virtual ~CIntegral(); // Variablen public: double pre_y_value; double pre_i_value; // Methoden: Initialisierung und Hilfsmethoden public: void Initialize(double int_value); double Int( double y, double Ts); double getValue(); }; #endif 31 10.4 EM_Aux.cpp #include "EM_Aux.h" CIntegral::CIntegral() { pre_y_value = 0; pre_i_value = 0; } CIntegral::~CIntegral() { } void CIntegral::Initialize(double int_value) { pre_y_value = 0; pre_i_value = int_value; } double CIntegral::Int( double y, double Ts) { // Variables double integral_value; // Operation integral_value = pre_i_value + Ts * pre_y_value + Ts * (pre_y_value+y)/2.0; //Store values and results to memories pre_y_value = y; pre_i_value = integral_value; return integral_value; } double CIntegral::getValue() { return pre_i_value; } 32 10.5 S-Function Energiemanager_SFun.c //======================================================================== // 1. Standard defines and includes //======================================================================== #define S_FUNCTION_NAME Energiemanager_SFun #define S_FUNCTION_LEVEL 2 #include "simstruc.h" // include simstruc.h for the definition of the SimStruct and its associated macro definitions //======================================================================== // DLL import //======================================================================== extern __declspec(dllimport) extern __declspec(dllimport) void void EM_Initialize(); EM_SetConfig( double Kp, void EM_Execute( double TI, double y_max, double y_min); extern __declspec(dllimport) double T, double w, double x, double *y); //======================================================================== // Parameters, constants, global variables, etc. //======================================================================== //======================================================================== // S-function methods //======================================================================== /* Function: mdlInitializeSizes =============================================== * Abstract: * The sizes information is used by Simulink to determine the S-function * block's characteristics (number of inputs, outputs, states, etc.). */ static void mdlInitializeSizes(SimStruct *S) { // Parameters ssSetNumSFcnParams(S, 0); // Expected number of parameters if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) { return; // Return if number of expected parameter != number of actual parameters } // States ssSetNumContStates(S, 0); ssSetNumDiscStates(S, 0); 33 // Inputs: general definition if (!ssSetNumInputPorts(S, 6)) return; // Number of inputs ssSetInputPortWidth (S, 0, 1); // w ssSetInputPortDataType (S, 0, SS_DOUBLE); // float ssSetInputPortRequiredContiguous(S, 0, true); // direct input signal access ssSetInputPortDirectFeedThrough (S, 0, 1); // direct feedthrough required, i.e. the signal is used in mdlStart and/or mdlUpdate ssSetInputPortWidth (S, 1, 1); // x ssSetInputPortDataType (S, 1, SS_DOUBLE); // float ssSetInputPortRequiredContiguous(S, 1, true); // direct input signal access ssSetInputPortDirectFeedThrough (S, 1, 1); // direct feedthrough required, i.e. the signal is used in mdlStart and/or mdlUpdate ssSetInputPortWidth (S, 2, 1); // Kp ssSetInputPortDataType (S, 2, SS_DOUBLE); // float ssSetInputPortRequiredContiguous(S, 2, true); // direct input signal access ssSetInputPortDirectFeedThrough (S, 2, 1); // direct feedthrough required, i.e. the signal is used in mdlStart and/or mdlUpdate ssSetInputPortWidth (S, 3, 1); // TI ssSetInputPortDataType (S, 3, SS_DOUBLE); // float ssSetInputPortRequiredContiguous(S, 3, true); // direct input signal access ssSetInputPortDirectFeedThrough (S, 3, 1); // direct feedthrough required, i.e. the signal is used in mdlStart and/or mdlUpdate ssSetInputPortWidth (S, 4, 1); // y_max ssSetInputPortDataType (S, 4, SS_DOUBLE); // float ssSetInputPortRequiredContiguous(S, 4, true); // direct input signal access ssSetInputPortDirectFeedThrough (S, 4, 1); // direct feedthrough required, i.e. the signal is used in mdlStart and/or mdlUpdate ssSetInputPortWidth (S, 5, 1); // y_min ssSetInputPortDataType (S, 5, SS_DOUBLE); // float ssSetInputPortRequiredContiguous(S, 5, true); // direct input signal access ssSetInputPortDirectFeedThrough (S, 5, 1); // direct feedthrough required, i.e. the signal is used in mdlStart and/or mdlUpdate // Outputs: general definitions 34 if (!ssSetNumOutputPorts(S, 1)) return; // Number of outputs ssSetOutputPortWidth (S, 0, 1); // y ssSetOutputPortDataType (S, 0, SS_DOUBLE); // float (32 bit) // Miscellaneous ssSetNumSampleTimes(S, 1); ssSetNumRWork(S, 0); // no "static" real ssSetNumIWork(S, 0); // no "static" integers ssSetNumPWork(S, 0); ssSetNumModes(S, 0); ssSetNumNonsampledZCs(S, 0); // No mexFunctions and no problematic mxFunctions are called so the exception free code option safely gives faster simulations. // => Don't use "mexErrMsgTxt(...)" // If an error must be indicated, "ssSetErrorStatus(S,msg)" shall be used! ssSetOptions(S,SS_OPTION_EXCEPTION_FREE_CODE | // Increase performance - NO code throwing exceptions may be used!! SS_OPTION_DISCRETE_VALUED_OUTPUT); // Only discrete outputs are used } /* Function: mdlInitializeSampleTimes ========================================= * Abstract: * This function is used to specify the sample time(s) for your * S-function. You must register the same number of sample times as * specified in ssSetNumSampleTimes. */ static void mdlInitializeSampleTimes(SimStruct *S) { ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME); // Inherit the sample time ssSetOffsetTime(S, 0, FIXED_IN_MINOR_STEP_OFFSET); // Do NOT change in minor integration steps // ssSetSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME); // Continuos sample time (since block has no inputs) } #define MDL_INITIALIZE_CONDITIONS /* Change to #undef to remove function */ #if defined(MDL_INITIALIZE_CONDITIONS) /* Function: mdlInitializeConditions ======================================== * Abstract: * In this function, you should initialize the continuous and discrete * states for your S-function block. The initial states are placed * in the state vector, ssGetContStates(S) or ssGetRealDiscStates(S). * You can also perform any other initialization activities that your * S-function may require. Note, this routine will be called at the * start of simulation and if it is present in an enabled subsystem * configured to reset states, it will be call when the enabled subsystem * restarts execution to reset the states. */ static void mdlInitializeConditions(SimStruct *S) { } 35 #endif /* MDL_INITIALIZE_CONDITIONS */ #define MDL_START /* Change to #undef to remove function */ #if defined(MDL_START) /* Function: mdlStart ======================================================= * Abstract: * This function is called once at start of model execution. If you * have states that should be initialized once, this is the place * to do it. */ static void mdlStart(SimStruct *S) { // Variables: input data // ... not necessary // Variables: output data // ... not necessary // Operation EM_Initialize(); } #endif /* MDL_START */ /* Function: mdlOutputs ======================================================= * Abstract: * In this function, you compute the outputs of your S-function * block. Generally outputs are placed in the output vector, ssGetY(S). */ static void mdlOutputs(SimStruct *S, int_T tid) { // Variables: input data double *w = (double *) ssGetInputPortSignal(S, 0); double *x = (double *) ssGetInputPortSignal(S, 1); double *Kp = (double *) ssGetInputPortSignal(S, 2); double *TI = (double *) ssGetInputPortSignal(S, 3); double *y_max = (double *) ssGetInputPortSignal(S, 4); double *y_min = (double *) ssGetInputPortSignal(S, 5); //current Timestep double T = ssGetT(S); // Variables: output data double *y = ssGetOutputPortSignal(S, 0); // If T = 0 (=> First Timestep, we have to set the configuration) if (T == 0) EM_SetConfig(Kp[0], TI[0], y_max[0], y_min[0]); // Operation EM_Execute(T, w[0], x[0], y); } #define MDL_UPDATE /* Change to #undef to remove function */ #if defined(MDL_UPDATE) /* Function: mdlUpdate ====================================================== * Abstract: * This function is called once for every major integration time step. * Discrete states are typically updated here, but this function is useful * for performing any tasks that should only take place once per * integration step. */ 36 static void mdlUpdate(SimStruct *S, int_T tid) { } #endif /* MDL_UPDATE */ #define MDL_DERIVATIVES /* Change to #undef to remove function */ #if defined(MDL_DERIVATIVES) /* Function: mdlDerivatives ================================================= * Abstract: * In this function, you compute the S-function block's derivatives. * The derivatives are placed in the derivative vector, ssGetdX(S). */ static void mdlDerivatives(SimStruct *S) { } #endif /* MDL_DERIVATIVES */ /* Function: mdlTerminate ===================================================== * Abstract: * In this function, you should perform any actions that are necessary * at the termination of a simulation. For example, if memory was * allocated in mdlStart, this is the place to free it. */ static void mdlTerminate(SimStruct *S) { } //======================================================================== // Required S-function trailer //======================================================================== #ifdef MATLAB_MEX_FILE #include "simulink.c" #else #include "cg_sfun.h" #endif /* Is this file being compiled as a MEX-file? */ /* MEX-file interface mechanism */ /* Code generation registration function */ 37 10.6 EnergieManagerWrapper.cs using using using using System; System.Collections.Generic; System.Text; System.Runtime.InteropServices; namespace CEnergiemanager_TestApp { class EnergieManagerWrapper { [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private unsafe static extern void GetVersion( short* short* short* short* m, s, b, i); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private unsafe static extern void GetDate( short* day, short* month, short* year); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private unsafe static extern void GetCompany( StringBuilder company, UIntPtr length); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private unsafe static extern void GetAuthor( StringBuilder author, UIntPtr length); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public unsafe static extern int EM_Initialize(); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public unsafe static extern void EM_SetConfig( double double double double Kp, TI, y_max, y_min); [DllImport("Energiemanager.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public unsafe static extern void EM_Execute( double T, double w, double x, double* y); public unsafe static string GetVersion() { short m; short s; short b; short i; GetVersion(&m, &s, &b, &i); 38 return m.ToString() + "." + s.ToString() + "." + b.ToString() + "." + i.ToString(); } public unsafe static string GetDate() { short day; short month; short year; GetDate(&day, &month, &year); return day.ToString() + "." + month.ToString() + "." + year.ToString(); } public static string GetCompany() { StringBuilder strb = new StringBuilder(255); UIntPtr length = new UIntPtr((uint)strb.Capacity); GetCompany(strb, length); return strb.ToString(); } public unsafe static string GetCompany2() { StringBuilder strb = new StringBuilder(255); UIntPtr length = new UIntPtr((uint)strb.Capacity); char* a = GetCompany2(strb, length); return strb.ToString(); } public static string GetAuthor() { StringBuilder strb = new StringBuilder(255); UIntPtr length = new UIntPtr((uint)strb.Capacity); GetAuthor(strb, length); return strb.ToString(); } } } 10.7 Kompilierung unter 64bit mit Visual Studio Express Edition Mit der Standard-Installation des Visual Studio Express Edition ist es nicht möglich, eine 64bit-Applikation zu erstellen. Der dafür notwendige Compiler, so wie die vorkompilierten Header-Dateien werden nur in den kommerziellen Versionen des Visual Studios ausgeliefert. Microsoft bietet für die Express Edition ein Software Development Kit (SDK) an, das den Compiler so wie die vorkompilierten Header-Dateien nachinstalliert. Für das Visual Studio 2010 muss die Version 7.1 des SDKs installiert werden. Danach muss im Konfigurations-Manager eine neue Projektmappenplattform erzeugt werden. Den Konfigurations-Manager findet man unter „Erstellen“ „Konfigurations-Manager“. In dem Konfigurations-Manager wird die neue Projektmappenplattform mit der Bezeichnung „x64“ angelegt. Die Einstellungen können von der Projektmappenplattform „Win32“ übernommen werden. 39 Danach muss in den Projekt-Eigenschaften („Projekt“ -> „Eigenschaften“) der Compiler gewechselt werden. Dazu muss unter „Konfigurationseigenschaften“ „Allgemein“ in dem Feld „Plattformtoolset“ die Option „Windows7.1 SDK“ gewählt werden. 10.8 Verwenden von unmanaged Code mit Visual Studio Express Edition Legt man in der C++-Version des Visual Studio Express Edition ein neues Projekt an, so ist dies bei den Standardeinstellungen ein Projekt für managed Code. Die Entscheidung, ob ein Projekt in managed oder unmanaged Code implementiert werden soll, kann nur bei der Erstellung des Projekts angegeben werden. Dazu muss über „Datei“ „Neues Projekt“ ein neues Projekt angelegt werden. Im folgenden Dialog kann dann unter „Visual C++“ „Win32“ die Option “Win32-Projekt“ ausgewählt werden. Bei den weiteren Fenstern ist es wichtig, die Option „Leeres Projekt“ unter „Zusätzliche Optionen“ zu aktivieren. Dadurch wird sichergestellt, dass keine .NET-spezifischen Dateien mit geladen werden. 40