Modbus RTU with the standard Arduino libraries

using ArduinoRS485 and ArduinoModbus

Tom

The Industruino IND.I/O has always had an RS485 port, which allows us to use the Modbus RTU protocol to communicate with standard industrial equipment such as sensors (typically Modbus slaves) and PLCs (typically Modbus Masters). We recommended the SimpleModbus set of libraries for use with the Industruino, as documented in a 2017 blog post. Over the years, we have used this in many industrial projects with great results.

In 2018 Arduino released its own RS485 and Modbus libraries, targetted for use with their MKR RS485 Shield. They also include Modbus TCP (over Ethernet), which we documented in a 2020 blog post.

Here we will show how these standard libraries can also be used for Modbus RTU between 2 INDIOs.

The only modification we need to make is explicitely define the RS485 port, as it is different from the default settings of the MKR RS485 Shield. On the INDIO, the RS485 is connected to hardware serial 'Serial' (D0/D1), and the TxEnablePin is D9 (MAX485 pins DE and RE).

So the constructor is:

RS485Class Rs485(Serial, 1, 9, 9);  // Industruino INDIO RS485 port

The below Master sketch (RTU Client) requests 20 holding registers from SlaveID 1, starting from address 0. The first register is printed on the LCD, and all 20 are printed in the Serial Monitor.

The Slave sketch (RTU Server) has 20 holding registers and updates all of them with the uptime in seconds, also shown on the LCD.

RS485 wiring is simple: A to A and B to B.

/*
 Modbus RTU Client Kitchen Sink == MODBUS MASTER
 This sketch creates a Modbus RTU Client and demonstrates
 how to use various Modbus Client APIs.

 created 18 July 2018
 by Sandeep Mistry
 modified by Tom Tobback for use with Industruino INDIO
 March 2023
*/

#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
#include <ArduinoModbus.h>
// RS485Class(HardwareSerial& hwSerial, int txPin, int dePin, int rePin);
RS485Class Rs485(Serial, 1, 9, 9); // Industruino INDIO RS485 port

/// LCD
#include <UC1701.h>
static UC1701 lcd;
#define LCD_PIN 26

void setup() {

 pinMode(LCD_PIN, OUTPUT); // LCD backlight ON
 digitalWrite(LCD_PIN, HIGH);

 // LCD intro
 lcd.begin();
 lcd.setCursor(0, 1);
 lcd.print("Modbus RTU Client");
 lcd.setCursor(0, 2);
 lcd.print("(master)");

 SerialUSB.begin(115200);
 delay(2000);

 SerialUSB.println("Modbus RTU Client = master");

 // start the Modbus RTU client
 while (!ModbusRTUClient.begin(Rs485, 9600)) {
 SerialUSB.println("Failed to start Modbus RTU Client!");
 lcd.setCursor(0, 3);
 lcd.print("failed to start!");
 delay(5000);
 }
}

void loop() {
 readHoldingRegisterValues();
 lcd.setCursor(0, 4);
 lcd.print("reg 0x00: ");
 lcd.print(ModbusRTUClient.holdingRegisterRead(1, 0));
 delay(2000);
 SerialUSB.println();
}

void readHoldingRegisterValues() {
 SerialUSB.print("Reading Holding Register values ... ");

 // read 10 Input Register values from (slave) id 1, address 0x00
 if (!ModbusRTUClient.requestFrom(1, HOLDING_REGISTERS, 0x00, 20)) {
 SerialUSB.print("failed! ");
 SerialUSB.println(ModbusRTUClient.lastError());
 } else {
 SerialUSB.println("success");
 while (ModbusRTUClient.available()) {
 SerialUSB.print(ModbusRTUClient.read());
 SerialUSB.print(' ');
 }
 SerialUSB.println();
 }
 // Alternatively, to read a single Holding Register value use:
 // ModbusRTUClient.holdingRegisterRead(...)
}

/*
 Modbus RTU Server Kitchen Sink == MODBUS SLAVE

 This sketch creates a Modbus RTU Server and demonstrates
 how to use various Modbus Server APIs.

 created 18 July 2018
 by Sandeep Mistry
 modified by Tom Tobback for use with Industruino INDIO
 March 2023
*/

#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
#include <ArduinoModbus.h>
// RS485Class(HardwareSerial& hwSerial, int txPin, int dePin, int rePin);
RS485Class Rs485(Serial, 1, 9, 9); // Industruino INDIO RS485 port

/// LCD
#include <UC1701.h>
static UC1701 lcd;
#define LCD_PIN 26

const int numHoldingRegisters = 20;
unsigned long ts = 0;

void setup() {

 pinMode(LCD_PIN, OUTPUT); // LCD backlight ON
 digitalWrite(LCD_PIN, HIGH);

 // LCD intro
 lcd.begin();
 lcd.setCursor(0, 1);
 lcd.print("Modbus RTU Server");
 lcd.setCursor(0, 2);
 lcd.print("(slave)");

 SerialUSB.begin(115200);
 delay(2000);

 SerialUSB.println("Modbus RTU Server = slave, id1, 9600");

 // start the Modbus RTU server, with (slave) id 1
 while (!ModbusRTUServer.begin(Rs485, 1, 9600)) {
 SerialUSB.println("Failed to start Modbus RTU Server!");
 lcd.setCursor(0, 3);
 lcd.print("failed to start!");
 delay(5000);
 }

 // configure holding registers at address 0x00
 ModbusRTUServer.configureHoldingRegisters(0x00, numHoldingRegisters);
}


void loop() {

 // poll for Modbus RTU requests
 ModbusRTUServer.poll();

 if (millis() - ts > 1000) {
 int now_sec = millis() / 1000;
 SerialUSB.println(now_sec);
 for (int i = 0; i < numHoldingRegisters; i++) {
 ModbusRTUServer.holdingRegisterWrite(i, now_sec);
 }

 lcd.setCursor(0, 4);
 lcd.print("write all regs: ");
 lcd.print(now_sec);

 ts = millis();
 }
}