Complete Modbus Guide

Modbus Overview

What is Modbus?

Despite it’s age, Modbus is still one of the most commonly used protocols for field communicaitons. It’s relative simplicity and robustness and openness made it a protocol of choice for many automation hardware and software vendors. Because of this, Modbus is a safe choice for organization to commit to as there are always devices that support it.

Another major benefit of Modbus is that it does not prescribe a specific physical layer. Instead, Modbus can work on top of RS-232, RS-485 or TCP/IP over Ethernet. Those are all cheap and already commonly used in enterprises. This means there is no need to invest into expensive protocol-specific network infrastructure.

There are different types of Modbus implementation depending on data encoding format, the transport layer and some other considerations. The most popular protocol types are:

  • Modbus RTU (binary over serial link)
  • Modbus ASCII (text-based over serial link)
  • Modbus TCP (binary over TCP/IP transport)

Modbus RTU Protocol Overview

Modbus RTU is a master-slave protocol. This means that only one device, the master, is allowed to initiate communication. The other devices on the network are called slaves and they may only respond to the requests. Modbus RTU can support up to 247 devices on the same physical network. It’s possible to modify the protocol to support more slaves, but in most applications the standard limit of slaves if enough.

Modbus RTU encodes data as binary and uses big-endian encoding for 16-bit values. This means that the most significant byte of a 16-bit word is sent first.

Below is an example of Modbus RTU Request and Response messages with explanation of each item. First, a master sends a request telling the slave 1 to return the value of one register starting at address 2.

slave id (1 byte)
|  function code (read holding registers)
|  |      address of first register to read (2 bytes)
|  |      |     number of registers to read (1 byte)
|  |      |     |  checksum (2 bytes)
|  |      |     |  | 
01 03 00 02 00 01 25 CA

The request also includes a checksum which is used to make sure the messaged is not corrupted on the way to the slave. All slaves except for 1 must ignore the message. Slave 1 is expected to send a response message similar to the following:

slave id (repeats own id)
|  function code (repeats requested code)
|  |   number of bytes of data (2)
|  |   |     the value of the register (0x07FF)
|  |   |     |     checksum
|  |   |     |     |
01 03 02 07 FF FA 34

Modbus ASCII

Modbus ASCII works similar to Modbus RTU, but it uses text-based encoding of data. This make requests and responses human-readable, which is the main benefit over RTU. On the other hand, it’s much less efficient because the messages become twice as long. Because of this, Modbus ASCII is only used for testing and rarely in production.

Limitations of Modbus RTU and ASCII

Low requirements and simplicity of the protocol has it’s drawbacks:

  • There is no good way to have multiple masters on the same network, or achive two-way communication. This is because there no mechanism to control media access and thus avoid collisions.
  • It’s hard to support many slaves with serial links such as RS-485. In fact, using more than a couple of dozens of devices is only possible by building a complex nested hierarchy of masters and slaves.
  • The bandwidth of serial links is limited to 115200 baud. This is quite low by modern standards, but still works for many applications.

Modbus TCP

Modbus TCP is an adaptation of Modbus to be used on top of modern TCP/IP networks. There are two types of Modbus TCP implementation:

  • Modbus RTU over TCP, which simply uses TCP as a transport layer for RTU messages
  • Normal Modbus TCP which has some changes in the message format.

Because Modbus TCP uses Ethernet networks, the data transmission speeds is much higher than in RTU using serial links. The drawback is that TCP/IP stack is much more difficult to support in some types of field devices where Modbus RTU would work fine.

Modbus RTU Data Frame

A Modbus data frame is a message transmitted over Modbus network. There are Request and Response frames. A request is a message from the master to a slave. A response is a message from the slave back to the master.

The length and the contents of the data frame vary based on the type of read/write operation being performed. We will cover all of them later. But first lets examine the basic structure of a request frame:

01 03 02 00 01 25 CA

Those are 8-bit hexadecimal characters that are sent over Modbus RTU network. The whole message in our case is seven bytes long. Let’s see what’s inside the frame:

