Modbus register mapping is the convention that defines how a device exposes its bits and values, which function code reads or writes each one, and how the numbers in a datasheet translate into the addresses that actually travel on the wire. Most integration headaches (reading the wrong value, an off-by-one address, a float that looks like garbage) trace back to misunderstanding one of three things: the four data models, the 0-based protocol address versus the 1-based documentation address, and the byte and word order of multi-register values. This guide walks through each so you can map a device correctly the first time.
The four Modbus data models
The Modbus Application Protocol specification defines four primary tables of data. Two are single-bit and two are 16-bit registers. Two are read-only and two are read/write. Historically these map to a leading reference digit (0xxxx, 1xxxx, 3xxxx, 4xxxx) that you still see in many datasheets, even though that digit is documentation shorthand, not part of the actual frame.
| Data model | Reference prefix | Size | Access | Typical use |
|---|---|---|---|---|
| Coils | 0xxxx | 1 bit | Read / Write | Relay outputs, on/off commands, enable flags |
| Discrete Inputs | 1xxxx | 1 bit | Read only | Digital inputs, limit switches, status bits |
| Input Registers | 3xxxx | 16 bit | Read only | Live measurements: voltage, current, temperature |
| Holding Registers | 4xxxx | 16 bit | Read / Write | Setpoints, configuration, accumulators, and often measurements too |
The split is logical, not physical. Many real devices, including most Modbus power meters, place every measurement in holding registers rather than input registers because holding registers are universally supported by polling masters. The data model a device uses is a vendor decision, so always trust the device map over the textbook expectation. For the difference between the RTU and TCP transports that carry these models, see Modbus RTU vs Modbus TCP.
Function codes: matching the verb to the data
A Modbus request is a function code plus a starting address and a quantity. Each data model has its own read code, and the writable models add single and multiple write codes. These are the eight you handle in practice.
| Code (dec / hex) | Name | Acts on | Direction |
|---|---|---|---|
| 01 / 0x01 | Read Coils | Coils (0xxxx) | Read bits |
| 02 / 0x02 | Read Discrete Inputs | Discrete Inputs (1xxxx) | Read bits |
| 03 / 0x03 | Read Holding Registers | Holding Registers (4xxxx) | Read 16-bit words |
| 04 / 0x04 | Read Input Registers | Input Registers (3xxxx) | Read 16-bit words |
| 05 / 0x05 | Write Single Coil | Coils (0xxxx) | Write one bit |
| 06 / 0x06 | Write Single Register | Holding Registers (4xxxx) | Write one word |
| 15 / 0x0F | Write Multiple Coils | Coils (0xxxx) | Write bit block |
| 16 / 0x10 | Write Multiple Registers | Holding Registers (4xxxx) | Write word block |
Two pairings cause the most failed polls. Function code 04 reads input registers, not holding registers, so pointing FC04 at a 4xxxx map returns either nothing or an illegal data address. And there is no write code for input registers or discrete inputs because both are read-only by definition. If a master needs to write a value, that value must live in a coil or holding register.
The 0-based versus 1-based addressing offset
This single point is the most common cause of off-by-one errors in Modbus integration. The protocol data unit (PDU) that travels on the wire uses a 0-based address. The reference numbers in datasheets are usually 1-based and include the leading model digit. To convert a documented address to the PDU address, drop the model prefix and subtract one.
- Datasheet says holding register 40001 → PDU address 0 with function code 03.
- Datasheet says holding register 40108 → PDU address 107 (40108 minus 40001).
- Datasheet says discrete input 10005 → PDU address 4 with function code 02.
The trap is that some vendors already publish 0-based PDU addresses, some publish 1-based reference numbers, and some publish both in adjacent columns without labelling which is which. Before trusting any map, read one register whose value you can predict (a firmware version or a fixed nameplate value) and confirm whether you need the minus-one offset. If a known value appears one register too high or too low, you have found your base mismatch. This and related fault patterns are covered in troubleshooting Modbus communication errors.
16-bit registers and 32-bit values across two registers
A Modbus register is exactly 16 bits, so it holds an unsigned integer from 0 to 65535 or a signed integer from -32768 to 32767. Anything larger, including a 32-bit integer or an IEEE 754 single-precision float, must occupy two consecutive registers, and a 64-bit value occupies four. The protocol itself has no concept of data type. It moves 16-bit words, and the meaning is entirely a contract between device and master defined by the register map.
Because the spec never standardized how to order those two registers, four combinations exist in the field. The first variable is byte order inside each register (big-endian, the Modbus default, sends the high byte first). The second variable is word order: whether the high-order register comes first (big-endian word order) or the low-order register comes first (a word swap, often labelled “little-endian” or “Daniel/Enron” in tools).
| Order name | Register sequence | Byte layout for 0x12345678 | Common on |
|---|---|---|---|
| Big-endian (ABCD) | High word first | 12 34 / 56 78 | Modbus default, many meters |
| Little-endian (DCBA) | Low word first, bytes swapped | 78 56 / 34 12 | Some controllers |
| Big-byte / word swap (BADC) | High word first, bytes swapped | 34 12 / 78 56 | Certain PLC libraries |
| Word swap (CDAB) | Low word first, bytes intact | 56 78 / 12 34 | Schneider, many gateways |
The practical test: read a register pair whose true value you know, such as a meter reading a stable line voltage near 230 V. If the decoded float reads 230.1 you have the right order. If it reads a wild number like 4.7e34 or a negative value, try the word swap first because CDAB versus ABCD is the most frequent mismatch. Lock the order once, document it in the map, and apply it to every 32-bit field on that device. A configurable Modbus-to-MQTT bridge such as the SURIOTA SRT-MGATE-1210 gateway lets you set the word and byte order per register block so floats reach the broker already correctly assembled, which removes the guesswork from the upstream application.
Modbus exception codes
When a request is malformed or out of range, the slave replies with the function code OR-ed with 0x80 and a one-byte exception code. Recognising these speeds up debugging far more than packet captures alone.
| Code | Name | Usual cause |
|---|---|---|
| 01 | Illegal Function | Device does not support that function code |
| 02 | Illegal Data Address | Address or quantity outside the device map (often the 0/1 offset) |
| 03 | Illegal Data Value | Value or count not allowed for that register |
| 04 | Slave Device Failure | Internal device error during processing |
| 05 | Acknowledge | Request accepted, long processing in progress |
| 06 | Slave Device Busy | Device busy, retry later |
| 0B | Gateway Target Failed | Gateway reached the bus but the target slave did not respond |
An exception 02 on a read you believe is valid almost always means the addressing base is wrong. An exception 0B on a TCP-to-RTU bridge points downstream to the serial side, not to your TCP request. Once the map, function codes, and word order are settled, moving the assembled values to an IoT platform is the next step, covered in our Modbus RTU to MQTT gateway guide.
Frequently Asked Questions
Why does function code 03 fail but 04 works on the same device?
The device stores its measurements in input registers (3xxxx), which are read with function code 04. Function code 03 targets holding registers (4xxxx). If those addresses are not populated, the device returns an illegal data address exception. Match the function code to the data model listed in the register map.
How do I know if my device uses 0-based or 1-based addressing?
Read a register with a known, fixed value such as a firmware version or nameplate constant. If the value appears one address higher than documented, the map is 1-based and you must subtract one to get the PDU address. Many master tools have a checkbox for this offset.
My 32-bit float reads as a huge or negative number. What is wrong?
The two registers are almost certainly in the wrong word order. Swap the high and low register (CDAB versus ABCD) and decode again against a value you can predict, like a known voltage. If it is still wrong, also try swapping the bytes within each register.
Can I write to an input register or a discrete input?
No. Input registers (3xxxx) and discrete inputs (1xxxx) are read-only by definition in the Modbus specification, so no write function code exists for them. Writable data must reside in coils (using FC05 or FC0F) or holding registers (using FC06 or FC10).