Objektorientiertes Programm zur Steuerung einer Sortieranlage

Die Programmiermittel der Objektorientierung sind sehr vielseitig und können auf unterschiedlichste Weise angewendet werden. Das folgende Beispiel verdeutlicht die Vorteile der Objektorientierung und zeigt einige Ideen auf, wie die Objektorientierung bei der SPS-/Maschinenprogrammierung verwendet werden kann. Das bei diesem Beispiel vorgestellte objektorientierte Konzept und die dahinter stehenden Ansätze erheben keinen Anspruch auf Vollständigkeit und können nicht pauschal auf jede beliebige Applikation übertragen werden.

Das Beispielprogramm zur Veranschaulichung der objektorientierten Programmierung (OOP) dient zur Steuerung einer Sortieranlage, die Metall- und Plastikkästen gemäß ihrer Materialart sortiert. Dabei beinhaltet der Sortiervorgang eine Vereinzelung und zwei Ausschleusungen. Diese Vorgänge werden mit Hilfe von Zylindern realisiert, von denen unterschiedlich komplexe Ausführungen existieren. Als Beispielanforderung gilt, dass es möglich sein soll, die an der Anlage eingesetzten Zylinder flexibel durch einen anderen Zylindertypen auszutauschen.

Anhand eines Sensors zur Metallerkennung kann die Anlage die verschiedenen Materialarten unterscheiden, sodass Metall- (grau) und Plastikkästen (grün) auf unterschiedlichen Nebenförderbändern abtransportiert werden. Das folgende Video verdeutlicht den Sortiervorgang der Anlage, die von oben zu sehen ist.

Objektorientiertes Programm zur Steuerung einer Sortieranlage 1:

Generelles Konzept der OOP

Ein allgemeines Ziel der objektorientierten Programmierung ist es beispielsweise, Automatisierungsmodule zu entwickeln. Bei diesen Objekten handelt es sich um applikationsneutrale, vorgefertigte Funktionseinheiten, die nur einmal entwickelt werden, aber mehrfach unverändert verwendet werden können. Automatisierungsmodule werden demnach nicht für eine bestimmte Anlage entwickelt, sondern stellen allgemein eine Anlagenfunktionalität zur Verfügung, die i.d.R. in mehreren Anlagen zum Einsatz kommt. So wird beispielsweise eine Klasse „FB_Axis“ zur Steuerung einer Achse einmal entwickelt und in mehreren Anlagen instanziiert und verwendet.

Mit Hilfe von Automatisierungsmodulen wird vor allem der Implementierungsaufwand bei der Programmierung einer Anlage erheblich reduziert. Gleichzeitig sind die Qualität und die Zuverlässigkeit der verwendeten Objekte vergleichsweise hoch, da die Module auch in anderen Anlagen eingesetzt und damit getestet und ggf. verbessert werden. Sind der Programmierstil und das Konzept bei der Implementierung von verschiedenen Modulen identisch, kann ein einheitliches „Look and Feel“ für die Verwendung der Objekte geschaffen werden. Dadurch kann eine Applikationsstandardisierung erreicht werden.

Für eine sinnvolle Entwicklung und Verwendung von Automatisierungsmodulen werden eine modulare Konzeption und eine modulare Programmierung der Anlage vorausgesetzt. Die Unterteilung der Anlage in Objekte kann beispielsweise so konzipiert werden, dass die Anlage aus verschiedenen Subsystemen besteht und diese Subsysteme wiederum verschiedene Submodule beinhalten. Die Maschine, ihre Subsysteme und deren Submodule werden als Automatisierungsmodule implementiert und agieren somit als separate, autarke Objekte.

Objektorientiertes Programm zur Steuerung einer Sortieranlage 2:

