OOP und Methoden

twincatter

Level-1
Beiträge
137
Reaktionspunkte
1
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo zusammen,

ich beschäftige mich aktuell mit TwinCAT3-OOP und habe zu Methoden noch einige Fragen.
In vielen Beispielen wird eine Methode aufgerufen und sofort (im gleichen Zyklus) ein Ergebnis geliefert. Beispielsweise bei der Berechnung einer Fläche. Dieser Fall ist klar.

Methode mit Schrittkette
Nehmen wir an, eine Methode benötigt mehrere Zyklen zur Ausführung. In der Methode sei eine Schrittkette enthalten.
Als Beispiel wähle ich einen Zylinder mit Endschaltern und Timeout-Überwachung. Er soll die Methoden „MoveToBase“ und „MoveToWork“ besitzen.

Aufruf der Methode
Da die Methode mehrere Zyklen zur Ausführung benötigt, muss dafür gesorgt werden, dass sie dementsprechend so oft aufgerufen wird, bis sie beendet ist.
Dies kann ja aus der übergeordneten Schrittkette leicht realisiert werden. Über eine Visu ist es allerdings nicht direkt möglich, jedenfalls wüsste ich nicht wie. In einem Beispiel von Beckhoff (Sortieranlage) wurde hierfür ein extra FB (fbVisu) programmiert. Über die Visu wird, wie gewohnt, eine Variable gesetzt, die von fBVisu ausgewertet wird und ggf. die Methode aufruft. Das ist auf alle Fälle ein höherer Aufwand.

Rückgabewerte der Methode
Ich denke es wäre sinnvoll, wenn jede Methode als Rückgabewert eine Struktur verwendet. Diese Struktur enthält Variablen wie: Busy, Error, Done, Message. Alternativ könnte natürlich auch der Funktionsblock die Rückgabewerte bereitstellen. Wenn jedoch die Methoden selbst die Rückgabewerte bereitstellen, könnten auch mehrere Methoden eines FBs „gleichzeitig“ aufgerufen werden.

Deklarierung von Variablen in der Methode
Bei normaler Deklaration von Variablen in einer Methode sind diese nur temporär.
Mit „VAR_INST“ können die Variablen so deklariert werden, dass sie sich so verhalten als seien sie im FB deklariert worden. Dies finde ich geschickt. Allerdings werden mir diese Variablen nicht angeboten (Intellisense), wenn ich eine Verknüpfung von der Visu herstellen will. Gebe ich den Namen direkt ein, funktioniert es aber.

Initialisieren der Methode.
Irgendwie muss beim Aufruf der Methode ein Init durchgeführt werden, damit z.B. die Schrittvariable der Schrittkette zurückgesetzt wird. Hierzu kann sicher ein Parameter der Methode verwendet werden, z.B: bStart. Wenn bStart := TRUE dann iStep := 0. Gefällt mit nicht so richtig, dann muss die Methode von der übergeordneten Schrittkette immer in 2 Schritten aufgerufen werden . Einmal mit Start = TRUE, und dann mit Start = FALSE. Das würde ich gerne vermeiden. Vielleicht ein SPS-Zykluszähler zur Detektion verwendet werden. Wenn Methode im letzten Zyklus nicht aufgerufen wurde, dann iStep := 0. Denke dies könnte funktionieren. Wenn allerdings die Methode aus irgendeinem Grund nur z.B. jeden 2ten SPS-Zyklus aufgerufen würde, gäbe es ein Problem.

Ja, es sind einige Fragen geworden. Diese hätte ich nicht, wenn ich wie bei „Nicht-OOP“ einfach eine boolsche Eingangsvariable des FBs verwenden würde.

Ich lasse mich gerne davon überzeugen, dass es dennoch gut ist mit Methoden zu arbeiten.

Bin gespannt welche Anmerkungen Ihr dazu habt.

Grüße vom Bodensee und bleibt gesund
Twincatter
 
Oft ist es ja mit einer einzelnen Steuervariable beim FB-Aufruf nicht getan, sondern man muss noch weitere Parameter für den Steuerbefehl übergeben. Für den Start eines Kommandos finde ich deshalb Methoden ganz praktisch, weil man bei ihrem Aufruf die Übergabe aller relevanten Daten erzwingen kann. Zyklische Funktionalität bringe ich aber grundsätzlich im FB unter. Für meinen Geschmack hat man damit mehr Sicherheit, dass diese Funktionalität genau einmal pro Zyklus aufgerufen wird. Ausserdem müsste man auch bei Folgeaufrufen einer Methode alle ihre VAR_INPUT übergeben. Was beim Start eines Kommandos noch vorteilhaft ist, wird dann zum Nachteil.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
OOP ist sehr eingeschränkt, da es das kein Polymorphie gibt. Daher du kannst keine Methode mit anderen Argumenten überschreiben. Also

