Como converter Arduino em um dispositivo Modbus

How to Convert Arduino to a Modbus Device

Modbus is a widely used communication protocol and an open source industrial networking standard. Developed in 1979 by Modicon (now Schneider Electric), Modbus has become a standard in the automation and industrial control systems (ICS) industries.

The communication protocol is known for its simplicity and easy-to-follow troubleshooting feature. It uses a master-slave or client-server communication model, where a master or client device initiates requests to read or write data to slave devices or servers.

The standard protocol is hardware-independent, meaning it can be implemented across multiple devices and platforms, including different types of microcontrollers (including Arduino) and operating systems. Modbus supports several data types, including discrete inputs, coils (digital outputs), input registers, and hold registers (analog data). It handles both analog and digital data.

Modbus has been used reliably in industrial environments for decades for automation, energy management, building automation, SCADA systems, agricultural monitoring, pump and motor control systems, and more. Remote terminal units (RTUs) often use it to transmit data to a central control center. Modbus can also be easily adapted to specific application requirements when using other industrial protocols or communication standards.

An advantage of using Modbus on Arduino is its ability to handle complex data communications over long distances. For example, Arduino can communicate with software/applications, human-machine interfaces, peripheral devices, or microcontrollers. Modbus is suitable for small to medium-sized automation applications and ideal for cost-focused projects.

Modbus protocols have three types: Modbus ASCII, Modbus RTU and Modbus TCP. In this article, we will turn Arduino into a Modbus device using Modbus ASCII/RTU and Modbus TCP. To do this, we will configure communication between two Arduino boards on Modbus.

What is Modbus?
Modbus is a popular open, low-speed serial communication protocol commonly used in the automation industry and maintained by the Modbus organization. Serves as a link between PLCs and intelligent automation devices.

With the help of the serial protocol, which is based on a master-slave configuration, a Modbus client can establish contact with up to 247 Modbus servers. Standard RS-485 or RS-232 ports facilitate physical interfacing. The bus includes a twisted pair of cables, insulated or uninsulated, with both ends terminated in 150 ohms. The cable can be quickly connected to devices using RJ-45 connectors, screw terminals or 9-pin D-shell connectors.

The Modbus client is typically a supervisory computer within a human-machine interface (HMI) or a supervisory control and data acquisition (SCADA) system. Remote terminal units (RTU) — such as sensor modules, programmable logic controllers (PLC), or programmable automation controllers (PAC) — are the Modbus servers. To learn more about the Modbus protocol, check out this link .

Why Modbus?
Modbus is a versatile and reliable protocol. It is currently the most used device for industrial automation. It is the communication standard for connecting and controlling devices such as PLCs, sensors, actuators and HMIs. It can also collect data from energy meters, monitor energy consumption and control HVAC (heating, ventilation and air conditioning) systems. It can also integrate multiple building subsystems, including lighting, HVAC, security and access control.

Modus is ideal for remote monitoring and telemetry applications, including monitoring environmental conditions, water levels or equipment status in remote locations. Because the protocol can handle both analog and digital data efficiently, it is also ideal for acquiring data from sensors and instruments in laboratory, research, and testing settings.

The protocol is often used as a communication protocol in SCADA systems to collect data from remote field devices.

Modbus is versatile and reliable for virtually any type of automation application, whether small or medium-sized. Allows half and full-duplex communication over long distances. Modbus RTU can extend up to 1,200 meters (on RS-485), while Modbus TCP has no range limit. Modbus also allows two-way communication between devices and connects to multiple devices on a network.

When to use Modbus with Arduino?
If the Arduino needs to interface with a PLC, HMI or peripheral device, Modbus is the obvious choice. It allows Arduino to communicate with any microcontroller or device over an extended range.

Home or office automation applications can benefit from the protocol, as the protocol allows the master and slave to communicate in both directions.

Typical communication protocols like UART, I2C and SPI available on microcontrollers (including Arduino) have their own limitations. When a network becomes very complex and multiple devices need to communicate bi-directionally, Modbus is the ideal communication protocol to handle this complexity without making the circuitry or code complicated.

For sensor networks on Arduino, Modbus again guarantees reliable, long-range, centralized and well-managed communication.