01         03        02 00 01                 25 CA
-----------------------------------------------------
slave id   function  function-specific data   CRC
1 byte     1 byte                             2 bytes

The first byte of every Modbus message is a slave id. The master specifies the id of the slave to which the request message is addressed. Slaves must specify their own id in every response message:

01 03 02 00 01 25 CA
|
slave id (1 byte)  

The second byte of every Modbus message is a function code. This code determines the type of operation to be performed by the slave. We’ll cover function codes in a moment.

01 03 02 00 01 25 CA
   |
   function code (1 byte)

The last two bytes of every Modbus message are CRC bytes. These are computed based on the preceding bytes of the frame and allow both master and slave to verify the integrity of the received message. We will cover the algorithm used in CRC calculation later.

01 03 02 00 01 25 CA
               -----
                 |
                 CRC (2 bytes)
                

Below is a Modbus message in general form, which we will use in the rest of this article:

[ID][FC][DATA][CRC]

Here ID corresponds to the slave id, FC is function code, data is function-specific variable length sequence of characters, and CRC is a 2-byte checksum.

Modbus Addresses

Modbus devices have 4 types of addresses:

  • Coil
  • Discrete Input
  • Input Register
  • Holding Register

Coils are 1-bit (boolean) read/write devices. Each coil can be in either on or off state. Discrete inputs are similar to coils, but they are read-only - it’s impossible to set the value of a discrete input. You can think of Coils as outputs of a PLC and Discrete Inputs as input of a PLC.

Holding registers are like PLC memory. They are 16-bit words which you can both read and write via Modbus protocol. Input Registers are also 16-bit words, but they are read-only, like readings of a sensor.

A Modbus address is a 16-bit unsigned integer that is transmitted with every request to indicate which data should be read or written. Address occupies two characters in the Modbus message and the most significant byte is sent first (big-endian).

In this example request we refer to the address of Holding Register 1 or HR1

01 03 02 00 01 25 CA
         -----
           |
           Address (2 bytes)

Function Codes

In this section we go through Modbus function codes and explain the specifics of data frame construction for each one of them.

Read Coils - 0x01

This function code allows the master to query the state of slave’s coils.

Request

[ID][FC][ADDR][NUM][CRC]
  • ADDR - the address of the first coil (2 bytes)
  • NUM - the number of coils to read (2 bytes)

A read coils request is always 8 bytes long. Here is example request to read 2 coils starting at address 0xA:

 ID   FC   ADDR     NUM    CRC
[01] [01] [00 0A] [00 02] [9D C9]

Response

[ID][FC][BC][DATA(1+)][CRC]
  • BC - the nubmer of bytes of DATA in the response (1 byte)
  • DATA - a sequence of bytes that contains the state of coils (1 byte per 8 coils)

A read coils response is at least 6 bytes long. In our example we have only one byte of DATA because we requested only two coils, so all data fits into a single byte:

 ID   FC   BC   DATA  CRC
[01] [01] [01]  [03] [11 89]

Read Discrete Inputs - 0x02

This function code allows the master to query the state of slave’s discrete inputs.

Request

[ID][FC][ADDR][NUM][CRC]
  • ADDR - the address of the first discrete input (2 bytes)
  • NUM - the number of inputs to read (2 bytes)

A read discrete inputs request is always 8 bytes long. Here is example request to read 2 discrete inputs starting at address 0x00:

 ID   FC   ADDR     NUM     CRC
[01] [02] [00 00] [00 02] [F9 CB]

Response

[ID][FC][BC][DATA(1+)][CRC]
  • BC - the number of bytes of DATA in the response (1 byte)
  • DATA - a sequence of bytes that contains the state of discrete inputs (1 byte per 8 inputs)

A read discrete inputs response is at least 6 bytes long. Example:

 ID   FC   BC  DATA   CRC
[01] [02] [01] [02] [20 49]

Read Holding Registers - 0x03

This function code allows the master to query the state of slave’s holding registers.

Request

[ID][FC][ADDR][NUM][CRC]
  • ADDR - the address of the first register (2 bytes)
  • NUM - the number of registers to read (2 bytes)

