Smart Home on Wire mit ModBus

Ich habe mir vorgenommen, im Haus alle Steuerungen mit Cat6-Leitungen zu realisieren, und alles Wichtige wird über Modbus mit Arduinos gesteuert. Das Ganze soll am Ende möglichst " kostenoptimiert " sein. :wink:
Da ich bisher noch kein ähnliches Projekt gefunden habe, das ich einfach kopieren könnte, möchte ich es hier selbst entwickeln und dokumentieren. Dabei brauche ich auch Hilfe, denn ich kann von allem ein bisschen, aber wenn man etwas ordentlich machen will, sind Erfahrungswerte und Expertenwissen unersetzbar. Ich hoffe, dass ich es schaffe, meine nächsten Nachrichten immer auf dem aktuellen Stand zu halten, damit alle informiert sind.:wave:Moderatoren :wave:

Nachtrag:

Aufbau:

Mehrere Cat6-Leitungen werden ringförmig im Haus verlegt und kreuzen möglichst viele Schalterdosen. In den Dosen wird die Leitung mit D-SUB-Flachbandklemmen abgezweigt. Dort sitzt dann ein Arduino Nano mit MAX485 und einem Step-Down-Wandler. An einem zentralen Ort, wo alle Cat6-Ringe sich treffen, sitzt der Arduino Mega. Die Cat6-Ringe werden aufgetrennt: Ein Ende eines Adernpaares wird an den Mega angeschlossen, das andere Ende bekommt einen Abschlusswiderstand. Zwei Adernpaare werden an die Stromversorgung (beide Enden) angeschlossen. Das vierte Adernpaar dient als Reserve für Bus oder Versorgung.

Wenn es mehr als vier Ringe gibt, können die Bus-Adern auch hintereinander geklemmt werden, bevor sie an den Arduino Mega gehen (maximale Leitungslänge der Busleitung ca. 600 m, theoretisch bis zu 1,2 km). Der Arduino Mega regelt den Modbus und sendet/empfängt über ein Ethernet-Modul die Daten in ein LAN-Netzwerk, an dem auch der Home Assistant eingebunden ist.

Arduino Nano: > Steuereinheit Sind die Gehirne vor Ort, an denen sich alle möglichen Sensoren und Aktoren regeln lassen. Sie dienen als Modbus-Slave (mit Prioritätsnachrichten).

MAX485 Modul > Steuereinheit Sitzt direkt am Arduino Nano und fungiert als Netzwerkkarte für den Modbus.

Step-Down-Regler > Steuereinheit Regelt die Versorgungsspannung von 24 V auf 5 V für die Steuereinheit.

Arduino MEGA 2560 > Bus-Zentrale Der Master im Modbus-Netzwerk. Er kann vier Busleitungen gleichzeitig verarbeiten, sammelt Daten, regelt den Bus und bildet die Brücke zwischen Bus und Ethernet. Er kann auch kleinere Regelungsaufgaben erledigen.

Wiznet W5500 Ethernet Shield > Bus-Zentrale Verbindet den Arduino MEGA mit dem LAN-Netzwerk.

Spannungsversorgung:
Bus-Zentrale Versorgt die Cat6-Leitung mit 24 V auf 2-3 Leitungspaaren.

Steuereinheit:
D-SUB: 0,59 €
Arduino Nano Atmega328: (ein ATTINY88 auch möglich)
MAX485 Modul:
Step-Down MP1584EN:

Bus-Zentrale:
Arduino MEGA 2560
4x MAX485 Modul
Wiznet W5500 Ethernet Shield
Spannungsversorgung 300 W

Home Assistant:
Fritzbox
NUC (gebraucht)

Code:

Arduino MEGA: ( im Test 2 UART Kanäle für zwei BusRinge mit speziellen Bibliotheken )

#include <SimpleModbusMasterSerial2.h>
#include <SimpleModbusMaster.h>
#define TxEnablePin 3   // RS485 Steuerung auf Pin 3
#define TxEnablePin_2 4   // RS485 Steuerung auf Pin 3
#define baud 19200
#define timeout 1000
#define polling 50
#define retry_count 2
#define TOTAL_NO_OF_REGISTERS 4  // Wir lesen jetzt 4 Register, um Register 3 zu erfassen
#define TOTAL_NO_OF_PACKETS 2

// Arrays für die Modbus-Daten
unsigned int regs[TOTAL_NO_OF_REGISTERS];  // Zum Lesen von Registern
unsigned int write_regs[1];                // Zum Schreiben von Registern
unsigned int regs2[TOTAL_NO_OF_REGISTERS];  // Zum Lesen von Registern
unsigned int write_regs2[1];                // Zum Schreiben von Registern
Packet packets[TOTAL_NO_OF_PACKETS];
Packet_2 packets_2[TOTAL_NO_OF_PACKETS];  // Modbus-Pakete

