Getting Started with Modbus RTU on Opta™

Learn how to use the Modbus RTU serial protocol on Opta™.

Overview

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.

Goals

  • Learn how to establish RS-485 interface connection between two Opta™ devices
  • Learn how to use the Modbus RTU communication protocol between two Opta™ devices

Required Hardware and Software

Hardware Requirements

  • Opta™ PLC with RS-485 support: Opta™ RS485, or Opta™ WiFi (x2)
  • 12 VDC / 1 A DIN rail power supply (x1)
  • USB-C® cable (x1)
  • Wire with either specification for RS-485 connection (x3)
  • STP/UTP 24-18AWG (Unterminated) 100-130 Ω rated
  • STP/UTP 22-16AWG (Terminated) 100-130 Ω rated

Software Requirements

Modbus Protocol

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™.

Instructions

Setting Up the Arduino IDE

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 the User Manual. 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.

Connecting the Opta™ Over RS-485

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.

Connecting two Opta™ devices via RS-485

Code Overview

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.

Modbus RTU Client

The Opta™ Client will require the following setup:

1#include <ArduinoModbus.h>
2#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
3
4constexpr auto baudrate { 19200 };
5
6// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
7// MODBUS over serial line specification and implementation guide V1.02
8// Paragraph 2.5.1.1 MODBUS Message RTU Framing
9// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
10constexpr 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 client
26 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_Client
4 Purpose: Writes Coil and Holding Register values; Reads Coil, Discrete Input, Holding Registers, and Input Register values.
5
6 @author Arduino
7*/
8
9#include <ArduinoModbus.h>
10#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
11
12constexpr auto baudrate { 19200 };
13
14// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
15// MODBUS over serial line specification and implementation guide V1.02
16// Paragraph 2.5.1.1 MODBUS Message RTU Framing
17// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
18constexpr 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 client
34 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 odd
64 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 0x00
69 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 0x00
91 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 0x00
115 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 counter
137 Serial.print("Writing Holding Registers values ... ");
138
139 // Write 10 coil values to (server) id 42, address 0x00
140 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 0x00
162 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}

Modbus RTU Server

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 library
2#include <ArduinoModbus.h>
3
4constexpr auto baudrate { 19200 };
5
6// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
7// MODBUS over serial line specification and implementation guide V1.02
8// Paragraph 2.5.1.1 MODBUS Message RTU Framing
9// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
10constexpr 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 client
29 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 0x00
36 ModbusRTUServer.configureCoils(0x00, numCoils);
37
38 // Configure discrete inputs at address 0x00
39 ModbusRTUServer.configureDiscreteInputs(0x00, numDiscreteInputs);
40
41 // Configure holding registers at address 0x00
42 ModbusRTUServer.configureHoldingRegisters(0x00, numHoldingRegisters);
43
44 // Configure input registers at address 0x00
45 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_Server
4 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 Arduino
7*/
8
9#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
10#include <ArduinoModbus.h>
11
12constexpr auto baudrate { 19200 };
13
14// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
15// MODBUS over serial line specification and implementation guide V1.02
16// Paragraph 2.5.1.1 MODBUS Message RTU Framing
17// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
18constexpr 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 client
37 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 0x00
43 ModbusRTUServer.configureCoils(0x00, numCoils);
44
45 // Configure discrete inputs at address 0x00
46 ModbusRTUServer.configureDiscreteInputs(0x00, numDiscreteInputs);
47
48 // Configure holding registers at address 0x00
49 ModbusRTUServer.configureHoldingRegisters(0x00, numHoldingRegisters);
50
51 // Configure input registers at address 0x00
52 ModbusRTUServer.configureInputRegisters(0x00, numInputRegisters);
53}
54
55void loop() {
56 // Poll for Modbus RTU requests
57 ModbusRTUServer.poll();
58
59 // Map the coil values to the discrete input values
60 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 values
67 for (int i = 0; i < numHoldingRegisters; i++) {
68 long holdingRegisterValue = ModbusRTUServer.holdingRegisterRead(i);
69
70 ModbusRTUServer.inputRegisterWrite(i, holdingRegisterValue);
71 }
72}

Testing the Modbus RTU Client and Server

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:

Modbus RTU Client and Server communication status

Conclusion

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.

Next Steps

Now that you know how to establish and use Modbus RTU communication with Opta™, you can take a look at Opta User Manual to discover more about all the connectivity possibilities that Opta™ has to offer.

Suggest changes

The content on docs.arduino.cc is facilitated through a public GitHub repository. If you see anything wrong, you can edit this page here.

License

The Arduino documentation is licensed under the Creative Commons Attribution-Share Alike 4.0 license.