Modbus and bit decomposition

The authors of modbus tables often use sixteen-bit registers not only for the transfer of analog values, but also for the transfer of binary signals, such as information about the status of the device (run / stop), faults (OK / alarm) and the like. When integrating these signals into the PLC, we have several options to read, write and process the data. We should always choose the simplest and most understandable one, even considering, for example, those who will work with our program in a few years in the role of service technicians.

The Modbus register, which therefore does not contain an analog value (temperature, pressure), but a set of bits, is usually transferred to the PLC as a word data type. This should indicate that it is not an integer with its own meaning, but a combination of bits - although of course in the modbus client we see the value as an integer or an unsigned integer.

As an example, let's take the Remak HVAC unit with the VCS control system. In its modbus table we find, among other things, the following:

It means that in register 13 we read the alarm states of the peripherals on individual bits, for example, bit 2 (third bit) in the True state means a burner failure. How to get this information into the software so that we ourselves, but also the service technicians who will work with the application software several years after the device was put into operation, are familiar with it?

There are several options:

  • reading individual bits using bit-oriented Modbus queries (if the server supports this access to the table),
  • reading the entire register and disassembling it in the PLC communication driver (if the programming software supports this),
  • read the entire registry and disassemble it in the application.

Reading and writing individual bits using bit-oriented Modbus queries

The modbus table must allow multiple modbus functions to access the same data:

For example, we can read the first bit of register 4 either by reading the entire register 4 and "separating" the first bit, or by a bit-oriented query directly on this bit. In the case of VCS, according to the documentation, this overlap does not exist, since bit functions such as Read coils (F01) and Read Discrete Input (F02) are used to transfer completely different values.

Why is the (bit-oriented) register number 49 entered when reading a single bit with the F01 function? In the case of reading the same data by different functions, the bits are numbered from the beginning of the table, so the first (word) register contains (from right to left, from the least significant bit) bits 1...16, the second 17...32, the third 33...48 and the first bit of the fourth register is therefore 49.

This is how we can work, for example, with the change-over bit of the FCR010 controller:

The bits in the register are numbered from 0 in this documentation, ie bit 5 is the sixth bit in the register.

The (bit-oriented) register number will be 150:

9 * 16 = 144 (number of bits in previous 9 registers)
144 + 6 = 150 (bit 5 is the 6th bit in register 10)

In the Merbon IDE we will create a reading group:


The data point with the change over signal then it looks like this:


The signal can be written with the F15 Write Multiple Coils bit function. The write group settings will then be as follows:


and the data point looks like this:


Unlike reading the entire register and disassembling it in the communication driver (see below), we do not need to worry about bit offsets when writing and reading with bit-oriented functions. Multibyte length is always 1, a data unit smaller than 1 byte cannot be sent.

Bit-oriented notations have the advantage that the other bits in the sixteen-bit register remain unchanged.

Reading the entire register and disassembling it in the PLC communication driver

This method is nice in that the variables are brought to the program already modified and it is not necessary to process them further in the application. If the programming software allows the creation of "hardware prototypes", predefined devices that can be inserted from the library, it is good to have specific signals already prepared in the prototype, corresponding to bits or their combinations.

So let's use the F03 function to read the entire register


and we create a variable that "picks" bit 5 from it. The variable is of type bool, of course.


The most difficult thing here is to correctly count the sequence of bits. We need to note the order in which registers and bytes arrive in a Modbus telegram:


Data Offset is set to 1 because we need to skip the first byte from the left (1 MSB). The bit offset is 5 because we need to skip the first 5 bits (taken within the byte from least significant to most significant, i.e. from right to left). The variables for the other bits would differ only in the value of the bit offset, or the data offset - which would be 0 if the bits were in the MSB of the byte.

Of course, it is also possible to load several 16-bit registers at once (there are four in the telegram in the picture) and select any bit within the entire telegram using offsets.

If we wanted to activate the mentioned change-over function - bit 5 - by writing the entire register (function F16, Write Multiple Registers), the other bits would also be overwritten, here specifically those that switch the outputs to manual mode. This could be a problem in certain situations.

For the VCS modbus table, the Burner Fault variable would have the following parameters:

Initial element: 13
Function: F04 (we know it by the notation 3x000...)
Number of elements: 1

and the variable would be set like this:

Offset gap for writing: 0 (we do not write)
Data offset: 1 (we skip the MSB byte)
Bit offset: 2 (we skip bits 0 and 1)
Multibyte lenght: 1
Multibyte order: 12345678 (we do not change the default order)

Some programs offer the option of masking the register (by which we filter out unwanted bits) and subsequent bit shifting (so that the variable takes on the values 0, 1, 2...), which is an elegant and fast way for this purpose.

Read the entire registry and disassemble it in the application

Sometimes, however, it is easiest to read the entire registry as word and break it down into individual bits only in the application. It is also probably the most readable for those who will work with the program after us. The entire F03 or F04 function register is read into a word type variable and this variable is fed to the bit decomposition function.

In the case of the Datakom D-300 diesel generator, we can read the states of the individual LED indicators:


In the application program, we would then reassemble the disassembled register with two bits for each LED into a variable indicating the state of the respective LED as an integer in the interval 0...3:


This approach is particularly convenient when the value we are interested in is bitwise somewhere "in the middle" of the register, surrounded by other bits whose states are not important for this variable. But the authors of modbus tables usually reserve an entire word (16-bit register) or at least a byte for a multi-state variable, so we can get the value already in the communication driver.

Again, especially when programming in the ST (structured text) language, the fastest - even if not so nicely readable at first glance - operation is masking and bit shifting.