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(); } }