Den Controllino in vorhandene Automatisierungssysteme integrieren (am Beispiel einer SIMATIC S7-1200)

Prozessdaten zwischen dem Controllino und der SIMATIC S7-1200 austauschen

In unserem Beitrag Software Open Source Steuerung: der Controllino haben wir den Controllino kurz vorgestellt. In diesem Beitrag möchte ich darauf eingehen, wie der Controllino in bereits vorhandene Steuerungssysteme integriert werden kann und welche Voraussetzungen dafür geschaffen werden müssen. Konkret schauen wir uns an, wie der Controllino auf Basis von ModbusTCP mit einer SIMATIC S7-1200 Daten austauschen kann.

In diesem Beitrag implementieren wir einen Datenaustausch zwischen einem Controllino Maxi Automation und einer SIMATIC S7-1211 DC/DC/DC auf Basis von ModbusTCP. Der Controllino fungiert als ModbusTCP Server (Modbus Slave) und die SIMATIC als ModbusTCP Client (Modbus Master).

Controllino (oder Arduino) als ModbusTCP Server (Modbus Slave), die SIMATIC S7-1200 als ModbusTCP Client (Modbus Master)

In diesem Projekt lesen wir mit dem Controllino einen analogen Messwert von jeweils einem Stromsensor (LEM AT 5 B10) ein, errechnen daraus einen dritten Wert und übertragen alle drei Werte mittels ModbusTCP an die SIMATIC S7-1200.

Der Stromsensor misst und wandelt den Messwert in ein analoges Signal um: 0...5A AC gemessener Strom entspricht 0...10V DC. Dieses Signal wird vom Controllino mittels AD-Wandler digitalisiert und kann dann im Programm weiterverarbeitet werden.

Die Sensoren messen den Strom und senden das Messsignal in Form von 0...10V DC an den Controllino.

Voraussetzungen

  • Controllino (und eingerichtete Arduino IDE)
  • Simatic S7-1200 und TIA Portal
  • Beide via Ethernet Switch verbunden (alternativ: Ethernet Router)

Da der Controllino kompatibel zu Arduino ist, funktioniert das hier gezeigte Szenario samt Source Code ebenso mit einem Arduino: z.B. Arduino Mega + Ethernet Shield

Implementierung Controllino

Für den Controllino benötigen wir eine ModbusTCP Server Bibliothek. Diese können wir auf der Herstellerwebsite herunterladen: Mudbus.h
Wir entpacken das Archiv und legen es in unseren Arduino Libraries Ordner.

Neben der Mudbus.h Bibliothek habe ich in dem Arduino-Code noch weitere Bibliotheken importiert. Gerade die Verwendung des Watchdogs halte ich im Controllino als industrielle Steuerung für sinnvoll, damit dieser im Notfall zurückgesetzt wird.

//#define DEBUG

#include <Controllino.h>
#include <avr/wdt.h>
#include <SPI.h>
#include <Ethernet.h>

#include "Mudbus.h"

Im nächsten Schritt erstellen wir eine Modbus Server Instanz mbtcp_server und definieren die Variablen dafür. Das werden quasi die Modbus (Holding) Register, die unsere SIMATIC im Anschluss lesen wird. Gemäß Modbus Spezifikation haben die Modbus Register eine Größe von 16 Bit - dementsprechend definieren wir unsere Register als uint16_t.

Mudbus mbtcp_server;
uint16_t ma_machine_total;
uint16_t ma_motor_only;
uint16_t ma_control_system;

Anschließend implementieren wir void setup(), konfigurieren den Watchdog, konfigurieren die TCP/IP Connection und legen den pinMode für unsere beiden analogen Sensoren fest.

void setup() {
  // watchdog
  wdt_enable(WDTO_1S);

  // ethernet
  uint8_t mac[] = { 0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02 };
  uint8_t ip[] = { 192, 168, 2, 202 };
  uint8_t gateway[] = { 192, 168, 2, 1 };
  uint8_t subnet[] = { 255, 255, 255, 0 };
  Ethernet.begin(mac, ip, gateway, subnet);
  
  // wait
  delay(500);

  // define input pins
  pinMode(CONTROLLINO_A0, INPUT);
  pinMode(CONTROLLINO_A1, INPUT);

#ifdef DEBUG
  // start serial communication
  Serial.begin(9600);
#endif
}

