Modbus RTU Master Library for industrial automation
Learn what Modbus RTU is and its applications
18 March, 2020 by
Modbus RTU Master Library for industrial automation
Boot & Work Corp. S.L., Fernandez Queralt Martinez



Introduction of Modbus RTU Master library for industrial automation

Introduction


In the Arduino automation area, the Modbus RTU protocol is a means of communication that allows the exchange of data between programmable logic controllers (industrial PLC controller Arduino) and computers.

Electronic devices can exchange information through serial lines using the Modbus protocol.

In this blog, you are going to learn how Modbus RTU works!

Index

1- What is Modbus RTU?

2- How does Modbus RTU work?

3- Modbus RTU format frame

4- Modbus Object Types

5- Modbus Function Codes

6- Modbus Data Format

    6.1- 01 (0x01) Read Coils

    6.2- 02 (0x02) Read discrete inputs

    6.3- 03 (0x03) Read Holding Registers

    6.4- 04 (0x04) Read Input Register

    6.5- 05 (0x05) Write Single Coil

    6.6- 06 (0x06) Write Single Register

    6.7- 15 (0x0F) Write Multiple Coils

    6.8- 16 (0x0F) Write Single Register    

7- Creating our Modbus RTU message

8- Software

1- What is Modbus RTU?

Modbus is a communication protocol located at levels 1, 2 and 7 of the OSI Model, based on the master/slave architecture, designed in 1979 by Modicon for its range of PLCs.

Converted into a de facto standard communications protocol in the industry, let’s see some of the main features:

  1. Designed with its use in industrial applications in mind

  2. It is public and free

  3. It is easy to implement and requires little development.

  4. Handles blocks of data without assuming restrictions

  5. Each of the messages includes redundant information that ensures its integrity at reception.

  6. The basic Modbus commands allow you to control an RTU device to modify the value of any of its registers or to request the content of these registers.

2- How does MODBUS RTU work?

Modbus RTU is the most common implementation available for Modbus.

Modbus RTU is used in serial communication and makes use of a compact, binary representation of the data for protocol communication.

Modbus messages are divided by idle periods as you can see in the picture below.

Odoo ‚ÄĘ Image and Text

Each device on a Modbus communication has a unique address. 

The Modbus RTU works by RS-485 which is a single cable multi-drop network, only the node assigned as the Master may initiate a command. All the other devices are slaves and answer to requests and commands.

A Modbus command contains the Modbus address of the device it is intended for. Only the addressed device will respond and act on the command, even though other devices might receive it.

Also, it is important to say that all Modbus commands contain checksum information to allow the recipient to detect transmission errors.

Let's give an example! Imagine that we have a Modbus serial network, where there is a master and up to 31 slaves, each with a unique slave address.

The master only wants to send a message to slave number 2 requesting the value of 6 input registers.

So, the master would send a message and all the slaves would receive the message, but only the slave number 2 will respond and act on the command, even though other devices might receive it.

With this example, we are going to create a Modbus RTU message along this post. 
Modbus message at the moment: 02 (slave address)

Odoo ‚ÄĘ Text and Image

3- Modbus RTU format frame

    Now, let's analyze the message and what is the structure of the Modbus RTU format frame. 

Odoo ‚ÄĘ Image and Text

Address: We set the slave address for the device which we want to send the message to.
Function Code: The number of the function code. You can see the table of the function codes in the "Modbus function codes" section.
Data: The message itself. This can vary depending on the function code.
CRC: The number of the cyclic redundancy check. It must be calculated.

4- Modbus Object Types

As we just mentioned in the section above, the data can vary depending on the function code. What does it mean? Modbus bases its data model on a series of tables that have distinguising characteristics.

The four primary tables are the following:

PRIMARY TABLESOBJECT TYPETYPE OFCOMMENTS
Discrete inputs (Inputs)
Single bitRead-OnlyThis type of data be provided by an I/O system.
Coils
(Outputs)
Single bitRead-WriteThis type of data can be alterable by an application.
Input Registers
(Inputs)
16-bit wordRead-OnlyThis type of data can be provided by an I/O system.
Holding Registers
(Outputs)
16-bit wordRead-WriteThis type of data can be alterable by an application program.

As we can see, discrete inputs and coils can handle single bit, and as we already know, a single bit is binary (0-off, 1-on), so that is a digital value. Otherwise, input registers and holding registers can handle 16-bit word multiplied by N, so they can work for analog input/outputs.

5- Modbus Function Codes 


Now that we already know what the four primary tables are, let's continue creating the Modbus RTU message.

We want the master to send a message to slave number 2 requesting the value of 6 input registers.

As we can see in the table below, we will choose function code number 4, as we want to read input registers.

So, our Modbus RTU message will be like this at the moment: 0204

02 (Slave Address) + 04 (Function Code)

