Hello SPS! Teil 2: Mit OpenPLC vom Lernen zum Anwenden

Der zweite Teil der Artikelserie zu speicherprogrammierbaren Steuerungen zeigt, wie Developer die Open-Source-SPS OpenPLC praktisch einsetzen können.

In Pocket speichern vorlesen Druckansicht 2 Kommentare lesen

(Bild: Stokkete/Shutterstock.com)

Lesezeit: 30 Min.
Von
  • Dr. Michael Stal
Inhaltsverzeichnis

Nachdem Teil 1 der Artikelserie den Grundstein zu speicherprogrammierbaren Steuerungen (SPS) gelegt hat, erklärt der vorliegende Beitrag, wie sich die Open-Source-SPS OpenPLC praktisch einsetzen lässt.

Der erste Teil hat die Grundlagen einer SPS dargelegt. Was sich auf den ersten Blick als zu viel des Guten für den täglichen Gebrauch anhört, besitzt Potenzial, sobald es sich um größere, komplexere Steuerungen und Regelungen wie etwa bei der Heimautomatisierung handelt. Natürlich kaufen sich Hobbyisten in der Regel keine kostspieligen, industriellen Hardware-Fertiglösungen inklusive zugehörigem Software-Werkzeugkasten. Ist es allerdings möglich, eine kostenlose Steuerung auf einem preisgünstigen Standard-Mikrocontroller oder SBC (Single Board Computer) zu betreiben, schaut die Sache schon vielversprechender aus. Die Frage lautet also: Es existieren bereits verschiedene kommerzielle Software-Steuerungen, die auch unter dem Begriff Soft-PLC firmieren (z. B. contrX, logiccloud), aber wie steht es um SPS-Open-Source-Anwendungen für Standardhardware à la Mikrocontroller, SBCs oder Desktopsysteme?

Der Pragmatische Architekt – Michael Stal

Prof. Dr. Michael Stal arbeitet seit 1991 bei Siemens Technology. Seine Forschungsschwerpunkte umfassen Softwarearchitekturen für große komplexe Systeme (Verteilte Systeme, Cloud Computing, IIoT), Eingebettte Systeme, und Künstliche Intelligenz. Er berät Geschäftsbereiche in Softwarearchitekturfragen und ist für die Architekturausbildung der Senior-Software-Architekten bei Siemens verantwortlich.

Wer danach sucht, wird nach kurzer Zeit fündig. OpenPLC ist Open Source, die den IEC-61131-3-Standard implementiert und demzufolge Sprachen wie Ladder Diagram, Function Block Diagram und weitere unterstützt. Die zugehörige Software gibt es kostenlos im Internet. Die Existenz von OpenPLC ist Thiago Rodrigues Alves zu verdanken, der es erdacht und umgesetzt hat.

Bevor es ans Eingemachte geht, sollte man sich eins vor Augen halten: OpenPLC besteht aus zwei Komponenten.

  • OpenPLC-EDITOR: Da ist zum einen der Editor bzw. die IDE, mit deren Hilfe Anwender die SPS programmieren, und danach das Programm auf der gewünschten Zielplattform installieren (hochladen), oder in der IDE simulieren und debuggen.
  • OpenPLC-RUNTIME: Und zum anderen gibt es eine Laufzeitumgebung, in der der Code für die SPS läuft, also die eigentliche Runtime der PLC. Laufzeitumgebungen existieren bei OpenPLC für verschiedenste Hardwareplattformen (z.B. diverse Arduino-Boards, ESP32, ESP8266, Raspberry Pi, UniPi Industrial Platform, Hardware mit Desktopbetriebssystemen).

Der OpenPLC-Editor als Entwicklungsumgebung (IDE) beschränkt sich hingegen auf macOS (als Beta-Version), Windows und Linux.

Die Webseite von OpenPLC ist über openplcproject.com erreichbar.

(Bild: openplcproject,com)

Die verschiedenen IDE-Implementierungen sind zum Herunterladen auf der Website des OpenPLC-Projektes verfügbar.

Installation:

  • macOS: Nach dem Download auf macOS müssen Nutzerinnen und Nutzer den OpenPLC-Editor ins Applications-Verzeichnis verschieben. Über das Terminal-Programm ist anschließend xcode-select --install aufzurufen. Startet der Editor zum ersten Mal, so fragt er in einem Dialog nach, ob er einige für Python benötigte Bibliotheken laden soll. Falls ja, also im Regelfall, öffnet sich ein Terminalfenster und bittet um Eingabe des Super-User-Passworts, weil die genannten Bibliotheken systemweit verfügbar sein sollen. Danach ist der Editor lauffähig und aufrufbar.
  • Linux: Der Download der Linux-Version besteht aus einer zip-Datei, die es zunächst zu extrahieren gilt. Im daraus extrahierten Verzeichnis wechselt man zum <Linux>-Unterverzeichnis und ruft über ein Terminal das Skript install.sh als regulärer Benutzer auf. Später bittet die Installation um Eingabe des Super-User-Passworts, worauf sich die Installation fortsetzt. Normalerweise müsste der OpenPLC-Editor nun im Applications-Verzeichnis der Linux-Distro auftauchen und sich starten lassen. Ist dies nicht der Fall, lässt sich der Editor auch einfach über das Skript openplc_editor.sh aufrufen. Im Übrigen funktioniert Opder enPLC-Editor für alle Debian-basierten Distributionen wie etwa Fedora oder Ubuntu.
  • Windows: Die einfachste Installation ist die für Windows, da der Download aus einem ausführbaren (self-contained) Installationsprogramm mit allen Bibliotheken und Dateien besteht. Nach der Ausführung des Installationsprogrammes lässt sich der Editor einfach über das Start-Menü finden und starten. In Windows ist die Installation systemweiter Bibliotheken nicht notwendig. Stattdessen erfolgt eine lokale Installation.