Des Weiteren ist es denkbar, die Daten und Funktionalitäten, die alle Submodule gemeinsam haben, in einer Submodul-Basisklasse zu generalisieren. Dieser Ansatz ist durch die Entwicklung einer Subsystem-Basisklasse ebenfalls auf die Subsysteme übertragbar, falls Gemeinsamkeiten zwischen den Subsystemen bestehen. Dadurch würde zum einen der Programmieraufwand erheblich reduziert und zum anderen müsste bei Änderungsbedarf der gemeinsamen Implementierungen nur die Basisklasse angepasst werden.

Sortieranlage – Vorgehen

Die Programmierung der Sortieranlage unter Verwendung der Objektorientierung wird im Folgenden ausführlich an dem Beispiel der Zylinder erläutert. Die Implementierung der anderen Submodule, wie den Antrieben und den Sensoren, lässt sich aus der Programmierweise der Zylinder ableiten. Zudem wird die Zusammenfassung von Submodulen zu Subsystemen am Ende kurz beschrieben. Bei der Implementierung der Zylinder als Submodule wird folgendermaßen vorgegangen:

Planung der Softwarestruktur

Die geforderten Zylindertypen umfassen einfache Zylinder, die lediglich in Grund- und Arbeitsstellung verfahren können, und Zylinder mit zusätzlichen Funktionalitäten. Diese komplexeren Zylinder können das Erreichen ihrer Endlagen überwachen, ihre Temperatur aufzeichnen oder diagnostizieren, wenn die Temperatur nicht mehr in einem vorgegebenen Intervall liegt. Die einzelnen Zylinder der Maschine sollen nicht auf einen bestimmten Zylindertyp beschränkt sein, sondern durch andere Typen ausgetauscht werden können. Auf diese Weise soll an der Anlage beispielsweise ein einfacher Zylinder durch einen Zylinder mit Zusatzfunktionalität ersetzt werden können.
Unabhängig davon, über welche Zusatzfunktion sie verfügen, müssen alle Zylinder die Grundfunktionalitäten des Ein- und Ausfahrens ausführen können. Da es sich hierbei um eine Anforderung handelt, die im Sinne der Applikation an jeden Zylinder gestellt wird, werden die zu der Anforderung gehörigen Methoden und Properties in einer Schnittstelle definiert. In einer Schnittstelle sind dabei keine Implementierungen, sondern nur die Definition der Methoden und Properties zu finden. Sie stellt somit lediglich eine Vereinbarung für die Funktionsblöcke dar, die die Schnittstelle einbinden, und wird erfüllt, wenn die definierten Methoden und Properties von den Bausteinen zur Verfügung gestellt werden. In den einbindenden Funktionsbausteinen werden letztlich die Implementierungen der in der Schnittstelle definierten Programmelemente umgesetzt.
Durch die Definition des Interfaces I_Cylinder und dessen Einbindung in den Zylinderbausteinen ist somit sichergestellt, dass die Zylinder den gestellten Anforderungen entsprechen und über die dazugehörigen Programmelemente zum Verfahren verfügen. Des Weiteren ist es durch die Definition und Einbindung einer Schnittstelle möglich, Bausteininstanzen über Interfaceinstanzen anzusprechen. Auf diese Weise ergibt sich die geforderte Austauschbarkeit der Zylinder.
Da alle Zylinder die Grundfunktionalitäten des Ein- und Ausfahrens bereitstellen müssen, ist es neben der Definition einer „Schnittstellen-Vereinbarung“ außerdem sinnvoll, diese Funktionalitäten zu generalisieren und ihre Implementierung über eine Basisklasse zur Verfügung zu stellen. Aus diesem Grund wird der Zylinder FB_Cylinder geplant, der Methoden zur Zylinderbewegung besitzt und die Basisklasse aller geforderten Zylinder darstellt. Dadurch verfügen alle abgeleiteten Klassen über die Grundfunktionalitäten des Ein- und Ausfahrens, obwohl diese nur einmal in dem Basiszylinder implementiert werden.
Von der Oberklasse FB_Cylinder wird der Funktionsblock FB_CylinderDiag abgeleitet, der zusätzlich das Erreichen der Endlagen überwacht und den Basiszylinder um diese Funktionalität erweitert. Er stellt die Lösung eines der geforderten Zylinder dar.
Da die Zylinder mit Aufzeichnung bzw. Überwachung der Temperatur den Wert der aktuellen Temperatur benötigen, wird von der Basisklasse FB_Cylinder ein Funktionsblock mit dieser Komponente abgeleitet. Durch den Zusatz der Temperatur heißt die Klasse FB_CylinderTemp und stellt die Oberklasse für die Zylinder mit Temperaturaufzeichnung und -überwachung dar. Diese beiden Unterklassen mit den Namen FB_CylinderTempDiag und FB_CylinderTempRecord werden um ihr spezifisches Verhalten und die Variablen erweitert, die für diese Funktionalitäten benötigt werden.

