Modbus Fundamentals

If you’re dealing with industrial protocols, you’ll certainly have seen Modbus packets in your network. But what are these packets used for, and what data do they contain?

Before we dive into Modbus, let’s have a quick look at an example. Let’s assume you have a meeting in your meeting room. You enter the room one hour before the meeting starts to have everything prepared, and you notice that it’s quite cold. Fortunately, there is a small display beside the door telling you that somebody set the room temperature to 19 °C.  Four presses on the button with the up-arrow and you’re quite confident that the room temperature will reach 23 °C until the meeting starts.

So, what happens in the background? Somewhere in the building, a PLC (Programmable Logic Controller) is responsible for controlling and orchestrating all the devices which play a role in cooling or heating the air which flows into the meeting room.

This PLC needs to read the setpoint from the small device on which you recently changed the setpoint from 19 to 23 °C. Afterwards, it spins up the fan’s rotation speed and tells a second PLC, which controls the heater, to increase the output.

Additionally, the technical staff oversees, controls and supervises all processes in the building using a SCADA system by acquiring data from the PLCs or sending data to them.

The conclusion of this example is that a lot of entities need to exchange information to have their job done. Historically, these communications have been realised using bus systems, as has Modbus using RS-232 or RS-485 (Modbus/RTU). (There also exists Modbus/ASCII, which uses ASCII characters, but we’ll not focus on this.)

To overcome the limitations of local buses, the Modbus protocol was slightly modified to be transported using TCP. Modbus/TCP was born.

Basically, the payload of Modbus/TCP equals Modbus/RTU, but with some necessary modifications.

The CRC field was dropped because TCP already cares about integrity. Additionally, an MBAP header (Modbus Application Protocol), which carries information about identifiers and data length, was prepended to the payload.

Terminology

Let’s talk about Modbus terminology. In Modbus, there are two types of devices. Devices which read from or write to other devices are called clients. Contrary, devices which receive and serve read or write requests are called servers. On serial lines, there can be only one client (formerly called master) but several servers (formerly called slaves), which all need to have a unique ID on the bus to be addressed properly (you may have noticed “Slave ID” in the figure above).

With Modbus/TCP, the information about a certain target ID for a particular message is obsolete as the TCP connection itself is already a one-to-one connection. Nevertheless, the ID field remains present as “Unit ID”, which becomes important for addressing if Modbus-to-Modbus-Gateways (e.g TCP <-> RTU) are used or if vendors use it as an additional identifier for their implementation.

Info: All the now discussed types are included in the Ultimate PCAP. Download and have a look at it by yourself! ;)

Functions and Data

Now, as we know how to reach a server, we should talk about function codes which are used to signal our intent about what to do (read/write) on one or multiple data entities. (We will only discuss the most common ones.)

  • Code 1: Read Coils
  • Code 2: Read Discrete Inputs
  • Code 3: Read Holding Registers
  • Code 4: Read Input Registers
  • Code 5: Write Coil
  • Code 6: Write Register
  • Code 15: Write Multiple Coils
  • Code 16: Write Multiple Registers

Modbus has basically four types of addressable data entities, each having their respective functions for reading and writing (if possible).

Coils represent switchable binary outputs. As they can be turned off and on, they are typically writable (until being write-protected) to set their state, and readable to retrieve their current state. Example: Relay output.

Discrete Inputs are binary inputs which are read-only. Example: Alarm input.

Input Registers allow read-only access to 2-byte registers, typically measurements. Example: Temperature sensor.

Holding Registers represent 2-byte registers with read and write access, typically measurements, setpoints or controller states. Example: Your 23°C setpoint.

Addressing

Now the tricky part, which can be a bit confusing for beginners. Because we need to read and write data, we also need to address a certain entity for doing this.

As we have already seen, there are four data entities (coils, discrete inputs, input registers, holding registers), each with 2-byte addresses, ranging from 0 – 65.535 (2^16).

A Modbus PDU contains the function code, the reference to the address (either the bit or register number) and the length. The length field determines how many consecutive bits or registers we want to read or write ahead of the address.

As you might suspect, reading address 44 using function code 3 may return a different value than when function code 4 is used. This is simply because function code 3 addresses a different data block (holding registers) than function code 4 (input registers) does.

Vendors and implementors often document addresses with prefixes per data block.

  • Prefix 0: Coils
  • Prefix 1: Discrete Inputs
  • Prefix 3: Input Registers
  • Prefix 4: Holding Registers

With this model, a vendor/implementor may refer to a measured temperature at address 4044. This address implies that the value of interest is stored in a holding register and should be read with function code 3. The actual on-wire address is 44 (see screenshot above).

A general recommendation is to use 6-digit representations. This prevents address ambiguity and therefore address 4044 from being interpreted as either holding register 44 or coil 4044 (coil prefix 0).

It’s also crucial to know about the indexing scheme. Let’s say you are interested in the very first holding register. Usually, it is located at register 0 but may be referred to as address 400000 (0-indexed) or 400001 (1-indexed).

As modern PLCs mostly have a complete process image stored in memory, usually a shared address space approach is used, which may be either shared globally or may be divided into a binary and a register address block. This means that a temperature stored at address 44 and it can be read using either function code 3 or 4 (because both read 2-byte registers), but using function code 1 would access the binary address block.

Take-away: Consult the vendor’s or implementor’s manual, really!

Data Types

Accessing coils or inputs is quite simple, as a single entity (address) can only be 0 or 1 (off or on). There is no need to distinguish between data types or representations. With registers, this is completely different.

Each register has a fixed length of one word (2 bytes). This is the most important information to keep in mind!

In a real-world scenario, we are interested in different types of values. A counter may be an unsigned integer which either fits in one or two bytes (UINT8, UINT16), so a single register is totally fine:

A floating-point value requires 4 bytes to be stored. In this case, we need two consecutive registers to hold such a value:

As Modbus can only read and write data at a certain address with a certain length, but has no information about the data type, the implementor and user must not only be aware of addresses, but also of data lengths and their interpretation.

The Wireshark Modbus dissector allows switching between different types of data representation:

Modbus/UDP and TLS

The standard implementation of Modbus over networks is Modbus/TCP. Additionally, Modbus can be encapsulated into UDP packets. The on-wire format is the same, but implementations may be easier on low-powered devices because of the lower complexity and memory requirements.

Contrary, to make it a bit more complex, Modbus/TCP can also be secured with TLS.

Conclusion

A Modbus client can read and write data from or to a Modbus server. The data is made up of a variable number of consecutive bits (off/on) or registers (2-byte values, words), referenced by a starting address. Data is organised in four different data blocks, each having their respective read and write functions (function code). A server may share the exact same data in different data blocks at the same address, but it depends on the implementation. Modbus does not care about the actual data and its representation. It’s up to you to have bytes interpreted in the way they should.

Key take-away: Read the manual! The more tables and figures, the better you’ll understand what the vendor or implementor did.

Photo by Scott Blake on Unsplash.

Leave a Reply

Your email address will not be published. Required fields are marked *