In allen drei Fällen empfiehlt es sich, nach der Installation zuerst über das Datei-Menü des Editors nach neuen Updates zu suchen. Nach einem Update sollte man den Editor schließen und neu starten.

Der Editor bringt auf den genannten Betriebssystemen Beispielprogramme und einen Simulator mit.

Zusätzlich ist die Installation der Laufzeitumgebung auf der anvisierten Zielhardware erforderlich. Wie erläutert, lässt sich die Runtime auf Microcontroller-Boards (z. B. Arduino, ESP32, ESP2866, STM), SBCs (z. B. Raspberry Pi), echten SPSen (z. B. UniPi Neuron PLC, Controllino) sowie den Desktop-Betriebssystemen macOS, Linux und Windows installieren. Auch eine Installation als Docker-Container ist möglich. Der Autor verzichtet aus Platzgründen auf die Beschreibung der Installation und verweist auf die Dokumentations-Webseite, die das jeweilige Vorgehen detailliert beschreibt.

Die Laufzeitumgebungen für Windows, macOS und Linux (inkl. Raspberry Pi) stehen als vollwertige Applikationen zur Verfügung, während für eingebettete Geräte, speziell für Mikrocontrollerboards, das Laufzeitsystem als Firmware über den Editor hochgeladen wird. Im Editor existiert zu diesem Zweck ein Arduino-Icon, das aber nicht nur für Arduino-Boards zuständig ist, sondern auch für alle anderen unterstützten Mikrocontrollerboards. Verfügt das Board über TCP/IP-Unterstützung, kann es sich sogar in ein (Modbus-)Netzwerk integrieren. Auf Mikrocontrollern ohne Ethernet-/WiFi-Hardware erlaubt OpenPLC immer auch Kommunikation von lokalem Host und Board über serielle Ports/USB.

Da die Desktop-Varianten als Modbus-Server fungieren – zu Modbus kommt nachfolgend ein eigener Abschnitt – können Nutzerinnen und Nutzer Modbus-Slaves wie etwa ein als Modbus-Client laufendes Arduino-Board an die Desktop-Laufzeitumgebung von OpenPLC anschließen und die I/O-Ports des Boards quasi als verlängerten Arm benutzen. Weder Windows noch macOS oder Linux enthalten schließlich eigene GPIO-Ports (General Purpose IO).