Objektorientiertes Programm zur Steuerung einer Sortieranlage 3:

Implementierung des Softwarekonzepts

Die geplanten Funktionsblöcke können nun implementiert werden. Um das Vorgehen aufzuzeigen, werden nachfolgend Teile der Basisklasse FB_Cylinder und der abgeleiteten Klasse FB_CylinderDiag beschrieben.
Der Funktionsblock FB_Cylinder bindet das Interface I_Cylinder mit Hilfe des Schlüsselworts IMPLEMENTS ein, wodurch die Methoden und Properties der Schnittstelle in der Oberklasse automatisch erstellt werden können. Dadurch ist garantiert, dass der Funktionsblock und seine Ableitungen über die vom Interface geforderten Elemente verfügen. Die durch das Interface bedingte Bausteinstruktur und die Deklaration des Funktionsblocks FB_Cylinder sind in folgender Abbildung dargestellt.

Objektorientiertes Programm zur Steuerung einer Sortieranlage 4:

Die Methode Reset, die den Zylinderausgang bMoveToWork des Funktionsblocks zurücksetzt, ist beispielhaft aufgeführt.

// =========================================================
// *** Method Reset of FB_Cylinder ***
 
    bMoveToWork := FALSE;
 
// =========================================================

Mit Hilfe des Schlüsselworts EXTENDS wird der Funktionsblock FB_CylinderDiag zu einer Ableitung von FB_Cylinder. Dadurch können (abhängig vom Zugriffsmodifizierer) die Variablen, Methoden und Properties der Basisklasse genutzt werden. Da die Unterklasse die Methoden Reset und StateMachine der Basisklasse erweitern soll, werden die Methoden in der Unterklasse eingefügt und können so verändert werden. Außerdem wird die zusätzliche Methode Diag integriert, über die die Basisklasse nicht verfügt. Die für die Diagnose-Funktionalität benötigten zusätzlichen Variablen werden in FB_CylinderDiag deklariert. Die Struktur und die Deklaration von FB_CylinderDiag sehen wie folgt aus:

Objektorientiertes Programm zur Steuerung einer Sortieranlage 5:

In den Methoden Reset und StateMachine der abgeleiteten Klasse können die gleichnamigen Methoden der Oberklasse erweitert oder überschrieben werden. Um eine Methodenerweiterung zu erhalten, wird die Methode der Basisklasse aufgerufen, indem das Schlüsselwort SUPER verwendet wird. Bei SUPER handelt es sich um einen Funktionsblockzeiger, der auf die Funktionsblockinstanz der Basisklasse zeigt. Durch weitere Anweisungen kann in der Methode der Unterklasse das Verhalten der Basisklasse erweitert werden, wodurch die Methode für den Zylinder FB_CylinderDiag angepasst wird. Die beispielhafte Methode Reset von FB_CylinderDiag, die die gleichnamige Methode der Basisklasse FB_Cylinder durch weitere Anweisungen erweitert, ist nachfolgend aufgeführt.

// =========================================================
// *** Method Reset of FB_CylinderDiag ***

    // Calling method Reset of base class FB_Cylinder via 'SUPER^.'
    SUPER^.Reset();

    // Reset error
    bError     := FALSE;
    sErrorMsg  := '';

