2 Arduino UNOs über Modbus RTU

Hallo Zusammen,

ich möchte 2 Arduinos mit einander verbinden. Das ganze soll über Modbus RTU erfolgen.
Zwischen den Arduinos sind etwa 10m Entfernung.
Modbus wird verwendet weil ich mir davon verspreche noch andere Geräte intrigieren zu können. Und deren Daten weiterzuverarbeiten.

Ich habe 2mal TTL RS-485 Konverter Modul Max485 Module.

Der erste Arduino soll ein Analogsignal (A0=Poti-Wert) und einen digitalen Eingang (6=DI) einlesen. Über verschiedene LEDs wird die Temp.(Poti-Wert) angezeigt.
pinMode(7, OUTPUT); pinMode(8, OUTPUT); pinMode(9, OUTPUT);

Der DI wird über pinMode(13, OUTPUT); ausgegeben

am 2ten Arduino möchte ich diese Werte verarbeiten und über ein Display (LCD) anzeigen und Meldungen generieren.

Kennt jemand ein geeignetes Tutorial hierzu ? Oder hat so etwas schon mal programmiert ?

Der angehängte Sketch ist für einen Arduino, also Auswertung der Eingänge und Display hängen Momentan an dem selben Arduino weil ich die Modbus-Kopplerei bisher nicht hinbekommen habe.

wenn ihr mir hier helfen könntet wäre ich sehr froh.
Ich freue mich auf Antworten und/oder Rückfragen.

==> Programmbeschreibung:

Warunungen Wassergeführten Kaminofen, Wassermelder

*/

// ==> Einbindung von externen Programmmodulen (Bibliotheken, Daten usw.)

// include the library code:

#include <LiquidCrystal.h>

// ==> Definition von Konstatnen und Makros

//Pin-Belegung

// initialize the library by associating any needed LCD interface pin

// with the arduino pin number it is connected to

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

//Programm

const int inputPin = A0;

// ==> Globale Variablen

int val;

int volt;

int temp;

// ==> Eigene Funtionen

// void setup wird beim Start genau einmal ausgeführt

void setup() {

//Programm

Serial.begin(9600);

pinMode(inputPin, INPUT);

pinMode(7, OUTPUT);

pinMode(8, OUTPUT);

pinMode(9, OUTPUT);

pinMode(13, OUTPUT);

pinMode(6, INPUT);

Serial.println("Hallo");

// set up the LCD's number of columns and rows:

lcd.begin(16, 2);

lcd.print("Hallo");

delay(1000);

lcd.clear();

}

// void loop ist das eigentliche Programm es läuft in Endlosschleife

// und kann nicht unterbrochen werden

void loop() {

val = analogRead(inputPin);

volt = map(val,0,1023,0,498.6);

temp = map(val,90,470,0,100);

Serial.print(val);

Serial.print(",");

Serial.print(volt/100.0);

Serial.print("V");

Serial.print(",");

Serial.print(temp/1.0);

Serial.println("°c");

Serial.println(digitalRead(6));

// LCD Display

lcd.clear();

lcd.print(temp/1.0);

lcd.print("\xDF"); \\ Zeichen ° als HEX

lcd.print("C");

delay(50);

if (digitalRead(6) == 1) {

lcd.setCursor(0,1);

lcd.print ("Al.Wasser");

digitalWrite(13, HIGH);

delay (250);

digitalWrite(13, LOW);

delay (250);

} else {

digitalWrite(13, LOW);

}

//LED Grün

if (temp >50) {

lcd.setCursor(9,0);

lcd.print ("P an");

digitalWrite(7, HIGH);

delay (250);

} else{

digitalWrite(7, LOW);

}

//LED Gelb

if (temp >75) {

digitalWrite(8, HIGH);

} else {

digitalWrite(8, LOW);

}

//LED Rot

if (temp >90) {

lcd.setCursor(0,1);

lcd.print ("ALARM Temp");

digitalWrite(9, HIGH);

delay (250);

} else {

digitalWrite(9, LOW);

}

}

