Modbus Address Converter
Manuals print 40011. The protocol wants 10. Convert either way and get the right function code with it.
Inputs
Check your library first. Some clients take the manual's number and subtract the offset for you. If yours does, pass 40011 instead. Read one register you can verify before trusting a whole map.
| Project | Circuit ref | ||
| Prepared by | Date | ||
| Checked by | Date |
Modbus Address Converter · Modbus data model vs PDU address · EnergyCalcHQ · energycalchq.com
Preliminary calculation. The figures behind it are representative values for the stated conditions, not a substitute for the current edition of the standard or the manufacturer's published data. Verify before issuing for construction. Not a substitute for a qualified engineer or a protection study.
For page numbers, keep Headers and footers ticked under More settings in the print dialog.
Two numbering systems, one register
Modbus documentation uses two conventions and rarely says which one it means.
The older data model numbering groups registers by type into blocks — coils from 1, discrete inputs from 10001, input registers from 30001, holding registers from 40001. It is what most manuals print because it is readable.
The protocol address is what actually travels in the request, and it starts at 0 within each block. The function code already says which block you mean, so the offset is redundant on the wire.
| Block | Manual | Protocol | Function |
|---|---|---|---|
| Coils | 00001–09999 | 0–9998 | 1 |
| Discrete inputs | 10001–19999 | 0–9998 | 2 |
| Input registers | 30001–39999 | 0–9998 | 4 |
| Holding registers | 40001–49999 | 0–9998 | 3 |
The off-by-one that catches everyone
Holding register 40011 is protocol address 10. Not 11, and not 40011.
The symptom is unmistakable once you know it: every value you read belongs to the register next door. Voltage shows up where you expected current, current where you expected power. If your whole map is shifted by exactly one, this is why — and the fix is one subtraction, not a rewrite.
Your library may already do this
Client libraries disagree. Most — pymodbus, modbus-tk, node-modbus — take the protocol address, so you subtract. Some HMI and SCADA packages take the manual's number and subtract internally, so passing the protocol address there reads the wrong register.
Settle it once with a register whose value you can predict. Line voltage is ideal: read it, and if you get something near 230 or 415 you have the convention right.
Input or holding?
Input registers (function 4) are read-only measurements. Holding registers (function 3) are readable and writable, and hold configuration such as CT ratio, baud rate and slave address.
Many energy meters expose the same measurement in both blocks. If function 3 returns an exception, try function 4 before assuming the address is wrong — plenty of meters publish measurements only as input registers.
Why the offset exists at all
Modbus dates from 1979, and its documentation numbered registers from 1 with a leading digit for the block: coils at 0xxxx, discrete inputs at 1xxxx, input registers at 3xxxx, holding registers at 4xxxx. That is the data model, and it is what a meter manual prints because that is what the manual has always printed.
What travels on the wire is different. The protocol frame carries a function code, which already says which block you mean, and a 16-bit address that starts at zero. The leading digit is redundant and the 1 was never sent. So 40011 becomes function code 3, address 10 — subtract 40001, not 40000.
Your library has already decided
The confusion is not really about Modbus. It is that client libraries disagree about which number they want, and almost none of them say so on the first page of the documentation.
- Protocol address — most Python, Node and Go libraries.
read_holding_registers(10, 2)reads what the manual calls 40011. - Data model number — some SCADA packages and PLC function blocks, which do the subtraction for you.
Guessing costs a day. Reading one register you can verify settles it in a minute: ask for the register the manual says holds line voltage. If you get something near 240 or 415, your library takes the number you gave it. If you get zero or an exception, try the other form.
Function codes, and reading the wrong block
| Block | Function code | Holds |
|---|---|---|
| Coils, 0xxxx | 1 | Read/write single bits |
| Discrete inputs, 1xxxx | 2 | Read-only bits |
| Input registers, 3xxxx | 4 | Read-only 16-bit words |
| Holding registers, 4xxxx | 3 | Read/write 16-bit words |
Meters are inconsistent about which block they expose measurements in. Some put everything in holding registers, some split measurements into input registers and configuration into holding registers, and a few mirror the same values in both. Reading the right address in the wrong block returns an illegal data address exception if you are lucky, and a plausible wrong number if you are not.