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.
The four tables
Section titled “The four tables”| 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:
- Use the prefix digit to pick the table and the function code.
- Subtract 1 from the remaining number.
- 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.
Encoding
Section titled “Encoding”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 0bit value 1 0 1 0 0 1 0 1 -> coils 0, 2, 5 and 7 are onWhen the quantity isn’t a multiple of eight, the unused high bits of the last byte are zero.
Values wider than 16 bits
Section titled “Values wider than 16 bits”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.
The PDComm data model
Section titled “The PDComm data model”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_setchanges part of one table, including the input tables. That simulates a changing device input; it doesn’t add a Modbus write function.model_setreplaces 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_getreturns the whole current model as JSON.
The JSON command reference describes the commands. To try them, follow Observe addresses and frames.