// =========================================================

Instanziierung der Programmelemente

Die mit Hilfe von Vererbung und den entsprechenden Schlüsselwörtern erstellten Funktionsblöcke, die Zylinder mit verschiedener Funktionalität repräsentieren, können nun instanziiert werden.
Damit ein Zylinder als variabel angesehen und durch jeden Zylindertyp dargestellt werden kann, werden die Funktionsblöcke FB_Cylinder, FB_CylinderDiag, FB_CylinderTemp, FB_CylinderTempDiag und FB_CylinderTempRecord je einmal instanziiert. Die Interfaceinstanz iCylinder ist das Objekt, das zur Laufzeit auf eine der Zylinderbausteininstanzen und damit auf den aktuell gewählten Zylindertyp verweist.
Die Variablen bCylinderDiag, bCylinderTemp und bCylinderRecord können durch den Benutzer verändert werden und geben an, ob der Zylinder über die Diagnose- bzw. die Temperaturfunktionalitäten verfügen soll.
Die Instanziierung der Funktionsblöcke und des Interfaces sowie die drei booleschen Variablen sind nachfolgend dargestellt.

// ===== Variables to enable/disable diagnosis and temperature mode ===
   bCylinderDiag         : BOOL;                   // If true the cylinder has diagnosis functionality
   bCylinderTemp         : BOOL;                   // If true the cylinder has temperature functionality
   bCylinderRecord       : BOOL;                   // If true the cylinder has recording functionality

// ===== Function block instances for cylinder ========================
   fbCylinder            : FB_Cylinder;            // Without diagnosis and temperature mode
   fbCylinderDiag        : FB_CylinderDiag;        // With diagnosis of states
   fbCylinderTemp        : FB_CylinderTemp;        // With temperature mode
   fbCylinderTempDiag    : FB_CylinderTempDiag;    // With diagnosis of temperature
   fbCylinderTempRecord  : FB_CylinderTempRecord;  // With record of temperatures

// ===== Interface instance for cylinder ==============================
   iCylinder             : I_Cylinder;             // Interface for flexible access to cylinder FBs

Zuweisung von Funktionsblockinstanzen zu einer Interface-Instanz

Bei einem gewünschten Austausch des Zylinders müssen nun lediglich die Variablen bCylinderDiag, bCylinderTemp und bCylinderRecord angepasst werden, da der Interface-Instanz iCylinder je nach Zustand dieser drei Variablen die entsprechende Funktionsblockinstanz zugewiesen wird.
Soll der Zylinder beispielsweise über die Funktionalität der Temperaturüberwachung verfügen, besitzen bCylinderDiag und bCylinderTemp den Wert TRUE und bCylinderRecord den Wert FALSE. Der gewünschte Funktionsblock ist demnach FB_CylinderTempDiag und die dazugehörige Instanz fbCylinderTempDiag wird der Interface-Instanz zugewiesen. Die für diese Klasse spezifischen Ausgänge bError und sErrorMsg werden separat abgefangen, indem sie lokalen Variablen zugewiesen werden. Dadurch, dass die FB-Instanz fbCylinderTempDiag der Interface-Instanz iCylinder zugewiesen wird, kann über die Interface-Instanz auf die Funktionsblockinstanz mit Temperaturüberwachung zugegriffen werden.
Besitzen die booleschen Variablen hingegen andere Werte, wird der Interface-Instanz entsprechend eine andere Funktionsblockinstanz zugewiesen. Zwei Beispiele der Funktionsblockzuweisung sind nachfolgend dargestellt.