sin(LREAL x) kann nicht mit sin(INT x) überschrieben werden.

Ich denke daher, dass bei ernsthaften Anwendungen die Interfaces wichtiger sind. Interfaces sind Pointer, die vom System gemanaged werden und die können auch auf Initialisierung (<> 0) getestet werden. Dazu kommt dann noch das die Interfaces zur Laufzeit geändert werden können. Ein Beispiel


A : InterfaceUmformer;

A := Umformer1;

A.Start(rpm=300);

A := Umformer2;

A.Start(rpm=120);

Die beiden Umformer müssen nicht einmal vom selben Hersteller sein.

In grösseren Systemen lohnt sich OOP definitiv, weil es die Übersicht erhöht und einen zwingt zu abstrahieren. Anstatt alles auf einen Hersteller zuzuschneidern, gibt es dann Klassen für jeden Hersteller eines Umformers.

Es lohnt sich Konfigurationswerte als Struktur zu referenzieren. Werte die PERSISTENT sind, sollten ebenfalls als Struktur referenziert werden.
 
Ja, es sind einige Fragen geworden. Diese hätte ich nicht, wenn ich wie bei „Nicht-OOP“ einfach eine boolsche Eingangsvariable des FBs verwenden würde.

Beim Nicht-OOP Ansatz musst du im Grunde alles was du aufgezählt hast auch tun, die Variable muss ja passen zurückgesetzt werden etc.da hast du dann noch den Nachteil, dass dies evtl. nicht an einen Ort geschieht und wenig standardisiert ist.
 
Oft ist es ja mit einer einzelnen Steuervariable beim FB-Aufruf nicht getan, sondern man muss noch weitere Parameter für den Steuerbefehl übergeben. Für den Start eines Kommandos finde ich deshalb Methoden ganz praktisch, weil man bei ihrem Aufruf die Übergabe aller relevanten Daten erzwingen kann. Zyklische Funktionalität bringe ich aber grundsätzlich im FB unter. Für meinen Geschmack hat man damit mehr Sicherheit, dass diese Funktionalität genau einmal pro Zyklus aufgerufen wird. Ausserdem müsste man auch bei Folgeaufrufen einer Methode alle ihre VAR_INPUT übergeben. Was beim Start eines Kommandos noch vorteilhaft ist, wird dann zum Nachteil.

Würdest du das nochmal genauer erläutern? Würdest du eine FB für den Befehl MoveToBase schreiben?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Nein, ich würde einen FB schreiben bzw. habe das schon getan, der den kompletten Zylinder darstellt, was in meinen Augen durchaus im Sinne der OOP ist. Der FB führt in seinem zyklischen Aufruf die Steuerbefehle aus und meldet ihre Erledigung bzw. Fehler zurück. Die Steuerbefehle gebe ich ereignisgesteuert, in der Regel bei Transitionen einer Schrittkette, indem ich entsprechende Methoden aufrufe. Bei einem Zylinder tut so eine Methode nicht viel mehr als eine interne Befehlsvariable zu setzen, und die Variantenanzahl für die zyklische Funktionalität ist begrenzt. Ich habe deshalb darauf verzichtet, für unterschiedliche Magnetventiltypen auch eigene FBs zu schreiben, sondern berücksichtige die Unterschiede per Konfiguration in nur einem Basis-FB. Somit ist dieser FB nicht so weit von herkömmlicher Programmierung entfernt.
Bei komplexeren Antrieben wie z. B. Servomotoren sieht das schon anders aus. Die Vorbereitungen zur Ausführung eines Befehls sind dort umfangreicher, und es macht Sinn, sie in den Befehlsmethoden unterzubringen. Ausserdem habe ich dort die zyklische Funktionalität in privaten Methoden untergebracht, um für herstellerspezifische Anpassungen nicht den ganzen FB umschreiben zu müssen.
 
Hallo StructuredTrash,

Deine Beschreibung bezüglich des Zylinders kann ich nachvollziehen.
Kannst Du bitte noch erläutern wie Du dies im Falle von komplexeren Antrieben realisiert hast?

Vielen Dank
twincatter
 
Hallo drfunfrock,

die Funktion von Interfaces ist mir klar. Meine Frage bezog sich nicht speziell auf die Methoden.
Ich wollte OOP nicht generell in Frage stellen, habe mich nicht präzise ausgedrückt.