void setup() {
  Serial.begin(baud);
  
 // Paket 1: Lesen von 4 Registern ab Adresse 0 vom Slave mit ID 2
  packets[0].id = 2;                               // Slave ID
  packets[0].function = READ_HOLDING_REGISTERS;    // Funktion
  packets[0].address = 0;                          // Startadresse (Register 0)
  packets[0].no_of_registers = 4;                  // Anzahl der zu lesenden Register (inklusive Register 3)
  packets[0].register_array = regs;                // Speicherort für die empfangenen Daten

  // Paket 1: Lesen von 4 Registern ab Adresse 0 vom Slave mit ID 2
  packets_2[0].id = 2;                               // Slave ID
  packets_2[0].function = READ_HOLDING_REGISTERS_2;    // Funktion
  packets_2[0].address = 0;                          // Startadresse (Register 0)
  packets_2[0].no_of_registers = 4;                  // Anzahl der zu lesenden Register (inklusive Register 3)
  packets_2[0].register_array = regs2;                // Speicherort für die empfangenen Daten

  // Paket 2: Schreiben von 1 Register ab Adresse 1 an Slave mit ID 2
  packets[1].id = 2;                               // Slave ID
  packets[1].function = PRESET_MULTIPLE_REGISTERS; // Funktion zum Schreiben
  packets[1].address = 1;                          // Zieladresse (Register 1)
  packets[1].no_of_registers = 1;                  // Anzahl der zu schreibenden Register
  packets[1].register_array = write_regs;          // Speicherort für die zu sendenden Daten

  // Paket 2: Schreiben von 1 Register ab Adresse 1 an Slave mit ID 2
  packets_2[1].id = 2;                               // Slave ID
  packets_2[1].function = PRESET_MULTIPLE_REGISTERS_2; // Funktion zum Schreiben
  packets_2[1].address = 1;                          // Zieladresse (Register 1)
  packets_2[1].no_of_registers = 1;                  // Anzahl der zu schreibenden Register
  packets_2[1].register_array = write_regs2;          // Speicherort für die zu sendenden Daten

  // Modbus FSM konfigurieren
  modbus_configure(baud, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS);

  modbus_configure_2(baud, timeout, polling, retry_count, TxEnablePin_2, packets_2, TOTAL_NO_OF_PACKETS);

  Serial.println("Master bereit...");
}

void loop() {
  // Modbus-Pakete aktualisieren

  modbus_update(packets);

  // Schreibe den Wert 34 in Register 1 des Slaves
  write_regs[0] = 34;  // Wert, der in das Register 1 geschrieben wird
  Serial.print("Gesendeter Wert an RegisterA 1: ");
  Serial.println(write_regs[0]);

 // delay(1000);  // Kurze Verzögerung
 //  modbus_update(packets);
   
  // Debug-Ausgabe für das Lesen von Register 3
  Serial.print("Vom Slave aus RegisterA 3 gelesen: ");
  Serial.println(regs[3]);  // Ausgabe des Wertes aus Register 3

//  delay(50);  // Kurze Verzögerung
  
  modbus_update_2(packets_2);

  // Schreibe den Wert 34 in Register 1 des Slaves
  write_regs2[0] = 34;  // Wert, der in das Register 1 geschrieben wird
  Serial.print("Gesendeter Wert an RegisterB 1: ");
  Serial.println(write_regs2[0]);

 // delay(1000);  // Kurze Verzögerung
 //  modbus_update(packets);
   
  // Debug-Ausgabe für das Lesen von Register 3
  Serial.print("Vom Slave aus RegisterB 3 gelesen: ");
  Serial.println(regs2[3]);  // Ausgabe des Wertes aus Register 3

  delay(50);  // Kurze Verzögerung
  
}

Bibliothek :

Steuert UART1 vom MEGA an:

SimpleModbusMaster.h ( .cpp Datei )

#include "SimpleModbusMaster.h"

#define BUFFER_SIZE 128

// modbus specific exceptions
#define ILLEGAL_FUNCTION 1
#define ILLEGAL_DATA_ADDRESS 2
#define ILLEGAL_DATA_VALUE 3

unsigned char transmission_ready_Flag;
unsigned char messageOkFlag, messageErrFlag;
unsigned char retry_count;
unsigned char TxEnablePin;
// frame[] is used to recieve and transmit packages.
// The maximum number of bytes in a modbus packet is 256 bytes
// This is limited to the serial buffer of 128 bytes
unsigned char frame[BUFFER_SIZE];
unsigned int timeout, polling;
unsigned int T1_5; // inter character time out in microseconds
unsigned int T3_5; // frame delay in microseconds
unsigned long previousTimeout, previousPolling;
unsigned int total_no_of_packets;
Packet* packet; // current packet

// function definitions
void constructPacket();
void checkResponse();
void check_F3_data(unsigned char buffer);
void check_F16_data();
unsigned char getData();
void check_packet_status();
unsigned int calculateCRC(unsigned char bufferSize);
void sendPacket(unsigned char bufferSize);


unsigned int modbus_update(Packet* packets)
{
    // Initialize the connection_status variable to the
    // total_no_of_packets. This value cannot be used as
    // an index (and normally you won't). Returning this
    // value to the main skecth informs the user that the
    // previously scanned packet has no connection error.

    unsigned int connection_status = total_no_of_packets;

    if (transmission_ready_Flag) {

        static unsigned int packet_index;

        unsigned int failed_connections = 0;

        unsigned char current_connection;

        do {

            if (packet_index == total_no_of_packets) // wrap around to the beginning
                packet_index = 0;

            // proceed to the next packet
            packet = &packets[packet_index];

            // get the current connection status
            current_connection = packet->connection;

            if (!current_connection) {
                connection_status = packet_index;

                // If all the connection attributes are false return
                // immediately to the main sketch
                if (++failed_connections == total_no_of_packets)
                    return connection_status;
            }

            packet_index++;

        } while (!current_connection); // while a packet has no connection get the next one

        constructPacket();
    }

    checkResponse();

    check_packet_status();

    return connection_status;
}

void constructPacket()
{
    transmission_ready_Flag = 0; // disable the next transmission

    packet->requests++;
    frame[0] = packet->id;
    frame[1] = packet->function;
    frame[2] = packet->address >> 8; // address Hi
    frame[3] = packet->address & 0xFF; // address Lo
    frame[4] = packet->no_of_registers >> 8; // no_of_registers Hi
    frame[5] = packet->no_of_registers & 0xFF; // no_of_registers Lo

    unsigned int crc16;

    // construct the frame according to the modbus function
    if (packet->function == PRESET_MULTIPLE_REGISTERS) {
        unsigned char no_of_bytes = packet->no_of_registers * 2;
        unsigned char frameSize = 9 + no_of_bytes; // first 7 bytes of the array + 2 bytes CRC+ noOfBytes
        frame[6] = no_of_bytes; // number of bytes
        unsigned char index = 7; // user data starts at index 7
        unsigned int temp;
        unsigned char no_of_registers = packet->no_of_registers;
        for (unsigned char i = 0; i < no_of_registers; i++) {
            temp = packet->register_array[i]; // get the data
            frame[index] = temp >> 8;
            index++;
            frame[index] = temp & 0xFF;
            index++;
        }
        crc16 = calculateCRC(frameSize - 2);
        frame[frameSize - 2] = crc16 >> 8; // split crc into 2 bytes
        frame[frameSize - 1] = crc16 & 0xFF;
        sendPacket(frameSize);

        if (packet->id == 0) { // check broadcast id
            messageOkFlag = 1; // message successful, there will be no response on a broadcast
            previousPolling = millis(); // start the polling delay
        }
    } else { // READ_HOLDING_REGISTERS is assumed
        crc16 = calculateCRC(6); // the first 6 bytes of the frame is used in the CRC calculation
        frame[6] = crc16 >> 8; // crc Lo
        frame[7] = crc16 & 0xFF; // crc Hi
        sendPacket(8); // a request with function 3, 4 & 6 is always 8 bytes in size
    }
}

