Learn everything about the industry's favorite Modbus serial communication protocol and use Arduino to implement your first Modbus RTU project.
In our RS-485 tutorial, we learned about everything that made the interface the de facto standard for industrial automation and communication everywhere around the world. It is the reliability and simplicity of the RS-485 physical layer that made it such a favorite interface. However, since the RS-485 standard did not come with a data format or data transport protocols, something was needed to fill that gap and bring out the full potential of the interface. That thing was Modbus. Modbus is an open data protocol originally developed by Modicon (now Schneider) in 1979 for PLCs (Programmable Logic Controllers. PLCs are electronic devices used in industries to control and monitor various equipment and processes. Modbus became popular due to its royalty-free nature, simplicity, and ease of maintenance. Today, Modbus and its improved versions are used by all types of industrial equipment and controllers. So it is really important to learn Modbus if you want to develop electronic products for industrial applications. In this tutorial, we are going to learn everything about Modbus, and show how you can implement Modbus protocol in any Arduino-supported boards.
Learn about the industry-favorite RS-485 (EIA-485) wired communication interface standard and learn how to interface the MAX485 module with Arduino.
CIRCUITSTATE can design and develop complete electronic products incorporating communication interfaces and protocols like RS-485, RS-232, 4-20mA, HART, Ethernet, MODBUS, etc. on all hardware platforms and frameworks, with robust performance. Contact us today to share your requirements.
Modbus is a request/response-based messaging protocol. While the messaging sits at the application layer of the OSI model, the physical interface being used can be Serial (UART, RS-485, RS-232, etc.), TCP (Ethernet), and others. Let’s summarise the most important features and aspects of Modbus in a list, and later we will explain them in detail.
Modbus assumes a bus topology for its communication network. For physical interfaces like RS-485, it is practically true, since they are usually multi-drop buses. But for interfaces like Ethernet (Modbus TCP/IP), it doesn’t have to be. Every device connected to the bus can be considered as a node. Data transmitted by a node is visible to all other nodes on the bus. If two nodes try to transmit at the same time, there can be data collisions. Therefore, there should be always a single node that is responsible for carrying out collision-free communication. Such a node is called a Client (master in obsolete terms). A client is responsible for initiating a new request. Every other node listening to such requests is called Server (slave in obsolete terms), because it is the node that responds to the request by serving the requested data. Modbus uses this Client-Server hierarchy for messaging. There should be at least two nodes in the bus for it to be possible. The words client and server are replaced by other words in some circumstances. They are shown in the table below. The terms master and slave are obsolete and no longer preferred by new implementations.
Request Initiation | Action & Response |
---|---|
Client | Server |
Central | Peripheral |
Master | Slave |
A client can initiate a request whenever the bus is free. Only the server with the correct address can respond to a request from a client. All other servers must remain silent and must not interfere with the communication. But what if a server with the address the client is trying to communicate is not present on the bus? In such a case, the client can wait for a certain time period for a valid response from the addressed client and retry the transmission if required. If a response is not received within a timeout period, the client can assume that the server is not present on the bus or there is an issue with the communication. That said, the client should have a list of addresses of all servers on the bus, because only then can the client find the devices on the bus faster.
Since Modbus is an application layer protocol, any physical interface that supports carrying digital data can be used to deploy Modbus. However, due to the prevalence of Modbus in industrial environments, the types of interfaces mainly used for Modbus are the ones that are the most robust.
As many new physical interface standards were introduced over the years, the Modbus protocol has been adapted to suit those interfaces to make communication efficient.
There are a few other application-tailored implementations from various companies and organizations. You can find more about them on the Wikipedia page.
Despite the different ways Modbus can transfer data over a physical medium, all they have in common is the message format. As we said earlier, Modbus employs a request/response scheme for all communication and it is based on frames. A Modbus message frame is the smallest data unit you can send or receive. The format of the Modbus RTU message frame is as follows.
The data field is variable and depending on the function code and the response, the data length, type, and format can vary. But regardless of the type of data, Modbus always uses BIG-Endian format for the byte order. That means the Hi byte is sent first, followed by the Lo byte. For example, a 16-bit value 0x1234 is sent as 0x12 followed by 0x34. But that does not mean that the most significant bit (MSB) of a data byte is sent first. Which bit to send first depends on the type of physical interface used. For example, a UART, often used to drive an RS-485 bus, sends LSB first. The data field can contain exception codes (during exceptions), address ranges, and actual user data.
Cyclic Redundancy Check (CRC) code is a 16-bit data that can be used to indicate bit errors in the data. The sending node will calculate a CRC based on the frame data it has and append the frame with it. If even only one bit is corrupted during transmission, the receiving node will fail to calculate the same CRC. So a mismatching CRC is an indication of an error in the message. Unlike other data in the packet, the CRC uses small-Endian for the byte order. So the Lo byte is sent first followed by the Hi byte.
Similar to the start condition, an end condition is also indicated by 28 bits of silence on the bus. This will allow the next message frame to start immediately after an end condition.
Modbus is purely a software-defined protocol. This means, there are no specific types of hardware or memory associated with it. A device must manage its own memory and provide an abstraction layer to the Modbus application in order to manage the data. The Modbus protocol defines how you can effectively organize your data in your system’s memory. Modbus data is organized into tables and there are four primary tables.
Primary Table | Access | Size | Address Space |
---|---|---|---|
Coil | Read-Write | 1 bit | 0 – 65535 |
Discrete Input | Read-Only | 1 bit | 0 – 65535 |
Input Register | Read-Only | 16 bits | 0 – 65535 |
Holding Register | Read-Write | 16 bits | 0 – 65535 |
What is a coil you ask? At the time Modbus was created, the main things the inventors wanted to control were PLCs that had relays inside them. You turn on a relay by energizing its coil. So a coil can remain in one of the two states, ON and OFF. In digital terms that can be represented as a binary bit which can be either 0 or 1 . So historically, the data used to save single bits in Modbus started to be known as a coil. Coils are also sometimes called contacts.
Coils store a single bit of data and they have both read and write accesses. So you can set the coil to either 0 or 1 or read its current state. The address space defines how much coil data can be in your system. As per Modbus protocol version V1.1b3, you can have 2 16 individual coil data items in a Modbus data table and they can be addressed from 0 to 65535. The reason why we can have 65536 items is because, obviously, all of them have a 16-bit address. Not to be confused with the coil data object and its address.
But how do we actually store coils in system memory? Do we use a single byte for each coil, only to store 0 or 1 ? The answer is it is up to you. The Modbus protocol doesn’t explain how you should store the data tables in your system’s memory because different systems have different properties. If you only have a handful of coil data to manage, you can use plain bytes to store them. Otherwise, you can store each coil as bits of a byte. So a single byte can store 8 coils. We will show how you can do this further down in this tutorial.
Discrete Input is the same as coil data but it is read-only. It can be used to read, for example, a switch, GPIO pin, sensor, etc. Discrete input also has an address range of 0 – 65535.
This is a 16-bit data register. It can only be used to store inputs from a system and therefore it is read-only. Writing you an input-only register will cause an exception. Input register data is stored as two bytes and the high-byte is always sent first. Input registers have an address range of 0 – 65535. Just because the address range is very large doesn’t mean you have to allocate memory for all of them. You just need to allocate memory for just enough data your system needs.
A Holding Register is a general-purpose data register that you can read or write. It is also 16-bit wide and stored as a pair of bytes. Holding registers also have an address range of 0 – 65535.
The data blocks of Modbus don’t necessarily be separate in the memory. Instead, they can also reside in the memory in a shared form. For example, a Coil data can be part of a Holding Register and such. You are free to implement them as you wish.
So we know we can send data using the data field of the Modbus message frame. But what kind of data can we send? For example, how do we send a floating point number? How do we send an integer? How do we send a boolean value? Well, it is possible for a user to send data in any format they like, as long as their systems are interoperable.
Name | Description | Range |
---|---|---|
INT16 | 16-bit signed integer (1 word) | -32768…+32767 |
UINT16 | 16-bit unsigned integer (1 word) | 0…65535 |
INT32 | 32-bit signed integer (2 words) | -2 147 483 648…+2 147 483 647 |
UINT32 | 32-bit unsigned integer (2 words) | 0…4 294 967 295 |
INT64 | 64-bit signed integer (4 words) | -9 223 372 036 854 775 808…9 223 372 036 854 775 807 |
UINT64 | 64-bit unsigned integer (4 words) | 0 to 18 446 744 073 709 600 000 |
Float32 | 32-bit value (2 words) | -3.4028E+38… +3.4028E+38 |
ASCII | 8-bit alphanumeric character | Table of ASCII Characters |
BITMAP | 16-bit field (1 word) | – |
Let’s now talk about the steps carried out by Modbus devices in order to send and receive data. As we said earlier, Modbus employs a client/server scheme for its messaging. Only one device can act as a client/central node at a time. All other devices have to remain as server/peripheral nodes. Only a client device can initiate a message in the form of a request. The request will have the address of the server it wants to communicate with. The address should be a valid one and a node with that address must reply to the client’s request. The below diagram illustrates the steps involved in successful communication between a client and server.
The function code in the client’s request will have information about the action to be performed by the server. It can be writing or reading the data registers, for example. After initiating a request, a client must wait for the response from the server. If no response is received within a reasonable time, the communication should be assumed to have failed. The client can retry the request in that case. To prevent the client from waiting for a response indefinitely, a timeout is always implemented.
In a successful (error-free) communication, the server will perform the action instructed by the client and return a valid response. But if the client’s request is invalid or it has any error, the server will only return an exception code as a response. The exception code is calculated by making the MSB of the requested function code to 1 . This has the effect of adding 128 to the function code. For example, if the function code is 3, then the exception code will be 3 + 128 = 131. Modifying the function code in the response message can only tell there was an exception. The actual exception code is sent just after the function code in the data field of the message frame. We will learn more about exception codes further below.
In this section, we will try to learn more about function codes. There are three types of function codes as we discussed earlier.
Function Type | Function Name | Function Code (Dec) | Comment | ||
---|---|---|---|---|---|
Data Access | Bit access | Physical Discrete Inputs | Read Discrete Inputs | 2 | |
Internal Bits or Physical Coils | Read Coils | 1 | |||
Write Single Coil | 5 | ||||
Write Multiple Coils | 15 | ||||
16-bit access | Physical Input Registers | Read Input Registers | 4 | ||
Internal Registers or Physical Output Registers | Read Multiple Holding Registers | 3 | |||
Write Single Holding Register | 6 | ||||
Write Multiple Holding Registers | 16 | ||||
Read/Write Multiple Registers | 23 | ||||
Mask Write Register | 22 | ||||
Read FIFO Queue | 24 | ||||
File Record Access | Read File Record | 20 | |||
Write File Record | 21 | ||||
Diagnostics | Read Exception Status | 7 | serial only | ||
Diagnostic | 8 | serial only | |||
Get Com Event Counter | 11 | serial only | |||
Get Com Event Log | 12 | serial only | |||
Report Server ID | 17 | serial only | |||
Read Device Identification | 43 | ||||
Other | Encapsulated Interface Transport | 43 |
This function code allows you to read one or more coils in a server node. In the data field of the request PDU, you have to specify the start address of the coil register you want to read and then the number of contiguous coil registers to read. The number of coils you can read at a time with a single PDU is limited by the size constraint of the PDU. Since we know the maximum PDU size is limited to 253, and 2 bytes will be used for the response overhead, only 251 bytes can be used to return the coil data. 251 * 8 = 2008 is the maximum number of coils you can read at a time. The formats for the request and normal response are given below.
The following screenshots show how requests generated by the client are received by the server and how they are processed.
The USB-to-RS485 adapter dongle is a generic product with a USB serial port on one end and a single RS-485 port on the other end. You can use it to send serial data through the RS-485 interface. The particular dongle we have uses CH340 as the USB to serial converter chip and MAX485 as the RS-485 transceiver.
Modbus emulators are software that can emulate Modbus client and server devices. From a client emulator, you can send Modbus requests to a real Modbus server and get the responses from it. Similarly, a server emulator can respond to requests made by an actual Modbus server device. These functionalities can be used to test and debug Modbus devices and networks. There are quite a few Modbus emulators out there, some paid and some free. Unfortunately, most such applications have very ancient designs and don’t work properly on all devices, even the paid ones.
Modbus Mechanic is an open-source Modbus Client/Server emulator application by SciFiDryer (Matt). The application is written in Java and should run fine on all operating systems with Java RE. You can download the latest version from GitHub and run the JAR executable. Before opening the application, you must connect the development board to the computer. You can do this in two ways,
In both cases, you will get a COM port opened in the OS. For this demonstration, we connected the USB of the ESP32 board to the PC which created a COM14 serial port, and the second serial port used for RS-485 to the PC via USB-Serial adapter (COM19). So this created two serial ports. COM14 can be used to monitor the server/client and the USB-Serial adapter can be used to send and receive Modbus messages.
When you open the application, you will be able to see the list of COM ports currently available. Select the COM port used by the server device. The application will open as a client emulator by default so you must flash the ModbusRTU_Server_LED example to your ESP32. After selecting the COM port, simply set the configuration as below. The server address entered here is 0x01 . You can select a function code, enter valid parameters, and click on the Transmit packet button. If everything is working, you will get the value back from the server as seen in the screenshots.
Similarly, you can upload the ModbusRTU_Client_LED example to the Arduino board and use the server functionality of Modbus Mechanic to test it.
Hope you find our tutorial helpful. Your feedback is very important to us. Please let us know how we can improve this tutorial in the comments.