Trotzdem vielen Dank!
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Kannst Du bitte noch erläutern wie Du dies im Falle von komplexeren Antrieben realisiert hast?
Hm, allein die Beschreibung der Basisklasse nimmt in meiner Doku 16 Seiten ein, aber ich versuche es mal in Kurzform.
Es handelt sich um eine Basisklasse für EtherCat-Antriebe, die vorgeben, nach dem CiA-Standard DS402 zu arbeiten. Ich habe einen FB, der nur die zyklisch synchronen Modi unterstützt, und davon abgeleitet einen FB, der auch die Profilmodi unterstützt. Also einen FB für Regler ohne und einen für solche mit Fahrprofilgenerator.
Die Steuerbefehle werden wie bei dem Zylinder-FB durch den Aufruf von Befehlsmethoden übergeben, wobei diese Methoden aber aufwändiger sind. Sie prüfen zunächst, ob der gewünschte Befehl beim aktuellen Zustand der Antriebs-Statusmaschine und dem Stand der Befehlsausführung überhaupt zulässig ist, und lehnen die Annahme ggfs. ab. Weitere Vorbereitungen reichen vom Eintragen von Fahrprofil-Parametern bis hin zur kompletten Berechnung von Fahrprofilen, die im Folgenden im zyklischen FB-Aufruf ausgegeben werden.
Die zyklische Funktionalität habe ich in mehrere Bereiche aufgeteilt und für jeden davon eine private Methode geschrieben. Diese Methoden werden im FB nacheinander aufgerufen. Die grobe Aufteilung sieht so aus:
Aktuellen Zustand der Antriebs-Statusmaschine bestimmen,
Aktuelle Werte für Position, Geschwindigkeit, Drehmoment lesen, ggfs. filtern, skalieren,
Aktuellen Befehl ausführen, zumeist mit Schrittketten,
Sollwerte ausgeben.
Die Steuerbefehle habe ich ebenfalls in mehrere Gruppen aufgeteilt, die auch in je einer Methode behandelt werden:
Antrieb ausschalten,
Antrieb einschalten,
Zyklisch synchrone Position,
Zyklisch synchrone Geschwindigkeit,
Zyklisch synchrones Drehmoment,
Homing,
Profil-Modus Position,
Profil-Modus Geschwindigkeit,
U/f-Modus für den Einsatz als Frequenzumrichter.
Meine Hoffnung dabei war, für die konkreten Antriebs-FBs nur wenige dieser Methoden anpassen zu müssen. Tatsächlich gibt es zwischen den Antrieben aber soviele Detailunterschiede, dass ich die Funktionalität noch wesentlich kleiner hätte hacken müssen, um jedes dieser Details in einer eigenen Methode zu behandeln. Soweit wollte ich den FB auf keinen Fall "zerfleddern". Darum habe ich aus der Basisklasse dann einen idealen DS402-Antrieb mit virtuellen I/O gemacht und dazu einen meiner Antriebe zum Standard erhoben. Die konkreten Antriebe übersetzen dann ihre HW-I/O so gut wie möglich auf die virtuellen I/O, und tatsächlich habe ich für die FBs nicht viel mehr als die dazu dienenden Methoden anpassen müssen.
Das Ergebnis ist sicher von der reinen OOP-Lehre noch ein Stück weit entfernt, ich bin aber auch der Ansicht, dass eine 1:1-Übertragung von Hochsprachen-OOP-Gepflogenheiten auf die SPS nicht sinnvoll ist. Man muss einen eigenen OOP-Weg für die SPS finden, und ich bin selbst auch immer noch auf der Suche.
 
Ich stand letztens vor einem ähnlichen Problem, allerdings nur mit Zwei verschiedenen Achstypen. Ich habe eine Abstrakte Basisklasse erstellt. Die spezifischen Achsen erben von der Basisklasse. Die in der abstrakten Basisklasse vorhandenen, leeren Funktionen sind virtuell und werden in der erst In der spezialisierten Klasse mit leben gefüllt (bzw. überschrieben). Jedoch programmiere ich Jetter Steuerungen, was bei anderen Herstellern möglich ist, weiß ich leider nicht.

Ich geb dir recht, nicht alles was OOP kann, muss oder sollte auch gemacht werden. Aber es gibt hier und da aber ein paar ganz nette Sachen, zum Beispiel das Strategie Pattern lerne ich momentan immer mehr zu schätzen.

Ich hätte aber noch eine Frage an dich. Wie sehr gehst du ins Detail, was die Doku deiner Klassen betrifft?
 
Zuletzt bearbeitet:
Ich hätte aber noch eine Frage an dich. Wie sehr gehst du ins Detail, was die Doku deiner Klassen betrifft?

Was meine FBs machen, beschreibe ich schon recht detailliert, das Wie dagegen nur soweit, wie es für die Anwendung von Interesse ist. Für alles Weitere muss man den Quelltext zu Rate ziehen.
Ein Beispiel:
Mein Antriebs-FB bearbeitet die meisten Befehle in Schrittketten. Wenn ein Fehler auftritt, wird neben einer allgemeinen Meldung auch der Fehlerschritt ausgegeben. Deshalb werden die Schritte auch in der Doku aufgeführt.
 
Zurück
Oben