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.

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).