Hardware
Für diese Applikation wird das I2C-Master Modul verwendet.
– Frei programmierbar mit Arduino-IDE
– Status-LED für SDA, SCL und INT
– Jumper zum aktivieren der Terminierung
– Blaue LED frei programmierbar. Z.B. für Status der LAN-Verbindung
– Steckplatz für WizNET W5500 LAN-Modul
– BUS-Stecker zum Anschluss weiterer I2C-Module
– Steckplatz für USB-RS232 Programmieradapter
Aufbau
Für unser Beispiel ist als Kopf der Arduino-I2C-Master mit gestecktem WizNET W5500 LAN-Modul. Daneben folgende Module
– Digitale I2C-Eingangskarte I2HE (Slave-Adresse 112)
– Digitale I2C-Ausgangskarte I2HA (Slave-Adresse 64)
– Analoge I2C-Eingangskarte I2HAE (Slave Adresse 16)
– Analoge I2C-Ausgangskarte I2HAA (Slave Adresse 176)
Software Modbus-TCP-Server
Die Modbus-TCP-Bibliothek für den Arduino kommt von André Sarmento Barbosa
http://github.com/andresarmento/modbus-arduino
Ich habe in paar verschiedene Bibliotheken ausprobiert und diese hat auf Anhieb super funktioniert.
Diese Bibliotheken werden benötigt:
#include <Wire.h> // I2C-Lib #include <avr/wdt.h> // Watchdog #include <Modbus.h> // ArduinoRS485 library #include <ModbusIP.h> // Modbus
//ModbusIP object
ModbusIP mb;
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 7};
Dann definieren wir die Variablen für die verwendeten Raspberry-SPS-Baugruppen.
Sollen mehrere Eingangskarten verwendet werden, einfach die drei Zeilen eines Blocks kopieren und aus der 1 eine 2 machen u.s.w.
// I2C-Baugruppen definieren und globale Variablen anlegen #define I2E1_ADDR 112 >> 1 // I2C-Digital INPUT Addresse als 7 Bit int I2E1_WERT=0; bool I2E1_OK; #define I2A1_ADDR 64 >> 1 // I2C-Digital OUTPUT Addresse als 7 Bit int I2A1_WERT=0; bool I2A1_OK; #define I2AE1_ADDR 16 >> 1 // I2C-Analog INPUT Addresse als 7 Bit int I2AE1_WERT[5]; bool I2AE1_OK; #define I2AA1_ADDR 176 >> 1 // I2C-Analog OUTPUT Addresse als 7 Bit int I2AA1_WERT[4]; bool I2AA1_OK;
Im Bereich Setup werden die Pin-Modes und die serielle Schnittstelle eingestellt.
Weiter unten kommt der Start und die Konfiguration vom Modbus-Server.
Die input-Register können von der SPS nur gelesen werden. In die Holding-Register kann die SPS Werte schreiben, die dann vom Modbus-TCP-Server gelesen und verarbeitet werden können.
// Modbus-TCP Server starten // ------------------------- Serial.println("Modbus TCP Server"); mb.config(mac, ip); // Input-Register anlegen (Server -> SPS) mb.addIreg(0); // Input-Register 0 = 30001 in der SPS mb.addIreg(1); // Input-Register 1 = 30002 in der SPS mb.addIreg(2); // Input-Register 2 = 30003 in der SPS mb.addIreg(3); // Input-Register 3 = 30004 in der SPS mb.addIreg(4); // Input-Register 4 = 30005 in der SPS mb.addIreg(5); // Input-Register 5 = 30006 in der SPS // Holding Register anlegen (SPS -> Server) mb.addHreg(0); // Holding-Register 0 = 40001 in der SPS mb.addHreg(1); // Holding-Register 1 = 40002 in der SPS mb.addHreg(2); // Holding-Register 2 = 40003 in der SPS mb.addHreg(3); // Holding-Register 3 = 40004 in der SPS mb.addHreg(4); // Holding-Register 4 = 40005 in der SPS
Im Loop-Bereich wird als erstes die Eingangskarte über den I2C-Bus eingelesen. Die Funktion I2E ist im Arduino-Programm ganz unten im Bereich „Globale Funktionen“ programmiert. Die Funktion kann für mehrere Karten immer wieder aufgerufen werden.
Der eingelesene Wert wird dann in das Modbus-Register o eingetragen. Der Wert erscheint dann in der SPS im Register 30001
// Einlesen der Bits von der I2C-INPUT Karte // ------------------------------------------ // 8-Bit von der Eingangkarte lesen I2E(I2E1_ADDR, I2E1_WERT, I2E1_OK); // Ein Byte von der DI-Karte Adr. 112 lesen if (I2E1_OK == 0) { Serial.print("Keine Antwort vom Slave "); Serial.println (I2E1_ADDR); } // Wert zur SPS mb.Ireg(0, I2E1_WERT) ; // Input-Register 0 = 30001 in der SPS
Der nächste Programm-Block schreibt den Wert aus dem Holding-Register 40001 zur digitalen Ausgangskarte
// Ausgeben der Bits an die I2C-OUTPUT Karte // ------------------------------------------ // Wert von der SPS umrangieren I2A1_WERT = mb.Hreg(0); // Holding-Register 0 = 40001 in der SPS // 8-Bit zur Ausgangskarte schicken I2A(I2A1_ADDR, I2A1_WERT, I2A1_OK);
Anschließend werden die analogen Eingänge mit der Funktion
I2AE(I2AE1_ADDR, I2AE1_WERT, I2AE1_OK);
eingelesen und in die Input-Register 1-5 einsortiert.
Die erscheinen dann in der SPS in den Registern 30002 bis 30006)
// Analogwerte einlesen und berechnen // ---------------------------------- // Werte von Analogkarte lesen I2AE(I2AE1_ADDR, I2AE1_WERT, I2AE1_OK); // Werte zur SPS mb.Ireg(1, I2AE1_WERT[0]); // Input-Register 1 = 30002 in der SPS mb.Ireg(2, I2AE1_WERT[1]); // Input-Register 2 = 30003 in der SPS mb.Ireg(3, I2AE1_WERT[2]); // Input-Register 3 = 30004 in der SPS mb.Ireg(4, I2AE1_WERT[3]); // Input-Register 3 = 30005 in der SPS mb.Ireg(5, I2AE1_WERT[4]); // Input-Register 3 = 30006 in der SPS
Die Analogen Ausgänge werden mit der Funktion
I2AA(I2AA1_ADDR, I2AA1_WERT, I2AA1_OK);
zur Analogkarte gesendet. Vorhermüssen aber die Werte von den Holding-Registern 4002 bis 4005 umrangiert werden
// Analogwerte ausgeben // -------------------- // Wert von der SPS I2AA1_WERT[0] = mb.Hreg(1); // Holding-Register 1 = 40002 in der SPS I2AA1_WERT[1] = mb.Hreg(2); // Holding-Register 2 = 40003 in der SPS I2AA1_WERT[2] = mb.Hreg(3); // Holding-Register 3 = 40004 in der SPS I2AA1_WERT[3] = mb.Hreg(4); // Holding-Register 4 = 40005 in der SPS // Werte zur Analogkarte schicken I2AA(I2AA1_ADDR, I2AA1_WERT, I2AA1_OK);
Software SIEMENS TIA-Portal
Als SPS habe ich eine SIEMENS SIMATIC S7-1511 CPU auf meinem Teststand.
Die soll jetzt als Modbus-TCP-Client die Werte vom Server lesen und schreiben.
Eine Ausführliche Beschreibung der Modbus-Kommunikation gibt es als PDF-Dokument auf der SIEMENS Seite support.industry.siemens.com
Modbus/TCP mit den Anweisungen MB_CLIENT und …
Im Beispiel verwende ich drei Datenbausteine
DB3 = DataWrite
Darin ist ein Array mit 5 WORD Variablen für das Holding-Register[0] .. [4] angelegt.
DB3 = DataRead
Darin ist ein Array mit 6 WORD Variablen für die Reading-Register[0] .. [5] angelegt.
DB100 = ModbusData
Hier sind die Verbindungsdaten und ein paar Variablen für den Status vom Funktionsbaustein MB_CLIENT definiert
es müssen zwei Strukturen mit dem UDT TCON_IP_V4 angelegt werden. Eine Struktur zum Lesen, eine Struktur zum Schreiben
InterfaceID ist die Hardware-Kennung der lokalen Schnittstelle.
Die Hardware-Kennung finden Sie in der Gerätekonfiguration der CPU. Markieren Sie die PROFINET-Schnittstelle – Eigenschaften – Systemkonstanten „Local_Profinet-Schnittstelle_1
ID: Über den Parameter wird eine Verbindung innerhalb der CPU eindeutig identifiziert. Jede einzelne Instanz der Anweisung „MB_CLIENT“ sowie „MB_SERVER“ muss eine eindeutige ID verwenden.
ConnectionType Verbindungstyp Wählen Sie 11 (dezimal) für TCP. Andere Verbindungstypen sind nicht zulässig.
ActiveEstablished Kennung für die Art des Verbindungsaufbaus. True: aktiver Verbindungsaufbau False: passiver Verbindungsaufbau
RemoteAddress IP-Adresse des entfernten Verbindungspartners
RemotePort Verwenden Sie die IP-Port-Nummer des Servers, zu dem der Client die Verbindung herstellt und über das TCP/IP-Protokoll kommuniziert
LocalPort Port-Nummer des lokalen Verbindungspartners 0= Beliebiger Port
FB1 ModbusTCP
// Input-Register vom Server lesen #MB_READ(REQ := TRUE, DISCONNECT :=FALSE, // Verbindung halten MB_MODE :=0, // 0=Lesen MB_DATA_ADDR := 30001, // Start bei Register 30001 MB_DATA_LEN :=6, // 6 Register auslesen DONE => "ModbusData".clientData.done_read, BUSY => "ModbusData".clientData.busy_read, ERROR => "ModbusData".clientData.error_read, STATUS => "ModbusData".clientData.status_read, MB_DATA_PTR := "Date_Read".InputRegister, CONNECT := "ModbusData".connectParamClient_read); // Holding-Register zum Server schreiben #MB_WRITE(:= TRUE, DISCONNECT:=FALSE, // Verbindung halten MB_MODE:=1, // 1=Schreiben MB_DATA_ADDR:=40001, // Start bei Register 40001 MB_DATA_LEN:=5, // 5 Register auslesen DONE=>"ModbusData".clientData.done_write, BUSY=>"ModbusData".clientData.busy_write, ERROR=>"ModbusData".clientData.error_write, STATUS=>"ModbusData".clientData.status_write, MB_DATA_PTR:="Data_Write".holdingRegister, CONNECT:="ModbusData".connectParamClient_write);
Hier kann eine TIA-V17 Bibliothek mit den Bausteinen aus der obigen Beschreibung heruntergeladen werden Modbus-TCP TIA-V17 Library (1 Download ) |
Und hier das komplette Arduino-Programm
/* ============================================== Test I2C-Modbus TCP Server ============================================== Board Arduino UNO Version 1.0 Quelle: Modbus-Arduino Example (Modbus IP) Copyright by André Sarmento Barbosa http://github.com/andresarmento/modbus-arduino ============================================== */ String Version = "V1.0"; // Version #include <Wire.h> // I2C-Lib #include <avr/wdt.h> // Watchdog #include <Modbus.h> // ArduinoRS485 library #include <ModbusIP.h> // Modbus //ModbusIP object ModbusIP mb; byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; byte ip[] = { 192, 168, 1, 7}; // Pins definieren int I2C_SCL = 19; // PC5 = I2C-Bus SCL int I2C_SDA = 18; // PC4 = I2C-Bus SDA int I2C_INT = 17; // PC3 = I2C-Bus INT int LED_BL = 8; // PB0 = LED Blau // I2C-Baugruppen definieren und globale Variablen anlegen #define I2E1_ADDR 112 >> 1 // I2C-Digital INPUT Addresse als 7 Bit int I2E1_WERT=0; bool I2E1_OK; #define I2A1_ADDR 64 >> 1 // I2C-Digital OUTPUT Addresse als 7 Bit int I2A1_WERT=0; bool I2A1_OK; #define I2AE1_ADDR 16 >> 1 // I2C-Analog INPUT Addresse als 7 Bit int I2AE1_WERT[5]; bool I2AE1_OK; #define I2AA1_ADDR 176 >> 1 // I2C-Analog OUTPUT Addresse als 7 Bit int I2AA1_WERT[4]; bool I2AA1_OK; // Variablen deklarieren byte ALTWERT; long ts; void setup() { // Pin-Modes einstellen // -------------------- pinMode(I2C_INT, INPUT); pinMode(LED_BL, OUTPUT); Serial.begin(9600); // Serielle Schnittstelle konfigurieren Wire.begin(); // I2C-Pins definieren Serial.println("Version " + Version); // Version ausgeben // setzten aller Bits der Eingabekarte auf 1 // ----------------------------------------- Wire.beginTransmission(I2E1_ADDR); // Start Übertragung zum PCF8574 Wire.write(0xFF); // Alle Bits sind Eingänge Wire.endTransmission(); // Ende // Blaue LED abschalten // -------------------- digitalWrite(LED_BL, LOW); // Blaue LED AUS // Modbus-TCP Server starten // ------------------------- Serial.println("Modbus TCP Server"); mb.config(mac, ip); // Input-Register anlegen (Server -> SPS) mb.addIreg(0); // Input-Register 0 = 30001 in der SPS mb.addIreg(1); // Input-Register 1 = 30002 in der SPS mb.addIreg(2); // Input-Register 2 = 30003 in der SPS mb.addIreg(3); // Input-Register 3 = 30004 in der SPS mb.addIreg(4); // Input-Register 4 = 30005 in der SPS mb.addIreg(5); // Input-Register 5 = 30006 in der SPS // Holding Register anlegen (SPS -> Server) mb.addHreg(0); // Holding-Register 0 = 40001 in der SPS mb.addHreg(1); // Holding-Register 1 = 40002 in der SPS mb.addHreg(2); // Holding-Register 2 = 40003 in der SPS mb.addHreg(3); // Holding-Register 3 = 40004 in der SPS mb.addHreg(4); // Holding-Register 4 = 40005 in der SPS } void loop() { //Call once inside loop() - all magic here mb.task(); // Modbus-Task // Einlesen der Bits von der I2C-INPUT Karte // ------------------------------------------ // 8-Bit von der Eingangkarte lesen I2E(I2E1_ADDR, I2E1_WERT, I2E1_OK); // Ein Byte von der DI-Karte Adresse 112 lesen if (I2E1_OK == 0) { Serial.print("Keine Antwort vom Slave "); Serial.println (I2E1_ADDR); } // Wert zur SPS mb.Ireg(0, I2E1_WERT) ; // Input-Register 0 = 30001 in der SPS // Ausgeben der Bits an die I2C-OUTPUT Karte // ------------------------------------------ // Wert von der SPS umrangieren I2A1_WERT = mb.Hreg(0); // Holding-Register 0 = 40001 in der SPS // 8-Bit zur Ausgangskarte schicken I2A(I2A1_ADDR, I2A1_WERT, I2A1_OK); // Analogwerte einlesen und berechnen // ---------------------------------- // Werte von Analogkarte lesen I2AE(I2AE1_ADDR, I2AE1_WERT, I2AE1_OK); // Werte zur SPS mb.Ireg(1, I2AE1_WERT[0]); // Input-Register 1 = 30002 in der SPS mb.Ireg(2, I2AE1_WERT[1]); // Input-Register 2 = 30003 in der SPS mb.Ireg(3, I2AE1_WERT[2]); // Input-Register 3 = 30004 in der SPS mb.Ireg(4, I2AE1_WERT[3]); // Input-Register 3 = 30005 in der SPS mb.Ireg(5, I2AE1_WERT[4]); // Input-Register 3 = 30006 in der SPS // Analogwerte ausgeben // -------------------- // Wert von der SPS I2AA1_WERT[0] = mb.Hreg(1); // Holding-Register 1 = 40002 in der SPS I2AA1_WERT[1] = mb.Hreg(2); // Holding-Register 2 = 40003 in der SPS I2AA1_WERT[2] = mb.Hreg(3); // Holding-Register 3 = 40004 in der SPS I2AA1_WERT[3] = mb.Hreg(4); // Holding-Register 4 = 40005 in der SPS // Werte zur Analogkarte schicken I2AA(I2AA1_ADDR, I2AA1_WERT, I2AA1_OK); // Link-Status auf blaue LED // ------------------------- bool takt_1hz = (millis() / 500) & 1; // Blinktakt 1 Hz auto link = Ethernet.linkStatus(); switch (link) { case Unknown: digitalWrite(LED_BL, takt_1hz); break; case LinkON: digitalWrite(LED_BL, HIGH); break; case LinkOFF: digitalWrite(LED_BL, LOW); break; } //alle 5 Sekunden lesen für Test if (millis() > ts + 1000) { ts = millis(); // Werte von der SPS Serial.print(mb.Hreg(0)); // Holding-Register 0 = 40001 in der SPS Serial.print("\t "); Serial.print(mb.Hreg(1)); // Holding-Register 1 = 40002 in der SPS Serial.print("\t "); Serial.print(mb.Hreg(2)); // Holding-Register 2 = 40003 in der SPS Serial.print("\t "); Serial.print(mb.Hreg(3)); // Holding-Register 3 = 40004 in der SPS Serial.print("\t "); Serial.print(mb.Hreg(4)); // Holding-Register 4 = 40005 in der SPS Serial.println (); } } // ============================================================================================================= // Globale Funktionen // ============================================================================================================= // --------------------------------------- // 8-Bit von digitale Eingangskarte lesen // --------------------------------------- void I2E(int I2C_Adresse, int &Wert, bool &Slave_OK) { Wire.requestFrom(I2C_Adresse, 1); // Anfrage an den I2C-Slave if (Wire.available()) { Wert = 255 - Wire.read(); // Ein Byte vom PCF8574 lesen und in invertierte Eingabe wandlen Slave_OK = 1; } else {Slave_OK = 0;} } // ------------------------------------------ // 8-Bit zur digitale Ausgangskarte schreiben // ------------------------------------------ void I2A(int I2C_Adresse, int &Wert, bool &Slave_OK) { Wire.beginTransmission(I2C_Adresse); // Start Übertragung zum PCF8574 Wire.write(255 - Wert); // Wert schreiben byte error = Wire.endTransmission(); if (error == 0) { Slave_OK = 1; } else {Slave_OK = 0;} // Fehlerauswertung } // --------------------------------------- // 5 Analogwerte von der I2HAE-Karte lesen // --------------------------------------- void I2AE(int I2C_Adresse, int (&Wert)[5], bool &Slave_OK) { int Array[11]; // Lokales Array zur Speicherung der gelesenen Daten Wire.requestFrom(I2C_Adresse, 11); // Anfrage an den I2C-Slave if (Wire.available()) { // 11 Bytes von Analogkarte lesen und in das array kopieren Slave_OK = 1; for(int i=0;i<11;i++){ int c = Wire.read(); Array[i]=c; } // Werte berechnen. Analogwert = Highbyte * 256 + Lowbyte Wert[0] = Array[ 2] * 256 + Array[1]; Wert[1] = Array[ 4] * 256 + Array[3]; Wert[2] = Array[ 6] * 256 + Array[5]; Wert[3] = Array[ 8] * 256 + Array[7]; Wert[4] = Array[10] * 256 + Array[9]; } else {Slave_OK = 0;} } // --------------------------------------- // 4 Analogwerte zur I2HAA-Karte schreiben // --------------------------------------- void I2AA(int I2C_Adresse, int (&Wert)[4], bool &Slave_OK) { Wire.beginTransmission(I2C_Adresse); // Start Übertragung zur ANALOG-OUT Karte Wire.write(0); // Kanal auf 0 setzen for(int i=0;i<4;i++){ byte HBy = Wert[i] / 256; // HIGH-Byte berechnen Wire.write(Wert[i] - HBy * 256); // LOW-Byte schreiben Wire.write(HBy); // HIGH-Byte schreiben } byte error = Wire.endTransmission(); if (error == 0) { Slave_OK = 1; } else {Slave_OK = 0;} // Fehlerauswertung }