void checkResponse()
{
    if (!messageOkFlag && !messageErrFlag) { // check for response
        unsigned char buffer = getData();

        if (buffer > 0) { // if there's something in the buffer continue
            if (frame[0] == packet->id) { // check id returned
                // to indicate an exception response a slave will 'OR'
                // the requested function with 0x80
                if ((frame[1] & 0x80) == 0x80) { // exctract 0x80
                    // the third byte in the exception response packet is the actual exception
                    switch (frame[2]) {
                    case ILLEGAL_FUNCTION:
                        packet->illegal_function++;
                        break;
                    case ILLEGAL_DATA_ADDRESS:
                        packet->illegal_data_address++;
                        break;
                    case ILLEGAL_DATA_VALUE:
                        packet->illegal_data_value++;
                        break;
                    default:
                        packet->misc_exceptions++;
                    }
                    messageErrFlag = 1; // set an error
                    previousPolling = millis(); // start the polling delay
                } else { // the response is valid
                    if (frame[1] == packet->function) { // check function number returned
                        // receive the frame according to the modbus function
                        if (packet->function == PRESET_MULTIPLE_REGISTERS)
                            check_F16_data();
                        else // READ_HOLDING_REGISTERS is assumed
                            check_F3_data(buffer);
                    } else { // incorrect function number returned
                        packet->incorrect_function_returned++;
                        messageErrFlag = 1; // set an error
                        previousPolling = millis(); // start the polling delay
                    }
                } // check exception response
            } else { // incorrect id returned
                packet->incorrect_id_returned++;
                messageErrFlag = 1; // set an error
                previousPolling = millis(); // start the polling delay
            }
        } // check buffer
    } // check message booleans
}

// checks the time out and polling delay and if a message has been recieved succesfully
void check_packet_status()
{
    unsigned char pollingFinished = (millis() - previousPolling) > polling;

    if (messageOkFlag && pollingFinished) { // if a valid message was recieved and the polling delay has expired clear the flag
        messageOkFlag = 0;
        packet->successful_requests++; // transaction sent successfully
        packet->retries = 0; // if a request was successful reset the retry counter
        transmission_ready_Flag = 1;
    }

    // if an error message was recieved and the polling delay has expired clear the flag
    if (messageErrFlag && pollingFinished) {
        messageErrFlag = 0; // clear error flag
        packet->retries++;
        transmission_ready_Flag = 1;
    }

    // if the timeout delay has past clear the slot number for next request
    if (!transmission_ready_Flag && ((millis() - previousTimeout) > timeout)) {
        packet->timeout++;
        packet->retries++;
        transmission_ready_Flag = 1;
    }

    // if the number of retries have reached the max number of retries
    // allowable, stop requesting the specific packet
    if (packet->retries == retry_count) {
        packet->connection = 0;
        packet->retries = 0;
    }

    if (transmission_ready_Flag) {
        // update the total_errors atribute of the
        // packet before requesting a new one
        packet->total_errors = packet->timeout +
                               packet->incorrect_id_returned +
                               packet->incorrect_function_returned +
                               packet->incorrect_bytes_returned +
                               packet->checksum_failed +
                               packet->buffer_errors +
                               packet->illegal_function +
                               packet->illegal_data_address +
                               packet->illegal_data_value;
    }
}

void check_F3_data(unsigned char buffer)
{
    unsigned char no_of_registers = packet->no_of_registers;
    unsigned char no_of_bytes = no_of_registers * 2;
    if (frame[2] == no_of_bytes) { // check number of bytes returned
        // combine the crc Low & High bytes
        unsigned int recieved_crc = ((frame[buffer - 2] << 8) | frame[buffer - 1]);
        unsigned int calculated_crc = calculateCRC(buffer - 2);

        if (calculated_crc == recieved_crc) { // verify checksum
            unsigned char index = 3;
            for (unsigned char i = 0; i < no_of_registers; i++) {
                // start at the 4th element in the recieveFrame and combine the Lo byte
                packet->register_array[i] = (frame[index] << 8) | frame[index + 1];
                index += 2;
            }
            messageOkFlag = 1; // message successful
        } else { // checksum failed
            packet->checksum_failed++;
            messageErrFlag = 1; // set an error
        }

        // start the polling delay for messageOkFlag & messageErrFlag
        previousPolling = millis();
    } else { // incorrect number of bytes returned
        packet->incorrect_bytes_returned++;
        messageErrFlag = 1; // set an error
        previousPolling = millis(); // start the polling delay
    }
}

void check_F16_data()
{
    unsigned int recieved_address = ((frame[2] << 8) | frame[3]);
    unsigned int recieved_registers = ((frame[4] << 8) | frame[5]);
    unsigned int recieved_crc = ((frame[6] << 8) | frame[7]); // combine the crc Low & High bytes
    unsigned int calculated_crc = calculateCRC(6); // only the first 6 bytes are used for crc calculation

    // check the whole packet
    if (recieved_address == packet->address &&
        recieved_registers == packet->no_of_registers &&
        recieved_crc == calculated_crc)
        messageOkFlag = 1; // message successful
    else {
        packet->checksum_failed++;
        messageErrFlag = 1;
    }

    // start the polling delay for messageOkFlag & messageErrFlag
    previousPolling = millis();
}