Willkommen im Forum.
Willst Du tatsächlich Modbus selbst programmieren?
Hier mal oberflächliche Info dazu:

Da muss der eine Arduino dann als Modbus-Server agieren und der andere als Modbus-Client, der den Server zyklisch abfragt.

Ich habe wegen des beschränkten Speichers bisher nur handgeschriebene Server auf kleinen Arduinos (Nano) benutzt, aber es gibt dafür bestimmt auch Libraries.

Hallo Vielen Dank für die Wilkommensgrüße und die schnelle Antwort,

Nein ich will und kann das auch nicht selbst programmieren :smiley:
ich dachte jetzt dass es eine Vorgefertigte Bibliothek gibt wo ich dann mit dem 2ten Arduino einfach über diese Modbus RTU Funktionscodes :
Beispiel: (0x01) Liest DO aus einem Modbus RTU Register des Slaves

Den Zustand aus dem 1ten Arduino auslesen kann und dann im 2ten weiterverarbeite.
Es soll so einfach wie möglich sein, da meine Programmierkenntnisse überschaubar sind :smiley:

Hallo, Miq1... so in etwa hatte ich mir das vorgestellt, doch leider weiß ich nicht wie ich das im Detail realisieren kann.

Tutorial hab ich keines.
Was du dir von Anfang an Klar sein soll, Modbus ist ein Master-Slave oder halt gewoked mittlerweile ein Client-Server System.
Du musst also zunächst mal festlegen wer dein Client (Master) und wer dein Server (Slave) ist.

Der Client fragt den Server um Werte.

In deiner Aufgabenstellung kann ich mir beide Richtungen vorstellen. Das heißt es liegt nun an dir zunächst mal zu definieren

  • Was soll alles am Client passieren (vieleicht der mit dem LCD?!?)
  • Was soll alles am Server passieren (evtl. das was du unter "der erste Arduino" geschrieben hast?)

Dann geht es bei Modbus um verschiedene Funktionen und "Register". Das heißt jeden Wert den du übermitteln willst, musst du ein "Register" zuweisen. Und je nach dem was du eigentlich machen willst, brauchst du verschiedene Function Codes.

Daher erst mal eine Liste machen was wo passieren soll, dann kann man gemeinsam drüber schauen was du benötigst.

Als Client Software würde ich die ModbusMaster von DocWalker nehmen,
Als Server Software kann ich guten Gewissen meine Library empfehlen:
https://werner.rothschopf.net/microcontroller/202112_arduino_modbus_server.htm

Guten Morgen noiasca,
Ich habe mal eine Liste gemacht.

Arduino 1 (Modbus-Server) sitzt im Keller.
Inputs:
misst die Temp. an A0
wertet einen Digitalen Eingang an Pin6 aus
Outputs:
Gibt DO an Pin7 Led Grün aus wenn Temp > 50°C
Gibt DO an Pin 8 Led Gelb aus wenn Temp > 75°C
Gibt DO an Pin 9 Led Rot aus wenn Temp > 90°C
Gibt Do am Pin 13 Led Blau aus wenn Pin6 High ist

Arduino 2 (Modbus Client) sitzt im Wohnzimmer
Inputs
\Optional ein Taster der für 1min die Anzeige einschaltet\

Outputs
LCD Display
Gibt ständig die Temp aus (Welche am Server auf A0 gemessen wird)

D.h. Meine Problemstellung ist: ich möchte die Werte A0 (Analogwert) und Pin6 (DI) vom Arduino1 (Server) über Modbus an den Arduino 2 (Client) senden. Um dann die Werte an Arduino 2 weiterzuverarbeiten.

Da liegt mein Problem weil ich nicht weiß wie ich den Analogwert/ Digitalwert in die Register von Arduino1 reinbekomme. Und dann am Arduino2 aus dem Register auslesen kann. :smiley: ich hoffe ihr versteht was ich meine.