A read holding registers request is always 8 bytes long. In the request below we read 1 holding register at address 0x02:

 ID   FC   ADDR     NUM    CRC
[01] [03] [00 02] [00 01] [25 CA]

Response

[ID][FC][BC][DATA(2+)][CRC]
  • BC - the number of bytes of DATA in the response (1 byte)
  • DATA a sequence of bytes that contains the values of holding registers (2 bytes per register)

A read holding registers response is at least 7 bytes long. Example:

 ID   FC   BC   DATA     CRC
[01] [03] [02] [07 FF] [FA 34]

Read Input Registers - 0x04

With this function code, master queries the state of slave’s input registers.

Request

[ID][FC][ADDR][NUM][CRC]
  • ADDR - the address of the first register (2 bytes)
  • NUM - the number of registers to read (2 bytes)

A read input registers request is always 8 bytes long. In the request below we read 1 holding register at address 0x00:

 ID   FC   ADDR     NUM    CRC
[01] [04] [00 00] [00 01] [31 CA]

Response

[ID][FC][BC][DATA(2+)][CRC]
  • BC - the number of bytes of DATA in the response (1 byte)
  • DATA a sequence of bytes that contains the values of holding registers (2 bytes per register)

A read holding registers response is at least 7 bytes long. Example:

 ID   FC   BC   DATA     CRC
[01] [04] [02] [03 FF] [F9 80]

Write Single Coil - 0x05

Sets the value of a single slave’s coil.

Request

[ID][FC][ADDR][VAL][CRC]
  • ADDR - the address of the coil to write (2 bytes)
  • VAL - the value of the coil to write (2 bytes), 0xFF00 sets coil to ON, 0x0000 sets coil to OFF

A write single coil request is always 8 bytes long. Example:

 ID   FC   ADDR     NUM    CRC
[01] [05] [00 0A] [00 00] [ED C8]

Response

A successful response to write single coil repeats the request exactly:

 ID   FC   ADDR     NUM    CRC
[01] [05] [00 0A] [00 00] [ED C8]

Write Single Register - 0x06

Sets the value of a single slave’s holding register.

Request

[ID][FC][ADDR][VAL][CRC]
  • ADDR - the address of the register to write (2 bytes)
  • VAL - the value of the register to write (2 bytes)

A write single register request is always 8 bytes long. Example:

 ID   FC   ADDR     VAL    CRC
[01] [06] [00 02] [0C 00] [2D 0A]

Response

A successful response to write single register is exacly like the request:

 ID   FC   ADDR     VAL    CRC
[01] [06] [00 02] [0C 00] [2D 0A]

Write Multiple Coils - 0x0F

Sets the value of a consecutive range of slave’s coils.

Request

[ID][FC][ADDR][NUM][BC][DATA(1+)][CRC]
  • ADDR - the address of the first coil to write (2 bytes)
  • NUM - the number of the coils to write (2 bytes)
  • BC - the number of bytes of data in the request (1 byte)
  • DATA - the values of coils to set (1 bytes per 8 coils)

In this example we set values of 10 (0x0A) coils starting at 0x01 to ON:

 ID   FC   ADDR    NUM     BC   DATA     CRC
[01] [0F] [00 01] [00 0A] [0F] [FF 03] [AB CD]

Response

 ID   FC   ADDR    NUM     CRC
[01] [0F] [00 01] [00 0A] [AB CD]
  • ADDR - the address of the first updated coil (2 bytes)
  • NUM - the number of updated coils (2 bytes)

Write Multiple Registers - 0x10

Sets the value of a consecutive range of slave’s holding registers.

Request

[ID][FC][ADDR][NUM][BC][DATA(2+)][CRC]
  • ADDR - the address of the first register to write (2 bytes)
  • NUM - the number of the registers to write (2 bytes)
  • BC - the number of bytes of data in the request (1 byte)
  • DATA - the values of registers to set (2 bytes per register)

In this example where we write 2 registers starting at 0x0A:

 ID   FC   ADDR    NUM     BC      DATA        CRC
[01] [10] [00 0A] [00 02] [04] [00 0A 01 02] [AB CD]

