32-bit floats in Modbus: ABCD, CDAB, BADC and DCBA byte orders
Why a perfectly valid register pair turns into nonsense, how IEEE 754 single precision is split across two registers and how to find the right byte and word order.
· 4 minA reference-style guide to Modbus data tables, 0-based versus 1-based addresses, the common function codes and exception responses, with the classic off-by-one trap explained.
A Modbus device exposes its data in four tables. Which one you read or write decides the function code and the size of each item.
| Table | Item | Access | Classic reference | Function codes |
|---|---|---|---|---|
| Coils | 1 bit | Read / write | 0xxxx | 01, 05, 15 |
| Discrete inputs | 1 bit | Read only | 1xxxx | 02 |
| Input registers | 16 bit | Read only | 3xxxx | 04 |
| Holding registers | 16 bit | Read / write | 4xxxx | 03, 06, 16 |
The five-digit “4xxxx” notation is a documentation convention: the leading digit names the table and the remaining digits count registers starting at 1. So 40001 is the first holding register. On the wire, however, the request carries a zero-based offset, so the first holding register is address 0x0000.
The classic example is holding register 40108. Its protocol address is 107, which is 0x006B. Documentation that says “read registers 108 to 110” and a frame that says “start address 0x006B, quantity 3” describe the same request.
This is the root of most off-by-one problems. Some vendors document 1-based references (40001), some document zero-based addresses (0, 1, 2), and some configuration tools add or subtract the offset for you. Registers above 9,999 use a six-digit form (400001) in many documents. Never guess: find out which convention your documentation and your software each use.
| Code | Name | Limit per request |
|---|---|---|
| 01 | Read coils | 2,000 coils |
| 02 | Read discrete inputs | 2,000 inputs |
| 03 | Read holding registers | 125 registers |
| 04 | Read input registers | 125 registers |
| 05 | Write single coil (0xFF00 = on, 0x0000 = off) | 1 coil |
| 06 | Write single register | 1 register |
| 15 (0x0F) | Write multiple coils | 1,968 coils |
| 16 (0x10) | Write multiple registers | 123 registers |
Read three holding registers starting at 40108 from unit 17 (0x11), over Modbus RTU:
11 03 00 6B 00 03 76 87
│ │ │ │ └─ CRC-16 (low byte first)
│ │ │ └─ quantity: 3 registers
│ │ └─ start address: 0x006B = 107 (register 40108)
│ └─ function 03: read holding registers
└─ unit (slave) address: 17
Over Modbus TCP the same request has no CRC and starts with a 7-byte MBAP header (transaction ID, protocol ID 0, length and unit ID):
00 01 00 00 00 06 11 03 00 6B 00 03
You can build both forms and check the CRC with the Modbus frame builder.
When a device cannot fulfil a request it replies with the function code plus 0x80, followed by an exception code. A reply of 11 83 02 means “function 03 failed: illegal data address”.
| Code | Meaning | Typical cause |
|---|---|---|
| 01 | Illegal function | The device does not support that function code |
| 02 | Illegal data address | Unmapped register, or an off-by-one start address |
| 03 | Illegal data value | Quantity outside the allowed range |
| 04 | Server device failure | The device failed while handling a valid request |
An “illegal data address” on the first register you try is the classic sign of a base-0 versus base-1 mix-up, or of reading across the end of a mapped block.
Let’s look at your machine, your data flow or your production goal together. Describe your situation in a few sentences and the ASP Dijital team will reply by email.