// get the serial data from the buffer
unsigned char getData()
{
    unsigned char buffer = 0;
    unsigned char overflowFlag = 0;

    while (Serial1.available()) {
        // The maximum number of bytes is limited to the serial buffer size of 128 bytes
        // If more bytes is received than the BUFFER_SIZE the overflow flag will be set and the
        // serial buffer will be red untill all the data is cleared from the receive buffer,
        // while the slave is still responding.
        if (overflowFlag)
            Serial1.read();
        else {
            if (buffer == BUFFER_SIZE)
                overflowFlag = 1;

            frame[buffer] = Serial1.read();
            buffer++;
        }

        delayMicroseconds(T1_5); // inter character time out
    }

    // The minimum buffer size from a slave can be an exception response of 5 bytes
    // If the buffer was partialy filled clear the buffer.
    // The maximum number of bytes in a modbus packet is 256 bytes.
    // The serial buffer limits this to 128 bytes.
    // If the buffer overflows than clear the buffer and set
    // a packet error.
    if ((buffer > 0 && buffer < 5) || overflowFlag) {
        buffer = 0;
        packet->buffer_errors++;
        messageErrFlag = 1; // set an error
        previousPolling = millis(); // start the polling delay
    }

    return buffer;
}

void modbus_configure(long baud, unsigned int _timeout, unsigned int _polling,
                      unsigned char _retry_count, unsigned char _TxEnablePin,
                      Packet* _packet, unsigned int _total_no_of_packets)
{
    Serial1.begin(baud);

    if (_TxEnablePin > 1) {
        // pin 0 & pin 1 are reserved for RX/TX. To disable set _TxEnablePin < 2
        TxEnablePin = _TxEnablePin;
        pinMode(TxEnablePin, OUTPUT);
        digitalWrite(TxEnablePin, LOW);
    }

    // Modbus states that a baud rate higher than 19200 must use a fixed 750 us
    // for inter character time out and 1.75 ms for a frame delay.
    // For baud rates below 19200 the timeing is more critical and has to be calculated.
    // E.g. 9600 baud in a 10 bit packet is 960 characters per second
    // In milliseconds this will be 960characters per 1000ms. So for 1 character
    // 1000ms/960characters is 1.04167ms per character and finaly modbus states an
    // intercharacter must be 1.5T or 1.5 times longer than a normal character and thus
    // 1.5T = 1.04167ms * 1.5 = 1.5625ms. A frame delay is 3.5T.

    if (baud > 19200) {
        T1_5 = 750;
        T3_5 = 1750;
    } else {
        T1_5 = 15000000/baud; // 1T * 1.5 = T1.5
        T3_5 = 35000000/baud; // 1T * 3.5 = T3.5
    }

    // initialize connection status of each packet
    for (unsigned char i = 0; i < _total_no_of_packets; i++) {
        _packet->connection = 1;
        _packet++;
    }

    // initialize
    transmission_ready_Flag = 1;
    messageOkFlag = 0;
    messageErrFlag = 0;
    timeout = _timeout;
    polling = _polling;
    retry_count = _retry_count;
    TxEnablePin = _TxEnablePin;
    total_no_of_packets = _total_no_of_packets;
    previousTimeout = 0;
    previousPolling = 0;
}

unsigned int calculateCRC(unsigned char bufferSize)
{
    unsigned int temp, temp2, flag;
    temp = 0xFFFF;
    for (unsigned char i = 0; i < bufferSize; i++) {
        temp = temp ^ frame[i];
        for (unsigned char j = 1; j <= 8; j++) {
            flag = temp & 0x0001;
            temp >>= 1;
            if (flag)
                temp ^= 0xA001;
        }
    }
    // Reverse byte order.
    temp2 = temp >> 8;
    temp = (temp << 8) | temp2;
    temp &= 0xFFFF;
    return temp; // the returned value is already swopped - crcLo byte is first & crcHi byte is last
}

void sendPacket(unsigned char bufferSize)
{
    if (TxEnablePin > 1)
        digitalWrite(TxEnablePin, HIGH);

    for (unsigned char i = 0; i < bufferSize; i++)
        Serial1.write(frame[i]);

    Serial1.flush();

    // allow a frame delay to indicate end of transmission
    delayMicroseconds(T3_5);

    if (TxEnablePin > 1)
        digitalWrite(TxEnablePin, LOW);

    previousTimeout = millis(); // initialize timeout delay
}

SimpleModbusMaster.h ( .h Datei )

#ifndef SIMPLE_MODBUS_MASTER_H
#define SIMPLE_MODBUS_MASTER_H