Ist eine Desktop-basierte OpenPLC-Zielhardware über TCP/IP angeschlossen, erreichen Nutzer die OpenPLC-Runtime dort lokal über den Browser (mit der URL http://localhost:8080) oder remote (über die URL http://<IP-Adresse>:8080).

Die OpenPLC-Runtime auf Windows, Linux, Raspberry Pi, macOS lässt sich aus einem Browser aufrufen.

Bevor es jetzt mit der ersten Programmierung der PLC losgeht, braucht es noch paar Grundlagen, konkret zu den Adressierungsschemata und dem Modbus.

Es ist der Natur ihres Einsatzgebiets geschuldet, dass eine SPS in der Regel nur einfache, das heißt systemnahe Datentypen unterstützt, etwa Bit beziehungsweise Bool (X), Byte (B) oder ein {einfaches, doppeltes, langes} Word (W = Word = 16 Bit, D = Double = 32 Bit, L = Long = 64 Bit). Bisweilen sind beispielsweise auch Felder und andere einfach strukturierte Datentypen anzutreffen.

Um Eingaben (I), Ausgaben (Q) und Speicherzugriffe (M) zu adressieren, gibt es eine eigene Notation aus dem IEC-Standard.

Drei Beispiele zur Veranschaulichung:

  • %IX1.7 : vom Eingangsport 2 (Zählungen fangen informatikgerecht bei 0 an, I steht für Input, X für Bit!), das achte (least-significant bzw. am weitesten rechtsstehende) Bit.
  • %QX2.3 : vom Ausgangsport 3 (Q steht für Output) das vierte Bit von rechts.
  • %MW0 : Das Speicherwort an Adresse 0

Was mit dem ersten und zweiten Beispiel auf der echten Hardware tatsächlich gemeint ist, hängt vom Mapping dieser Adressen auf die physikalische Hardware ab.

Wer sich im Übrigen über das Q für Ausgabeports wundert: Vermutlich hat man im Standard deshalb Q statt O für Ausgabe genutzt, weil sich Q leichter von der Zahl 0 unterscheiden lässt, aber das ist reine Spekulation des Autors.

Auf einem Arduino Mega oder Due beispielsweise würde die Abbildung auf die reale Physik wie folgt aussehen (links die physikalischen Pin-Nummern, rechts die in der PLC genutzten Adressen):

Digital In ​

62, 63, 64, 65, 66, 67, 68, 69 => %IX0.0 bis %IX0.7​

22 , 24, 26, 28, 30, 32, 34, 36 => %IX1.0 bis %IX1.7​

38, 40, 42, 44, 46, 48, 50, 52 => %IX2.0 bis %IX2.7​

Digital Out​

14, 15, 16, 17, 18, 19, 20, 21 => %QX0.0 bis %QX0.7​

23, 25, 27, 29, 31, 33, 35, 37 => %QX1.0 bis %QX1.7​

39, 41, 43, 45, 49, 49, 51, 53 => %QX2.0 bis %QX2.7​

Analog In​

A0, A1, A2, A3, A4, A5, A6, A7 => %IW0 bis %IW7​

Analog Out​

2, 3, 4, 5, 6, 7, 8, 9 => %QW0 bis %QW7​

10, 11, 12, 13 => %QW8 bis %QW11​

Für andere Zielsysteme gibt es eigene Adressabbildungen, die Anwender der OpenPLC-Dokumentation entnehmen können.

In Automatisierungslösungen kommunizieren alle möglichen Geräte miteinander, etwa die SPS mit einer anderen SPS oder die SPS mit einem Motor, einer Pumpe, einem Ventil oder einem Sensor, oder aber ein SCADA-Server (siehe weiter unten) mit einer SPS. Hierfür existieren eine ganze Reihe von Kommunikationsprotokollen wie Profibus, BacNet, Modbus und Profinet. OpenPLC integriert das einfache und offene Modbus-Protokoll, das es seit den Siebzigerjahren in verschiedenen Geschmacksrichtungen gibt: über serielle Ports mit Binärdaten (RTU) oder ASCII-Daten (ASCII), und als Protokoll oberhalb TCP/IP. Letzteres ist heutzutage in Mode, weil dadurch parallel auch andere Protokolle wie HTTP koexistieren können. OpenPLC unterstützt übrigens Modbus RTU sowie Modbus TCP.

Der Modbus-Standard existiert schon seit den 1970er Jahren.

(Bild: modbus.com)

Modbus obliegt einer Master-Slave-Architektur – was inzwischen freilich kein politisch korrekter Begriff mehr sein dürfte. Trotzdem übernimmt der Autor die bestehende Nomenklatur. Jedenfalls initiiert der Master eine Kommunikation mit einem einzelnen Slave oder per Broadcast mit mehreren Slaves – bis zu 247 Slaves lassen sich anschließen – und bittet um Ausführung einer Funktion. Ein angesprochener Slave empfängt die Nachricht (den Request), führt die angeforderte Funktion aus oder meldet im Fehlerfall eine Exception beziehungsweise versendet im Normalfall eventuelle Rückgaben (die Response) an den Master.

Die nutzbaren Nachrichtentypen und ihr genauer Aufbau sind bei Modbus fest vorgegeben: etwa "Lesen des analogen Inputs von Port %IW0". Alle verwendeten Adressierungsschemata entsprechen dabei den weiter oben erläuterten.

Während die OpenPLC-Laufzeitumgebungen auf Desktop-Betriebssystemen auch als Modbus-Master fungieren können, arbeiten Mikrocontroller-basierte Systeme in OpenPLC ausschließlich als Slaves. Auf den von OpenPLC unterstützten Embedded-Boards lassen sich Modbus-Nachrichten auch über USB oder serielle Eingänge empfangen und versenden. Demzufolge kommt hier Modbus RTU zum Einsatz.

Der voreingestelle Standard-Port für Modbus TCP lautet 502, was sich aber in den Einstellungen ändern lässt, sollten Konflikte auftreten.

Adressierungsschemata für angeschlossene PLCs innerhalb OpenPLC sind auf der Projekt-Website verfügbar.

Zu beachten ist dabei, dass Modbus zwar dasselbe Adressierungsschema benutzt, sich aber in der Terminologie für Datentypen unterscheidet. So gelten folgende Zuordnungen:

PLC Datentyp................Modbus Datentyp​

Digital Input...............InputStatus​

Digital Output..............Coil​

Analog Input................Input Register​

Analog Output...............Holding Register​

Speicher....................Holding Register​

Jede über Modbus übertragene Nachricht besteht aus vier hintereinander folgenden Bestandteilen:

| MBAP Header | Slave ID | Function Code | Data |​
  • Der optionale MBAP-Header enthält für das Transportprotokoll (TCP) bestimmte Information. Bei RTU/ASCII fehlt er indes.
  • Die Slave ID besteht aus einem Byte, das eindeutig den gewünschten Modbus-Slave spezifiziert. Modbus TCP ignoriert manchmal diese ID, wenn das Endgerät sich eindeutig über dessen IP-Adresse identifizieren lässt.
  • Function Code definiert, welche Aktion die Modbus-Instanz ausführen soll, also meistens Lesen oder Schreiben. Beispiele sind FC 01 (Read Coils), FC 16 (Read Multiple Registers) oder FC 05 (Force Coil).
  • Data dient, wenig überraschend, zur Spezifikation der Daten für die aufzurufende Funktion. Bei Antworten des Geräts stehen dort die Daten, die das Gerät gelesen oder geschrieben hat.

Kleine Tipps am Rande:

  • Ein eigenes Testwerkzeug, um einen Modbus-Master zu simulieren, ist der kostenlose Radzio! Modbus Master Simulator (RMMS) für Windows (Download hier). Der Simulator unterstützt wie OpenPLC auch Modbus TCP und Modbus RTU. Mithilfe von RMMS können Anwender den Status der Ports eines angeschlossenen Mikrocontrollerboards abfragen. Dadurch behält er den Überblick über die aktuellen Zustände angeschlossener Modbus-Slaves wie etwa eines Arduino mit aktiver OpenPLC-Runtime.
  • Es gibt darüber hinaus Open-Source-Software, um auf Basis oder anstelle von Modbus MQTT-Messaging zu implementieren (siehe GitHub).
  • Wer will, kann auch Python zusammen mit dem PyModbus-Paket nutzen, um von einer beliebigen Plattform aus mit OpenPLC-Instanzen zu kommunizieren. Nähere Information dazu gibt es in der PyModbus Dokumentation.

Nach diesen Grundlagen und Installationshinweisen ist es an der Zeit, ein "echtes" Programm für einen Mikrocontroller mit dem OpenPLC-Editor zu schreiben, um es anschließend als OpenPLC-Runtime auf das Board zu übertragen. Die Qual der Wahl besteht darin, die für diesen Zweck richtige Sprache des IEC 61131-3-Standards auszuwählen. Alle Sprachen vorzustellen, würde den Rahmen dieses Artikels sprengen, deshalb hat sich der Autor entschieden, exemplarisch die grafische und anschauliche Sprache LD (Ladder Diagram) zu verwenden.

Als Einstieg eignet sich folgendes simples Beispiel, das lediglich aus einem Schalter und einer Lampe/LED besteht. Drückt jemand den Schalter (Push Button), soll die Lampe/LED für eine vorgegebene Zeit angehen und nach abgelaufener Zeit wieder ausgehen.

Das SPS-Programm benötigt dazu drei Komponenten. Als Eingabe den Schalter, als Ausgabe die Lampe/LED und einen TOF-Timer als Funktionsblock für die gewünschte Leuchtdauer (Eingang PT = Preset Time). Den Timer können Entwicklerinnen und Entwickler mit einer vordefinierten Zeit konfigurieren. Läuft die Zeit ab, schaltet der TOF-Timer seinen Ausgang Q, wodurch dieser Strom zur Lampe/LED durchlässt und diese leuchtet.

Ein neues Projekt lässt ich über File/New (Datei/Neu) im OpenPLC-Editor anlegen und in einem neuen Verzeichnis speichern. Es darf kein existierendes Verzeichnis sein, in dem sich schon Dateien befinden. Jedes so angelegte Verzeichnis enthält also exakt ein Projekt.

Danach erscheint ein Dialog, der fragt, welchen Namen das Projekt haben soll, in welcher Sprache die Programmierung erfolgt und welche Art von POU (Program Organization Unit) erstellt werden soll:

  • Ein Program ist eine vollständige Anwendung, die auf einer PLC läuft.
  • Eine Function repräsentiert eine wiederverwendbare Funktion mit einem Resultatswert.
  • Ein Function Block ist ein wiederverwendbarer Codeblock, der sich seinen Zustand zyklusübergreifend mithilfe von Variablen merken kann.

An dieser Stelle wählt der Autor beispielsweise “LEDTimer”, “Program” und “LD”, worauf OpenPLC eine Konfiguration, eine Ressource (bestehend aus einem Task und einer Instanz) anlegt, die sich im Projektfenster selektieren und inspizieren lassen.

Projektverzeichnis im OpenPLC-Editor: Ist das Projekt angelegt, erzeugt der Editor eine baumartige Projektstruktur aus Programmname und Ressourcen.

Ganz oben im Hauptfenster zeigt sich – nach Doppelklick auf das Projektsymbol LEDTimer (oberhalb des Ressource-Symbols) im Projektverzeichnis – nun ein Formular, in das sich die Variablen (-> +-Symbol) eintragen lassen.

Beispiele:

  • Schalter auf einem ersten digitalen Eingabeport mit dem Typ Bool.
  • LED auf einem digitalen Ausgabeport ebenfalls mit dem Typ Bool.

Die IEC-Adressen in Spalte 5 wie etwa %IX0.0 oder %QX0.0 fehlen hier freilich noch.

Auf dem Hauptfenster lassen sich die Variablen, Eingänge, Ausgänge für das Programm festlegen.

Unter Tasks lässt sich die Zykluszeit definieren. In der Regel dürften die Werte bei 20 ms und mehr liegen. Ist die Zahl zu hoch, reagiert die PLC unter Umständen zu träge. Ist sie zu niedrig, etwa 1 ms, könnte es sein, dass die PLC nicht in der Lage ist, die Zykluszeit einzuhalten. Standardmäßig ist eine Zykluszeit von 20 ms voreingestellt.

Es kann losgehen, mittels der LD-Icons im Editor-Fenster das Programm zu komponieren – zumindest fast:

Bei der Programmierung mit Ladder Logic lassen sich Komponenten aus dem oberen Menü des Editors auf das Programm ziehen.

Zunächst ist allerdings noch ein kleiner Ausflug zum Thema wiederverwendbare Funktionsblöcke notwendig. Sobald man ein Programm für OpenPLC erstellt, stehen vordefinierte Funktionsblöcke wie Flip-Flops, Timer, Zähler und vieles mehr zur Verfügung. Entwicklerinnen und Entwickler greifen auf diese Bibliothek sehr gerne und häufig zu. Zusätzlich lassen sich auch eigene Funktionsblöcke einbinden, was aber momentan zu Änderungen des internen Editor-Codes führt. Eine einfachere Möglichkeit gibt es leider nicht.

Die vordefinierten Funktionsblöcke sind im Editor-Code fest integriert, die eigenen Funktionsblöcke kommen zum Sourcecode hinzu und müssen per Kompilation zu einer Binärdatei verschmolzen werden. Sobald ein neues Update für OpenPLC installiert wird, gehen aus diesem Grund die eigenen Funktionsblöcke unwiderruflich verloren, weshalb man deren Quellen immer separat sichern sollte.

Zu beachten ist des Weiteren, dass OpenPLC als Implementierungssprache C nutzt (OpenPLC transpiliert also Programme wie die in Ladder Diagram nach C). Microcontroller-Firmware wie zum Beispiel die bei einem Arduino-Board setzt aber C++ ein. Daher müssen Funktionsblöcke, die auf Arduino-Funktionalität zugreifen wollen, eine Brücke zwischen beiden Welten vorsehen, etwa den Aufruf von Arduino-C++-Funktionalität über den Extern-C-Mechanismus von C++. Mehr zum Thema gibt es in der Dokumentation zum OpenPLC-Editor.

Im Beispiel (siehe übernächstes Bild) würde man eine linke Ladder/Leiter (Input) und eine rechte Ladder/Leiter (Output) mit jeweils mehreren Strängen über das Menü in den Editor ziehen. Linke Ladder referenzieren die Eingänge, rechte Ladder die Ausgänge. Die horizontalen Verbindungen, von denen es auch mehrere geben kann, führen daher immer von linker Ladder zu rechter Ladder. Danach lässt sich der Schalter (normal offener Schalter mit positiver Flanke) auf die Schaltung ziehen und mit der linken Ladder verbinden. Über die Bibliothek holen wir uns als Standardbaustein einen Off-Timer (TOF) und verbinden dessen IN-Eingang mit dem Schalter.

OpenPLC enthält zahlreiche vordefinierte Bausteine wie zum Beispiel Timer.

Am PT-Eingang (PT = Preset Time) des TOF steht per Konstante die Leuchtdauer fest, also beispielsweise 5000 Millisekunden als T#5000ms. Der Ausgang Q des Timers lässt sich mit der LED verbinden und diese wiederum mit der rechten Ladder. Jetzt ist die Verbindung von linker Ladder zu rechter Ladder geschlossen. Den ET-Ausgang (Elapsed Time) des TOF-Bausteins könnten Developer theoretisch für die Abfrage nutzen, wie lange der Timer schon läuft. Im vorliegenden Beispiel bleibt er unbenutzt.

Einfaches Ladder-Logic-Programm, bei dem beim Schließen eines Schalters eine Lampe für eine festgelegte Zeit leuchtet.

Durch Betätigen des "laufenden Mannes" im Menü lässt sich das Beispiel simulieren. Zur Visualisierung der Simulation drückt man das oberste Brillensymbol im Bereich links unten. Durch Anklicken weiterer Brillen-Symbole lassen sich die zugehörigen Variablen beobachten. Hier eignet sich am besten der Schalter, da der Autor durch dessen Schließen die gewünschte Reaktion erzielen möchte.

Noch passiert nichts, weil der Schalter standardmäßig offen (FALSE) bleibt. Selektiert man den Schalter und schließt ihn per Kontextmenü, indem man auf das Schlosssymbol des Schalters klickt und ihn auf TRUE setzt (Schalter-Variable -> TRUE), so startet der Timer und die Lampe fängt an zu leuchten. Im Simulator bedeutet dies, dass die Lampe und die zu ihr führende Leitung nun grün erscheinen. Nach 5 Sekunden läuft der Timer ab und setzt dann seinen Q-Ausgang auf FALSE, wodurch die Lampe erlischt (Lampe und Zuleitung sind dann wieder schwarz).

Programme lassen sich in OpenPLC simulieren. Grün bedeutet an, Schwarz aus.

Ist das Ergebnis der Simulation zufriedenstellend, klickt man auf das Arduino-Icon im Menü, um das Programm beziehungsweise die OpenPLC-Firmware auf das Mikrocontrollerboard hochzuladen. Dazu wählt der Autor das von ihm verwendete Board aus dem Dialog und gibt weitere benötigte Angaben wie Boardtyp, SSID des optionalen WiFi-Netzes, Passwort und so weiter ein.

Im Vorfeld hat der Autor an das Board einen Push-Button über einen Pull-Down-Widerstand (1 bis 10 kOhm) an einen digitalen Eingang angeschlossen und die LED über einen 220+-100 Ohm-Widerstand an einen digitalen Ausgang des Boards. Die Variablen im OpenPLC-Editor haben einen Schalter und LED mit den richtigen Adressen – z. B. %IX0.0 und %QX0.0 – erhalten.

Der OpenPLC-Editor erlaubt den Upload der Firmware (des Programmes) auf die Zielhardware.

In Ladder-Diagram-Programmen lassen sich einzelne Komponenten zu logischen Kombinationen verbinden (siehe nachfolgende Abbildung). Die Eingänge haben eckige Klammern, die Ausgänge runde Klammern oder Kreise. Hinter allem stecken Variablen. Ein Ausgang ist auch als Eingang (für die nächsten Schritte) nutzbar. Das Ausführen dieser Programme passiert von links nach rechts und von oben nach unten. Funktionsblöcke enthalten eigene Logik hinter einer Black Box, verwenden Eingänge für Parameter und Ausgänge für Resultate.

Logische Verarbeitung im Ladder Diagram.

Es handelt sich hierbei um ein recht einfaches Beispiel – in komplexeren finden sich auch Verzweigungen, mehrfach auftretende Variablen, Bausteine und verschiedene parallele Verbindungen zwischen den beiden Laddern. Das folgende Beispielbild zeigt eine Ampelschaltung von Yekki inklusive Start-Schalter:

Ladder-Logic-Diagramme können auch sehr komplex werden, wie das Beispiel einer Ampelschaltung (inklusive Start-Schalter) zeigt.

(Bild: Yekki, 2020)

Hier noch ein weiteres Exemplar für ein PLC-Programm mit einem Funktionsblock (siehe folgende Abbildung). Es nutzt einen Zähler des Typs CTUD, der sowohl hoch- als auch runterzählen kann. Das zugehörige LD-Programm verändert abhängig vom Wert der Sensoren IN_sensor_A und IN_sensor_B den Zählerstand. Die Sensoren stellen fest, sobald Personen ein Restaurant (Kapazität : 25 Personen) betreten oder verlassen. Sobald der CTUD-Funktionsblock in der Variablen CV (Current Value) den Schwellwert von 25 erreicht, hat das Restaurant seine Kapazität ausgeschöpft und signalisiert dies über eine an der PLC angeschlossene Anzeigetafel nach aussen. Die beiden Sensoren könnten sich beispielsweise auf Lichtschranken beziehen, die vor und nach der Eingangstür platziert sind.

Stimmen die Eingangsbedingungen zählt der Counter entweder hoch oder runter, bis die vordefinierte Grenze von 25 erreicht ist.

(Bild: techocrazed.com)

Ein Up-Down-Counter (CTUD) verfügt über folgende Eingänge und Ausgänge:

  • Eingang CU (Count Up): inkrementiert Zählerstand bei jedem Signal.
  • Eingang CD (Count Down): dekrementiert Zählerstand bei jedem Signal.
  • Eingang R (Reset): dient zum Reset des Counters.
  • Eingang PV (Preset Value): Schwellwert, bis zu dem der Counter hochzählt.
  • Eingang LD (Load): setzt Zählerstand auf Wert von PV zurück.
  • Eingang PV (Preset Value): Schwellwert, bis zu dem der Counter hochzählt.
  • Ausgang QU (Output for Count Up): wird aktiv, sobald der Zähler CV den Wert von PV erreicht.
  • Ausgang QD (Output for Count Down): wird aktiv, sobald der Zähler CV den Wert 0 annimmt.
  • Ausgang CV (Current Value): aktueller Zählerstand.

Dem LD-Programm ist Folgendes zu entnehmen:

  • Wenn NOT IN_sensor_A einen Wert TRUE besitzt und gleichzeitig IN_sensor_B mit einer negativen Flanke von TRUE auf FALSE geändert wurde, erfolgt ein Inkrementieren des Counters (am CU-Eingang). Das könnte zum Beispiel den Vorgang Gast betritt von Straße aus das Restaurant repräsentieren.
  • Wenn der Eingang NOT IN_sensor_B den Wert TRUE hat und gleichzeitig IN_sensor_A mit einer negativen Flanke von TRUE auf FALSE gesetzt wurde, erfolgt ein Dekrementieren des Counters (am CD-Eingang). Das wiederum könnte den Vorgang Gast betritt vom Restaurant aus die Straße darstellen.
  • Der Counter lässt sich über den Schalter IN_switch_reset am R(ESET)-Eingang zurücksetzen.
  • Der obere Schwellwert (PV) liegt in der Momentaufnahme bei 25, der augenblickliche Zählerstand (CV) bei 0.
  • Der QU-Ausgang versendet ein Signal, sobald PV == CV (Zählerstand) gilt.

Es gibt bei anderen Versionen des CTUD noch zwei weitere Eingänge CD_T (Count Down Time) und CU_T (Count Up Time), an denen man über Konstanten festlegen kann, wie lange ein Dekrementieren beziehungsweise Inkrementieren in Millisekunden dauern soll.

Darüber hinaus gibt es auch rein inkrementierende Zähler (CTU) oder rein dekrementierende Zähler (CTD) als Funktionsblöcke.

Doch damit genug zu LD-Beispielen. Wie bereits erwähnt, ist LD nur eine von fünf Sprachen des IEC-Standards, die OpenPLC zur Verfügung stellt. Eine weitere Sprache heißt ST (Structured Text). Diese Sprache ist in diesem Zusammenhang insofern interessant, als zum einen OpenPLC jedes Programm letztendlich nach ST übersetzt (und ST wiederum nach C), und als es sich zum anderen im Gegensatz zu LD um eine rein textuelle Sprache handelt. Hier ein kleines Code-Fragment, das dies veranschaulicht. Darin geht es um Starten und Stoppen einer Pumpe, abhängig vom Zustand eines Kontrollventils.

(* Diese Routine startet eine Pumpe sobald das Kontrollventil geöffnet ist und
stoppt die Pumpe, wenn das Kontrolventil schließt oder nicht offen ist *)
IF #Kontrollventil1_Closed = false AND #Kontrollventil1_Open = True THEN
    #Pumpen_Start := True;
ELSIF #Kontrollventil1_Closed = true OR #Kontrollventil1_Open = False THEN
    #Pumpen_Start := False;
END_IF;

ST unterstützt unter anderem:

  • while- und repeat-Schleifen
  • for-Schleifen,
  • bedingte Anweisungen,
  • case-Anweisungen,
  • arithmetische Operationen,
  • logische Operationen,
  • Vergleichsoperatoren,
  • Zuweisungen,
  • Aufrufe von Funktionen und Funktionsblöcken,
  • und einiges mehr.

Das ist zwar nur ein minimales Beispiel, vermittelt aber zumindest eine Idee von der Syntax und der Semantik. Offensichtlich können die Schöpfer von ST eine gewisse Verwandtschaft zu Algol- oder C-artigen Programmiersprachen nicht abstreiten.

Im Übrigen sind LD und ST zwei der am meisten genutzten Sprachen aus dem IEC-Standard, weshalb der Fokus auf diese Sprachen nicht ganz zufällig war.

Nach dieser eher pragmatischen Vorstellung fehlt noch eine kurze Erläuterung zu der generellen Strukturierung von PLC-Anwendungen. Die genannten Konzepte sollten an dieser Stelle, das heißt nach den vorangegangenen Abschnitten und Beispielen Sinn ergeben.

Gemäß des IEC 61131-3-Standards setzen sich PLC-Anwendungen nämlich aus folgenden Ingredienzen zusammen:

  • Program: besteht aus einer Sequenz von Anweisungen, die eine PLC ausführt.
  • Task: besteht aus ein oder mehreren Programs. Es kann mehrere Tasks geben, die jeweils in einem bestimmten Zeitintervall oder aufgrund eines bestimmten Ereignisses zur Ausführung gelangen.
  • Networks: Programs können sich aus Networks zusammensetzen, von denen jedes eine eigene Substruktur des Programms oder der Schaltung definiert.
  • Inputs und Outputs: definieren die Eingänge beziehungsweise Ausgänge einer Anwendung.
  • Function Blocks: sind wiederverwendbare Komponenten, die dem Anwender das wiederholte Schreiben komplexer Sequenzen von Instruktionen ersparen.
  • Variables: gibt es in zwei Facetten: lokale Variablen gelten nur innerhalb eines Program oder Function Block, globale Variablen sind Program- und sogar Task-weit verfügbar.

Nicht jede PLC oder Programmierumgebung für eine PLC unterstützt all diese Konzepte vollumfänglich.

Wer auf der OpenPLC-Runtime für Raspberry-Pi, Linux- oder Windows-Computer ein Mikrocontrollerboard nutzen möchte, um dessen Ports über die Host-Runtime anzusprechen, erreicht dies über ein "leeres" Dummy-Projekt. Ein adäquates Projekt speziell für diesen Zweck befindet sich auf der Webseite für Slave-Devices als Datei Blank Project.zip. Diese Datei müssen Entwicklerinnen und Entwickler entpacken und über den OpenPLC-Editor als Projekt laden, um danach die Runtime auf das Board zu bringen. Anschließend melden sie sich über die URL bei der Host-Runtime an und fügen im Menüpunkt Slaves über <add new Device> das entsprechende Board hinzu. Als Einstellung ist unter anderem erforderlich, ob das Target-Gerät über einen seriellen Port (USB) oder über TCP erreichbar ist. Im ersteren Fall verbindet sich der Host mit dem Target über Modbus RTU, andernfalls über Modbus TCP. Bei Modbus RTU sind noch die Baudrate, die serielle Schnittstelle oder COM-Port des Hosts und die Slave ID anzugeben. Verfügt das Board über TCP, also einen Ethernet- oder WiFi-Anschluss, kann stattdessen Modbus TCP zum Einsatz kommen. Angeben müssen wir dann unter anderem die IP-Adresse des gewünschten Slaves.

Die OpenPLC-Runtime des Hosts bildet die I/O-Ports des als Slave angeschlossenen Boards gemäß oberem Adressierungsschema in Host-eigene Adressen ab: zum Beispiel auf %IX100.0 bis %I100.3, sofern das Board vier digitale Ports anbietet, oder entsprechend auf %QX100.0 bis %QX100.5 bei Verfügbarkeit von sechs digitalen Ausgabeports – ähnliches gilt für analoge Eingabe- und Ausgabeports. Bei Anschluss eines weiteren Boards, mit beispielsweise vier digitalen Eingabe-Anschlüssen, setzt OpenPLC einfach die Nummerierung auf dem Host fort, etwa mit %IX100.4 bis %IX100.7. Ein auf der Host-Runtime ablaufendes Programm kann diese Adressen dann transparent wie eigene Eingänge und Ausgänge nutzen, um I/O-Operationen auf dem Target durchzuführen. Der Mikrocontroller, genauer gesagt die auf ihm laufende Runtime, unterstützt somit eine Erweiterung des Hosts mit I/O-Schnittstellen.

Keine PLC unterstützt jede erdenkliche Sensorik- oder Aktorik-Hardware, etwa alle weltweit verfügbaren Sensoren. Gehört zum Beispiel ausgerechnet der in einem Projekt benötigte Sensor mangels Treiber nicht zu den unterstützten Komponenten einer PLC, so erweist sich dies bei kommerziellen PLCs unter Umständen als echtes Problem. Dort sind die Treiber meistens fest integriert, also durch Nutzer nicht änderbar.

Für Runtime-Instanzen auf Windows, macOS oder Linux/Raspberry Pi hat OpenPLC eine Lösung für dieses Problem geschaffen. Entsprechende PSM-Treiber (PSM = Python SubModule) lassen sich dort in der OpenPLC-Runtime unter dem Menüpunkt Hardware selbst erstellen. Als Programmiersprache ist dafür – nomen est omen – Python ab Version 3 notwendig.

Ein solcher Treiber besteht aus drei Teilen: einer Initialisierungsmethode für die Hardware sowie einer Methode zum Update der Eingänge und einer zum Update der Ausgänge.

Da die OpenPLC-Runtime mit privilegierten Super-User-Rechten läuft, sollten die im Treiber benötigten Python-Bibliotheken auf macOS-, Linux- oder Raspberry-Pi-Umgebungen immer mit sudo pip3 install <Paketname> installiert werden.

Hier exemplarisch ein Code-Template, das sich in der OpenPLC-Runtime über einen Browser (Menüpunkt Hardware) nutzen lässt. Für Windows ist beispielsweise PSM für Windows zu selektieren und für Linux oder ein Raspberry-Pi-Board PSM für Linux.

# Importieren der notwendigen Bibliotheken:
import psm
import time
# ...

# Variablen:
# z.B. var_state = True
# ...

# Initialisierungsroutine
def hardware_init():
    # ...
    psm.start()
    
# Code zum Setzen von Inputs
def update_inputs(var_state):
    # z.B. psm.set_var("IX0.0", var_state)
    # ...
    
    
# Code zum Schreiben der Outputs:
def update_outputs():
    # z.B. a = psm.get_var("QX2.1")
    # ...
# Zyklische Hauptroutine
if __name__ == "__main__":
    hardware_init()
    while (not psm.should_quit()):
        #Inputs
        #PSM HeartBeat
            # ...
        else:
            # ...
        update_inputs(var_state)
        
        #Outputs
        # ...
        update_outputs()
        time.sleep(0.1) # PSM Zyklus Zeit einstellen
    psm.stop()

In einem PLC-Programm sind die Messungen des Sensors über im Treiber festgelegte Adressen als Variablen definierbar, etwa als eine Variable temp mit Adresse %QW0. Dadurch erlauben sie ein Einbringen dieser Messwerte in eigene Programme (-> Variablendefinition im OpenPLC-Editor).

Der PSM-Treiber läuft in einem eigenen Thread, um den Zyklen der Runtime nicht in die Quere zu kommen. Manche Sensorzugriffe können Sekunden dauern.

Dank PSM sind entsprechende Änderungen und Erweiterungen der OpenPLC-Runtime kein Problem mehr.

Einen Wermutstropfen gibt es jedoch. Wer auf einer OpenPLC-Runtime für Raspberry Pi einen PSM-Treiber integriert, ersetzt damit vollständig die von OpenPLC bereitgestellte Treiberunterstützung für GPIO-Pins. Darum müsste sich dann ebenfalls der PSM-Treiber kümmern.

OpenPLC bietet ein exzellentes Instrument, um programmatisch die Technik von PLCs kennenzulernen. Es ist Open Source, hat eine aktive Community und implementiert alle fünf Sprachen des ISO-Standards. Zudem lässt es sich auf diverser Standard-Hardware installieren, egal ob preisgünstiges Mikrocontrollerboard oder heimischer Computer. Wer es noch bequemer haben möchte, besorgt sich eine SPS-Hardware wie die von Controllino oder UniPi. Auch dort steht OpenPLC zur Verfügung. Freilich gibt es auch preisgünstige Starter-Sets wie die Siemens Logo!-Serie, bei der sich Technik eines industriellen Herstellers einsetzen lässt, aber für den Einstieg in die Materie und für Experimente oder fürs Studium bietet OpenPLC eine optimale und kostenlose Lösung.

Nachdem die beiden bisherigen Artikel die Welt der SPSen und deren Programmieren mit OpenPLC thematisiert haben, stellt der abschließende dritte Teil vor, in welcher Umgebung eine SPS arbeitet.

(mdo)