View all lessons
Part 1
Fundamentals
Part 2
Modbus RTU
- Lesson 5RS485 explained: A, B, common and termination20 min
- Lesson 6Baud rate and parity: why 8E1 is the default16 min
- Lesson 7The Modbus RTU frame byte by byte, plus CRC18 min
- Lesson 8Multiple Modbus devices on one RS485 bus16 min
- Lesson 9Read your first Modbus device with mbpoll22 min
- Lesson 10Modbus RTU troubleshooting: symptom to cause20 min
Part 3
Modbus TCP
Part 4
Advanced
- Lesson 16Writing to a Modbus device without breaking it18 min
- Lesson 17Word order and floats: same bytes, other value22 min
- Lesson 18Calculate your poll interval and bus load18 min
- Lesson 19Modbus security: the protocol will not help16 min
- Lesson 20Modbus integration: PLC, Home Assistant, cloud20 min
- Lesson 21Modbus commissioning checklist and cheat sheet18 min
Modbus addressing: from 40001 to the wire
Modbus addressing explained with a real frame. Convert 40001 to the address on the wire, learn the four data blocks and spot the classic off-by-one error.
What this lesson covers
- The four data blocks and the function codes that go with them
- From 40108 on the datasheet to address 107 on the wire
- What your tool means by address, and the test that proves it
Read first: Learn Modbus: why a 1979 protocol still runs
Modbus addressing is the point where 40001 on your datasheet turns into address 0 on the wire. The block digit and the plus one are documentation habits, not part of the message. After this lesson you can convert any register number from a datasheet into the value your tool needs, and prove you got it right with a test that takes half a minute.
Why your meter shows 2300 and your software shows 0
Because the tool did exactly what you typed, and what you typed was a documentation number instead of an address. The meter on the DIN rail holds 2300 in its voltage register, which is 230.0 V with a scaling factor of ten. Its datasheet lists that register as 40001. You enter 40001 in Home Assistant or in mbpoll, and you get an empty sensor.
A tool that counts from 1, such as mbpoll, sends that out as start address 40000, or 0x9C40 in hex; a tool that counts from 0, such as Home Assistant or pymodbus, sends 40001 itself, 0x9C41. On a small energy meter neither address exists, so the device answers with exception 02, illegal data address. On an inverter with a large register area you get a number back that looks plausible, and that is the worse outcome, because nothing warns you. The address you actually needed was 0.
For the register map as a look-up page, the guide to Modbus register maps is the reference. This lesson turns it into an operation you carry out yourself, on top of the request and response pattern from lesson 1 on how Modbus works.
The four data blocks side by side
Modbus has exactly four data blocks: two that hold single bits and two that hold 16 bit words. The function code decides which one you are talking to.
Each block can hold up to 65536 items. Read coils with FC01, discrete inputs with FC02, input registers with FC04 and holding registers with FC03. Coils and holding registers can also be written; discrete inputs and input registers cannot.
One nuance settles a lot of arguments. The standard states that it is perfectly acceptable, and very common, to regard all four tables as overlaying one another. So on one brand input register 30001 and holding register 40001 return the same value, and on the next brand they do not. Neither device is broken.
Three numbering schemes people mix up
The same 16 bits can carry three different numbers in documentation, and only one number travels over the wire.
| Notation on the datasheet | Where it comes from | Example | Goes out as |
|---|---|---|---|
| Five digits, 40001 to 49999 | Modicon legacy, not in the standard | 40108 | 107 (0x006B) |
| Six digits, 400001 and up | industry convention, no normative source | 400108 | 107 (0x006B) |
| Data model number, 1 to n | the standard, counted from 1 | 108 | 107 (0x006B) |
The conversion in three rules
Start with the request the standard itself publishes. A client wants three holding registers beginning at what a datasheet would call 40108. These five bytes are the PDU, the part that is identical on Modbus RTU and Modbus TCP.
| Byte | Value | Field | Meaning |
|---|---|---|---|
| 1 | 03 | function code | read holding registers |
| 2 | 00 | data | start address, high byte |
| 3 | 6B | data | start address, low byte |
| 4 | 00 | data | quantity, high byte |
| 5 | 03 | data | quantity, low byte |
Start address 0x006B is 107 decimal. There is no 4 in those five bytes and no 40108. The answer comes back in the same shape.
| Byte | Value | Field | Meaning |
|---|---|---|---|
| 1 | 03 | function code | read holding registers |
| 2 | 06 | data | byte count, 3 registers of 2 bytes |
| 3 and 4 | 02 2B | data | first register, 555 decimal |
| 5 and 6 | 00 00 | data | second register, 0 |
| 7 and 8 | 00 64 | data | third register, 100 decimal |
On the datasheet those three registers are 40108, 40109 and 40110. Now the rules:
- The PDU address is the data model number minus 1. The standard puts it in one line: a Modbus data numbered X is addressed in the PDU as X-1.
- The PDU address is the classic number minus the block offset minus 1. So 40001 goes out as 0x0000 and 40108 as 0x006B.
- The block digit is not in the message at all. The block follows from the function code.
Drop the block digit, then subtract one more.
Change one thing and the rest stays put. The example for input registers reads 04 00 08 00 01: function code 0x04, start address 0x0008, quantity 1. That is 8 on the wire, which a datasheet calls 30009, and the answer 04 02 00 0A holds one register with the value 10. Same arithmetic, different block, different function code.
The same address, four meanings
Because the message carries an address and a function code, and nothing that names a block.
| Request | Block it reads | Datasheet number |
|---|---|---|
| FC01 at 0x0000 | coils | 00001 |
| FC02 at 0x0000 | discrete inputs | 10001 |
| FC03 at 0x0000 | holding registers | 40001 |
| FC04 at 0x0000 | input registers | 30001 |
The legacy Modicon documentation is blunt about it: the function code field already specifies a holding register operation, therefore the 4XXXX reference is implicit. That is why "read address 0" is never a complete instruction while "read holding register 40001" is. Which codes exist and what each is allowed to do is set out in the Modbus function codes reference, and lesson 3 on function codes and exceptions covers what a device answers when you pick the wrong one.
What your tool means by "address"
Ask the tool, not the datasheet. mbpoll counts registers from 1 unless you add -0. pymodbus, libmodbus and the Home Assistant modbus integration all take the PDU address as you give it, counted from 0.
| What the manual says | What you type in mbpoll |
|---|---|
| 40001, classic notation | -r 1 |
| holding register 1 | -r 1 |
| address 0, or PDU 0x0000 | -r 0 -0 |
In Home Assistant the field is called address and it is passed through unchanged as the PDU start address, so 40001 from the manual becomes address: 0 with input_type: holding. The Home Assistant Modbus integration guide has the full configuration.
Some manufacturers have already done the conversion and say so. Fronius states that the address sent out is always one lower than the register number, so register 40001 is read at address 40000 (0x9C40). SolarEdge prints base 0 and base 1 as two columns side by side. Daikin needs three words: register base 0. When a datasheet says nothing, assume the classic number and settle it with the test below.
Some clients take the question off your hands. The Register Explorer in ModbusCloud Diagnostics shows the protocol address and the data model address of the same register side by side, so you can see which of the two you are typing. It is free and runs offline; mbpoll stays the route that works on any system.
The test that ends the guessing
Read address 0 and address 1, then compare both against what the datasheet claims sits at 40001. Half a minute of work, and the off-by-one is gone for good.
Register type
Notation used by your datasheet
- Classic, 5 digits
- 40001
- Classic, 6 digits
- 400001
- 1-based
- 1
- 0-based, decimal
- 0
- 0-based, hex
- 0x0000
- Function code to read
- 03
- Function code to write
- 06, 16
What to enter per tool
| Tool | Enter |
|---|---|
| mbpoll | -t 4 -r 1-0 -t 4 -r 0 |
| QModMaster | Start Address: 0Base Addr 1: 1 |
| Modbus Poll | Address: 0PLC Base 1: 40001 |
| pymodbus | read_holding_registers(0, count=1) |
| Home Assistant | address: 0input_type: holding |
Tools differ in their default notation. mbpoll counts from 1, pymodbus and Home Assistant from 0.
Being exactly 1 out gives you the neighbouring register. That returns a plausible number and no error message at all.
The converter does the arithmetic; the device settles the argument. If the value at PDU address 0 matches what the datasheet promises at 40001, your count is right. If the value at PDU address 1 matches it instead, the datasheet was already written in base 0 and every number in it is one lower than you assumed.
Common mistakes
Entering 40001 literally. A 1-based tool sends start address 40000 (0x9C40), a 0-based tool sends 40001 (0x9C41). Most devices answer exception 02 either way and you spend an hour on wiring and baud rates. On a device with thousands of registers you get a value that looks reasonable, which costs a day instead of an hour.
Only checking the start address. Exception 02 looks at start address plus quantity together. On a device with 100 registers, PDU addresses run from 0 to 99. Asking for 4 registers from address 96 works; asking for 5 fails, because the fifth would be register 100 and that does not exist.
Treating the block digit as part of the address. The 4 in 40001 is not in the message. The block follows from the function code, which is precisely why PDU address 0x0000 means four different things. Change the function code and you change the block; change the address and you stay in the same block.
Try it yourself
The goal is not to read a value. It is to run the same read twice by two different routes and watch both results come out identical. mbpoll and the pymodbus simulator are both free to use, including commercially. Wiring the adapter and setting mbpoll up is covered in full in the lesson on reading your first device; with no hardware at hand, run the same exercise on the server from the lesson on practising with a simulator.
mbpoll -m rtu -a 1 -b 9600 -P none -t 4 -r 1 -c 2 -1 /dev/ttyUSB0
mbpoll -m rtu -a 1 -b 9600 -P none -t 4 -r 0 -c 2 -0 -1 /dev/ttyUSB0
mbpoll -m rtu -a 1 -b 9600 -P none -t 4 -r 1 -c 2 -0 -1 /dev/ttyUSB0
- 1
Read two registers the way the datasheet counts
Run the first command and write down both values. On Windows the port is
COM3, on macOS it is/dev/cu.usbserial-XXXXXXXX. Without-0mbpoll counts from 1, so-r 1means holding register 1, which the datasheet calls 40001. - 2
Read the same registers by PDU address
Run the second command. The
-0switch puts mbpoll in zero based counting, so-r 0is PDU address 0. Same 16 bits, requested a different way. - 3
Compare the two outputs
They should be identical. That is your proof that
-r 1without-0and-r 0with-0point at the same register, and that the block digit never left your desk. - 4
Break it on purpose
Run the third command, which combines
-r 1with-0. You now get the next register instead. This is the field mistake, reproduced deliberately so that you recognise the symptom later. - 5
Check against the datasheet
Look up what your device publishes at 40001 and see whether step 1 delivers it. A reading of 2300 where you expect 230.0 V is a scaling factor. A serial number where you expect a voltage is an addressing error.
Without hardware. Start the pymodbus simulator, which serves Modbus TCP on port 5020, and run the same three reads against 127.0.0.1 instead of a serial port. pymodbus is free to use as well, including commercially.
pymodbus.simulator --modbus_server server --modbus_device device
mbpoll -m tcp -p 5020 -a 1 -t 4 -r 1 -c 2 -1 127.0.0.1
mbpoll -m tcp -p 5020 -a 1 -t 4 -r 0 -c 2 -0 -1 127.0.0.1
mbpoll -m tcp -p 5020 -a 1 -t 4 -r 1 -c 2 -0 -1 127.0.0.1
Expected result: two identical outputs, a third that has shifted by one register, and the insight that the tool did nothing wrong.
Summary
- A Modbus message carries a function code and an address from 0 to 65535, and nothing that names a data block.
- The PDU address is the data model number minus 1, so the datasheet number 40108 goes out as 107, or 0x006B.
- The block digit in 40001 is documentation only: FC01 to FC04 give PDU address 0x0000 four different meanings.
- mbpoll counts from 1 unless you pass
-0, while pymodbus and Home Assistant expect the PDU address as you type it. - Reading PDU address 0 and PDU address 1 and comparing them with the datasheet settles which numbering your device uses.
Check yourself
Four questions about this lesson. Every answer comes with an explanation.
Question 1 of 4
Want to see how it works?
The ModbusCloud Gateway reads the devices from this course without you programming a single register.