Modbus register addressing: 40001, 0-based and off-by-one errors

· By Vladimir Chemeris, developer of Modbus TCP FieldKit

Short answer: 40001 means the first holding register, and a Modbus frame carries it as address 0. The 4 picks the area, and the remaining digits count from 1. Subtract one and drop the prefix: 40108 becomes address 107.

Three numbers for one register

The Modbus specification defines two numbering schemes. Section 4.4 of the Application Protocol Specification V1.1b3 says it plainly: “In a MODBUS PDU each data is addressed from 0 to 65535”, while “in the MODBUS data Model each element within a data block is numbered from 1 to n”. An element numbered X travels in the PDU as X-1.

Vendors then add a third habit: the area prefix. That gives you these names for the same register:

Written as Style Means
0 or 0x0000 Protocol address What the frame carries
1 1-based register number The first element in the block
40001 5-digit reference First holding register
400001 6-digit reference First holding register, extended range

The prefix isn’t in the specification’s frame format. It comes from old Modicon documentation and stuck around because it also names the area.

Prefix Area Read with Write with
0xxxx Coils 01 05, 15
1xxxx Discrete inputs 02 none
3xxxx Input registers 04 none
4xxxx Holding registers 03 06, 16

The address converter does this arithmetic for the number you paste.

What the wrong number looks like on the wire

We ran a pymodbus 3.16.1 server with three holding registers at address 0: 100, 250 and 1024. A raw read of address 0, quantity 3, returned all three:

TX 00 01 00 00 00 06 01 03 00 00 00 03
RX 00 01 00 00 00 09 01 03 06 00 64 00 FA 04 00

Then we made the classic mistake and sent 40000 as the address, as if the prefix belonged in the frame. 0x9C40 is 40000:

TX 00 02 00 00 00 06 01 03 9C 40 00 01
RX 00 02 00 00 00 03 01 83 02

83 02 is exception 02, Illegal Data Address. You got lucky here. If the device has registers up there, you read the wrong data and get no error at all.

An off-by-one gives no error

Off-by-one is worse, because the device answers. Ask for address 1 when you wanted address 0, and you get 250 instead of 100. Nothing on screen says so. A 32-bit value is the nastiest case: start one register late and the app combines the low half of one value with the high half of the next, which often looks like a plausible number.

A test that settles it

  1. Pick a register with a value you can verify: a firmware version, a serial number, or a setpoint you can change on the device’s own display.
  2. Read a small window around the documented number, for example 5 registers starting two below it.
  3. Find the known value in the response. Its position tells you the offset.
  4. Write down the offset for this device family, and keep it in your notes or a saved profile.

Tools disagree about defaults, so check yours. mbpoll starts at reference 1 unless you pass -0 for PDU addressing. In FieldKit you choose 0-based, 1-based or PLC 4xxxx next to the connection and can switch without disconnecting.

The 5-digit limit

Five digits give you 9999 registers per area, but the protocol allows 65536. That is why manuals for larger devices use six digits: 400001 to 465536. If a manual shows 412345, read it as holding register 12345, which is protocol address 12344.

Sources

FAQ

Is Modbus 0-based or 1-based?

Both, at different layers. The protocol frame uses addresses 0 to 65535. The data model numbers elements from 1. Under the specification, an element numbered X travels in the PDU as X-1.

What address do I send for holding register 40001?

Address 0, with function code 03. The leading 4 selects the holding register area and is not part of the address sent on the wire.

How do I find out which numbering a manual uses?

Read one register whose value you know, such as a serial number or a setpoint you can change on the device. Try the documented number, then one lower. The one that returns the known value tells you the numbering.