// =========================================================
// Selecting cylinder by checking variables to enable / disable diagnosis and temperature mode

   IF bCylinderDiag THEN
       IF bCylinderTemp THEN
           // =========== FB with diagnosis and temperature mode ==================
           bError     := fbCylinderTempDiag.bError;              // Assigning output variable of chosen FB to local variable
           sErrorMsg  := fbCylinderTempDiag.sErrorMsg;
           iCylinder  := fbCylinderTempDiag;                     // Assigning chosen FB instance to interface instance

       ELSE
           // =========== FB with diagnosis and without temperature mode ===========
           fbCylinderDiag.tTimeOut  := tTimeOutCylinder;         // Setting special data for selected FB
           bCylError                := fbCylinderDiag.bError;    // Assigning output variable of chosen FB to local variable
           sCylErrorMsg             := fbCylinderDiag.sErrorMsg;
           iCylinder                := fbCylinderDiag;           // Assigning chosen FB instance to interface instance

       …

   END_IF
 
// =========================================================

Verwendung einer Funktionsblockinstanz über eine Interface-Instanz

Die Interface-Instanz, der eine der FB-Instanzen zugewiesen wurde, kann ihre Methoden mit Hilfe der Punktnotation aufrufen. Bei diesem Methodenaufruf über die Interface-Instanz wird, aufgrund des Interfacepointers, die Methode der Funktionsblockinstanz aufgerufen, auf die das Interface verweist.
Im folgenden Programmausschnitt ist dargestellt, wie der gewählte Zylinderbaustein über die Interfaceinstanz in Grund- bzw. in Arbeitsstellung verfahren wird.

// =========================================================
// Manual cylinder control (Calling methods of FB via interface instance)
 
   // Cylinder to work position
   IF fbButtonCylToWork.bOut THEN
       iCylinder.MoveToWork();
   // Cylinder to base position
   ELSIF fbButtonCylToBase.bOut THEN
       iCylinder.MoveToBase();
   END_IF
 
// =========================================================

Weiteres Softwarekonzept: Zusammenfassung von Submodulen zu Subsystemen

Die vorgestellte Implementierung von Funktionsblöcken und die Verwendung ihrer Instanzen mit Hilfe einer Interface-Instanz stellt die objektorientierte Programmierung im Kleinen dar. Zur optimalen Anwendung der Objektorientierung wird diese um die OOP im Großen ergänzt. Dabei werden z. B. verschiedene Submodule zu Subsystemen zusammengefasst.
Bei der Sortieranlage bietet es sich an, einen Sensor zur Materialerkennung, einen Antrieb, der die Bewegung eines Nebenförderbands regelt, und einen Ausschleusungszylinder zu einem Ausschleusungsmodul zusammenzufassen. Auf diese Weise können aus dieser Klasse beliebig viele Objekte bzw. Sortiermodule instanziiert werden, wobei nur ein Ausschleusungsmodul programmiert wird. Dadurch können auf einfachste Weise verschiedene Anlagen umgesetzt werden, die unterschiedlich viele Sortiermodule besitzen und auf andere Materialarten spezialisiert sind (z. B. Metall, Kunststoff, Folie, Glas…).
Ein anderes Subsystem der Sortieranlage stellt die Vereinzelung dar, die aus einem Sensor zur Boxerkennung, einem Antrieb zur Bewegung der Boxen auf dem Hauptförderband und zwei Zylindern zur Umsetzung der Vereinzelung besteht.
Da die beiden Subsysteme der Vereinzelung und der Ausschleusung über Gemeinsamkeiten verfügen, werden die Subsystem-spezifischen Daten und Funktionalitäten in einer Subsystem-Basisklasse zusammengefasst. Die Vereinzelung und die Ausschleusung werden daraufhin als Unterklasse dieses Funktionsblocks angelegt, wodurch sie die generalisierten Programmelemente von der Subsystem-Basisklasse erben.

Durch die Entwicklung von Submodulen und Subsystemen werden die objektorientierten Programmiermöglichkeiten und -vorteile auf verschiedenen Ebenen eingesetzt und genutzt.

TC3.1-Sourcen

Die gesamten TC3.1-Sourcen zu dem Beispielprogramm können hier entpackt werden: TC3_PlcSample_OOPExtendedSample.zip

Um das Beispielprogramm zu starten:

Siehe auch: