Skip to content
Start free evaluation

Data model and addresses

A Modbus server keeps its values in four tables. A client reads and writes each table with its own function codes. This page is based on sections 4.2 (data encoding), 4.3 (data model) and 4.4 (addressing model) of the MODBUS Application Protocol Specification V1.1b3.

Table Size Access Read function Write functions Typical content
Coils 1 bit Read and write 01 05, 15 Output contacts, run commands
Discrete inputs 1 bit Read only 02 None Input contacts, sensor states
Input registers 16 bits Read only 04 None Measurements
Holding registers 16 bits Read and write 03 06, 16, 22, 23 Setpoints, commands
  • The tables are independent. Holding register 0 and input register 0 are different values.
  • The specification allows a device to map the tables onto overlapping memory, so some devices expose the same memory as coils and as registers. Check the device manual for the actual layout.
  • A client can’t write the input tables (discrete inputs and input registers). The device changes them itself.

One-based numbers and zero-based addresses

Section titled “One-based numbers and zero-based addresses”

Section 4.4 numbers the elements of each table from 1 to n, while a PDU addresses them from 0 to 65535. “Holding register 1” is therefore PDU address 0.

Device manuals often add a table prefix digit to the number. That notation is a manual convention: the prefix never travels in the PDU.

Manual notation Table Function PDU address
00001 Coils 01 0
00010 Coils 01 9
10001 Discrete inputs 02 0
30001 Input registers 04 0
40001 Holding registers 03 0
40108 Holding registers 03 107 (0x006B)
400001 (six digits) Holding registers 03 0

To convert a manual number:

  1. Use the prefix digit to pick the table and the function code.
  2. Subtract 1 from the remaining number.
  3. Put the result in the PDU as a 16-bit address.

Some manuals list zero-based addresses from the start. Look for a statement such as “address 0 = 40001” first. An off-by-one address returns the neighboring register without any error, which makes the mistake easy to miss. See also Wrong address.

Section 4.2 sends addresses, quantities and register values big-endian: the high byte goes first, so register value 0x1234 travels as 12 34.

Bit tables are packed eight bits to a byte, and the bit of the first address is the least significant bit of the first byte. This example writes 0xA5 to coils 0–7:

request PDU 0F 0000 0008 01 A5
| | | | +-- value 0xA5 = 1010 0101b
| | | +----- byte count 1
| | +---------- quantity 8
| +--------------- start address 0
+------------------ function 15 (Write Multiple Coils)
coil address 7 6 5 4 3 2 1 0
bit value 1 0 1 0 0 1 0 1 -> coils 0, 2, 5 and 7 are on

When the quantity isn’t a multiple of eight, the unused high bits of the last byte are zero.

A Modbus register holds exactly 16 bits. The specification doesn’t define signs, units, scaling or how 32-bit values span registers; every device chooses its own.

  • Signed 16-bit values: -5 travels as 0xFFFB (65531). The device manual tells you whether to read it as two’s complement.
  • Scaling: many devices send 23.1 °C as the integer 231.
  • 32-bit integers and floats use two registers in a device-specific order. For the IEEE 754 single-precision value 10.0 (0x41200000):
Layout Register n Register n+1 Meaning
ABCD (big-endian) 0x4120 0x0000 High word first.
CDAB (word swapped) 0x0000 0x4120 Low word first.

PDComm handles registers as 16-bit integers only. Your application assembles and splits 32-bit values the way the device manual specifies.

A PDComm endpoint acting as a server has its own data model:

  • A new endpoint holds 1024 zeroed entries per table, starting at address 0.
  • data_set changes part of one table, including the input tables. That simulates a changing device input; it doesn’t add a Modbus write function.
  • model_set replaces the start address and size of all four tables, the file records, the FIFO queues and the device identification objects in one step. A request outside the model gets exception 02 (Illegal Data Address).
  • data_get returns the whole current model as JSON.

The JSON command reference describes the commands. To try them, follow Observe addresses and frames.