/*
  SimpleModbusMaster allows you to communicate
  to any slave using the Modbus RTU protocol.
  
  To communicate with a slave you need to create a
  packet that will contain all the information
  required to communicate to the slave. There are
  numerous counters for easy diagnostic.
  These are variables already implemented in a
  packet. You can set and clear these variables
  as needed.
  
  There are general modbus information counters:
  requests - contains the total requests to a slave
  successful_requests - contains the total successful requests
  total_errors - contains the total errors as a sum
  timeout - contains the total time out errors
  incorrect_id_returned - contains the total incorrect id returned errors
  incorrect_function_returned - contains the total incorrect function returned errors
  incorrect_bytes_returned - contains the total incorrect bytes returned errors
  checksum_failed - contains the total checksum failed errors
  buffer_errors - contains the total buffer errors
  
  And there are modbus specific exception counters:
  illegal_function - contains the total illegal_function errors
  illegal_data_address - contains the total illegal_data_address errors
  illegal_data_value - contains the total illegal_data_value errors
  misc_exceptions - contains the total miscellaneous returned exceptions
  
  And finally there is variable called "connection" that
  at any given moment contains the current connection
  status of the packet. If true then the connection is
  active. If false then communication will be stopped
  on this packet untill the programmer sets the connections
  variable to true explicitly. The reason for this is
  because of the time out involved in modbus communication.
  EACH faulty slave that's not communicating will slow down
  communication on the line with the time out value. E.g.
  Using a time out of 1500ms, if you have 10 slaves and 9 of them
  stops communicating the latency burden placed on communication
  will be 1500ms * 9 = 13,5 seconds!!!!
  
  In addition to this when all the packets are scanned and
  all of them have a false connection a value is returned
  from modbus_port() to inform you something is wrong with
  the port. This is most likely to happen when there is
  something physically wrong with the RS485 line.
  This is only for information. You have to explicitly set
  each packets connection attribute to false.
  Packets scanning and communication will automatically
  revert to normal.
  
  All the error checking, updating and communication multitasking
  takes place in the background!
  
  In general to communicate with to a slave using modbus
  RTU you will request information using the specific
  slave id, the function request, the starting address
  and lastly the number of registers to request.
  Function 3 & 16 are supported. In addition to
  this broadcasting (id = 0) is supported for function 16.
  Constants are provided for:
  Function 3 -  READ_HOLDING_REGISTERS
  Function 16 - PRESET_MULTIPLE_REGISTERS
  
      Note:
  The Arduino serial ring buffer is 128 bytes or 64 registers.
  Most of the time you will connect the arduino to a master via serial
  using a MAX485 or similar.
  
  In a function 3 request the master will attempt to read from your
  slave and since 5 bytes is already used for ID, FUNCTION, NO OF BYTES
  and two BYTES CRC the master can only request 122 bytes or 61 registers.
  
  In a function 16 request the master will attempt to write to your
  slave and since a 9 bytes is already used for ID, FUNCTION, ADDRESS,
  NO OF REGISTERS, NO OF BYTES and two BYTES CRC the master can only write
  118 bytes or 59 registers.
  
  Using the FTDI USB to Serial converter the maximum bytes you can send is limited
  to its internal buffer which is 60 bytes or 30 unsigned int registers.
  
  Thus:
  
  In a function 3 request the master will attempt to read from your
  slave and since 5 bytes is already used for ID, FUNCTION, NO OF BYTES
  and two BYTES CRC the master can only request 54 bytes or 27 registers.
  
  In a function 16 request the master will attempt to write to your
  slave and since a 9 bytes is already used for ID, FUNCTION, ADDRESS,
  NO OF REGISTERS, NO OF BYTES and two BYTES CRC the master can only write
  50 bytes or 25 registers.
  
  Since it is assumed that you will mostly use the Arduino to connect to a
  master without using a USB to Serial converter the internal buffer is set
  the same as the Arduino Serial ring buffer which is 128 bytes.
*/

#include "Arduino.h"

#define READ_HOLDING_REGISTERS 3
#define	PRESET_MULTIPLE_REGISTERS 16

typedef struct {
    // specific packet info
    unsigned char id;
    unsigned char function;
    unsigned int address;
    unsigned int no_of_registers;
    unsigned int* register_array;

    // modbus information counters
    unsigned int requests;
    unsigned int successful_requests;
    unsigned long total_errors;
    unsigned int retries;
    unsigned int timeout;
    unsigned int incorrect_id_returned;
    unsigned int incorrect_function_returned;
    unsigned int incorrect_bytes_returned;
    unsigned int checksum_failed;
    unsigned int buffer_errors;

    // modbus specific exception counters
    unsigned int illegal_function;
    unsigned int illegal_data_address;
    unsigned int illegal_data_value;
    unsigned char misc_exceptions;

    // connection status of packet
    unsigned char connection;

} Packet;

typedef Packet* packetPointer;

// function definitions
unsigned int modbus_update(Packet* packets);
void modbus_configure(long baud, unsigned int _timeout, unsigned int _polling,
                      unsigned char _retry_count, unsigned char _TxEnablePin,
                      Packet* packets, unsigned int _total_no_of_packets);

#endif

Steuert UART2 vom MEGA an:
SimpleModbusMaster2.h ( .cpp Datei )

#include "SimpleModbusMasterSerial2.h"

#define BUFFER_SIZE_2 128

// modbus specific exceptions for Serial2
#define ILLEGAL_FUNCTION_2 1
#define ILLEGAL_DATA_ADDRESS_2 2
#define ILLEGAL_DATA_VALUE_2 3

unsigned char transmission_ready_Flag_2;
unsigned char messageOkFlag_2, messageErrFlag_2;
unsigned char retry_count_2;
unsigned char TxEnablePin_2;
// frame[] is used to receive and transmit packages for Serial2.
// The maximum number of bytes in a modbus packet is 256 bytes.
// This is limited to the serial buffer of 128 bytes for Serial2.
unsigned char frame_2[BUFFER_SIZE_2];
unsigned int timeout_2, polling_2;
unsigned int T1_5_2; // inter character time out in microseconds
unsigned int T3_5_2; // frame delay in microseconds
unsigned long previousTimeout_2, previousPolling_2;
unsigned int total_no_of_packets_2;
Packet_2* packet_2; // current packet for Serial2

// function definitions for Serial2
void constructPacket_2();
void checkResponse_2();
void check_F3_data_2(unsigned char buffer);
void check_F16_data_2();
unsigned char getData_2();
void check_packet_status_2();
unsigned int calculateCRC_2(unsigned char bufferSize);
void sendPacket_2(unsigned char bufferSize);


