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.
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.Moderatoren
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.
by HarryP: Zusammenführung Doppelpost