Response

 ID   FC   ADDR    NUM     CRC
[01] [10] [00 0A] [00 02] [AB CD]
  • ADDR - the address of the first updated register (2 bytes)
  • NUM - the number of updated registers (2 bytes)

Exception Response

In some cases a slave might be unable to process a master request. For such occasions Modbus defines an Exception Response Frame:

[ID][FC][EC][CRC]
  • FC - the function code of the request that led to the exception with the most significant bit set to 1 (1 byte)
  • EC - an exception code explaining what happened (1 byte)

Example of an exception response to a read coils request:

 ID   FC   EC    CRC
[01] [81] [02] [C1 91]

The function code of read coils is 0x01, but in the exception response the most significant bit has been set to 1, so the code becomes 0x81:

0000 0001 => 1000 0001
 (0x01)       (0x81)

Standard Exception Codes

Here is the list of most common Modbus exception codes:

  • 01 - Illegal Function - The specified function code is not support by the slave
  • 02 - Illegal Data Address - The specified data address is not defined on the slave
  • 03 - Invalid Data Value - The specified data is not valid
  • 04 - Device Failure - Slave has failed to generate a response
  • 05 - Acknowledge - Slave accepted the command and is processing it
  • 06 - Busy - Slave is busy and will not process the message

Modbus RTU CRC Calculation

Every Modbus RTU message (frame) inludes two CRC bytes at the end:

01 03 02 00 01 25 CA
               -----
                 |
         CRC bytes

Those bytes are used by both master and slave to verify integrity of all received messages.

A CRC value is calculated based on the rest of the message. In other words, all bytes except for the last two are used:

01 03 02 00 01 25 CA
--------------
 used to 
 compute CRC

When a Modbus device recieves a message, it computes it’s own the CRC value and compares it with the received CRC value.

The CRC Algorithm

CRC stands for Cyclic Redundancy check. Here is a sample code to calculate CRC in C#

public static void CRC(byte[] data, out byte msb, out byte lsb)
{
    ushort crcFull = 0xFFFF;
    for (var i = 0; i < data.length; i++)
    {
        crcFull = (ushort)(crcFull ^ data[i]);
        for (var j = 0; j < 8; j++)
        {
            var crclsb = (char)(crcFull & 0x0001);
            crcFull = (ushort)((crcFull >> 1) & 0x7FFF);
            if (crclsb == 1)
            {
                crcFull = (ushort)(crcFull ^ 0xA001);
            }
        }
    }

    lsb = (byte)((crcFull >> 8) & 0xFF);
    msb = (byte)(crcFull & 0xFF);
}

Note that Modbus ASCII uses a different algorithm to calculate message checksum, called LRC.

Modbus TCP

Modbus TCP is the protocol designed for transmitting Modbus frames using TCP/IP stack, typically over Ethernet physical layer.

There are two ways Modbus and TCP can work together. One is the actual Modbus TCP protocol, the other is Modbus RTU-over-TCP.

Modbus TCP vs RTU-over-TCP

In both cases, TCP is a transport protocol that carries Modbus messages. But the messages are different.

In Rtu-over-TCP, TCP is used to transport the exact same messages as are used in Modbus RTU (serial).

In Modbus TCP, on the other hand, the message (frame) itself has different structure, so the two formats are not compatible.

Modbus TCP Frame

A Modbus TCP frame is different from a regular Modbus RTU frame.

An RTU frame has the following general structure:

[slave ID][data][CRC bytes]

To transform it into a TCP frame we must:

  • remove slave ID
  • remove CRC bytes
  • add MBAP header in the front of the message

Like this:

[slave ID][data][CRC bytes]
    [MBAP][data]

MBAP stands for Modbus Application Protocol. The MBAP header itself has the following structure:

[transaction ID][protocol ID][ length ][unit id]
    2 bytes        2 bytes    2 bytes   1 byte

Transaction ID is random number that is set by the master for each new request and must be used by the slave in the response. Protocol ID is always 0 in Modbus TCP. Length is the number of bytes following, including unit id and the remaining data. Unit ID is a device address similar to slave id