Industrial protocols

Modbus addressing and function codes: why 40001 is register 0

A reference-style guide to Modbus data tables, 0-based versus 1-based addresses, the common function codes and exception responses, with the classic off-by-one trap explained.

Four data tables

A Modbus device exposes its data in four tables. Which one you read or write decides the function code and the size of each item.

TableItemAccessClassic referenceFunction codes
Coils1 bitRead / write0xxxx01, 05, 15
Discrete inputs1 bitRead only1xxxx02
Input registers16 bitRead only3xxxx04
Holding registers16 bitRead / write4xxxx03, 06, 16

Why 40001 is register 0

The five-digit “4xxxx” notation is a documentation convention: the leading digit names the table and the remaining digits count registers starting at 1. So 40001 is the first holding register. On the wire, however, the request carries a zero-based offset, so the first holding register is address 0x0000.

The classic example is holding register 40108. Its protocol address is 107, which is 0x006B. Documentation that says “read registers 108 to 110” and a frame that says “start address 0x006B, quantity 3” describe the same request.

This is the root of most off-by-one problems. Some vendors document 1-based references (40001), some document zero-based addresses (0, 1, 2), and some configuration tools add or subtract the offset for you. Registers above 9,999 use a six-digit form (400001) in many documents. Never guess: find out which convention your documentation and your software each use.

Common function codes

CodeNameLimit per request
01Read coils2,000 coils
02Read discrete inputs2,000 inputs
03Read holding registers125 registers
04Read input registers125 registers
05Write single coil (0xFF00 = on, 0x0000 = off)1 coil
06Write single register1 register
15 (0x0F)Write multiple coils1,968 coils
16 (0x10)Write multiple registers123 registers

Anatomy of a request

Read three holding registers starting at 40108 from unit 17 (0x11), over Modbus RTU:

11 03 00 6B 00 03 76 87
│  │  │     │     └─ CRC-16 (low byte first)
│  │  │     └─ quantity: 3 registers
│  │  └─ start address: 0x006B = 107 (register 40108)
│  └─ function 03: read holding registers
└─ unit (slave) address: 17

Over Modbus TCP the same request has no CRC and starts with a 7-byte MBAP header (transaction ID, protocol ID 0, length and unit ID):

00 01 00 00 00 06 11 03 00 6B 00 03

You can build both forms and check the CRC with the Modbus frame builder.

Exception responses

When a device cannot fulfil a request it replies with the function code plus 0x80, followed by an exception code. A reply of 11 83 02 means “function 03 failed: illegal data address”.

CodeMeaningTypical cause
01Illegal functionThe device does not support that function code
02Illegal data addressUnmapped register, or an off-by-one start address
03Illegal data valueQuantity outside the allowed range
04Server device failureThe device failed while handling a valid request

An “illegal data address” on the first register you try is the classic sign of a base-0 versus base-1 mix-up, or of reading across the end of a mapped block.

A short checklist before commissioning

  • Confirm whether your documentation is 0-based or 1-based, and whether your software adds an offset.
  • Confirm the table (coil, discrete input, input register, holding register) and use the matching function code.
  • Read one register whose value you know before reading the whole block.
  • For 32-bit values, settle the byte and word order: see 32-bit floats in Modbus.
  • Record every decision in the register map. The register map validation checklist gives you the sequence, and the choice between serial and Ethernet is covered in Modbus TCP vs RTU.
THE NEXT STEP

From calculation to implementation.

Let’s look at your machine, your data flow or your production goal together. Describe your situation in a few sentences and the ASP Dijital team will reply by email.

Talk to ASP Dijital