Every Modbus device comes with a register map. It is usually a table in the user manual or a separate PDF that lists all the data points the device makes available — voltages, currents, setpoints, alarms, status bits, and configuration parameters.
Reading a register map sounds simple. But it is one of the most common sources of errors in Modbus systems. Engineers misread the addressing (off by one), choose the wrong function code, misinterpret the data type (unsigned vs signed vs float), or get the byte order wrong for 32-bit values.
The result: the SCADA system shows 0, garbage data, or the wrong value — and nobody can figure out why.
This guide teaches you how to read any Modbus register map correctly. It covers the four register types, the zero-based offset trap, data types and byte order, how to match registers to function codes, and how to handle real register maps from actual devices.
Table of Contents
1. What Is a Modbus Register Map
A register map is a table that documents every data point a Modbus device exposes. It tells you:
- What data is available — voltage, current, power, frequency, temperature, setpoints, alarms
- Where the data lives — the register address
- What type of register — holding register, input register, coil, or discrete input
- How to interpret the data — unsigned integer, signed integer, floating point, bitmask
- The scaling factor — for example, a value of 2345 might mean 234.5 V (scale factor = 0.1)
- Read/write access — whether you can only read the value or also write to it
Without the register map, you are guessing. And guessing with Modbus always leads to wrong data.
2. The Four Register Types
Modbus organizes data into four tables. Each table has its own address space and access rules.
| Register Type | Legacy Prefix | Address Range | Size | Access | Function Code (Read) | Function Code (Write) |
|---|---|---|---|---|---|---|
| Coils | 0xxxx | 00001–09999 | 1 bit | Read/Write | FC 01 | FC 05 / FC 15 |
| Discrete Inputs | 1xxxx | 10001–19999 | 1 bit | Read-Only | FC 02 | — |
| Input Registers | 3xxxx | 30001–39999 | 16 bits | Read-Only | FC 04 | — |
| Holding Registers | 4xxxx | 40001–49999 | 16 bits | Read/Write | FC 03 | FC 06 / FC 16 |
What Each Type Is Used For
- Coils (0xxxx): Digital outputs you can control — relay outputs, motor start/stop, valve open/close.
- Discrete Inputs (1xxxx): Digital inputs you can only read — limit switches, door contacts, alarm inputs.
- Input Registers (3xxxx): Analog inputs you can only read — live measurements like voltage, current, temperature.
- Holding Registers (4xxxx): General-purpose read/write registers — setpoints, configuration parameters, and often measurements too.
The Reality
Most modern devices map everything to holding registers (4xxxx). You use FC 03 to read and FC 06 or FC 16 to write. The four-table model exists in the spec, but many devices do not bother separating their data into different tables.
Always check the register map. If it says “holding register” or shows addresses starting with 4xxxx, use FC 03. If it says “input register” or 3xxxx, use FC 04.
3. The Zero-Based Offset: Why You Are Off by One
This is the single biggest source of Modbus configuration errors.
The Problem
The Modbus data model numbers registers starting at 1. Register 40001 is the first holding register.
The Modbus PDU (the actual bytes on the wire) addresses registers starting at 0. Address 0x0000 is the first holding register.
So when you want to read register 40001, you send address 0x0000 in the Modbus frame. When you want register 40002, you send 0x0001.
The rule: PDU address = register number − 1
Example
Your register map says: “Active Power is in register 40130.”
| What the manual says | What you send in the PDU |
|---|---|
| Register 40130 | Address 0x0081 (129 decimal) |
| Register 40001 | Address 0x0000 |
| Register 30001 | Address 0x0000 (but use FC 04) |
The Confusion
Some register maps already show the PDU address (zero-based). Others show the data model number (one-based). Some show both. Some do not tell you which one they are using.
How to tell:
- If the first register in the map is 0 or 0x0000 → the map shows PDU addresses (zero-based). Use these directly.
- If the first register is 1, 40001, or 30001 → the map shows data model numbers (one-based). Subtract 1 (and drop the prefix) to get the PDU address.
SCADA Software Behavior
Different SCADA software handles the offset differently:
- Some expect the data model number (40001) and subtract 1 internally.
- Some expect the PDU address (0) and send it directly.
- Some let you choose with a “zero-based” or “one-based” setting.
Always check your SCADA software documentation. If you configure register 40001 and get the data from register 40002, your software is not applying the offset — switch to zero-based or subtract 1 manually.
4. How to Match Registers to Function Codes
| Register Map Shows | Register Type | Read FC | Write FC |
|---|---|---|---|
| 0xxxx or “Coil” | Coil | FC 01 | FC 05 (single) / FC 15 (multiple) |
| 1xxxx or “Discrete Input” | Discrete Input | FC 02 | Not writable |
| 3xxxx or “Input Register” | Input Register | FC 04 | Not writable |
| 4xxxx or “Holding Register” | Holding Register | FC 03 | FC 06 (single) / FC 16 (multiple) |
If the register map does not specify the type and just shows plain numbers (e.g., “register 100”), try FC 03 first. Most devices use holding registers for everything.
For a full explanation of each function code with frame examples, see: Modbus Function Codes Explained with Real Examples
5. Reading a Real Register Map: Step by Step
Here is a typical register map entry from an energy meter:
| Register | Description | Data Type | Unit | Scale | R/W |
|---|---|---|---|---|---|
| 40001 | Voltage L1-N | UINT16 | V | ×0.1 | R |
| 40002 | Voltage L2-N | UINT16 | V | ×0.1 | R |
| 40003 | Voltage L3-N | UINT16 | V | ×0.1 | R |
| 40004–40005 | Active Power Total | FLOAT32 | kW | — | R |
| 40006–40007 | Reactive Power Total | FLOAT32 | kVAr | — | R |
| 40050 | CT Ratio Primary | UINT16 | — | ×1 | R/W |
| 40051 | CT Ratio Secondary | UINT16 | — | ×1 | R/W |
Step 1. Identify the Register Type
Addresses start with 4xxxx → these are holding registers. Use FC 03 to read and FC 06 / FC 16 to write.
Step 2. Calculate the PDU Address
Register 40001 → PDU address = 40001 − 40001 = 0 (0x0000) Register 40004 → PDU address = 40004 − 40001 = 3 (0x0003) Register 40050 → PDU address = 40050 − 40001 = 49 (0x0031)
Step 3. Determine How Many Registers to Read
- Voltage L1-N: 1 register (UINT16)
- Active Power Total: 2 registers (FLOAT32 uses 2 × 16-bit registers)
- To read all three voltages at once: start at address 0, quantity = 3
Step 4. Apply the Scale Factor
Your SCADA reads register 40001 and gets the raw value 2301. The scale factor is ×0.1.
Actual voltage = 2301 × 0.1 = 230.1 V
Step 5. Handle the Data Type
Register 40004–40005 is FLOAT32. You need to read 2 consecutive registers and combine them into a 32-bit IEEE 754 floating-point number. The register map should tell you the byte order (see Section 7).
6. Data Types Inside Registers
A single Modbus register is 16 bits. But the value it represents depends on the data type.
| Data Type | Size | Registers | Value Range | Example |
|---|---|---|---|---|
| UINT16 | 16 bits | 1 | 0 to 65,535 | Raw value 2301 = 2301 |
| INT16 | 16 bits | 1 | −32,768 to +32,767 | Raw value 0xFFF6 = −10 |
| UINT32 | 32 bits | 2 | 0 to 4,294,967,295 | Two registers combined |
| INT32 | 32 bits | 2 | −2,147,483,648 to +2,147,483,647 | Two registers combined |
| FLOAT32 | 32 bits | 2 | ±3.4 × 10³⁸ | IEEE 754 floating point |
| FLOAT64 | 64 bits | 4 | ±1.7 × 10³⁰⁸ | IEEE 754 double precision |
| ASCII | Variable | N | Text string | 2 characters per register |
Common Pitfalls
- UINT16 vs INT16: If a temperature register shows 65526 instead of −10, you are reading it as unsigned when it should be signed.
- Scale factors: A value of 2301 with scale ×0.1 means 230.1. A value of 2301 with scale ×0.01 means 23.01. Wrong scale = wrong reading.
- FLOAT32: If the value looks like 4.567E+15 or −0.0000001, the byte order is probably wrong.
For a deep dive into all Modbus data types, see: Modbus Data Types & Formats Guide
7. 32-Bit and Floating-Point Values: Byte Order Matters
When a value spans two registers, the order in which you combine them depends on the device manufacturer. There is no universal standard.
The Four Possible Byte Orders
For a FLOAT32 value using registers R1 (high word) and R2 (low word):
| Name | Byte Order | Also Called | Example |
|---|---|---|---|
| Big-Endian | R1 R2 (AB CD) | Standard, Motorola | Most common |
| Little-Endian | R2 R1 (CD AB) | Word-swapped | Siemens, some ABB |
| Mid-Big | R1 R2 (BA DC) | Byte-swapped | Rare |
| Mid-Little | R2 R1 (DC BA) | Byte-and-word-swapped | Rare |
How to Find the Right Order
- Read the register map — some manuals explicitly state the byte order.
- If not documented, read a known value. For example, read a voltage that the device displays as 230.5 V.
- Try each byte order in your SCADA software until the value matches the device display.
- Document the correct order for all future configuration.
Example
Registers 40004 and 40005 contain: 0x4368 and 0xC000.
| Byte Order | Combined Hex | Float Value |
|---|---|---|
| AB CD (Big-Endian) | 0x4368C000 | 232.75 |
| CD AB (Little-Endian) | 0xC0004368 | −2.004 |
If the device displays 232.75 V, big-endian is correct.
8. Bit-Level Data Inside Registers
Some registers pack multiple status flags or alarm bits into a single 16-bit word. The register map will show which bit means what.
Example: Alarm Register (40100)
| Bit | Description | 0 = | 1 = |
|---|---|---|---|
| 0 | Overvoltage alarm | Normal | Alarm |
| 1 | Undervoltage alarm | Normal | Alarm |
| 2 | Overcurrent alarm | Normal | Alarm |
| 3 | Overtemperature | Normal | Alarm |
| 4–15 | Reserved | — | — |
If the register reads 0x0005 (binary 0000 0000 0000 0101), bits 0 and 2 are set → overvoltage and overcurrent alarms are active.
How to Extract Bits in SCADA
Most SCADA software has a “bit extraction” function. You read the full 16-bit register with FC 03, then extract individual bits using masking:
- Bit 0: value AND 0x0001
- Bit 1: (value AND 0x0002) >> 1
- Bit 2: (value AND 0x0004) >> 2
9. Coil Maps vs Register Maps
Coils are single-bit values (ON/OFF). They use a different addressing space and different function codes.
| Feature | Coils (0xxxx) | Holding Registers (4xxxx) |
|---|---|---|
| Size | 1 bit | 16 bits |
| Read FC | FC 01 | FC 03 |
| Write FC | FC 05 (single) / FC 15 (multiple) | FC 06 (single) / FC 16 (multiple) |
| Typical use | Relay control, motor start/stop | Measurements, setpoints, configuration |
Some devices use coils for control outputs and holding registers for everything else. Others pack everything into holding registers and use bit extraction for digital values. Check the register map.
10. Common Register Map Formats You Will Encounter
Register maps come in different formats depending on the manufacturer. Here are the most common:
Format A: One-Based with Prefix
| Register | Description |
|---|---|
| 40001 | Voltage L1 |
| 40002 | Voltage L2 |
PDU address = register − 40001. Very clear, no ambiguity.
Format B: Zero-Based (PDU Address)
| Address | Description |
|---|---|
| 0 | Voltage L1 |
| 1 | Voltage L2 |
PDU address = address as shown. No math needed. But you must know these are holding registers (use FC 03).
Format C: One-Based without Prefix
| Register | Description |
|---|---|
| 1 | Voltage L1 |
| 2 | Voltage L2 |
Ambiguous. Is register 1 = PDU address 0 or PDU address 1? Check if the document says “zero-based” or “one-based.” If not stated, try both.
Format D: Hex Addresses
| Address | Description |
|---|---|
| 0x0000 | Voltage L1 |
| 0x0001 | Voltage L2 |
PDU address = hex value directly. Always zero-based.
Format E: Mixed Table with Function Code
| FC | Address | Description | Type |
|---|---|---|---|
| 03 | 0 | Voltage L1 | UINT16 |
| 04 | 0 | Current L1 | UINT16 |
This is the clearest format. It tells you the function code and the PDU address. No guessing.
11. Mistakes Engineers Make Reading Register Maps
| Mistake | What Happens | Fix |
|---|---|---|
| Not subtracting the offset | Reading register 40002 instead of 40001 | Check if your SCADA uses zero-based or one-based addressing |
| Using FC 03 for input registers | Exception code 02 on some devices | Check the register type. Use FC 04 for 3xxxx registers. |
| Reading FLOAT32 from one register | Garbage value | FLOAT32 needs 2 consecutive registers. Set quantity = 2. |
| Wrong byte order for 32-bit values | Value is completely wrong (e.g., −2.004 instead of 232.75) | Try all four byte orders. Match against the device display. |
| Ignoring the scale factor | Value is 10× or 100× too large or too small | Apply the scale factor from the register map. |
| Reading signed value as unsigned | Temperature shows 65526 instead of −10 | Set data type to INT16 (signed) in your SCADA. |
| Assuming all devices start at 40001 | First registers return zero or exception | Some devices start at 40000, others at 40100. Read the map. |
| Reading too many registers at once | Timeout or exception code 02 | Some devices limit reads to 50 or 100 registers per request. Split into smaller blocks. |
12. How to Test Your Register Map Configuration
Before connecting a device to production SCADA, always test the register map independently.
Step 1. Use a Standalone Modbus Tool
Use a free tool like ModbusPoll, QModMaster, or OpenModScan. Connect directly to the device and read individual registers to verify values match the device display.
Step 2. Start with Known Values
Read a register with a known physical value — for example, the device displays 230.1 V. Read the voltage register and verify:
- The raw value makes sense (e.g., 2301 with scale ×0.1)
- The data type is correct (UINT16, FLOAT32, etc.)
- The byte order matches (for 32-bit values)
Step 3. Test Edge Cases
- Read the first and last register in the map — verify no offset errors.
- Write a test value to a writable register — verify the device accepts it.
- Read a bitmask register — verify individual bits match the device status LEDs.
Step 4. Document Everything
Create a configuration sheet listing: register address, PDU address, function code, data type, scale factor, byte order, and verified value. This saves hours during SCADA configuration and future troubleshooting.
💡 Tip: Use the free Modbus Frame Decoder Tool to verify that your request frames are addressing the correct registers and that the response data matches your expected values.
Summary
A Modbus register map is your only guide to communicating with a device correctly. Every error — wrong address, wrong function code, wrong data type, wrong byte order — starts with misreading the register map.
The key things to remember:
- Subtract the offset — register 40001 = PDU address 0
- Match the register type to the function code — FC 03 for holding registers, FC 04 for input registers
- Check the data type — UINT16, INT16, FLOAT32 all look different for the same raw bytes
- Verify byte order for 32-bit values — there are four possible orders, only one is correct
- Apply the scale factor — raw value × scale = real value
- Test with a standalone tool first — never configure SCADA from the register map alone
Get the register map right, and Modbus is the simplest protocol you will ever work with. Get it wrong, and you will spend days debugging a problem that takes five minutes to fix.
