In this lesson

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.

BlockSizeAccessFunction codesCoils0xxxx1 bitread and write01 / 05, 15Discrete inputs1xxxx1 bitread only02Input registers3xxxx16 bitread only04Holding registers4xxxx16 bitread and write03 / 06, 16
Four blocks, two sizes, two access rights. The function code picks the block, not the address.

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 datasheetWhere it comes fromExampleGoes out as
Five digits, 40001 to 49999Modicon legacy, not in the standard40108107 (0x006B)
Six digits, 400001 and upindustry convention, no normative source400108107 (0x006B)
Data model number, 1 to nthe standard, counted from 1108107 (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.

ByteValueFieldMeaning
103function coderead holding registers
200datastart address, high byte
36Bdatastart address, low byte
400dataquantity, high byte
503dataquantity, 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.

ByteValueFieldMeaning
103function coderead holding registers
206databyte count, 3 registers of 2 bytes
3 and 402 2Bdatafirst register, 555 decimal
5 and 600 00datasecond register, 0
7 and 800 64datathird register, 100 decimal

On the datasheet those three registers are 40108, 40109 and 40110. Now the rules:

  1. 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.
  2. The PDU address is the classic number minus the block offset minus 1. So 40001 goes out as 0x0000 and 40108 as 0x006B.
  3. The block digit is not in the message at all. The block follows from the function code.
40001Datasheet, classic notation0x0000Address on the wire (PDU)400010x0000400020x0001400030x0002401000x0063499990x270E401080x006B

Drop the block digit, then subtract one more.

40001 travels as 0x0000 and 40108 as 0x006B. Always one lower than the datasheet says.

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.

RequestBlock it readsDatasheet number
FC01 at 0x0000coils00001
FC02 at 0x0000discrete inputs10001
FC03 at 0x0000holding registers40001
FC04 at 0x0000input registers30001

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 saysWhat 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

ToolEnter
mbpoll-t 4 -r 1-0 -t 4 -r 0
QModMasterStart Address: 0Base Addr 1: 1
Modbus PollAddress: 0PLC Base 1: 40001
pymodbusread_holding_registers(0, count=1)
Home Assistantaddress: 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.

Type the address from your datasheet and read off what to enter in your own tool.

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. 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 -0 mbpoll counts from 1, so -r 1 means holding register 1, which the datasheet calls 40001.

  2. 2

    Read the same registers by PDU address

    Run the second command. The -0 switch puts mbpoll in zero based counting, so -r 0 is PDU address 0. Same 16 bits, requested a different way.

  3. 3

    Compare the two outputs

    They should be identical. That is your proof that -r 1 without -0 and -r 0 with -0 point at the same register, and that the block digit never left your desk.

  4. 4

    Break it on purpose

    Run the third command, which combines -r 1 with -0. You now get the next register instead. This is the field mistake, reproduced deliberately so that you recognise the symptom later.

  5. 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
Your datasheet says: 40108, Total active power. Which start address ends up in the frame?
One tool sends FC03 with start address 0x0000. On the same device a second tool sends FC04 with start address 0x0000. What can you say about the two readings?
You are reading a meter with Home Assistant. The datasheet puts the line voltage at 40001. You enter address: 40001 and the sensor stays empty or shows nonsense. What do you change first?
Why can the same PDU address 0x0000 mean four different things?

Stuck on a project?

Describe what you see and where it goes wrong. We will look at it and tell you what we would do.

Further reading in the knowledge base