How does Modbus work?
A Modbus network can have one or more master devices and several slave devices. The master device is called a Modbus client and the slaves are Modbus servers.

In serial versions of Modbus, a master device can communicate with up to 247 devices. The Ethernet version allows a master to communicate with hundreds of devices on a single Ethernet network segment.

Communication is based on a request-response method. The master sends a request to one or more slave devices, specifying the type of operation (read or write) and the address of the data it wants to access. The types of data that can be read or written include discrete inputs (binary data), coils (binary outputs), input registers (analog data), and hold registers (analog data).

Each data point on a Modbus network has a unique address. The master specifies the address of the data it wants to access in the request when communicating with a slave. For example, a Modbus device may have discrete inputs starting at address 1, coils starting at address 10001, input registers starting at address 30001, and hold registers starting at address 40001.

For a Modbus request, the type of operation is specified by a function code. Common function codes include F01 (Read Discrete Inputs), F02 (Read Coils), F03 (Read Analog Input Registers), F04 (Read Analog Output Hold Registers), F05 (Write Single Coil), F06 (Write Register single retention register), F15 (Write Multiple Coils) and F16 (Write Multiple Retention Registers). When a slave device receives a valid request, it processes the request and sends a response back to the master. The response contains the requested data or a confirmation of a successful write operation. The master typically polls slave devices at regular intervals to collect data or issue commands.

There are error checking mechanisms like CRC or LRC to ensure data integrity during transmission. The response provides error codes to indicate problems such as invalid requests or communication problems. To learn more about how Modbus works, check out this link .

Modbus RTU/ASCII and Modbus TCP
There are three popular types of Modbus: Modbus RTU, Modbus ASCII and Modbus TCP. Although Modbus RTU and Modbus ASCII are different protocol versions, they are both used in serial communication environments such as RS-232, RS-422 and RS-485.

Modbus RTU is most commonly associated with RS-485 due to its binary nature. Many devices support Modbus ASCII and Modbus RTU. Modbus ASCII and Modbus RTU can be implemented with the same hardware on Arduino. However, it is important to distinguish between them.

  • Modbus RTU represents data in binary format as raw 8-bit binary values, while for Modbus ASCII, data is transmitted as ASCII characters, where two ASCII characters represent each byte.
  • Modbus RTU frames consist of binary data with start and stop bits for synchronization, while Modbus ASCII frames begin with a colon character (“:”) and end with a carriage return and line feed sequence (“\ r\n”).
  • Modbus RTU uses cyclic redundancy check (CRC) for data frame error detection, while Modbus ASCII uses LRC (longitudinal redundancy check) for error detection. Modbus RTU is commonly used in industrial applications, especially in environments that require high-speed communication.
  • Modbus ASCII is preferred where readable communication or troubleshooting is essential. A microcontroller must use Modbus RTU when communicating with sensor modules. But when communicating with HMI devices, Modbus ASCII is a better solution.
  • Modbus TCP operates on Ethernet networks using the TCP/IP protocol suite. Modbus RTU/ASCII are best suited for point-to-point or multidrop serial networks. Modbus TCP is designed for Ethernet-based local area networks (LANs) and can communicate over longer distances using standard networking equipment.
  • Modbus TCP uses standard Ethernet frames, which differ from Modbus RTU/ASCII data frames. It relies on TCP/IP for reliable data transport. Modbus RTU/ASCII requires point-to-point or multidrop network topologies. Modbus TCP is very flexible and allows network topologies such as star, bus or ring configurations.
  • Modbus RTU/ASCII supports single transactions only. When a master sends a request via Modbus RTU/ASCII, it must wait for a Modbus response or timeout before sending another request. However, Modbus TCP supports multiple transactions, so a master can send multiple requests on Modbus TCP and does not need to wait for a response or timeout from Modbus. The response to a request is identified by a transaction ID, where the transaction ID remains the same for the request and the response.
  • Although Modbus RTU/ASCII is commonly used in industrial automation and control systems where serial communication is predominant, Modbus TCP is more suitable for modern industrial networks and building automation.

Turning Arduino into a Modbus device
If the Arduino needs to communicate with a PLC or HMI device, you must first turn the Arduino into a Modbus device. The Arduino can be easily configured to participate in Modbus RTU/ASCII/TCP networks. To do this, hardware and software components are needed.