unsigned int modbus_update_2(Packet_2* packets_2)
{
    unsigned int connection_status_2 = total_no_of_packets_2;

    if (transmission_ready_Flag_2) {

        static unsigned int packet_index_2;

        unsigned int failed_connections_2 = 0;

        unsigned char current_connection_2;

        do {

            if (packet_index_2 == total_no_of_packets_2) // wrap around to the beginning
                packet_index_2 = 0;

            // proceed to the next packet
            packet_2 = &packets_2[packet_index_2];

            // get the current connection status
            current_connection_2 = packet_2->connection;

            if (!current_connection_2) {
                connection_status_2 = packet_index_2;

                if (++failed_connections_2 == total_no_of_packets_2)
                    return connection_status_2;
            }

            packet_index_2++;

        } while (!current_connection_2); 

        constructPacket_2();
    }

    checkResponse_2();

    check_packet_status_2();

SimpleModbusMasterSerial2.h ( .h Datei )

#ifndef SIMPLE_MODBUS_MASTER_SERIAL2_H
#define SIMPLE_MODBUS_MASTER_SERIAL2_H

#include "Arduino.h"

#define READ_HOLDING_REGISTERS_2 3
#define PRESET_MULTIPLE_REGISTERS_2 16

typedef struct {
    unsigned char id;
    unsigned char function;
    unsigned int address;
    unsigned int no_of_registers;
    unsigned int* register_array;

    unsigned int requests;
    unsigned int successful_requests;
    unsigned long total_errors;
    unsigned int retries;
    unsigned int timeout;
    unsigned int incorrect_id_returned;
    unsigned int incorrect_function_returned;
    unsigned int incorrect_bytes_returned;
    unsigned int checksum_failed;
    unsigned int buffer_errors;

    unsigned int illegal_function;
    unsigned int illegal_data_address;
    unsigned int illegal_data_value;
    unsigned char misc_exceptions;

    unsigned char connection;

} Packet_2;

typedef Packet_2* packetPointer_2;

// function definitions for Serial2
unsigned int modbus_update_2(Packet_2* packets_2);
void modbus_configure_2(long baud, unsigned int _timeout_2, unsigned int _polling_2,
                      unsigned char _retry_count_2, unsigned char _TxEnablePin_2,
                      Packet_2* packets_2, unsigned int _total_no_of_packets_2);

#endif

Der Code für einen Slave: ( und ja man nennt es jetzt anderes )

#include <SimpleModbusSlave.h>

#define TxEnablePin 3
#define TOTAL_REGS_SIZE 10  // Größe des Registers (genau 10 Register)
#define ledPin 12           // Pin für eine LED (optional)

// Definition der 10 Register im Slave
enum {
  ADC0,        // Register 0
  ADC1,        // Register 1 (wird vom Master beschrieben)
  ADC2,        // Register 2
  ADC3,        // Register 3  (wird an Master gesendet)
  ADC4,        // Register 4
  ADC5,        // Register 5
  LED_STATE,   // Register 6 
  REG_7,       // Register 7 (zusätzliches Register für allgemeine Daten)
  REG_8,       // Register 8 (zusätzliches Register für allgemeine Daten)
  TOTAL_ERRORS // Register 9 für Fehlerzähler
};

unsigned int holdingRegs[TOTAL_REGS_SIZE];  // Array zur Speicherung der 10 Register

void setup() {
  // Modbus-Konfiguration: 9600 Baud, Slave-ID 2, TxEnablePin, Anzahl der Register, keine Latenz
  modbus_configure(19200, 2, TxEnablePin, TOTAL_REGS_SIZE, 0);
  pinMode(ledPin, OUTPUT);  // Pin für eine LED (optional)
  Serial.begin(19200);
  Serial.println("Slave bereit...");
}

void loop() {
  // Modbus-Anfragen verarbeiten
  int errors = modbus_update(holdingRegs);

  // Prüfen, ob keine Fehler aufgetreten sind
  if (errors == 0) {
    // Wert von Register 1 lesen, 2 hinzufügen und in Register 3 speichern
    holdingRegs[3] = holdingRegs[1] + 2;

    // Ausgabe von Register 1 und Register 3
    Serial.print("Register 1 (Originalwert): ");
    Serial.println(holdingRegs[1]);

    Serial.print("Register 3 (Ergebnis): ");
    Serial.println(holdingRegs[3]);
  }

  delay(200);  // Kurze Verzögerung für bessere Lesbarkeit
}

Die Bibliothek ist die Standard : SimpleModbusSlave.h

Die H-Datei:

#ifndef SIMPLE_MODBUS_SLAVE_H
#define SIMPLE_MODBUS_SLAVE_H

/*
  SimpleModbusSlave allows you to communicate
  to any slave using the Modbus RTU protocol.
  
  The crc calculation is based on the work published
  by jpmzometa at
  http://sites.google.com/site/jpmzometa/arduino-mbrt
  
  By Juan Bester : bester.juan@gmail.com
  
  The functions implemented are functions 3 and 16.
  read holding registers and preset multiple registers
  of the Modbus RTU Protocol, to be used over the Arduino serial connection.
  
  This implementation DOES NOT fully comply with the Modbus specifications.
  
  Specifically the frame time out have not been implemented according
  to Modbus standards. The code does however combine the check for
  inter character time out and frame time out by incorporating a maximum
  time out allowable when reading from the message stream.
  
  These library of functions are designed to enable a program send and
  receive data from a device that communicates using the Modbus protocol.
  
  SimpleModbusSlave implements an unsigned int return value on a call to modbus_update().
  This value is the total error count since the slave started. It's useful for fault finding.
  
  This code is for a Modbus slave implementing functions 3 and 16
  function 3: Reads the binary contents of holding registers (4X references)
  function 16: Presets values into a sequence of holding registers (4X references)
  
  All the functions share the same register array.
  
  Exception responses:
  1 ILLEGAL FUNCTION
  2 ILLEGAL DATA ADDRESS
  3 ILLEGAL DATA VALUE
  
  Note:
  The Arduino serial ring buffer is 128 bytes or 64 registers.
  Most of the time you will connect the arduino to a master via serial
  using a MAX485 or similar.
  
  In a function 3 request the master will attempt to read from your
  slave and since 5 bytes is already used for ID, FUNCTION, NO OF BYTES
  and two BYTES CRC the master can only request 122 bytes or 61 registers.
  
  In a function 16 request the master will attempt to write to your
  slave and since a 9 bytes is already used for ID, FUNCTION, ADDRESS,
  NO OF REGISTERS, NO OF BYTES and two BYTES CRC the master can only write
  118 bytes or 59 registers.
  
  Using the FTDI converter ic the maximum bytes you can send is limited
  to its internal buffer which is 60 bytes or 30 unsigned int registers.
  
  Thus:
  
  In a function 3 request the master will attempt to read from your
  slave and since 5 bytes is already used for ID, FUNCTION, NO OF BYTES
  and two BYTES CRC the master can only request 54 bytes or 27 registers.
  
  In a function 16 request the master will attempt to write to your
  slave and since a 9 bytes is already used for ID, FUNCTION, ADDRESS,
  NO OF REGISTERS, NO OF BYTES and two BYTES CRC the master can only write
  50 bytes or 25 registers.
  
  Since it is assumed that you will mostly use the Arduino to connect to a
  master without using a USB to Serial converter the internal buffer is set
  the same as the Arduino Serial ring buffer which is 128 bytes.
  
  The functions included here have been derived from the
  Modbus Specifications and Implementation Guides
  
  http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
  http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf
  http://www.modbus.org/docs/PI_MBUS_300.pdf
*/

#include "Arduino.h"

// function definitions
void modbus_configure(long baud, byte _slaveID, byte _TxEnablePin, unsigned int _holdingRegsSize, unsigned char _lowLatency);
unsigned int modbus_update(unsigned int *holdingRegs);


#endif

Die Cpp-Datei:

#include "SimpleModbusSlave.h"

#define BUFFER_SIZE 128

// frame[] is used to recieve and transmit packages.
// The maximum serial ring buffer size is 128
unsigned char frame[BUFFER_SIZE];
unsigned int holdingRegsSize; // size of the register array
unsigned char broadcastFlag;
unsigned char slaveID;
unsigned char function;
unsigned char TxEnablePin;
unsigned int errorCount;
unsigned int T1_5; // inter character time out
unsigned int T3_5; // frame delay

// function definitions
void exceptionResponse(unsigned char exception);
unsigned int calculateCRC(unsigned char bufferSize);
void sendPacket(unsigned char bufferSize);

unsigned int modbus_update(unsigned int *holdingRegs)
{
    unsigned char buffer = 0;
    unsigned char overflow = 0;

    while (Serial.available()) {
        // The maximum number of bytes is limited to the serial buffer size of 128 bytes
        // If more bytes is received than the BUFFER_SIZE the overflow flag will be set and the
        // serial buffer will be red untill all the data is cleared from the receive buffer.
        if (overflow)
            Serial.read();
        else {
            if (buffer == BUFFER_SIZE)
                overflow = 1;
            frame[buffer] = Serial.read();
            buffer++;
        }
        delayMicroseconds(T1_5); // inter character time out
    }

    // If an overflow occurred increment the errorCount
    // variable and return to the main sketch without
    // responding to the request i.e. force a timeout
    if (overflow)
        return errorCount++;

    // The minimum request packet is 8 bytes for function 3 & 16
    if (buffer > 6) {
        unsigned char id = frame[0];

        broadcastFlag = 0;

        if (id == 0)
            broadcastFlag = 1;

        if (id == slaveID || broadcastFlag) { // if the recieved ID matches the slaveID or broadcasting id (0), continue
            unsigned int crc = ((frame[buffer - 2] << 8) | frame[buffer - 1]); // combine the crc Low & High bytes
            if (calculateCRC(buffer - 2) == crc) { // if the calculated crc matches the recieved crc continue
                function = frame[1];
                unsigned int startingAddress = ((frame[2] << 8) | frame[3]); // combine the starting address bytes
                unsigned int no_of_registers = ((frame[4] << 8) | frame[5]); // combine the number of register bytes
                unsigned int maxData = startingAddress + no_of_registers;
                unsigned char index;
                unsigned char address;
                unsigned int crc16;

                // broadcasting is not supported for function 3
                if (!broadcastFlag && (function == 3)) {
                    if (startingAddress < holdingRegsSize) { // check exception 2 ILLEGAL DATA ADDRESS
                        if (maxData <= holdingRegsSize) { // check exception 3 ILLEGAL DATA VALUE
                            unsigned char noOfBytes = no_of_registers * 2;
                            unsigned char responseFrameSize = 5 + noOfBytes; // ID, function, noOfBytes, (dataLo + dataHi) * number of registers, crcLo, crcHi
                            frame[0] = slaveID;
                            frame[1] = function;
                            frame[2] = noOfBytes;
                            address = 3; // PDU starts at the 4th byte
                            unsigned int temp;

                            for (index = startingAddress; index < maxData; index++) {
                                temp = holdingRegs[index];
                                frame[address] = temp >> 8; // split the register into 2 bytes
                                address++;
                                frame[address] = temp & 0xFF;
                                address++;
                            }

                            crc16 = calculateCRC(responseFrameSize - 2);
                            frame[responseFrameSize - 2] = crc16 >> 8; // split crc into 2 bytes
                            frame[responseFrameSize - 1] = crc16 & 0xFF;
                            sendPacket(responseFrameSize);
                        } else
                            exceptionResponse(3); // exception 3 ILLEGAL DATA VALUE
                    } else
                        exceptionResponse(2); // exception 2 ILLEGAL DATA ADDRESS
                } else if (function == 6) {
                    if (startingAddress < holdingRegsSize) { // check exception 2 ILLEGAL DATA ADDRESS
                        unsigned int startingAddress = ((frame[2] << 8) | frame[3]);
                        unsigned int regStatus = ((frame[4] << 8) | frame[5]);
                        unsigned char responseFrameSize = 8;

                        holdingRegs[startingAddress] = regStatus;

                        crc16 = calculateCRC(responseFrameSize - 2);
                        frame[responseFrameSize - 2] = crc16 >> 8; // split crc into 2 bytes
                        frame[responseFrameSize - 1] = crc16 & 0xFF;
                        sendPacket(responseFrameSize);
                    } else
                        exceptionResponse(2); // exception 2 ILLEGAL DATA ADDRESS
                } else if (function == 16) {
                    // check if the recieved number of bytes matches the calculated bytes minus the request bytes
                    // id + function + (2 * address bytes) + (2 * no of register bytes) + byte count + (2 * CRC bytes) = 9 bytes
                    if (frame[6] == (buffer - 9)) {
                        if (startingAddress < holdingRegsSize) { // check exception 2 ILLEGAL DATA ADDRESS
                            if (maxData <= holdingRegsSize) { // check exception 3 ILLEGAL DATA VALUE
                                address = 7; // start at the 8th byte in the frame

                                for (index = startingAddress; index < maxData; index++) {
                                    holdingRegs[index] = ((frame[address] << 8) | frame[address + 1]);
                                    address += 2;
                                }

                                // only the first 6 bytes are used for CRC calculation
                                crc16 = calculateCRC(6);
                                frame[6] = crc16 >> 8; // split crc into 2 bytes
                                frame[7] = crc16 & 0xFF;

                                // a function 16 response is an echo of the first 6 bytes from the request + 2 crc bytes
                                if (!broadcastFlag) // don't respond if it's a broadcast message
                                    sendPacket(8);
                            } else
                                exceptionResponse(3); // exception 3 ILLEGAL DATA VALUE
                        } else
                            exceptionResponse(2); // exception 2 ILLEGAL DATA ADDRESS
                    } else
                        errorCount++; // corrupted packet
                } else
                    exceptionResponse(1); // exception 1 ILLEGAL FUNCTION
            } else // checksum failed
                errorCount++;
        } // incorrect id
    } else if (buffer > 0 && buffer < 8)
        errorCount++; // corrupted packet

    return errorCount;
}

void exceptionResponse(unsigned char exception)
{
    errorCount++; // each call to exceptionResponse() will increment the errorCount
    if (!broadcastFlag) { // don't respond if its a broadcast message
        frame[0] = slaveID;
        frame[1] = (function | 0x80); // set the MSB bit high, informs the master of an exception
        frame[2] = exception;
        unsigned int crc16 = calculateCRC(3); // ID, function + 0x80, exception code == 3 bytes
        frame[3] = crc16 >> 8;
        frame[4] = crc16 & 0xFF;
        sendPacket(5); // exception response is always 5 bytes ID, function + 0x80, exception code, 2 bytes crc
    }
}

void modbus_configure(long baud, unsigned char _slaveID, unsigned char _TxEnablePin, unsigned int _holdingRegsSize, unsigned char _lowLatency)
{
    slaveID = _slaveID;
    Serial.begin(baud);

    if (_TxEnablePin > 1) {
        // pin 0 & pin 1 are reserved for RX/TX. To disable set txenpin < 2
        TxEnablePin = _TxEnablePin;
        pinMode(TxEnablePin, OUTPUT);
        digitalWrite(TxEnablePin, LOW);
    }

    // Modbus states that a baud rate higher than 19200 must use a fixed 750 us
    // for inter character time out and 1.75 ms for a frame delay.
    // For baud rates below 19200 the timeing is more critical and has to be calculated.
    // E.g. 9600 baud in a 10 bit packet is 960 characters per second
    // In milliseconds this will be 960characters per 1000ms. So for 1 character
    // 1000ms/960characters is 1.04167ms per character and finaly modbus states an
    // intercharacter must be 1.5T or 1.5 times longer than a normal character and thus
    // 1.5T = 1.04167ms * 1.5 = 1.5625ms. A frame delay is 3.5T.
    // Added experimental low latency delays. This makes the implementation
    // non-standard but practically it works with all major modbus master implementations.

    if (baud == 1000000 && _lowLatency) {
        T1_5 = 1;
        T3_5 = 10;
    } else if (baud >= 115200 && _lowLatency) {
        T1_5 = 75;
        T3_5 = 175;
    } else if (baud > 19200) {
        T1_5 = 750;
        T3_5 = 1750;
    } else {
        T1_5 = 15000000/baud; // 1T * 1.5 = T1.5
        T3_5 = 35000000/baud; // 1T * 3.5 = T3.5
    }

    holdingRegsSize = _holdingRegsSize;
    errorCount = 0; // initialize errorCount
}

unsigned int calculateCRC(byte bufferSize)
{
    unsigned int temp, temp2, flag;
    temp = 0xFFFF;
    for (unsigned char i = 0; i < bufferSize; i++) {
        temp = temp ^ frame[i];
        for (unsigned char j = 1; j <= 8; j++) {
            flag = temp & 0x0001;
            temp >>= 1;
            if (flag)
                temp ^= 0xA001;
        }
    }
    // Reverse byte order.
    temp2 = temp >> 8;
    temp = (temp << 8) | temp2;
    temp &= 0xFFFF;
    return temp; // the returned value is already swopped - crcLo byte is first & crcHi byte is last
}

void sendPacket(unsigned char bufferSize)
{
    if (TxEnablePin > 1)
        digitalWrite(TxEnablePin, HIGH);

    for (unsigned char i = 0; i < bufferSize; i++)
        Serial.write(frame[i]);

    Serial.flush();

    // allow a frame delay to indicate end of transmission
    delayMicroseconds(T3_5);

    if (TxEnablePin > 1)
        digitalWrite(TxEnablePin, LOW);
}

Bei dem Code wird vom Client eine Zahl gesendet und der Server addiert dieser eine 2 hinzu und sendet diese an den Client zurück. Der Client gibt die Ergebnisse im seriel Print wieder. ( Ich weiß nicht ob ich mich an diese Begriffe gewöhnen kann aber ich will ja nicht meine Arduino´s erniedrigen ).

Es ist nur ein Test Code für die grundlegende Kommunikation.
:crayon:by HarryP: Zusammenführung Doppelpost

1 „Gefällt mir“