Skip to content
EnergyCalcHQ
Modbus data model vs PDU address

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.