|
| 1 | +--- |
| 2 | +title: 'Getting Started with Modbus RTU on the Arduino Opta®' |
| 3 | +description: "Learn how to use the Modbus RTU serial protocol on the Arduino Opta®." |
| 4 | +difficulty: intermediate |
| 5 | +tags: |
| 6 | + - Getting started |
| 7 | + - Modbus RTU |
| 8 | + - RS-485 |
| 9 | +author: 'José Bagur and Taddy Chung' |
| 10 | +libraries: |
| 11 | + - name: ArduinoRS485 |
| 12 | + url: https://www.arduino.cc/reference/en/libraries/arduinors485/ |
| 13 | + - name: ArduinoModbus |
| 14 | + url: https://www.arduino.cc/reference/en/libraries/arduinomodbus/ |
| 15 | +software: |
| 16 | + - ide-v1 |
| 17 | + - ide-v2 |
| 18 | +hardware: |
| 19 | + - hardware/05.pro-solutions/solutions-and-kits/opta |
| 20 | +--- |
| 21 | + |
| 22 | +## Overview |
| 23 | + |
| 24 | +Modbus is an open serial protocol derived from the client/server architecture initially developed and published by Modicon (now Schneider Electric) in 1979 for use with programmable logic controllers (PLCs); since 1979, Modbus has become a standard communications protocol in industrial electronic devices. The Opta™, with its industrial hardware and software capabilities and the Arduino ecosystem tools such as the Arduino IDE and its libraries, can implement this communications protocol. In this tutorial, we will learn how to implement this communications protocol over RS-485 between two Opta™ devices. |
| 25 | + |
| 26 | +## Goals |
| 27 | + |
| 28 | +- Learn how to use the Modbus RTU communications protocol over RS-485 between two Opta™ devices |
| 29 | + |
| 30 | +### Required Hardware and Software |
| 31 | + |
| 32 | +### Hardware Requirements |
| 33 | + |
| 34 | +- [Arduino Opta](https://store.arduino.cc/pages/opta) (x2) |
| 35 | +- 12VDC/1A DIN rail power supply (x1) |
| 36 | +- USB-C® cable (x1) |
| 37 | + |
| 38 | +### Software Requirements |
| 39 | + |
| 40 | +- [Arduino IDE 1.8.10+](https://www.arduino.cc/en/software), [Arduino IDE 2.0+](https://www.arduino.cc/en/software), or [Arduino Web Editor](https://create.arduino.cc/editor) |
| 41 | + |
| 42 | +## Modbus 101 |
| 43 | + |
| 44 | +Modbus is a widely used open and royalty-free serial communications protocol in industrial electronic devices, especially in Building Management Systems (BMS) and Industrial Automation Systems (IAS). It has become a _de facto_ standard communications protocol among industrial electronic devices since it was published in 1979 (more than 40 years ago). |
| 45 | + |
| 46 | +***Modbus communications protocol is often used to connect a supervisory device with a Remote Terminal Unit (RTU) in Supervisory Control and Data Acquisition (SCADA) Systems.*** |
| 47 | + |
| 48 | +Using messages with a simple 16-bit structure with a Cyclic-Redundant Checksum (CRC), reliability in communications between electronic devices is ensured with Modbus. |
| 49 | + |
| 50 | +***If you want a more in-depth article explaining the entirety of Modbus communications protocol, look at our [Modbus article](https://docs.arduino.cc/learn/communication/modbus).*** |
| 51 | + |
| 52 | +## Instructions |
| 53 | + |
| 54 | +### Setting Up the Arduino IDE |
| 55 | + |
| 56 | +First, let's ensure we have the latest Arduino IDE version installed on our computers; you can download the latest Arduino IDE version [here](https://www.arduino.cc/en/software). If you are using Opta for the first time, please look at our [getting started tutorial](/tutorials/opta/getting-started) and install the device drivers on your computer. Modbus RTU communications protocol will be implemented using the [`ArduinoModbus`](https://www.arduino.cc/reference/en/libraries/arduinomodbus/) library, be sure to install the latest version of the library. |
| 57 | + |
| 58 | +***`ArduinoModbus` library requires the `ArduinoRS485` library as the Modbus library is dependent on it; remember to install both libraries.*** |
| 59 | + |
| 60 | +### Connecting the Optas Over RS-485 |
| 61 | + |
| 62 | +Now that we have the Arduino IDE configured and the libraries installed, let's connect both Opta™ devices via RS-485, as shown in the image below: |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | +### Code Overview |
| 67 | + |
| 68 | +The objective of the example described below is to configure and use Modbus RTU communications protocol over RS-485 between two Opta devices, one acting as a Client and the other acting as a Server. The Client is responsible for writing and reading `Coil`, `Holding`, `Discrete Input`, and `Input` register values. The Server will poll for Modbus RTU requests and return values accordingly to each request. To help you understand better how the example works, we will briefly explain the essential parts of the code used in this tutorial. |
| 69 | + |
| 70 | +#### Modbus RTU Client |
| 71 | + |
| 72 | +The Opta™ Client will require the following setup: |
| 73 | + |
| 74 | +```arduino |
| 75 | +#include <ArduinoModbus.h> |
| 76 | +#include <ArduinoRS485.h> |
| 77 | +
|
| 78 | +constexpr auto baudrate { 9600 }; |
| 79 | +
|
| 80 | +// Calculate preDelay and postDelay in microseconds as per Modbus RTU specification |
| 81 | +constexpr auto bitduration { 1.f / baudrate }; |
| 82 | +constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 }; |
| 83 | +constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 }; |
| 84 | +
|
| 85 | +#define pause_trigger 15 |
| 86 | +int counter = 0; |
| 87 | +int oper_pause = 0; |
| 88 | +
|
| 89 | +void setup() { |
| 90 | + Serial.begin(9600); |
| 91 | + while (!Serial); |
| 92 | +
|
| 93 | + Serial.println("Modbus RTU Client"); |
| 94 | + RS485.setDelays(preDelayBR, postDelayBR); |
| 95 | +
|
| 96 | + // Start the Modbus RTU Client |
| 97 | + if (!ModbusRTUClient.begin(baudrate, SERIAL_8E1)) { |
| 98 | + Serial.println("Failed to start Modbus RTU Client!"); |
| 99 | + while (1); |
| 100 | + } |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +Given Modbus RTU specification, `preDelay` and `postDelay` must be configured for correct operation. The baud rate can be configured as `4800`, `9600`, and `19200`; in the current example, we are using a baud rate of `9600`, but it can be changed depending on the system requirements. The `SERIAL_8E1` defines the serial port parameters setting (8 data bits, even parity, and one stop bit). |
| 105 | + |
| 106 | +In this example, an Opta device is defined as a Modbus Server from which information will be retrieved. The Server can be a module or a sensor with registers that can be accessed using specified addresses to obtain desired information about what's being measured or monitored. Inside the loop function of the example code of the Client, we will have several tasks in charge of reading and writing specific values to test Modbus RTU communication with the Server. |
| 107 | + |
| 108 | +```arduino |
| 109 | +void loop(){ |
| 110 | + writeCoilValues(); |
| 111 | + readCoilValues(); |
| 112 | + readDiscreteInputValues(); |
| 113 | + writeHoldingRegisterValues(); |
| 114 | + readHoldingRegisterValues(); |
| 115 | + readInputRegisterValues(); |
| 116 | +
|
| 117 | + counter++; |
| 118 | + oper_pause++; |
| 119 | +
|
| 120 | + if (oper_pause >= pause_trigger) { |
| 121 | + oper_pause = 0; |
| 122 | + delay(5000); |
| 123 | + } |
| 124 | + Serial.println(); |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +As we are interested in obtaining specific values, we will define a simple operation pause counter that will be flagged if conditions are met with the defined pause trigger. This will keep the communication busy to keep data flowing and create a headroom for the devices to process if required. |
| 129 | + |
| 130 | +The complete code for the Client is shown below: |
| 131 | + |
| 132 | +```arduino |
| 133 | +#include <ArduinoModbus.h> |
| 134 | +#include <ArduinoRS485.h> |
| 135 | +
|
| 136 | +constexpr auto baudrate { 9600 }; |
| 137 | +
|
| 138 | +// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification |
| 139 | +constexpr auto bitduration { 1.f / baudrate }; |
| 140 | +constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 }; |
| 141 | +constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 }; |
| 142 | +
|
| 143 | +#define pause_trigger 15 |
| 144 | +int counter = 0; |
| 145 | +int oper_pause = 0; |
| 146 | +
|
| 147 | +void setup() { |
| 148 | + Serial.begin(9600); |
| 149 | + while (!Serial); |
| 150 | +
|
| 151 | + Serial.println("Modbus RTU Client"); |
| 152 | + RS485.setDelays(preDelayBR, postDelayBR); |
| 153 | +
|
| 154 | + // Start the Modbus RTU Client |
| 155 | + if (!ModbusRTUClient.begin(baudrate, SERIAL_8E1)) { |
| 156 | + Serial.println("Failed to start Modbus RTU Client!"); |
| 157 | + while (1); |
| 158 | + } |
| 159 | +} |
| 160 | +
|
| 161 | +void loop() { |
| 162 | + writeCoilValues(); |
| 163 | + readCoilValues(); |
| 164 | + readDiscreteInputValues(); |
| 165 | + writeHoldingRegisterValues(); |
| 166 | + readHoldingRegisterValues(); |
| 167 | + readInputRegisterValues(); |
| 168 | +
|
| 169 | + counter++; |
| 170 | + oper_pause++; |
| 171 | +
|
| 172 | + if (oper_pause >= pause_trigger){ |
| 173 | + oper_pause = 0; |
| 174 | + delay(5000); |
| 175 | + } |
| 176 | + Serial.println(); |
| 177 | +} |
| 178 | +
|
| 179 | +void writeCoilValues() { |
| 180 | + // Set the coils to 1 when counter is odd |
| 181 | + byte coilValue = ((counter % 2) == 0) ? 0x00 : 0x01; |
| 182 | + Serial.print("Writing Coil register values... "); |
| 183 | +
|
| 184 | + // Write 10 Coil register values to ID 42, address 0x00 |
| 185 | + ModbusRTUClient.beginTransmission(42, COILS, 0x00, 10); |
| 186 | + for (int i = 0; i < 10; i++) { |
| 187 | + ModbusRTUClient.write(coilValue); |
| 188 | + } |
| 189 | + if (!ModbusRTUClient.endTransmission()) { |
| 190 | + Serial.print("Failed! "); |
| 191 | + Serial.println(ModbusRTUClient.lastError()); |
| 192 | + } else { |
| 193 | + Serial.println("Success!"); |
| 194 | + } |
| 195 | +} |
| 196 | +
|
| 197 | +void readCoilValues() { |
| 198 | + Serial.print("Reading Coil register values... "); |
| 199 | +
|
| 200 | + // Read 10 Coil values from ID 42, address 0x00 |
| 201 | + if (!ModbusRTUClient.requestFrom(42, COILS, 0x00, 10)) { |
| 202 | + Serial.print("Failed! "); |
| 203 | + Serial.println(ModbusRTUClient.lastError()); |
| 204 | + } else { |
| 205 | + Serial.println("Success!"); |
| 206 | +
|
| 207 | + while (ModbusRTUClient.available()) { |
| 208 | + Serial.print(ModbusRTUClient.read()); |
| 209 | + Serial.print(' '); |
| 210 | + } |
| 211 | + Serial.println(); |
| 212 | + } |
| 213 | +} |
| 214 | +
|
| 215 | +void readDiscreteInputValues() { |
| 216 | + Serial.print("Reading Discrete Input register values... "); |
| 217 | +
|
| 218 | + // Read 10 Discrete Input values from ID 42, address 0x00 |
| 219 | + if (!ModbusRTUClient.requestFrom(42, DISCRETE_INPUTS, 0x00, 10)) { |
| 220 | + Serial.print("Failed! "); |
| 221 | + Serial.println(ModbusRTUClient.lastError()); |
| 222 | + } else { |
| 223 | + Serial.println("success"); |
| 224 | +
|
| 225 | + while (ModbusRTUClient.available()) { |
| 226 | + Serial.print(ModbusRTUClient.read()); |
| 227 | + Serial.print(' '); |
| 228 | + } |
| 229 | + Serial.println(); |
| 230 | + } |
| 231 | +} |
| 232 | +
|
| 233 | +void writeHoldingRegisterValues() { |
| 234 | + // Set the Holding register values to counter value |
| 235 | + Serial.print("Writing Holding registers values... "); |
| 236 | +
|
| 237 | + // write 10 coil values to (slave) id 42, address 0x00 |
| 238 | + ModbusRTUClient.beginTransmission(42, HOLDING_REGISTERS, 0x00, 10); |
| 239 | + for (int i = 0; i < 10; i++) { |
| 240 | + ModbusRTUClient.write(counter); |
| 241 | + } |
| 242 | + if (!ModbusRTUClient.endTransmission()) { |
| 243 | + Serial.print("failed! "); |
| 244 | + Serial.println(ModbusRTUClient.lastError()); |
| 245 | + } else { |
| 246 | + Serial.println("Success"); |
| 247 | + } |
| 248 | +} |
| 249 | +
|
| 250 | +void readHoldingRegisterValues() { |
| 251 | + Serial.print("Reading Holding Register values ... "); |
| 252 | +
|
| 253 | + // Read 10 Input Register values from ID 42, address 0x00 |
| 254 | + if (!ModbusRTUClient.requestFrom(42, HOLDING_REGISTERS, 0x00, 10)) { |
| 255 | + Serial.print("Failed! "); |
| 256 | + Serial.println(ModbusRTUClient.lastError()); |
| 257 | + } else { |
| 258 | + Serial.println("Success!"); |
| 259 | +
|
| 260 | + while (ModbusRTUClient.available()) { |
| 261 | + Serial.print(ModbusRTUClient.read()); |
| 262 | + Serial.print(' '); |
| 263 | + } |
| 264 | + Serial.println(); |
| 265 | + } |
| 266 | +} |
| 267 | +
|
| 268 | +void readInputRegisterValues() { |
| 269 | + Serial.print("Reading Input register values ... "); |
| 270 | +
|
| 271 | + // Read 10 discrete input values from ID 42, |
| 272 | + if (!ModbusRTUClient.requestFrom(42, INPUT_REGISTERS, 0x00, 10)) { |
| 273 | + Serial.print("Failed! "); |
| 274 | + Serial.println(ModbusRTUClient.lastError()); |
| 275 | + } else { |
| 276 | + Serial.println("Success!"); |
| 277 | +
|
| 278 | + while (ModbusRTUClient.available()) { |
| 279 | + Serial.print(ModbusRTUClient.read()); |
| 280 | + Serial.print(' '); |
| 281 | + } |
| 282 | + Serial.println(); |
| 283 | + } |
| 284 | +} |
| 285 | +``` |
| 286 | + |
| 287 | +#### Modbus RTU Server |
| 288 | + |
| 289 | +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 is found in the `setup()` function: |
| 290 | + |
| 291 | +```arduino |
| 292 | +// Start the Modbus RTU Server |
| 293 | +void setup() { |
| 294 | + Serial.begin(9600); |
| 295 | + while (!Serial); |
| 296 | +
|
| 297 | + Serial.println("Modbus RTU Server"); |
| 298 | + RS485.setDelays(preDelayBR, postDelayBR); |
| 299 | +
|
| 300 | + if (!ModbusRTUServer.begin(42, baudrate, SERIAL_8E1)) { |
| 301 | + Serial.println("Failed to start Modbus RTU Client!"); |
| 302 | + while (1); |
| 303 | + } |
| 304 | +
|
| 305 | + // Configure Coils registers at address 0x00 |
| 306 | + ModbusRTUServer.configureCoils(0x00, numCoils); |
| 307 | +
|
| 308 | + // Configure Discrete Inputs registers at address 0x00 |
| 309 | + ModbusRTUServer.configureDiscreteInputs(0x00, numDiscreteInputs); |
| 310 | +
|
| 311 | + // Configure Holding registers at address 0x00 |
| 312 | + ModbusRTUServer.configureHoldingRegisters(0x00, numHoldingRegisters); |
| 313 | +
|
| 314 | + // Configure Input registers at address 0x00 |
| 315 | + ModbusRTUServer.configureInputRegisters(0x00, numInputRegisters); |
| 316 | +} |
| 317 | +``` |
| 318 | + |
| 319 | +In the Server `setup()` function, we assign the Server address; the given Server address will be an identifier to be recognized by the Client. Also, we will configure the initial values of the `Coils`, `Discrete Input`, `Holding`, and `Input` registers. These will be the data that the Client will locate and retrieve. In the Server `loop()` function, the following line will be necessary: |
| 320 | + |
| 321 | +```arduino |
| 322 | +ModbusRTUServer.poll(); |
| 323 | +``` |
| 324 | + |
| 325 | +This is the line that will poll for Modbus RTU requests. The complete code for the Server is shown below: |
| 326 | + |
| 327 | +```arduino |
| 328 | +#include <ArduinoRS485.h> |
| 329 | +#include <ArduinoModbus.h> |
| 330 | +
|
| 331 | +constexpr auto baudrate { 9600 }; |
| 332 | +
|
| 333 | +// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification |
| 334 | +constexpr auto bitduration { 1.f / baudrate }; |
| 335 | +constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 }; |
| 336 | +constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 }; |
| 337 | +
|
| 338 | +const int numCoils = 10; |
| 339 | +const int numDiscreteInputs = 10; |
| 340 | +const int numHoldingRegisters = 10; |
| 341 | +const int numInputRegisters = 10; |
| 342 | +
|
| 343 | +void setup() { |
| 344 | + Serial.begin(9600); |
| 345 | + while (!Serial); |
| 346 | +
|
| 347 | + Serial.println("Modbus RTU Server"); |
| 348 | + RS485.setDelays(preDelayBR, postDelayBR); |
| 349 | +
|
| 350 | + // Start the Modbus RTU client |
| 351 | + if (!ModbusRTUServer.begin(42, baudrate, SERIAL_8E1)) { |
| 352 | + Serial.println("Failed to start Modbus RTU Client!"); |
| 353 | + while (1); |
| 354 | + } |
| 355 | +
|
| 356 | + // Configure Coils registers at address 0x00 |
| 357 | + ModbusRTUServer.configureCoils(0x00, numCoils); |
| 358 | +
|
| 359 | + // Configure Discrete Inputs registers at address 0x00 |
| 360 | + ModbusRTUServer.configureDiscreteInputs(0x00, numDiscreteInputs); |
| 361 | +
|
| 362 | + // Configure Holding registers at address 0x00 |
| 363 | + ModbusRTUServer.configureHoldingRegisters(0x00, numHoldingRegisters); |
| 364 | +
|
| 365 | + // Configure Input registers at address 0x00 |
| 366 | + ModbusRTUServer.configureInputRegisters(0x00, numInputRegisters); |
| 367 | +} |
| 368 | +
|
| 369 | +void loop() { |
| 370 | + // Poll for Modbus RTU requests |
| 371 | + ModbusRTUServer.poll(); |
| 372 | +
|
| 373 | + // Map the Coil registers values to the discrete input values |
| 374 | + for (int i = 0; i < numCoils; i++) { |
| 375 | + int coilValue = ModbusRTUServer.coilRead(i); |
| 376 | + ModbusRTUServer.discreteInputWrite(i, coilValue); |
| 377 | + } |
| 378 | +
|
| 379 | + // Map the Holding registers values to the Input registers values |
| 380 | + for (int i = 0; i < numHoldingRegisters; i++) { |
| 381 | + long holdingRegisterValue = ModbusRTUServer.holdingRegisterRead(i); |
| 382 | + ModbusRTUServer.inputRegisterWrite(i, holdingRegisterValue); |
| 383 | + } |
| 384 | +} |
| 385 | +``` |
| 386 | + |
| 387 | +### Testing the Modbus RTU Client and Server |
| 388 | + |
| 389 | +Once you have uploaded the Modbus RTU Client and Server code for each Opta™ device, we can open the Serial Monitor on the Client side to debug the communication status between the devices. If everything is working correctly, you will be able to see `Success!` messages after each read-and-write tasks as shown in the image below: |
| 390 | + |
| 391 | + |
| 392 | + |
| 393 | +## Conclusion |
| 394 | + |
| 395 | +In this tutorial, we established a Modbus RTU connection between two Opta devices using the Arduino ecosystem tools, such as the Arduino IDE and Arduino libraries. The `ArduinoRS485` and `ArduinoModbus` libraries are essential components that enable communication with compatible Modbus RTU devices. With the demonstrative example described in this tutorial, we have established communication between a Modbus RTU Server and a Client; we can now configure and set a secondary Arduino Opta® or use a Modbus RTU-compatible module for your project developments. |
0 commit comments