I2C-Arduino-Master als Modbus TCP-Server

Hardware

I2C-Arduino-Master für die Hutschiene

I2C-Arduino-Master für die Hutschiene

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

Beispiel Hardware für den I2C-Modbus-TCP-Server

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
Dann muss die IP-Adresse angegeben werden, über die Der I2C-Master mit der SPS-Steuerung kommuniziert
//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

S7-1511Als 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

TCON_IP_V4 Struktur für MB_CLIENT read InterfaceID = 64 (Aus Hardware-Konfig)
ID = 2
ConnectionType = 11
ActiveEstablished = 1 (aktiver Verbindungsaufbau )
IP-Adresse = 192.168.1.7
RemotePort = 502
LocalPort = 0
TCON_IP_V4 Struktur für MB_CLIENT write InterfaceID = 64 (Aus Hardware-Konfig)
ID = 1
ConnectionType = 11
ActiveEstablished = 1 (aktiver Verbindungsaufbau )
IP-Adresse = 192.168.1.7
RemotePort = 502
LocalPort = 0

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
}
Speichere in deinen Favoriten diesen permalink.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert