How to Read Modbus Holding Registers (FC03): A Practical Walkthrough
Reading a Modbus device comes down to one operation more than any other: read holding registers, function code 03. Voltage, current, power, setpoints, energy counters — almost every meaningful value a Modbus device exposes lives in a holding register, and FC03 is how you pull it. Get this one operation right and the rest of Modbus follows.
This walkthrough shows exactly what goes on the wire, how the slave replies, and the two gotchas — the off-by-one address and the 32-bit float byte order — that account for almost every "my reading is wrong" support ticket. For the full function-code reference, see Modbus Function Codes Explained.
What You Need Before You Start
- Transport. Modbus TCP (IP + port 502) or Modbus RTU (serial, typically USB-RS485). See RTU vs TCP.
- Slave ID. The device's Modbus address, 1–247. Every device on an RTU bus needs a unique ID.
- Register map. The vendor's document listing which register holds which value, its type, and scale.
- A master tool. Anything that issues FC03 — a software poll tool, a PLC, or a SCADA driver.
The FC03 Request Frame
A Modbus TCP master sends a request containing the slave ID, function code 0x03, the starting address, and the quantity of registers. To read holding register 40001 (protocol address 0) for 2 registers from slave 1:
Slave ID: 01
Function: 03 (Read Holding Registers)
Start addr: 0000 (protocol address, 0-based)
Quantity: 0002 (two registers = 4 bytes)
Over Modbus TCP this payload is wrapped in an MBAP header (transaction ID, protocol ID 0, length, unit ID) on TCP port 502. Over RTU it is one continuous frame terminated with a 2-byte CRC.
The FC03 Response
The slave replies with the function code, a byte count, and the data:
Slave ID: 01
Function: 03
Byte count: 04 (2 registers × 2 bytes)
Data: 00 7B 02 58 (register 0 = 123, register 1 = 600)
Each register is 16 bits, sent big-endian within the register (high byte first). That much is standardized. Where vendors diverge is how they pack values larger than 16 bits.
Reading 32-bit Values (Floats and Integers)
A 32-bit float or integer occupies two consecutive registers. Modbus does not define the order, so every vendor picks one. The four conventions, named by how the four bytes ABCD are arranged in memory:
- ABCD — Big-Endian (most common on Schneider, ABB)
- DCBA — Little-Endian (byte-reversed)
- BADC — Big-Endian, bytes swapped within each word
- CDAB — Little-Endian, words swapped (common on some Siemens/Mitsubishi mappings)
If your 230 V reading shows as 0.003 or a huge number, you have the wrong byte order. Cycle the four conventions until the value matches expectation. See Modbus Float Byte Order Explained for the full worked example.
Step-by-Step in a Poll Tool
- Open the master and create a connection (TCP: IP + port 502; RTU: serial port, baud, parity).
- Set the slave ID to the device's address.
- Enter function code 03, start address, and quantity.
- Poll. The grid fills with raw 16-bit values, refreshed at the poll interval.
- For 32-bit values, set the cell type to Float/INT32 and the byte order to match the vendor's map.
Read Registers on macOS
MacTools Modbus Poll issues FC03 (and every other standard code) on TCP and RTU, with per-cell type and byte-order selection and built-in device templates. $29.99 one-time.
Get Modbus Poll on the Mac App StoreFrequently Asked Questions
What is Modbus function code 03?
Function code 03 is Read Holding Registers. The master requests a contiguous block of 16-bit holding registers from a slave by specifying the slave ID, a start address (protocol address, 0-based), and the number of registers to read. The slave returns the byte count followed by the register values, high byte first (big-endian within each register).
How many holding registers can I read in one FC03 request?
The Modbus specification allows up to 125 holding registers (250 data bytes) in a single FC03 request. In practice the limit also depends on the slave's buffer size and the RTU frame timing. For values larger than 16 bits (floats, 32-bit integers), one logical value spans two or more registers.
Why does my 32-bit Modbus float read wrong?
A 32-bit float occupies two consecutive 16-bit registers, and vendors disagree on byte order. The four common word/byte orders are ABCD, DCBA, BADC, and CDAB (Big-Endian, Little-Endian, Big-Endian Byte Swap, Little-Endian Byte Swap). If your reading shows a tiny or nonsensical value, cycle through the four byte orders until it matches the expected engineering value.
Is holding register 40001 address 0 or 1?
40001 is the human-readable PLC address (the 4xxxx range denotes holding registers). The protocol address on the wire is 0-based, so 40001 maps to protocol address 0, 40002 to 1, and so on. The off-by-one is the most common Modbus addressing mistake.
Related: Continuous Monitoring
When the reads are correct, Voltrus SCADA takes over — polling, dashboards, alarms, historian. Lifetime license from $249.