Odoo ‚ÄĘ Image and Text

6- Modbus Data Format

Function codes descriptions


Modbus requests and responses contain an Application Data Unit (ADU) which contains a Protocol Data Unit (PDU).
Let's see how they are.

Odoo ‚ÄĘ Image and Text

01 - (0x01) Read Coils

This function code is used to read from 1 to 2000 contiguous status of coils in a remote device.

The coils in the response message are packed as one coil per bit of the data field. Status is indicated as 1: ON and 0: OFF. The LSB of the first data byte contains the output addressed in the query. The other coils follow toward the high order end of this byte, and from low order to high order in subsequent bytes.

Request

Function Code1 Byte0x01
Starting Address2 Bytes0x0000 to 0xFFFF
Quantity of coils2 Bytes1 to 2000 (0x7D0)

Response

Function Code1 Byte0x01
Byte count1 ByteN*
Coil statusn Bytesn = N or N+1

*N = Quantity of Outputs / 8, if the remainder is different of 0 => N = N+1


Example of a request to read discrete outputs 20 ‚Äď38:

Request
Response
Field NameHexField NameHex
Function01Function01
Starting Address Hi00Byte Count03
Starting Address Lo13Outputs status 27-20CD
Quantity of Outputs Hi00Outputs status 35-286B
Quantity of Outputs Lo13Outputs status05

*The CRC must be calculated.


02 - (0x02) Read Discrete Inputs

 This function code is used to read from 1 to 2000 contiguous status of discrete inputs in a remote device.

The Request PDU specifies the starting address, i.e. the address of the first input specified, and the number of inputs. In the PDU Discrete Inputs a re addressed starting at zero. Therefore Discrete inputs numbered 1-16 are addressed as 0-15.

The discrete inputs in the response message are packed as one input per bit of the data field. Status is indicated as 1= ON; 0= OFF. The LSB of the first data byte contains the input addressed in the query. The other inputs follow toward the high order end of this byte, and from low order to high order in subsequent bytes.

Request

Function Code1 Byte0x02
Starting Address2 Bytes0x0000 to 0xFFFF
Quantity of Inputs2 Bytes1 to 2000 (0x7D0)


Response

Function Code1 Byte0x02
Byte count1 ByteN*
Input statusN* x 1 Byte


Example of a request to read discrete inputs 197 ‚Äď 218:

Request
Response
Field NameHexField NameHex
Function02Function02
Starting Address Hi00Byte Count03
Starting Address LoC4Outputs status 27-20AD
Quantity of Outputs Hi00Outputs status 35-28DB
Quantity of Outputs Lo16Outputs status35



03 - (0x03) Read Holding Registers

 This function code is used to read the contents of a contiguous block of holding registers in a remote device. The Request PDU specifies the starting register address and the number of registers. In the PDU Registers are addressed starting at zero. Therefore registers numbered 1-16 are addressed as 0-15.

The register data in the response message are packed as two bytes per register, with the binary contents right justified within each byte. For each register, the first byte contains the high order bits and the second contains the low order bits.

Request

Function Code1 Byte0x03
Starting Address2 Bytes0x0000 to 0xFFFF
Quantity of Registers2 Bytes1 to 125 (0x7D0)

Response

Function Code1 Byte0x03
Byte count1 Byte2 x N*
Register valueN* x 2 Bytes

*N = Quantity of Registers


Example of a request to¬†read registers 108 ‚Äď 110:

Request
Response
Field NameHexField NameHex
Function03Function03
Starting Address Hi00Byte Count06
Starting Address Lo6BRegister value Hi (108)
Register value Lo (108)
02

2B
No. of Registers Hi00Register value Hi (109)
Register value Lo (109)
00

00
No. of Registers Lo03Register value Hi (110)
Register value Lo (110)
00

64




04 - (0x04) Read Input Registers

This function code is used to read from 1 to 125 contiguous input registers in a remote device. The Request PDU specifies the starting register address and the number of registers. In the PDU Registers are addressed starting at zero. Therefore input registers n umbered 1-16 are addressed as 0-15. 

The register data in the response message are packed as two bytes per register, with the binary contents right justified within each byte. For each register, the first byte contains the high order bits and the second contains the low order bits.

Request

Function Code1 Byte0x04
Starting Address2 Bytes0x0000 to 0xFFFF
Quantity of Input Registers2 Bytes0x0001 to 0x007D

Response

Function Code1 Byte0x04
Byte count1 Byte2 x N*
Input RegistersN* x 2 Bytes

*N = Quantity of Input Registers


Example of a request to read input register 9:

Request
Response
Field NameHexField NameHex
Function04Function04
Starting Address Hi00Byte Count02
Starting Address Lo08Input Reg. 9 Hi00


Quantity of Input Reg. Hi00Input Reg. 9 Lo
0A
Quantity of Input Reg. Lo
01



