Modbus - The Art of Data Transmission ⚙️

By Tobias Serr-Li

Modbus is an industrial communication protocol used for exchanging data between devices in automation systems, offering simplicity and reliability for connecting and controlling industrial devices.

The Types of Modbus

There are three main types of Modbus, Modbus RTU, Modbus TCP and Modbus ASCII.

Modbus RTU is characterised by it use of a RS-232 or RS-485 connection, which is a type of serial communication.

Modbus TCP uses Ethernet as the underlying communication medium, instead of the serial communication Modbus RTU uses. It's essentially a Modbus RTU packet wrapped in a TCP wrapper, so I will explain more towards the end.

Modbus ASCII is essentially Modbus RTU but every character of the Modbus RTU request and response is converted into the characters ASCII code. It's incredibly rare, so I won't mention it from now.

During the rest of the explanation I will mostly talk about Modbus RTU.

The Frame

Everything in Modbus starts with the frame. It's split up into two parts, a Application Data Unit (ADU), which includes the other part, a Protocol Data Unit (PDU). I will come back to this later.

A Modbus frame looks like this (in Hexadecimal) - 11 06 0001 0003 9A9B

Let's examine each part of the frame in sections.

Slave ID

As mentioned before, Modbus works on a Master/Slave relationship. Each slave is given a unique unit address from 1 to 247 to identify itself. When the master requests data, the first byte it sends is the Slave address. This way each slave knows after the first byte whether or not to ignore the message.

In the shown frame, 11 is given (17 in decimal). This address is in 8 bits, meaning that the address can run from 00 to FF (0 to 255 in decimal). This means that the Master is sending a message to the Slave with ID 17.

Object types

This is the meat 🥩 and potatos 🥔 of Modbus.

Within the Slave, data is stored in four different tables. Two tables store on/off discrete values (coils) and two store numerical values (registers). The coils and registers each have a read-only table and read-write table.

  • Each table has 9999 values and has an address between 0000 and 270E.
  • Each of those on/off discrete values are 1 bit each.
    We will call them coils from now on!
  • Each register is 1 word = 16 bits = 2 bytes.
    We will call them registers from now on!

Each of the different tables have different names.

  • Coils are Read/write with addresses from 00001 – 09999.
  • Discrete Inputs are Read-only with addresses from 10001 – 19999.
  • Input registers are Read-only with addresses from 30001 – 39999
  • Holding registers are Read/write with addresses from 40001 – 49999

Function Codes

We've established the different data types, but how do we actually read and write to those values? The second byte sent by the Master (see the previous frame) is the Function code. This number tells the slave which table to access and whether to read from or write to the table.

Code Action Table Name
01 (01 hex) Read Discrete Output Coils
05 (05 hex) Write Single Discrete Output Coil
15 (0F hex) Write multiple Discrete Output Coils
02 (02 hex) Read Discrete Input Contacts
04 (04 hex) Read Analog Input Registers
03 (03 hex) Read Analog Output Holding Registers
06 (06 hex) Write Single Analog Output Holding Register
16 (10 hex) Write Multiple Analog Output Holding Registers

Cyclic Redundancy Check (CRC)

This is the last part of the frame. It is two bytes added to the end of every modbus message for error detection. Every byte in the message is used to calculate the CRC. The receiving device also calculates the CRC and compares it to the CRC from the sending device.

If even one bit in the message is received incorrectly, the CRCs will be different and an error will result.

The Frame (Again)

Let's finish this section off by revisiting the frame. I mentioned before that a Modbus frame looks like this (in Hexadecimal) - 11 06 0001 0003 9A9B

This is the Write a Single Register command (FC 06). Let's go through the entire frame, now that we know what is what.

  • 11: The Slave Address (11 hex = address17 )
  • 06: The Function Code 6 (Preset Single Register)
  • 0001: The Data Address of the register.
    • ( 0001 hex = 1 , + 40001 offset = register #40002 )
  • 0003: The value to write
  • 9A9B: The CRC (cyclic redundancy check) for error checking.

Okay, now that we've sent the command, what now? The normal response is an echo of the query from the slave, returned after the register contents have been written. It's as followed (each response is different for each function code).

  • 11: The Slave Address (11 hex = address17 )
  • 06: The Function Code 6 (Preset Single Register)
  • 0001: The Data Address of the register.
  • 0003: The value to write
  • 9A9B: The CRC (cyclic redundancy check) for error checking.

Obviously I can't show you the request/response of every function code, so have a look here. You can read about each function code and it's associated requests/responses.

The Errors! (Exceptions)

Okay we've sent the command and we've just realised we did something we shouldn't of. Will I write to the wrong address with the wrong function code, causing everything to break? No! Modbus will only let us do something that is allowed by the protocol.

Let's say the request is received without a communication error, but cannot be processed by the slave for another reason. The slave replies with an exception response. There a lots of these, you can read all about them here.
Some of the fun ones are -

  • 01 (01 hex): Illegal Function. Meaning you've used the wrong Functon Code.
  • 02 (02 hex): Illegal Data Address. Meaning that you're trying to write to the wrong area.
  • 03 (03 hex): Illegal Data Value. Meaning that you're writing the wrong type of value (like a Boolean to an Integer area).

Instead of the usual response mentioned above, you'll get something like this -

  • 11: The Slave Address (11 hex = address17 ).
  • 06: The Function Code 6 (Preset Single Register).
  • 01: The Exception Code.
  • 9A9B: The CRC (cyclic redundancy check) for error checking.

TCP vs RTU

We've pretty much covered everything, but one thing I wanted to cover is RTU vs TCP. Simply put, Modbus TCP is a Modbus RTU message transmitted with a TCP/IP wrapper and sent over Ethernet. The Slaves does not have a SlaveID since it uses an IP Address instead (there are some technicalities around this but I won't get into it).

A new 7-byte header called the MBAP header (Modbus Application Header) is added to the start of the message. This header has the following data:

  • Transaction Identifier: 2 bytes set by the Client to uniquely identify each request. These bytes are echoed by the Server since its responses may not be received in the same order as the requests.
  • Protocol Identifier: 2 bytes set by the Client, always = 00 00
  • Length: 2 bytes identifying the number of bytes in the message to follow.
  • Unit Identifier: 1 byte set by the Client and echoed by the Server for identification of a remote slave connected on a serial line or on other buses.

The Slave ID and CRC are removed from the packet.