The Opta™, with its industrial hardware and software capabilities, and the Arduino ecosystem tools such as the Arduino IDE and its libraries, provide several types of Modbus communication protocol with effortless implementation thanks to its robust design.
The Modbus RTU protocol is one of the protocols available within Opta™. In this tutorial, we will learn how to implement Modbus RTU communications protocol over RS-485 between two Opta™ devices.
ArduinoRS485
, and ArduinoModbus
. You can install these libraries via Library Manager of the Arduino IDE.Modbus is an open and royalty-free serial communication protocol derived from the client/server architecture. It is widely used in industrial electronic devices, especially in Building Management Systems (BMS) and Industrial Automation Systems (IAS).
It was published by Modicon (now Schneider Electric) in 1979 and has become a de facto standard communication protocol among industrial electronic devices to be used with programmable logic controllers (PLCs).
Modbus communication protocol is often used to connect a supervisory device with a Remote Terminal Unit (RTU) in Supervisory Control and Data Acquisition (SCADA) systems. Reliability in communications between electronic devices is ensured with Modbus by using messages with a simple 16-bit structure with a Cyclic-Redundant Checksum (CRC).
If you want more insights on the Modbus communication protocol, take a look at Modbus article complying as well with Opta™.
If you haven't already, head over here and install the most recent version of the Arduino IDE along with the necessary device drivers for your computer. For additional details on Opta™, check out getting started tutorial. Make sure you install the latest version of the ArduinoModbus and the ArduinoRS485 libraries, as they will be used to implement the Modbus RTU communication protocol.
It requires setting up an RS-485 connection to enable the Modbus RTU communication protocol. Refer to the following diagram for connecting two Opta™ devices via the RS-485 interface.
The goal of the following example is to configure and use the Modbus RTU communication protocol over the RS-485 interface between two Opta™ devices.
The Modbus is a renowned Client-Server protocol for its reliability. The Modbus Client is responsible as a requesting device, and the Modbus Server provides requested information when available. Several Modbus Servers are allowed, but only one Modbus Client can be present. In this example, an Opta™ Client handles writing and reading
Coil
, Holding
, Discrete Input
, and Input
register values, while an Opta™ Server will poll for Modbus RTU requests and return the appropriate values.The crucial components of the code used in this tutorial are discussed in detail in the following sections to make the example easier to understand.
You can access the complete example code here; after extracting the files,
Opta_ModbusRTU_client
and Opta_ModbusRTU_server
sketches are available to try with your Opta™ devices.The Opta™ Client will require the following setup:
1#include <ArduinoModbus.h>2#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library3
4constexpr auto baudrate { 19200 };5
6// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification7// MODBUS over serial line specification and implementation guide V1.028// Paragraph 2.5.1.1 MODBUS Message RTU Framing9// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf10constexpr auto bitduration { 1.f / baudrate };11constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 };12constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };13// constexpr auto preDelayBR { bitduration * 10.0f * 3.5f * 1e6 };14
15int counter = 0;16
17void setup() {18 Serial.begin(9600);19 while (!Serial);20
21 Serial.println("Modbus RTU Client");22
23 RS485.setDelays(preDelayBR, postDelayBR);24
25 // Start the Modbus RTU client26 if (!ModbusRTUClient.begin(baudrate, SERIAL_8E1)) {27 Serial.println("Failed to start Modbus RTU Client!");28 while (1);29 }30}
The
preDelay
and postDelay
parameters are configured for a proper operation per Modbus RTU specification. The method RS485.setDelays(preDelayBR, postDelayBR)
is then called to correctly set and use Modbus RTU over RS-485 interface on Opta™. In this example, such parameters are applied based on the message RTU framing specifications explained in depth in this guide.The typical baud rates are usually
9600
and 19200
; in the current example, we are using a baud rate of 19200
, but it can be changed depending on the system requirements. For the serial port parameter, SERIAL_8E1
is used to set 8 data bits, even parity, and one stop bit.The Modbus Server can be a module or a sensor with registers that can be accessed using specified addresses to obtain the monitored information or measurements. Inside the loop function of the sketch for the Client device, there are several tasks in charge of reading and writing specific values to access these types of data. Such data are
Coil
, Holding
, Discrete Input
, and Input
register values.1void loop() {2 writeCoilValues();3
4 readCoilValues();5
6 readDiscreteInputValues();7
8 writeHoldingRegisterValues();9
10 readHoldingRegisterValues();11
12 readInputRegisterValues();13
14 counter++;15
16 delay(5000);17 Serial.println();18}
The complete code for the Client is shown below:
1/**2 Getting Started with Modbus RTU on Opta™3 Name: Opta_Client4 Purpose: Writes Coil and Holding Register values; Reads Coil, Discrete Input, Holding Registers, and Input Register values.5
6 @author Arduino7*/8
9#include <ArduinoModbus.h>10#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library11
12constexpr auto baudrate { 19200 };13
14// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification15// MODBUS over serial line specification and implementation guide V1.0216// Paragraph 2.5.1.1 MODBUS Message RTU Framing17// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf18constexpr auto bitduration { 1.f / baudrate };19constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 };20constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };21// constexpr auto preDelayBR { bitduration * 10.0f * 3.5f * 1e6 };22
23int counter = 0;24
25void setup() {26 Serial.begin(9600);27 while (!Serial);28
29 Serial.println("Modbus RTU Client");30
31 RS485.setDelays(preDelayBR, postDelayBR);32
33 // Start the Modbus RTU client34 if (!ModbusRTUClient.begin(baudrate, SERIAL_8E1)) {35 Serial.println("Failed to start Modbus RTU Client!");36 while (1);37 }38}39
40void loop() {41 writeCoilValues();42
43 readCoilValues();44
45 readDiscreteInputValues();46
47 writeHoldingRegisterValues();48
49 readHoldingRegisterValues();50
51 readInputRegisterValues();52
53 counter++;54
55 delay(5000);56 Serial.println();57}58
59/**60 Writes Coil values to the server under specified address.61*/62void writeCoilValues() {63 // Set the coils to 1 when counter is odd64 byte coilValue = ((counter % 2) == 0) ? 0x00 : 0x01;65
66 Serial.print("Writing Coil values ... ");67
68 // Srite 10 Coil values to (server) id 42, address 0x0069 ModbusRTUClient.beginTransmission(42, COILS, 0x00, 10);70 for (int i = 0; i < 10; i++) {71 ModbusRTUClient.write(coilValue);72 }73 if (!ModbusRTUClient.endTransmission()) {74 Serial.print("failed! ");75 Serial.println(ModbusRTUClient.lastError());76 } else {77 Serial.println("success");78 }79
80 // Alternatively, to write a single Coil value use:81 // ModbusRTUClient.coilWrite(...)82}83
84/**85 Reads Coil values from the server under specified address.86*/87void readCoilValues() {88 Serial.print("Reading Coil values ... ");89
90 // Read 10 Coil values from (server) id 42, address 0x0091 if (!ModbusRTUClient.requestFrom(42, COILS, 0x00, 10)) {92 Serial.print("failed! ");93 Serial.println(ModbusRTUClient.lastError());94 } else {95 Serial.println("success");96
97 while (ModbusRTUClient.available()) {98 Serial.print(ModbusRTUClient.read());99 Serial.print(' ');100 }101 Serial.println();102 }103
104 // Alternatively, to read a single Coil value use:105 // ModbusRTUClient.coilRead(...)106}107
108/**109 Reads Discrete Input values from the server under specified address.110*/111void readDiscreteInputValues() {112 Serial.print("Reading Discrete Input values ... ");113
114 // Read 10 Discrete Input values from (server) id 42, address 0x00115 if (!ModbusRTUClient.requestFrom(42, DISCRETE_INPUTS, 0x00, 10)) {116 Serial.print("failed! ");117 Serial.println(ModbusRTUClient.lastError());118 } else {119 Serial.println("success");120
121 while (ModbusRTUClient.available()) {122 Serial.print(ModbusRTUClient.read());123 Serial.print(' ');124 }125 Serial.println();126 }127
128 // Alternatively, to read a single Discrete Input value use:129 // ModbusRTUClient.discreteInputRead(...)130}131
132/**133 Writes Holding Register values to the server under specified address.134*/135void writeHoldingRegisterValues() {136 //Set the Holding Register values to counter137 Serial.print("Writing Holding Registers values ... ");138
139 // Write 10 coil values to (server) id 42, address 0x00140 ModbusRTUClient.beginTransmission(42, HOLDING_REGISTERS, 0x00, 10);141 for (int i = 0; i < 10; i++) {142 ModbusRTUClient.write(counter);143 }144 if (!ModbusRTUClient.endTransmission()) {145 Serial.print("failed! ");146 Serial.println(ModbusRTUClient.lastError());147 } else {148 Serial.println("success");149 }150
151 // Alternatively, to write a single Holding Register value use:152 // ModbusRTUClient.holdingRegisterWrite(...)153}154
155/**156 Reads Holding Register values from the server under specified address.157*/158void readHoldingRegisterValues() {159 Serial.print("Reading Holding Register values ... ");160
161 // Read 10 Input Register values from (server) id 42, address 0x00162 if (!ModbusRTUClient.requestFrom(42, HOLDING_REGISTERS, 0x00, 10)) {163 Serial.print("failed! ");164 Serial.println(ModbusRTUClient.lastError());165 } else {166 Serial.println("success");167
168 while (ModbusRTUClient.available()) {169 Serial.print(ModbusRTUClient.read());170 Serial.print(' ');171 }172 Serial.println();173 }174
175 // Alternatively, to read a single Holding Register value use:176 // ModbusRTUClient.holdingRegisterRead(...)177}178
179/**180 Reads Input Register values from the server under specified address.181*/182void readInputRegisterValues() {183 Serial.print("Reading input register values ... ");184
185 // Read 10 discrete input values from (server) id 42,186 if (!ModbusRTUClient.requestFrom(42, INPUT_REGISTERS, 0x00, 10)) {187 Serial.print("failed! ");188 Serial.println(ModbusRTUClient.lastError());189 } else {190 Serial.println("success");191
192 while (ModbusRTUClient.available()) {193 Serial.print(ModbusRTUClient.read());194 Serial.print(' ');195 }196 Serial.println();197 }198
199 // Alternatively, to read a single Input Register value use:200 // ModbusRTUClient.inputRegisterRead(...)201}
In the Opta™ Server, the main task will be to poll for Modbus RTU requests and return configured values when requested. It requires following the same initial configuration as the Opta™ Client. The main difference between the Client and the Server devices lies in the
setup()
function:1#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library2#include <ArduinoModbus.h>3
4constexpr auto baudrate { 19200 };5
6// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification7// MODBUS over serial line specification and implementation guide V1.028// Paragraph 2.5.1.1 MODBUS Message RTU Framing9// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf10constexpr auto bitduration { 1.f / baudrate };11constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 };12constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };13// constexpr auto preDelayBR { bitduration * 10.0f * 3.5f * 1e6 };14
15const int numCoils = 10;16const int numDiscreteInputs = 10;17const int numHoldingRegisters = 10;18const int numInputRegisters = 10;19
20void setup() {21 Serial.begin(9600);22 while (!Serial);23
24 Serial.println("Modbus RTU Server");25
26 RS485.setDelays(preDelayBR, postDelayBR);27
28 // Start the Modbus RTU client29 if (!ModbusRTUServer.begin(42, baudrate, SERIAL_8E1)) {30 Serial.println("Failed to start Modbus RTU Server!");31
32 while (1);33 }34
35 // Configure coils at address 0x0036 ModbusRTUServer.configureCoils(0x00, numCoils);37
38 // Configure discrete inputs at address 0x0039 ModbusRTUServer.configureDiscreteInputs(0x00, numDiscreteInputs);40
41 // Configure holding registers at address 0x0042 ModbusRTUServer.configureHoldingRegisters(0x00, numHoldingRegisters);43
44 // Configure input registers at address 0x0045 ModbusRTUServer.configureInputRegisters(0x00, numInputRegisters);46}
In the
setup()
function of the sketch dedicated to the Modbus server, the Server address is assigned with an identifier that will be recognized by the Client. Also, the initial values of the Coils
, Discrete Input
, Holding
, and Input
registers are configured. These are the data that the Client will locate and retrieve. The following method is necessary in the Server loop()
function:1ModbusRTUServer.poll();
This is the method that polls for Modbus RTU requests. The complete code for the Server is shown below:
1/**2 Getting Started with Modbus RTU on Opta™3 Name: Opta_Server4 Purpose: Configures Coils, Discrete Inputs, Holding and Input Registers; Polls for Modbus RTU requests and maps the coil values to the Discrete Input values, and Holding Registers to the Input Register values.5
6 @author Arduino7*/8
9#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library10#include <ArduinoModbus.h>11
12constexpr auto baudrate { 19200 };13
14// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification15// MODBUS over serial line specification and implementation guide V1.0216// Paragraph 2.5.1.1 MODBUS Message RTU Framing17// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf18constexpr auto bitduration { 1.f / baudrate };19constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 };20constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };21// constexpr auto preDelayBR { bitduration * 10.0f * 3.5f * 1e6 };22
23const int numCoils = 10;24const int numDiscreteInputs = 10;25const int numHoldingRegisters = 10;26const int numInputRegisters = 10;27
28void setup() {29 Serial.begin(9600);30 while (!Serial);31
32 Serial.println("Modbus RTU Server");33
34 RS485.setDelays(preDelayBR, postDelayBR);35
36 // Start the Modbus RTU client37 if (!ModbusRTUServer.begin(42, baudrate, SERIAL_8E1)) {38 Serial.println("Failed to start Modbus RTU Client!");39 while (1);40 }41
42 // Configure coils at address 0x0043 ModbusRTUServer.configureCoils(0x00, numCoils);44
45 // Configure discrete inputs at address 0x0046 ModbusRTUServer.configureDiscreteInputs(0x00, numDiscreteInputs);47
48 // Configure holding registers at address 0x0049 ModbusRTUServer.configureHoldingRegisters(0x00, numHoldingRegisters);50
51 // Configure input registers at address 0x0052 ModbusRTUServer.configureInputRegisters(0x00, numInputRegisters);53}54
55void loop() {56 // Poll for Modbus RTU requests57 ModbusRTUServer.poll();58
59 // Map the coil values to the discrete input values60 for (int i = 0; i < numCoils; i++) {61 int coilValue = ModbusRTUServer.coilRead(i);62
63 ModbusRTUServer.discreteInputWrite(i, coilValue);64 }65
66 // Map the holding register values to the input register values67 for (int i = 0; i < numHoldingRegisters; i++) {68 long holdingRegisterValue = ModbusRTUServer.holdingRegisterRead(i);69
70 ModbusRTUServer.inputRegisterWrite(i, holdingRegisterValue);71 }72}
Once the Modbus RTU Client and Server code for each Opta™ device has been uploaded, a
Success!
message will be displayed on the Serial Monitor of Opta™ Client after each read-and-write task:This tutorial demonstrates how to use the Arduino ecosystem's
ArduinoRS485
and ArduinoModbus
libraries, as well as the Arduino IDE, to implement the Modbus RTU protocol between two Opta™ devices. These are necessary elements to enable connection with Modbus RTU compliant devices.With the help of these examples, it is easy to understand how to enable Modbus RTU communication between a Server and a Client. For further project developments, it offers a scalable architecture to link additional Modbus Server devices, such as secondary Opta™ or a Modbus RTU-compatible module.
Now that you know how to establish and use Modbus RTU communication with Opta™, you can take a look at Getting started with connectivity on the Opta™ tutorial to discover more about all the connectivity possibilities that Opta™ has to offer.