To implement Modbus RTU/ASCII, the Arduino must communicate via the RS-485, RS-422 or RS-232 ports. RS-485 is the most popular port to use. This requires interfacing the Arduino with an RS-485 to TTL converter. The Arduino Modbus library is also required.

To implement Modbus TCP, the Arduino must communicate over the TCP/IP stack. This requires an Ethernet shield over Arduino. The Arduino Modbus library is required, which can be downloaded from this Github link .

RS485 to TTL Converter
To convert the Arduino to a Modbus RTU/ASCII network as a master or slave, an RS485 transceiver or an RS485 to TTL converter is required. One of these transceivers based on the MAX485 chip is in the image below.

On a Modbus, a binary '0' is represented by a voltage level of +2 to +6V. Binary '1' is represented by a voltage level of -2 to -6V. Arduino operates at TTL logic levels, so a logic converter is required as shown above.

An RS485 transceiver has the following pin diagram.

The pin configuration of the RS-485 transceiver is described in the table below.

To use the module as a transmitter, connect the RE and DE pins to 5V and connect the DI pin to the TX pin of the Arduino. Data is sent from the Arduino's TX pin to the module's DI pin, which is then sent through the AB.

To use the module as a receiver, connect the RE and DE pins to ground and connect the RO pin to the Arduino's RX pin. The data received by the AB is sent to the RO pin, which is connected to the Arduino's RX pin — thus, the data is read by the Arduino.

Arduino Modbus Library
ArduinoModbus is a free and open-source library for Arduino boards to communicate with Modbus devices via serial (RS485) and network (TCP) connections. It is based on the libmodbus library, but has been modified to use the Arduino Serial/RS485 and Client APIs. It is an easy to use API for sending and receiving Modbus messages, handling Modbus frames and error checking. It also supports all Modbus function codes.

The library is compatible with the megaavr, samd, mbed_nano, mbed_portenta and mbed_opta architectures and can be used on Arduino Uno, Arduino Nano, Arduino Mega, Arduino Due, Arduino MKR Zero, Arduino MKR 1000, Arduino MKR WiFi 1010, Arduino MKR WAN 1310 , Arduino MKR FOX 1200, Arduino MKR GSM 1400, Arduino MKR NB 1500, Arduino Portenta H7, Arduino Nano 33 IoT and Arduino Nano RP2040 Connect.

It can also be used on other Arduino compatible boards such as Genuino M100 and Adafruit Feather M0. The library can be installed through the Arduino IDE library manager or downloaded from this Github link.

The library provides the following method to configure Arduino as a Modbus RTU client.

modbusRTUClient.begin:

The function starts the Modbus RTU client with the specified parameters. The function returns '1' on success and '0' on failure. It has the syntax below.

ModbusRTUClient.begin(baud rate);
ModbusRTUClient.begin(baud rate, configuration);

The library provides the following methods to configure Arduino as a Modbus TCP client.

ModbusTCPClient: The method creates a Modbus TCP client using the provided client for the transport. The method has the following syntax:

ModbusTCPCliente(client);

modbusTCPClient.begin: The method starts the Modbus TCP client with the specified parameters. It has this syntax:

modbusTCPClient.begin(ip,port);

modbusTCPClient.connected: The method returns the connection status. It has this syntax:

modbusTCPClient.connected;

modbusTCPClient.stop: The method disconnects the server. It has this syntax:

modbusTCPClient.stop;

The library provides the following method to configure Arduino as a Modbus RTU server.

modbusRTUServer.begin: The method starts the Modbus RTU server with the specified parameters. The function returns '1' for success or '0' for failure. It has the following syntax:

ModbusRTUServer.begin(id, baud rate);
ModbusRTUServer.begin(id, baud rate, configuration);

The library provides the following methods to configure Arduino as a Modbus TCP server.

ModbusTCPServer: The method creates a Modbus TCP server. It has the following syntax.

ModbusTCPServer;

modbusTCPServer.begin: The method starts the Modbus server. May require the slave ID to be passed as an argument. If no ID is provided, the default is 0xFF (TCP). It has this syntax:

modbusTCPserver.begin;
modbusTCPserver.begin(id);

modbusTCPServer.accept: The method accepts a client connection. It has this syntax:

modbusTCPserver.accept(client);

The library provides the following methods to operate as a Modbus client.

client.coilRead : The method performs a “Read Coils” operation for the specified address for a single coil. Takes slave ID and/or address as arguments. It has this syntax:

int coilRead (internal address);
int coilRead(int id, int address);

client.discreteInputRead : The method performs a “Read Discrete Inputs” operation for the specified address for a single discrete input. Takes slave ID and/or address as arguments. It has this syntax:

int discreteInputRead(int address);
int discreteInputRead(int id, internal address);

client.holdingRegisterRead: The method performs a “Read held records” operation for a single held record. Takes slave ID and/or address as arguments. It has this syntax:

long holdingRegisterRead(int address);
long holdingRegisterRead(int id, int address);

client.inputRegisterRead: The method performs a “Read input records” operation for a single input record. Takes slave ID and/or address as arguments. It has this syntax:

inputRegisterRead long (int address);
inputRegisterRead long (int id, internal address);

client.coilWrite: The method performs a “Write Single Coil” operation to the specified address and value. The target slave id, operation address and coil value are required to write as arguments. It has this syntax:

int coilWrite(int address, uint8_t value);
int coilWrite(int id, int address, uint8_t value);

client.holdingRegisterWrite: The method performs a “Write Single Holding Register” operation to the specified address and value. It takes the target slave ID, operation address, and hold register value to write as arguments. It has this syntax:

int holdingRegisterWrite(int address, uint16_t value);
int holdingRegisterWrite(int id, int address, uint16_t value);

client.registerMaskWrite: The method performs a “Mask Write Registers” operation for the specified address, AND mask and OR mask. It takes the destination slave ID, the operation address, the AND mask to use in the operation, and the OR mask to use in the operation as arguments. It has this syntax:

int RegisterMaskWrite(int address, uint16_t eMask, uint16_t ouMask);
int RegisterMaskWrite(int id, int address, uint16_t andMask, uint16_t orMask);

client.beginTransmission: The method starts the multi-reel recording or record retention process. It takes the ID of the target slave, the type of recording to be performed (COILS or HOLDING_REGISTERS), the address of the operation and the number of values ​​to be written as arguments. It has this syntax:

int startTransmission(int type, int address, int nb);
int startTransmission(int id, int type, int address, int nb);

client.write: The method sets the values ​​of a write operation initiated by beginTransmission. It takes value to write as an argument. The method returns '1' on success or '0' on failure. It has this syntax:

int write(unsigned int value);

client.endTransmission: The method ends the multi-reel recording or record retention process. It returns '1' on success or '0' on failure. It has this syntax:

int endTransmission;

client.requestFrom : The method reads multiple coils, discrete inputs, holding registers, or input register values. It uses available and read to process the read values. It takes the ID of the target slave, the type of read to be performed (COILS, DISCRETE_INPUTS, HOLDING_REGISTERS or INPUT_REGISTERS), the starting address to be used for operation and the number of values ​​to be read as arguments. It has this syntax:

int requestFrom(int type, int address, int nb);
int requestFrom(int id, int type, int address, int nb);

client.available: The method queries the number of values ​​available for reading after calling requestFrom. It has the following syntax.

int available;

client.read : The method reads a value after calling requestFrom . It returns -1 on failure and a value on success. It has this syntax:

long read;

client.lastError: The method reads the reason for the last error as a string. It has this syntax:

const char* lastError ;

client.end: The method stops the client and performs cleanup. It has this syntax:

empty end;

The library provides the following methods to operate as a Modbus server.

modbusServer.configureCoils: The method configures the server coils. The starting address of the coils and the number of coils to configure as arguments are required. It has the following syntax:

int configureCoils(int startAddress, int nb);

modbusServer.configureDiscreteInputs: The method configures the server's discrete inputs. It returns '0' on success or '1' on failure. The beginning of the address of the discrete inputs and the number of discrete inputs to configure as arguments are required. It has this syntax:

int configureDiscreteInputs(int startAddress, int nb);

modbusServer.configureHoldingRegisters: The method configures the server's holding records. It takes the beginning of the holding registers address and the number of holding registers to configure as arguments. It has this syntax:

int configureHoldingRegisters(int startAddress, int nb);