05 - (0x05) Write Single Coil

This function code is used to write a single output to either ON or OFF in a remote device.

The requested ON/OFF state is specified by a constant in the request data field. A value of FF00 hex requests the output to be ON. A value of 00 00 requests it to be OFF. All other values are illegal and will not affect the output. 

The Request PDU specifies the address of the coil to be forced. Coils are addressed starting at zero. Therefore coil numbered 1 is addressed as 0. The requested ON/OFF state is specified by a constant in the Coil Value field. A value of 0XFF00 requests the coil to be ON. A value of 0X0000 requests the coil to be off. All other values are illegal and will not affect the coil.

The normal response is an echo of the request, returned after the coil state has been written.

Request

Function Code1 Byte0x05
Output Address2 Bytes0x0000 to 0xFFFF
Output value2 Bytes0x0000 to 0xFF00

Response

Function Code1 Byte0x05
Byte count2 Byte0x0000 to 0xFFFF
Input Registers2 Bytes0x0000 or 0xFF00


Example of  a request to write Coil 173 ON:

Request
Response
Field NameHexField NameHex
Function05Function05
Starting Address Hi00Output Address Hi
00
Starting Address LoACOutput Address Lo
AC


Quantity of Input Reg. HiFFOutput Value HiFF
Quantity of Input Reg. Lo
00Output Value Lo
00
 


06 - (0x06) Write Single Register

This function code is used to write a single holding register in a remote device.

The Request PDU specifies the address of the register to be written. Registers are addressed starting at zero. Therefore register numbered 1 is addressed as 0.

The normal response is an echo of the request, returned after the register contents have been written.

Request

Function Code1 Byte0x06
Register Address2 Bytes0x0000 to 0xFFFF
Register value2 Bytes0x0000 to 0xFFFF

Response

Function Code1 Byte0x06
Register Address2 Byte0x0000 to 0xFFFF
Register value2 Bytes0x0000 or 0xFF00


Example of a request to write register 2 to 00 03 hex:

Request
Response
Field NameHexField NameHex
Function06Function06
Starting Address Hi00Output Address Hi
00
Starting Address Lo01Output Address Lo
01


Quantity of Input Reg. Hi00Output Value Hi00
Quantity of Input Reg. Lo
03Output Value Lo
03



15 - (0x0F) Write Multiple Coils

This function code is used to force each coil in a sequence of coils to either ON or OFF in a remote device. The Request PDU specifies the coil references to be forced. Coils are addressed starting at zero. Therefore coil numbered 1 is addressed as 0.

The requested ON/OFF states are specified by contents of the request data field. A logical ' 1' in a bit position of the field requests the corresponding output to be ON. A logical '0' requests it to be OFF.

The normal response returns the function code, starting address, and quantity of coils forced. 


Request

Function Code1 Byte0x0F
Starting Address2 Bytes0x0000 to 0xFFFF
Quantity of Outputs2 Bytes0x0001 to 0x07B0
Byte Count1 ByteN*
Outputs ValueN* x 1 Byte 

*N = Quantity of Outputs / 8, if the remainder is different of 0 => N = N+1


Response

Function Code1 Byte0x0F
Starting Address2 Byte0x0000 to 0xFFFF
Quantity of Outputs2 Bytes0x0001 or 0x07B0


Example of a request to write register 2 to 00 03 hex:

Request
 Response
Field NameHexField NameHex
Function0FFunction0F
Starting Address Hi00Starting Address Hi00
Starting Address Lo13Starting Address Lo13
Quantity of Outputs Hi00Quantity of Outputs Hi00
Quantity of Outputs Lo0AQuantity of Outputs Lo0A
Byte Count02

Outputs Value HiCD

Outputs Value Lo01



16 - (0x10) Write Multiple Registers

This function code is used to write a block of contiguous registers (1 to 123 registers) in a remote device.

The requested written values are specified in the request data field. Data is packed as two bytes per register.

The normal response returns the function code, starting address, and quantity of registers written.


Request

Function Code1 Byte0x10
Starting Address2 Bytes0x0000 to 0xFFFF
Quantity of Registers2 Bytes0x0001 to 0x007B
Byte Count1 Byte2 x N*
Registers  ValueN* x 2 Bytesvalue

*N = Quantity of Registers


Response

Function Code1 Byte0x10
Starting Address2 Byte0x0000 to 0xFFFF
Quantity of Registers2 Bytes0x123 or (0x7B)


Example of a request to write two registers starting at 2 to 00 0A and 01 02 hex:

Request
 Response
Field NameHexField NameHex
Function10Function10
Starting Address Hi00Starting Address Hi00
Starting Address Lo01Starting Address Lo01
Quantity of Registers Hi00Quantity of Outputs Hi00
Quantity of Registers Lo
02Quantity of Outputs Lo02
Byte Count04