Ich freue mich auf eure Antworten

also einen Analog-Input könntest du mit Function Code 3 = Holding Register definieren.

In meinem Beispiel:
0202_Modbus_Server_HelloWorld

werden einige Holding Register mit ADC Werte befüllt. Das könntest mal zum ersten Testen probieren. Holding Register gibt immer je Register zwei Byte zurück, das passt also genau für so einen ADC Eingang.

Aber eigentlich würde ich es so machen wie im Beispiel 0218_Modbus_Server_all_FC.
Da gibts auch das Beispiel ab ca Zeile 188. Dort statt der Ausgabe der Variable halt ein analogRead

PS.: Liste ist gut ... jetzt arbeitest diese einfach ab und vergibst passende Function Codes + Register

aber wenn ich es so richtig sehe, gehts eh nur mehr darum dass der Client den Server um dessen A0 und D6 fragt.

Die D6 Abfrage ist aus meiner Sicht ein FC2 Read Discrete Inputs:.

mal zwei vereinfachte Beispiele:

Die Register habe ich jetzt alle in FC4 Read Input Registers gelegt
2000 ein Analogeingang
2001 ein weiterer Analogeingang
2002 ein Digitaleingang
2003 ein weiterer Digitaleingang

Der Client fragt alle 2 Sekunden die 4 Werte vom Server mit der Adresse 2 ab

/*******************************************************
  Modbus Client Example

  This is a reduced example based on 0311_modbus_Client_FC (Modbus Master)

  This Modbus Client
  - reads from Modbus Servers periodically 

  hardware
  - a MAX485-TTL adapter

  by noiasca
  2023-01-31

 *******************************************************/

#include <ModbusMaster.h>  // Modbus Master 2.0.0 by Doc Walker - install with Library Manager
// client and servers must know the same registers
enum
{
  programVersion,    //
  HOLDING_REGS_SIZE  // leave this one
};

// client specific

constexpr byte modbus_enable_pin = 11;            // The GPIO used to control the MAX485 TX pin. Set to 255 if you are not using RS485 or a selfsensing adapter
constexpr uint32_t modbus_baud = 9600;            // use slow speeds with SoftSerial
constexpr byte rxPin = 2;                         // for Softserial
constexpr byte txPin = 3;
uint32_t previousTx = 0;
uint16_t restTx = 25000; // rest time between transmissions - microseconds

ModbusMaster serverA;  // instantiate ModbusMaster object - slave - node

// example to mirror data to global variables
int serverA_A0 = 0;
int serverA_D6 = LOW;


void preTransmission()
{
  while (micros() - previousTx < restTx)
  {
    yield(); // wait some time and do background tasks.
  }
  digitalWrite(modbus_enable_pin, HIGH);
}

void postTransmission()
{
  previousTx = micros();   // remember last timestamp
  digitalWrite(modbus_enable_pin, LOW);
}

/* *******************************************************
   Serial Interface
 * **************************************************** */
// if you don't have enough HW Serial (i.e. on an UNO)
// you are forced to use SoftwareSerial or AltSoftSerial
#include <SoftwareSerial.h>
SoftwareSerial mySerial(rxPin, txPin);        // RXpin, TXpin

void requestData()
{
  static uint32_t previousMillis = 0;
  uint32_t currentMillis = millis();
  if (currentMillis - previousMillis > 2000)  // set the poll interval in ms
  {
    previousMillis = currentMillis;
    uint16_t reg = 2000;
    uint16_t noOfValues = 4;
    int result = 0;

    result = serverA.readInputRegisters(reg, noOfValues); //FC4
    if (result == serverA.ku8MBSuccess) // do something with data if read is successful
    {
      for (uint16_t i = 0; i < noOfValues; i++)
      {
        Serial.print(F("FC4 Input Register "));
        Serial.print(reg + i);
        Serial.print('=');
        Serial.println(serverA.getResponseBuffer(i));
      }
      serverA_A0 = serverA.getResponseBuffer(0); // example to mirror data from Server to Client
      serverA_D6 = serverA.getResponseBuffer(2); 
    }
    else
    {
      Serial.print(F("E ServerA no input register ")); Serial.print(reg); Serial.print(F(" result=")); Serial.println(result, HEX);
    }
    Serial.println();
  }
}