modbusServer.configureInputRegisters: The method configures the server input registers. It takes the beginning of the input registers address and the number of input registers to configure as arguments. It has this syntax:

int configureInputRegisters(int startAddress, int nb);

modbusServer.coilRead: The method performs a “Read Coils” operation for the specified address for a single coil. You need the target slave ID and/or address to use in the operation as arguments. It has this syntax:

int coilRead (internal address);
int coilRead(int id, int address);

modbusServer.discreteInputRead : The method performs a “Read Discrete Inputs” operation for the specified address for a single discrete input. You need the target slave ID and/or address to use in the operation as arguments. It has this syntax:

int discreteInputRead(int address);
int discreteInputRead(int id, internal address);

modbusServer.holdingRegisterRead: The method performs a “Read held registers” operation for a single held register. It takes the target slave ID and the start of the address to use in the operation as arguments. It has this syntax:

long holdingRegisterRead(int address);
long holdingRegisterRead(int id, int address);

modbusServer.inputRegisterRead: The method performs a “Read Input Registers” operation for a single input register. The target slave ID and address to use in the operation are required as arguments. It has this syntax:

inputRegisterRead long (int address);
inputRegisterRead long (int id, internal address);

modbusServer.coilWrite: The method performs a “Write Single Coil” operation for the specified address and value. The ID of the target slave, the address to be used for operation and the coil value to write as arguments are required. It has this syntax:

int coilWrite(int address, uint8_t value);
int coilWrite(int id, int address, uint8_t value);

modbusServer.holdingRegisterWrite: The method performs a “Write Single Holding Register” operation to the specified address and value. It takes the ID of the target slave, the address to be used for operation, and the value of the holding register to write as arguments. It has this syntax:

int holdingRegisterWrite(int address, uint16_t value);
int holdingRegisterWrite(int id, int address, uint16_t value);

modbusServer.registerMaskWrite: The method performs a “Mask Write Registers” operation for the specified address, AND mask and OR mask. It takes the ID of the target slave, the address to be used for operation, the AND mask to be used for operation and the OR AND mask to be used for operation as arguments. It has this syntax:

int RegisterMaskWrite(int address, uint16_t eMask, uint16_t ouMask);
int registerMaskWrite(int id, int address, uint16_t andMask, uint16_t orMask);

modbusServer.discreteInputWrite: The method writes the server's discrete input value to the specified address and value. You need an address to use in the operation and a discrete value to write as arguments. It has this syntax:

int discreteInputWrite(int address, uint8_t value);

modbusServer.writeDiscreteInputs: The method writes values ​​to the server's discrete inputs to the specified address and values. It requires the address to be used for operation, the array of discrete input values ​​to be written, and the number of discrete inputs to be written as arguments. It has this syntax:

int writeDiscreteInputs(int address, uint8_t values, int nb);

modbusServer.inputRegisterWrite: The method writes the server input register value to the specified address and value. The address to be used for operation and the input register value to write as arguments are required. It has this syntax:

int inputRegisterWrite(int address, uint16_t value);

modbusServer.writeInputRegisters: The method writes values ​​to the server input registers for the specified address and values. It requires the address to be used for operation, the array of input register values ​​to be written, and the number of input registers to be written as arguments. It has this syntax:

int writeInputRegisters(int address, uint16_t values, int nb);

modbusServer.poll: The method polls requests. It has the following syntax:

virtual void poll = 0;

modbusServer.end: The method for the server. It has the following syntax:

empty end;

Arduino Modbus RTU Example
To set up communication between two Arduino boards using Modbus RTU and the Arduino Modbus library, first connect the two Arduino boards using an RS485 transceiver. You will need a pair of RS485 transceivers – one to configure an Arduino as a Modbus client and another to configure another Arduino as a Modbus server.

  • To configure the first Arduino board as a Modbus transmitter, connect the module's RE and DE pins to 5V and connect the module's DI pin to the Arduino's TX pin.
  • To configure the other Arduino board as a Modbus receiver, connect the module's RE and DE pins to ground and connect the module's RO pin to the Arduino's RX pin.
  • Then connect the 5V and ground pins of both modules with the 5V and GND output of the respective Arduino(s). Then connect the data B and data A pins of one module to the data B and data A pins of the other module.

