Skip to content
EnergyCalcHQ
Modbus Application Protocol V1.1b3 · data model vs PDU

Modbus Address Converter

Manuals print 40011. The protocol wants 10. Convert either way and get the right function code with it.

Inputs

I have
Address to send
10
Pass 10 to your client library, not 40011.
Manual number40011
Protocol address10
Function code3
FunctionRead Holding Registers
Protocol address, hex0x000A
Manual number, hex0x9C4B

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.

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.

BlockManualProtocolFunction
Coils00001–099990–99981
Discrete inputs10001–199990–99982
Input registers30001–399990–99984
Holding registers40001–499990–99983

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

BlockFunction codeHolds
Coils, 0xxxx1Read/write single bits
Discrete inputs, 1xxxx2Read-only bits
Input registers, 3xxxx4Read-only 16-bit words
Holding registers, 4xxxx3Read/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.

Past 9999, where the convention runs out

The classic five-digit notation has room for exactly 10000 registers per block — 40001 to 49999 for holding registers. The protocol itself addresses 65536 of them. Anything above the ten-thousandth register has no five-digit form at all, and vendors have solved that in incompatible ways.

The tidy solution is six-digit notation: 400001 to 465536, which is unambiguous and increasingly common on newer equipment. The untidy one is to keep five digits and let the block roll over, so register 10000 appears as 40000 or wraps into what looks like the input-register range. Both exist in shipping documentation.

If a device has more than about ten thousand registers and the manual quotes five digits, treat every address near the boundary as unverified until you have read a known value back. A register map that works perfectly for the first ten thousand entries and returns nonsense above them is the signature of this problem, not of a broken cable.

Slave ID is not part of the address

Two numbers travel in every request and they are routinely confused. The unit or slave identifier says which device on the bus should answer; the register address says what to read once it does. A device at slave ID 3 holding a value at 40001 is not addressed as 340001, and no amount of arithmetic on the register number will reach a different device.

On Modbus TCP the identifier survives even though it has nothing to route — every device has its own IP address. It is kept because gateways need it: a TCP-to-RTU bridge uses the unit identifier to decide which serial device on the far side the request belongs to. Talking straight to a native TCP device, the field is conventionally set to 1 or 255 and ignored, which is why code that hard-codes 1 works everywhere until the day it meets a gateway.

The practical consequence is worth remembering when a poll fails. An exception response means the device answered and refused — wrong register, wrong function code, wrong block. A timeout with no response at all usually means the request never reached the device, and the unit identifier is as likely a cause as the wiring.

Questions people ask

Every register I read is one place off. Why?
Because the manual's number and the protocol address are different things, and they differ by one more than you expect. The Modbus Application Protocol specification defines the address that travels in the frame as starting at zero within each block, while manuals print the older data model numbering. Holding register 40011 is protocol address 10 — subtract 40001, not 40000. The symptom is unmistakable once you know it: voltage appears where you expected current, current where you expected power, and the whole map is shifted by exactly one. The fix is one subtraction, not a rewrite.
Does my client library want 40011 or 10?
It depends on the library, and almost none of them say so on the first page. Most Python, Node and Go libraries take the protocol address, so read_holding_registers(10, 2) reads what the manual calls 40011. Some SCADA packages and PLC function blocks take the manual's number and do the subtraction internally. 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.
Function code 3 returns an exception. Is the address wrong?
Try function 4 before assuming that. 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. Meters are inconsistent about which block they publish measurements in — some put everything in holding registers, some split measurements from configuration, and a few mirror 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.
How are registers above 9999 addressed?
Inconsistently, and it is worth knowing before you trust a large map. The five-digit notation has room for exactly 10,000 registers per block, while the protocol addresses 65,536 — so anything past the ten-thousandth register has no five-digit form. The tidy answer is six-digit notation, 400001 to 465536, which is unambiguous and increasingly common. The untidy one is to keep five digits and let the block roll over. A map that works perfectly for the first ten thousand entries and returns nonsense above them is this problem, not a broken cable.
Is the slave ID part of the register address?
No, and they are routinely confused. The unit or slave identifier says which device on the bus should answer; the register address says what to read once it does. A device at slave ID 3 holding a value at 40001 is not addressed as 340001, and no arithmetic on the register number will ever reach a different device. On Modbus TCP the field survives even though IP does the routing, because a TCP-to-RTU gateway uses it to pick the serial device on the far side — which is why code that hard-codes 1 works everywhere until the day it meets a gateway.