Registers Value Hi00

Registers Value Lo
0A

Registers Value Hi
01

Registers Value Lo

02





7- Creating our Modbus RTU message

Now we already know a little bit more about Modbus RTU and its frame format, let's finish our Modbus message from the example we gave at the beginning of this blog post.

We wanted the master to send a message to slave number 2 requesting the value of 6 input registers.

Our Modbus RTU message looks like this at the moment: 0204 ( 02 (Slave Address) + 04 (Function Code) )

As our function code is number 04: Read Input Register, the data must contain: Starting Address Hi + Starting Address Lo + Quantity of Input Reg. Hi + Quantity of Input Reg. Lo + CRC.

So, let's fill the request ADU in order to get all the message: 

Request ADU
Field NameHEX
Slave Address02
Function Code04
Starting Address Hi00
Starting Address Lo
00
Quantity of Input Reg. Hi00
Quantity of Input Reg. Lo
06
CRC-
CRC-


To calculate the CRC, just type the Modbus message: 020400000006 in this website. Select HEX input type and get the CRC-16 (Modbus) number.

As it is LSB, we will reverse it. If the result of the CRC is: 0x3B70, now it will be: 703B.

Finally, this is how our Modbus message looks like: 

020400000006703B

8- Software

Modbus RTU Master with Arduino IDE


The Modbus RTU Master Module implements the Modbus RTU Master capabilities. We are going to work with the modbusrtumaster.h function:

#include <ModbusRTUMaster.h>


There is the possibility to use any hardware Serial Arduino stream:

  • RS-485

#include <RS485.h>

ModbusRTUMaster master(RS485);


  • RS-232

#include <RS232.h>

ModbusRTUMaster master(RS232);


Before using it, it is required to call the begin function in the setup for both the serial and the Modbus variable. It is a good practice to set the baud rate (default: 19200 bps) also in the Modbus variable to define the Modbus internal timeouts.

RS485.begin(9600, HALFDUPLEX, SERIAL_8E1);
master.begin(9600);


The functions to read and write slave values are:

readCoils(slave_address, address, quantity);
readDiscreteInputs(slave_address, address, quantity);
readHoldingRegisters(slave_address, address, quantity);
readInputRegisters(slave_address, address, quantity);
writeSingleCoil(slave_address, address, value);
writeSingleRegister(slave_address, address, value);
writeMultipleCoils(slave_address, address, values, quantity);
writeMultipleRegisters(slave_address, address, values, quantity);


Where :

  • slave_address¬†is the Modbus RTU slave address.

  • address¬†is the coil, digital input, holding register or input register address. Usually, this address is the coil, digital input, holding register or input register number minus 1: the holding register number¬†40009¬†has the address¬†8.
    quantity is the number of coils, digital, holding registers or input registers to read/write.
  • value¬†is the given value of the coil or holding registers on a write operation. Depending on the function the data type changes. A coil is represented by a¬†bool¬†value and a holding register is represented by a¬†uint16_t¬†value.

On a multiple read/write function the address argument is the first address. On a multiple write function, the values argument is an array of values to write.

It is important to say that these functions are non-blocking, so they do not return the read value. They return true or false depending on the current module state. If there is a pending Modbus request, they return false.  

// Read 5 holding registers from address 0x24 of slave with address 0x10
if (master.readHoldingRegisters(0x10, 0x24, 5)) {
	// OK, the request is being processed
} else {
	// ERROR, the master is not in an IDLE state
}

There is the function available() to check for responses from the slave:

ModbusResponse response = master.available();
if (response) {
	// Process response
}


The ModbusResponse implements some functions to get the response information:

hasError();
getErrorCode();
getSlave();
getFC();
isCoilSet(offset);
isDiscreteInputSet(offset);
isDiscreteSet(offset);
getRegister(offset);
ModbusResponse response = master.available();
if (response) {
	if (response.hasError()) {
		// There is an error. You can get the error code with response.getErrorCode()
	} else {
		// Response ready: print the read holding registers
		for (int i = 0; i < 5; ++i) {
			Serial.println(response.getRegister(i));
		}
	}
}

 

The possible error codes are:

0x01 ILLEGAL FUNCTION
0x02 ILLEGAL DATA ADDRESS
0x03 ILLEGAL DATA VALUE
0x04 SERVER DEVICE FAILURE


Check out for more >>>

Modbus RTU Master Library for industrial automation
Boot & Work Corp. S.L., Fernandez Queralt Martinez
18 March, 2020
Share this post
Archive

Looking for your ideal PLC?

Take a look at this product comparison with other industrial controllers Arduino-based.

We are comparing inputs, outputs, communications and other features with the ones of the relevant brands.


Industrial PLC comparison >>

Do you want more information?

Just fill the form!

Tell me more!