The circuit connections are shown below.

Upload the following sketch to the Arduino configured as a Modbus transmitter.

#include “ArduinoModbus.h”
ArduinoModbus Master;
empty configuration {
Serial.begin(9600);
master.begin;
}
empty loop {
// Sends a Modbus read request to slave address 1 and reads 10 registers starting at register address 0
internal response = master.read(1, 0, 10);
//Check the response
if (response == 0) {
// The request was successful, gets the values ​​read from the response buffer
values ​​uint16_t(10);
master.getResponseBuffer(values, 10);
// Do something with the read values
Serial.println(“Read values:”);
for (int i = 0; i < 10; i++) {
Serial.print(values(i));
Serial.print(" ");
}
Serial.println;
}
other {
//The request failed
Serial.println(“Read request failed”);
}
//Wait 1 second before sending the next request
delay(1000);
}

Upload the following sketch to the Arduino configured as a Modbus receiver.

#include “ArduinoModbus.h”
ArduinoModbus Slave(1);
empty configuration {
//Configures serial communication
Serial.begin(9600);
// Initialize the Modbus slave
slave.begin;
}
empty loop {
// Checks incoming Modbus requests
int request = slave.available;
//If there is a request, process it
if (request > 0) {
// Gets the type of request
byte type = slave.getRequestType(request);
// If the request is a read request, send the read values ​​to the master
if (type == MODBUS_READ) {
// Get the record address and number of records to read
wordaddress = slave.getRequestAddress(request);
byte count = slave.getRequestCount(request);
//Read the Arduino memory registers
uint16_t values ​​(count);
for (int i = 0; i < count; i++) {
values ​​(i) = slave.readRegister (address + i);
}
// Sends the read values ​​to the master
slave.sendResponse(request, values, count);
}
}
}

You can observe the values ​​transmitted between two Arduino(s) in the Arduino IDE's Serial Monitor by connecting either Arduino to a computer.

Arduino Modbus TCP Example
To set up communication between the two Arduino boards using Modbus TCP and the Arduino Modbus library, install Ethernet shields on both boards and connect both Arduinos to the same network via Ethernet cables or a WiFi connection.

Upload the following sketch to the Arduino configured as a Modbus transmitter.

#include “ArduinoModbus.h”
ArduinoModbus Master;
empty configuration {
//Configures serial communication
Serial.begin(9600);
// Connect to the slave Arduino board to your network's IP address
master.connect(“IP_ADDRESS”);
}
empty loop {
// Sends a Modbus read request to slave address 1 and reads 10 registers starting at register address 0
internal response = master.read(1, 0, 10);
//Check the response
if (response == 0) {
//The request was successful
// Gets the values ​​read from the response buffer
values ​​uint16_t(10);
master.getResponseBuffer(values, 10);
// Do something with the read values
Serial.println(“Read values:”);
for (int i = 0; i < 10; i++) {
Serial.print(values(i));
Serial.print(" ");
}
Serial.println;
} other {
//The request failed
Serial.println(“Read request failed”);
}
//Wait 1 second before sending the next request
delay(1000);
}

Upload the following sketch to the Arduino configured as a Modbus receiver.

#include “ArduinoModbus.h”
ArduinoModbus Slave(1);

empty configuration {
//Configures serial communication
Serial.begin(9600);
//Start the Modbus server
slave.beginServer;
}

empty loop {
// Checks incoming Modbus requests
int request = slave.available;
//If there is a request, process it
if (request > 0) {
// Gets the type of request
byte type = slave.getRequestType(request);
// If the request is a read request, send the read values ​​to the master
if (type == MODBUS_READ) {
// Get the record address and number of records to read
wordaddress = slave.getRequestAddress(request);
byte count = slave.getRequestCount(request);
//Read the Arduino memory registers
uint16_t values ​​(count);
for (int i = 0; i < count; i++) {
values ​​(i) = slave.readRegister (address + i);
}
// Sends the read values ​​to the master
slave.sendResponse(request, values, count);
}
}
}

You can observe the values ​​transmitted between the two Arduino boards in the Arduino IDE's Serial Monitor by connecting either board to a computer.

Related Content

Back to blog

Leave a comment

Please note, comments need to be approved before they are published.