Im Anschluss implementieren wir die Funktion read_current_sensor(), welche als Parameter einen analogen Eingang nimmt und einen uint16_t Wert returnt. Wie der Name schon vermuten lässt, nutzen wir diese Funktion, um die Stromsensoren auszulesen und die gemessene Spannung gemäß Sensorspezifikation in einen realen Messwert zu überführen.

Da der Sensor einen Strom von 0...5A messen kann, habe ich mich entschieden den Messwert der Sensoren im Programm in mA zu verarbeiten, dies entspricht einem Messwert von 0...5000 mA.

5000 < 2^16, bedeutet wir können den Messwert in einem Modbus Register übertragen. Außerdem erspart es die Konvertierung zwischen Gleitkomma- und Ganzzahlen, sowohl auf dem Controllino als auch später auf der SIMATIC.

uint16_t read_current_sensor(int analog_input_pin) {
  uint16_t adc_value = 0;
  uint16_t read_voltage_mv = 0;
  uint16_t sensor_current_ma = 0;
  /* read mv from current sensor and convert into current in ma
   *  Controllino ADC:
   *  -     0 mv => analog_read: 0
   *  - 30400 mV => analog_read: 1023
   *  
   *  Sensor charakteristic:
   *  -     0 mv =>     0 mA
   *  - 10000 mV =>  5000 mA
   */
  adc_value = analogRead(analog_input_pin);
  read_voltage_mv = map(adc_value, 0, 1023, 0, 30400);
  sensor_current_ma = map(read_voltage_mv, 0, 10000, 0, 5000);
  return sensor_current_ma;
}

Zu guter letzt implementieren wir die void loop(). Wir starten mit dem Reset des Watchdogs, rufen die Methode Run unseres ModbusTCP Servers auf und lesen danach unsere Messwerte mittels read_current_sensor() ein.

Für unsere Messwerte hält unser ModbusTCP Server mbtcp_server ein Array bereit, in das wir unsere Werte ablegen können. Dieses Array kann dann von einem ModbusTCP Client (unsere SIMATIC) als Holding Register gelesen werden. 

  mbtcp_server.R[0] = ma_machine_total;   // - SIMATIC S7-1200 Implementierung Register: 40001
  mbtcp_server.R[1] = ma_motor_only;      // - SIMATIC S7-1200 Implementierung Register: 40002
  mbtcp_server.R[2] = ma_control_system;  // - SIMATIC S7-1200 Implementierung Register: 40003
void loop() {
  wdt_reset();
  mbtcp_server.Run();

  // sensor 0
  ma_machine_total = read_current_sensor(CONTROLLINO_A0);
  // sensor 1
  ma_motor_only = read_current_sensor(CONTROLLINO_A1);
  // current (mA) control system
  ma_control_system = ma_machine_total - ma_motor_only;
  
#ifdef DEBUG
  Serial.print("Machine Total (mA): ");
  Serial.println(ma_machine_total);

  Serial.print("Motor Only (mA): ");
  Serial.println(ma_motor_only);

  Serial.print("Control System (mA): ");
  Serial.println(ma_control_system);
#endif

  mbtcp_server.R[0] = ma_machine_total;   // - SIMATIC S7-1200 Implementierung Register: 40001
  mbtcp_server.R[1] = ma_motor_only;      // - SIMATIC S7-1200 Implementierung Register: 40002
  mbtcp_server.R[2] = ma_control_system;  // - SIMATIC S7-1200 Implementierung Register: 40003
}

Der gesamte Code im Controllino sieht dann so aus:

// get modbus library from https://www.controllino.com/wp-content/uploads/2017/09/Mudbus.zip

//#define DEBUG

#include <Controllino.h>
#include <avr/wdt.h>
#include <SPI.h>
#include <Ethernet.h>

#include "Mudbus.h"

Mudbus mbtcp_server;
uint16_t ma_machine_total;
uint16_t ma_motor_only;
uint16_t ma_control_system;