void setup()
{
  Serial.begin(9600);
  Serial.println(F("Modbus Client Example"));
  // use Serial (port 0); initialize Modbus communication baud rate
  mySerial.begin(modbus_baud);
  serverA.begin(2, mySerial);   // communicate with Modbus server ID 2 over the given Serial interface

  // Init enable pins for modbus master library
  pinMode(modbus_enable_pin, OUTPUT);
  digitalWrite(modbus_enable_pin, LOW);
  // Callbacks allow us to configure the RS485 transceiver correctly
  serverA.preTransmission(preTransmission);
  serverA.postTransmission(postTransmission);
}

void loop()
{
  requestData();
}

Der Code wirf ein Warning aus der CRC Berechnung. Das ist aber in der Library die nicht mir gehört. Sollte sonst weiter nichts stören. Weiters ist im preTransmission / postTransmission eine "Bremse" drinnen, um den Server nicht mit Anfragen zu überhäufen. Da aber nur 1 Paket alle 2 Sekunden gelesen wird, schlägt diese Bremse de facto nicht zu. Aber man kann relativ einfach weitere Pakete ergänzen ohne dass was passiert.

Und das ist ein Server mit Adresse 2:
der je zwei Analogeingänge und Digitaleingänge ausliest:

/*******************************************************
  Modbus Server (Modbus Slave)

  This is a reduced example of 0218_Modbus_Server_all_FC

  FC   Modbus                             Bit Arduino Example           address in this sketch
  --------------------------------------------------------------------------------------------
  FC4  Read Input Registers               16  analogRead                2000..2001
  FC4  Read Input Registers               16  digitalRead               2002..2003

  structure of this sketch:
  - definitions for the Serial interface - either Software or Hardware and if necessary the TX enable pin
  - definition of the Modbus library

  hardware
  - several GPIOs configured as Output for relays (or LEDs) to simulate "Coils"
  - a MAX485-TTL adapter

  by noiasca
  2023-01-31 ok

 *******************************************************/

#include <NoiascaModbusServerSimple.h>           // Modbus Server Simple library https://werner.rothschopf.net/microcontroller/202112_arduino_modbus_server.htm

/* **************************************************** *
   settings, variables, objects ...
 * **************************************************** */

constexpr byte modbus_slave_id = 2;               // Modbus address of this Modbus Server (slave)

constexpr byte inputRegisterPin[] {A0, A1};       // GPIOs as input Registers (FC4)
constexpr byte digitalPin[] { 6, 12};             // GPIOs for digital pins to be read with  (FC4)
constexpr byte modbus_enable_pin = 11;            // The GPIO used to control the MAX485 TX pin. Set to 255 if you are not using RS485 or a selfsensing adapter
constexpr byte rxPin = 2;                         // for Softserial
constexpr byte txPin = 3;

/* *******************************************************
   Serial Interface
 * **************************************************** */
// if you don't have enough HW Serial (i.e. on an UNO)
// you are forced to use SoftwareSerial or AltSoftSerial
#include <SoftwareSerial.h>
SoftwareSerial mySerial(rxPin, txPin);               // RXpin, TXpin

// On a Mega you can simply use
// a Reference to an existing HW Serial:
// HardwareSerial &mySerial = Serial3;

// If you don't need debugging output, you can even use
// a reference to Serial
//HardwareSerial &mySerial = Serial;

constexpr byte modbus_format = SERIAL_8N1;       // if you can use Hardware Serial, Modbus prefered value is SERIAL_8N2
constexpr uint32_t modbus_baud = 9600;          // use slow speeds with SoftSerial. 19200 is OK

/* **************************************************** *
   Modbus
 * **************************************************** */