void setup() {
  // watchdog
  wdt_enable(WDTO_1S);

  // ethernet
  uint8_t mac[] = { 0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02 };
  uint8_t ip[] = { 192, 168, 2, 202 };
  uint8_t gateway[] = { 192, 168, 2, 1 };
  uint8_t subnet[] = { 255, 255, 255, 0 };
  Ethernet.begin(mac, ip, gateway, subnet);
  
  // wait
  delay(500);

  // define input pins
  pinMode(CONTROLLINO_A0, INPUT);
  pinMode(CONTROLLINO_A1, INPUT);

#ifdef DEBUG
  // start serial communication
  Serial.begin(9600);
#endif
}

void loop() {
  wdt_reset();
  mbtcp_server.Run();

  // sensor 0
  ma_machine_total = read_current_sensor(CONTROLLINO_A0);
  // sensor 1
  ma_motor_only = read_current_sensor(CONTROLLINO_A1);
  // current (mA) control system
  ma_control_system = ma_machine_total - ma_motor_only;
  
#ifdef DEBUG
  Serial.print("Machine Total (mA): ");
  Serial.println(ma_machine_total);

  Serial.print("Motor Only (mA): ");
  Serial.println(ma_motor_only);

  Serial.print("Control System (mA): ");
  Serial.println(ma_control_system);
#endif

  mbtcp_server.R[0] = ma_machine_total;   // - SIMATIC S7-1200 Implementierung Register: 40001
  mbtcp_server.R[1] = ma_motor_only;      // - SIMATIC S7-1200 Implementierung Register: 40002
  mbtcp_server.R[2] = ma_control_system;  // - SIMATIC S7-1200 Implementierung Register: 40003
}

uint16_t read_current_sensor(int analog_input_pin) {
  uint16_t adc_value = 0;
  uint16_t read_voltage_mv = 0;
  uint16_t sensor_current_ma = 0;
  /* read mv from current sensor and convert into current in ma
   *  Controllino ADC:
   *  -     0 mv => analog_read: 0
   *  - 30400 mV => analog_read: 1023
   *  
   *  Sensor charakteristic:
   *  -     0 mv =>     0 mA
   *  - 10000 mV =>  5000 mA
   */
  adc_value = analogRead(analog_input_pin);
  read_voltage_mv = map(adc_value, 0, 1023, 0, 30400);
  sensor_current_ma = map(read_voltage_mv, 0, 10000, 0, 5000);
  return sensor_current_ma;
}

Zu Debugging-Zwecken habe ich mir die Messwerte der Sensoren zusätzlich per Serial.print ausgegeben. Wenn wir unseren Controllino jetzt starten, dann sehen wir im Serial Monitor die Messwerte, die auch gleichzeitig über ModbusTCP bereitgestellt werden.

Implementierung SIMATIC S7-1200 im TIA Portal

Nachdem wir die Schnittstelle auf dem Controllino erfolgreich implementiert haben, widmen wir uns nun der Implementierung des ModbusTCP Clients auf unserer SIMATIC. Hierzu legen wir ein neues Projekt im TIA Portal an und fügen als erstes die S7-1200 Steuerung hinzu. In diesem Beispiel verwende ich eine S7-1211 DC/DC/DC.

Anschließend starten wir mit einem leeren Datenblock, den wir zur Konfiguration der ModbusTCP Verbindung und zum Speichern der ausgetauschten Daten verwenden. Bei mir heißt dieser Controllino_Modbus_Client.

die Modbus Client Konfiguration vorbereiten

In diesem Datenblock legen wir fünf Variablen an:

  • disconnect - Typ Bool - zum Trennen der Verbindung (falls nötig)
  • ip_config- Typ TCON_IP_v4 - zum Konfigurieren der TCP/IP Verbindung
  • data_conf- Typ Struct - zum Konfigurieren des Modbus-Registerbereichs
  • data - Typ Struct - zum Zwischenspeichern der ausgelesenen Daten
  • log - Typ Struct - zum Speichern der Statusmeldungen der MB_CLIENT Instanz

Wir legen die Variablen entsprechend an. Sobald beim Anlegen von ip_config unter Data Type der Datentyp TCON_IP_v4 eingegeben wird, ergänzt TIA automatisch alle zusätzlich benötigten Identifier für diesen Datentyp - ip_config lässt sich dann aufklappen (siehe Screenshot).
Wir schauen uns folgende Variablen genauer an und bereiten damit auch gleichzeitig die Konfiguration unseres Modbus Client vor - den Client selbst implementieren wir erst im nächsten Schritt:

ip_config

  • InterfaceId - gibt das Interface an (Ethernet Port), das die SIMATIC zur Kommunikation nutzen soll
  • ConnectionType - gibt an, welches IP Protokoll verwendet werden soll (TCP oder UDP), wir verwenden TCP und setzen deswegen hier 11 (dec) ein (DEC11 = 16#000B)
  • RemoteAddress.ADDR.ADDR[] - hier geben wir die IP Adresse von unserem Controllino ein, jedes Oktett belegt einen eigenen Index im Array vom Typ Byte
  • RemotePort - ist der Port auf dem der Server die Verbindung annimmt, 502 ist für ModbusTCP Standard (könnte in der Arduino Bibliothek geändert werden)

Um die InterfaceId zu finden, müssen wir in der Device Configuration der S7-1200 schauen. Dazu gehen wir in die Eigenschaften und anschließend auf System Constants. Dort suchen wir den Profinet Port, mit dem die S7-1200 an das Netzwerk angebunden ist, an dem auch der Controllino hängt. (Hat die S7 mehr als einen Profinet Port, muss hier der richtige rausgesucht werden).
Im Datenblock kann dann die InterfaceId wahlweise als Integer angegeben werden, oder als String.

data_conf

  • mb_method - ModbusTCP bzw. Modbus generell unterstützt mehrer Modi, darunter lesen/schreiben von Coils und Registern. Wir möchten Holding Register lesen, das ist nach Modbus Spezifikation der Modus 3. Laut der TIA Hilfe (siehe folgenden Screenshot) benötigt unser SIMATIC MB_CLIENT für den Modbus Modus 3 den Parameter 0.
  • mb_data_addr - die Adresse des 1. Modbus Registers das gelesen werden soll. Laut Modbus Spezifikation beginnen die Holding Register bei 40001. Da wir die ersten drei Register lesen wollen starten wir mit dem ersten Register.
  • mb_data_len - die Anzahl der Holding Register die gelesen werden sollen (drei Register insgesamt; dementsprechend 40001, 40002 und 40003)

Die Registeradressierung im Sourcecode unserer beiden Steuerungen sieht dementsprechend folgendermaßen aus:

data

Ein Array vom Typ Word mit der Länge von drei. Hier speichern wir die vom Controllino gelesenen Holding Register ab.

log

Hier speichern wir den Status unseres MB_CLIENT und können Fehlercodes einsehen sowie den aktuellen Betriebszustand.

den Modbus Client implementieren (MB_CLIENT)

Nachdem wir nun den Datenblock angelegt und für unseren Anwendungsfall entsprechende Konfiguration hinterlegt haben, kommen wir zu der Implementierung unseres eigentlichen Modbus Clients in der SIMATIC. Glücklicherweise steht uns im TIA Portal dazu eine geeignete Bibliothek - MB_CLIENT - zur Verfügung.

Zur besseren Organisation des Projekts habe ich dafür eine neue Funktion mit dem Namen mb_client angelegt und den MB_CLIENT dort implementiert. Per Drag & Drop wird der MB_CLIENT aus dem Bereich Instructions/Communication in den Editor gezogen.

Daraufhin bittet uns das TIA Portal noch den zugehörigen Datenblock anzulegen und anschließend können wir unseren importierten MB_CLIENT mit den vorig festgelegten Parametern versehen.

Für den Parameter REQ habe ich als Clock Signal 5Hz gewählt, das entspricht einer Zykluszeit von ca. 200 ms. Bedeutet: alle 200 ms werden die Register des Controllino gelesen. Je nach Auslastung des Controllino muss die Zykluszeit etwas angepasst werden.

Zum Schluss habe ich mir in der SIMATIC noch PLC-Tags angelegt, in die ich die vom Controllino ausgelesenen Register kopiere. Dann fehlt nur noch der Aufruf unserer Funktion mb_client in der Main und wir können das Projekt kompilieren und auf die SIMATIC übertragen.