// Both client and server must know the registers keeping the register
// holding registers (occupy memory)
enum
{
  // just add or remove registers and your good to go...
  // The first register starts at address 0 which is for Function 3 Register 40001
  programVersion,       //
  HOLDING_REGS_SIZE  // leave this one
};
unsigned int holdingRegs[HOLDING_REGS_SIZE]; // function 3 and 16 register array

ModbusServer modbusServer(modbus_slave_id, mySerial);

/*
    Modbus Client callbacks
*/
void cbPreTransmission()
{
  digitalWrite(modbus_enable_pin, HIGH);  // handle the DE/RE pins
}

void cbPostTransmission()
{
  digitalWrite(modbus_enable_pin, LOW);   // handle the DE/RE pins
}


// callback to evaluate if a FC4 Input Register is valid
bool cbIsValidInputRegister(const uint16_t reg)
{
  switch (reg)
  {
    case 2000 ... 2003 :  // a range of valid input register
      return true;
    default :
      return false;
  }
}

// callback to respond to FC4 read: Client has requested an input register
uint16_t cbInputRegisterViewed(const uint16_t reg)
{
  //Serial.print(F("requested input register:")); Serial.println(reg);
  switch (reg)
  {
    case 2000: return analogRead(inputRegisterPin[0]);    // you could read a value on demand
    case 2001: return analogRead(inputRegisterPin[1]);
    case 2002: return digitalRead(digitalPin[0]) == LOW ? 0 : 1;
    case 2003: return digitalRead(digitalPin[1]) == LOW ? 0 : 1;
    default: return 0xEEEE;                               // example for error (when user has requested a non existing register
  }
}



/* **************************************************** *
   Arduino default setup and loop
 * **************************************************** */
void setup()
{
  holdingRegs[programVersion] = 1;
  Serial.begin(9600);                 // only used for Debugging
  Serial.println(F("Modbus Server with Noiasca Modbus Server Simple"));
  Serial.flush();

  mySerial.begin(modbus_baud);          // start the Serial used for Modbus. Remember: Softserial doesn't support modbus_format
  //mySerial.begin(modbus_baud, modbus_format); // Hardware Serial supports modbus_format

  modbusServer.begin(&mySerial, modbus_baud, modbus_format, modbus_slave_id, HOLDING_REGS_SIZE, holdingRegs);
  pinMode(modbus_enable_pin, OUTPUT);                          // if you need a TX enable pin, set it to Output
  modbusServer.setPreTransmission(cbPreTransmission);          // if you need a TX enable pin, set a callback to your pin handling
  modbusServer.setPostTransmission(cbPostTransmission);        // if you need a TX enable pin, set a callback to your pin handling

  modbusServer.setInputRegisterViewed(cbInputRegisterViewed);  // set callback for: FC4 Read Input Register
  modbusServer.setIsValidInputRegister(cbIsValidInputRegister);// the server must know the valid FC4 Read Input Registers


  // init local hardware

  for (auto & i : digitalPin) pinMode(i, INPUT);
  for (auto & i : inputRegisterPin) pinMode(i, INPUT);
}

void loop()
{
  modbusServer.update();   // call modbus in loop
  // do other things non blocking in loop
}

Beide male verwende ich Soft-Serial an 2/3 sowie den enable Pin an 11.

Mit Hardware getestet. Soll funktionieren.

Der Client gibt auf der Seriellen so etwas aus:

FC4 Input Register 2000=1023
FC4 Input Register 2001=1023
FC4 Input Register 2002=1
FC4 Input Register 2003=1

Antwortet der Server nicht kommt es zu einer Error Meldung:

E ServerA no input register 2000 result=E2

Hallo noiasca,
Ich werde dein Beispiel mal studieren und versuchen das um zusetzten. Ich melde mich mit dem Ergebnis. Ich denke dies wird einige Tage dauern.
Mfg

@Chris_113 wie schaut es aus? Gibt es schon erste Erfolge?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.