# Module 5: Modbus Addressing

> 0-based wire addresses vs 1-based documentation, and how off-by-one bugs are born.

**Phase:** Addressing & Data Meaning  
**Estimated time:** 6 min  
**Release status:** Published  
**Content version:** 1.0.0  
**Source:** https://learnmodbus.studioseventeen.io/lesson/modbus-addressing

## What you'll be able to do
- Convert documented (40001-style) addresses to PDU wire addresses correctly.
- Explain why the same register is called 40001 in one tool and 0 in another.
- Spot off-by-one bugs from a capture in under a minute.

## Three things people get wrong
1. **Subtracting 1 instead of 40001.** Documented holding 40001 → PDU 0, not 40000. The 4xxxx digit prefix marks the data table, not an offset.
2. **Confusing 'register number' with 'address'.** Always restate as 'documented address X → PDU address Y' before sending anything.
3. **Assuming all tools use the same convention.** Some tools are 1-based, some 0-based. Verify with a known-good register before any write.

## From the field
**The off-by-one that swapped a setpoint**

A SCADA integrator copied the documented address straight into a 0-based tool. The PLC happily wrote the requested value to the register one below the intended setpoint — which happened to be a calibration constant. Production drifted for two days before someone diffed the trends.

## References
- [Modbus Application Protocol V1.1b3](https://www.modbus.org/file/secure/modbusprotocolspecification.pdf) — Addressing model — section 4.4

## Lesson

# Module 5 Lesson: Modbus Addressing
## Learner Outcome

By the end of this module, learners can translate common Modbus documentation references such as `40001`, `40010`, and `400001` into the function code and zero-based PDU address used in a real request. They can also diagnose a likely off-by-one addressing problem without changing unrelated serial, TCP, Unit Identifier, byte-order, or scaling settings.

## Key Concepts

- The Modbus request carries a function code, starting PDU address, and quantity.
- PDU addresses are zero-based offsets from `0` to `65535`.
- Legacy prefixes such as `0xxxx`, `1xxxx`, `3xxxx`, and `4xxxx` are documentation conventions, not bytes sent on the wire.
- The function code selects the logical table.
- Human documentation is often one-based; protocol addressing is zero-based.
- Client tools differ in what they ask learners to enter.

## Key Terms

- Documented address: the register or point reference shown in a manual, map, tool, or worksheet.
- PDU address: the zero-based address field sent inside the Modbus request.
- Zero-based: counting starts at `0`, as the protocol address field does.
- One-based: counting starts at `1`, as many human-facing register maps do.
- Table prefix: a documentation convention such as `4xxxx`; it is not sent as a protocol byte.
- Address-entry mode: the convention a specific tool expects when the learner enters an address.

## Source-Grounded Explanation

Official Modbus behavior is simpler than most vendor tables make it look. A read request does not send the text `40010`. It sends a function code, such as `03` for Read Holding Registers, and a two-byte starting address, such as `00 09` for PDU address `9`.

The confusing layer is documentation. A traditional holding-register reference starts at `40001`, but the first PDU address in the holding-register table is `0`. That means a documentation row named `40010` usually points to PDU address `9`.

The prefix is not the table on the wire. The function code is the table selection. For example:

| Documentation Reference | Logical Table | Likely Function Code | First PDU Address To Try |
|---|---|---:|---:|
| `00017` | Coils | `01` read, `05` or `15` write | `16` |
| `10008` | Discrete inputs | `02` read | `7` |
| `30025` | Input registers | `04` read | `24` |
| `40010` | Holding registers | `03` read, `06` or `16` write | `9` |
| `400001` | Holding registers, 6-digit notation | `03` read, `06` or `16` write | usually `0` |

Field note: some devices, drivers, gateways, or dialects use different conventions. Treat those as documented implementation behavior, not as the default protocol rule. When the manual is ambiguous, record the assumption and test both likely offsets before changing unrelated settings.

## Practical Example

Device documentation:

| Name | Register | Access | Type | Scale | Unit |
|---|---:|---|---|---:|---|
| Line voltage L1-L2 | `40010` | Read | unsigned 16-bit | `0.1` | V |

Expected request:

| Item | Value |
|---|---|
| Transport | Modbus TCP or Modbus RTU; PDU fields are the same |
| Function code | `03` Read Holding Registers |
| Documented address | `40010` |
| Actual PDU address | `9` |
| Quantity | `1` register |
| Data type | unsigned 16-bit |
| Scale/unit | raw value times `0.1 V` |
| Expected normal response | function `03`, byte count `02`, two data bytes |
| Likely exception | `02` Illegal Data Address if the offset/range is not implemented |

Request PDU:

```text
03 00 09 00 01

03      function code: Read Holding Registers
00 09   starting PDU address: 9
00 01   quantity: 1 register
```

Normal response PDU:

```text
03 02 09 C4

03      function code echoed
02      byte count: 2
09 C4   raw register value: 2500 decimal
```

Engineering value:

```text
2500 * 0.1 V = 250.0 V
```

## Common Pitfalls

- Entering `40010` into a tool that already asks for `Read Holding Registers` and expects a zero-based address.
- Entering `10` when the tool expects PDU address `9`.
- Removing the `4` prefix but forgetting to subtract one.
- Treating `400001` as a literal PDU address. It is documentation notation, not a value that fits in the 16-bit protocol address field.
- Treating a wrong-but-plausible value as a byte-order problem before checking the exact requested address.
- Changing Unit Identifier, baud rate, parity, TCP port, or scale factor before testing the adjacent address.

## Instructor Flow

1. Start with the simple rule: function code selects the table; address selects the offset.
2. Show why `40010` usually becomes function `03`, address `9`.
3. Contrast three tool modes:
   - full documentation reference: learner enters `40010`;
   - one-based table number: learner enters `10`;
   - zero-based PDU address: learner enters `9`.
4. Make learners record the mode before judging the result.
5. Use the off-by-one lab before introducing byte order, word order, or scaling problems.

## Check-Your-Understanding

1. A vendor table lists input register `30025`, signed 16-bit, scale `0.1 degrees C`. What function code and PDU address should you try first?
2. A client tool has separate fields for function code and address. You select `03 Read Holding Registers`. The manual says `40001`. Should the address field usually be `40001`, `1`, or `0`?
3. You read `40010` and get a plausible but wrong value. What two address-base tests should you run before changing byte order or scaling?
4. Why is `400001` not a literal protocol address?

## Source Notes

- Official rule: Modbus Application Protocol Specification V1.1b3 defines function-code-based requests with address fields inside the PDU.
- Official rule: Modbus logical data tables and common public function codes are protocol concepts.
- Official citation targets: [official-citation-map.md](https://learnmodbus.studioseventeen.io/resource/official-citation-map) rows for addressing model and zero-based PDU addresses, four primary logical data tables, public function-code categories, read holding registers quantity limit, read input registers quantity limit, normal response vs exception response, and exception codes and exception response PDU shape.
- Documentation convention: legacy reference prefixes such as `0xxxx`, `1xxxx`, `3xxxx`, and `4xxxx` are not sent on the wire.
- Local traceability: see `sources/source-traceability-matrix.md`, Modules 5 Addressing.
- Tool behavior: address-entry modes differ by client, driver, and library. Any named tool example needs tool documentation or verified lab evidence before release.
- Field note: JBus-style or vendor-specific addressing conventions may appear in the field. Label them as exceptions or implementation notes.

## Completion Checkpoint

Before moving on, learners should convert a documented reference into function code, logical table, first PDU address to try, and quantity. They should also record the client/tool address-entry mode before changing Unit Identifier, serial settings, byte order, or scale.

## Reusable Assets

- [Addressing cheat sheet](https://learnmodbus.studioseventeen.io/resource/addressing-cheat-sheet)
- [Off-by-one addressing lab](https://learnmodbus.studioseventeen.io/lesson/modbus-addressing?page=lab-1)
- [Module 5 quiz](https://learnmodbus.studioseventeen.io/lesson/modbus-addressing?page=quiz)

## Diagram source

```mermaid
flowchart LR
  Doc["Vendor doc:<br/>40001 → 40010"]
  Doc -- "subtract 40001" --> Wire["PDU wire address:<br/>0 → 9"]
  Wire -- "FC 03, qty 10" --> Server
```

## Labs

### Lab 1

# Lab 06: Off-By-One Addressing
## Learner Outcome

Learners can prove whether a client tool is using documentation notation, one-based table numbering, or zero-based PDU addressing before changing unrelated settings.

## Prerequisites

- Module 2: request/response and PDU basics.
- Module 3: data tables.
- Module 4: function codes.
- Module 5 lesson through the worked `40010` example.

## Safety And Scope

This lab uses synthetic data only. Do not point these steps at production equipment. If a real client tool is used later, connect only to an isolated simulator or training device.

## Scenario

A manual says the value `Line voltage L1-L2` is at holding register `40010`, unsigned 16-bit, scale `0.1 V`.

Your simulator or provided table has:

| PDU Address | Raw Value | Intended Meaning |
|---:|---:|---|
| `9` | `2500` | Correct line voltage: `250.0 V` |
| `10` | `0` | Adjacent register used to expose off-by-one mistakes |

The learner must determine which tool input returns the correct value and why.

## Protocol Target

| Field | Value |
|---|---|
| Transport | Modbus TCP or paper/saved-byte exercise |
| Function code | `03` Read Holding Registers |
| Documented register | `40010` |
| Correct PDU address | `9` |
| Quantity | `1` |
| Data type | unsigned 16-bit |
| Scale/unit | raw value times `0.1 V` |
| Expected normal response | function `03`, byte count `02`, raw value `2500` |
| Expected failure mode | wrong-but-valid value from address `10`, or exception `02` if only address `9` is implemented |

## No-Hardware Procedure

1. Write the expected request PDU for the documented value `40010`.
2. Decode the expected response PDU `03 02 09 C4`.
3. Complete the worksheet table below for three possible client inputs.
4. Decide which client mode each input represents.
5. State the next troubleshooting step if all three attempts fail.

Worksheet:

| Attempt | Client Input | Assumed Tool Mode | PDU Address Sent | Expected Raw Value | Expected Engineering Value | Interpretation |
|---:|---|---|---:|---:|---:|---|
| 1 | `40010` | Full documentation reference | tool-dependent | tool-dependent | tool-dependent | Needs tool-mode evidence |
| 2 | `10` | One-based table item number | usually `9` if tool subtracts one | `2500` | `250.0 V` | Correct only in this tool mode |
| 3 | `9` | Zero-based PDU address | `9` | `2500` | `250.0 V` | Correct when tool expects protocol offset |

## Optional Simulator Track

The course-owned localhost smoke simulator now verifies the core PDU address `9` fixture:

```text
read_request_pdu: 03 00 09 00 01
read_response_pdu: 03 02 09 C4
```

After `lab-toolchain-bom.md` pins third-party learner tools, convert this paper lab into a tool-specific runnable lab:

1. Configure holding register PDU address `9` to raw `2500`.
2. Configure holding register PDU address `10` to raw `0`.
3. Read the value using each tool input mode the selected client supports.
4. Capture request bytes or tool logs showing the actual PDU address.
5. Save expected output screenshots or logs in the lab folder.

## Expected Observations

- If the client sends PDU address `9`, the raw value should be `2500`, which scales to `250.0 V`.
- If the client sends PDU address `10`, the learner may see `0.0 V` or another wrong-but-valid value.
- If the device does not implement the requested address, the server may return exception `02` Illegal Data Address.
- If there is no response at all, do not start with byte order or scaling. First verify transport reachability, server/unit, and function code.

## Troubleshooting Notes

- Wrong-but-valid value usually means the request reached a real register. Check address base and table selection first.
- Exception `02` means the server rejected the requested address or range. Check PDU address, quantity, and supported range.
- Timeout means no valid response came back before the client deadline. Check IP/port, serial settings, Unit Identifier/server address, and gateway path.

## Check Questions

1. Why is `40010` usually PDU address `9`?
2. Which field selects holding registers: the `4` prefix or function code `03`?
3. If address `10` returns a plausible but wrong value, what should you test next?
4. What evidence proves the actual PDU address sent by a client tool?

## Answer Key

1. Traditional documentation is usually one-based, so item 10 maps to zero-based offset 9.
2. Function code `03` selects holding registers. The `4` prefix is documentation notation.
3. Test the adjacent zero-based and one-based interpretations, record tool mode, then confirm quantity and table selection.
4. Request bytes, packet capture, raw client log, simulator log, or a tool display that shows the actual request address.

## Instructor Notes

- Require learners to show both documented reference and PDU address before accepting an answer.
- Treat `40010`, `10`, and `9` as tool-input hypotheses until request bytes or simulator logs prove the actual PDU address.
- If learners change scale or data type first, redirect them to address-base evidence and table/function selection.
- Keep third-party client behavior marked pending until the selected tool/version is verified in `lab-toolchain-bom.md`.

## Source Notes

- Official source: Modbus Application Protocol Specification V1.1b3 for PDU structure, function codes, address fields, and exception responses.
- Local source: `modbus-wiki.md` Addressing and Exception Responses.
- Local traceability: `sources/source-traceability-matrix.md`, Modules 5 Addressing.
- Tool behavior: course-owned localhost smoke simulator verifies the fixture response; third-party client UI behavior remains pending in `lab-toolchain-bom.md`.

## Knowledge check

# Module 5 Quiz: Modbus Addressing
## Questions

### Multiple Choice

1. A manual lists holding register `40001`. A client tool separately asks for function code and zero-based address. What address should you usually enter?
   - A. `40001`
   - B. `1`
   - C. `0`
   - D. `4`

2. Which part of a Modbus request selects the holding-register table?
   - A. The leading `4` in `40010`
   - B. Function code `03`
   - C. The TCP port
   - D. The Unit Identifier

3. A vendor table lists input register `30025`. What function code and first PDU address should you try first?
   - A. Function `03`, address `25`
   - B. Function `04`, address `24`
   - C. Function `04`, address `30025`
   - D. Function `02`, address `24`

4. A read of `40010` returns a plausible but wrong value. Which check should usually come before changing byte order?
   - A. Try the adjacent address-base interpretation.
   - B. Change TCP port from `502` to `802`.
   - C. Reverse every byte in the response.
   - D. Increase the quantity to the maximum supported value.

5. Why is `400001` not sent as a literal PDU address?
   - A. Modbus TCP forbids holding registers.
   - B. The PDU address field is a 16-bit offset and the prefix is documentation notation.
   - C. Function code `03` can only read one register.
   - D. It is a Modbus Security-only address.

6. A vendor table lists coil `00017`. What logical table, likely read function code, and first PDU address should you try?
   - A. Coils, function `01`, PDU address `16`.
   - B. Coils, function `01`, PDU address `17`.
   - C. Discrete inputs, function `02`, PDU address `16`.
   - D. Holding registers, function `03`, PDU address `17`.

7. A vendor table lists discrete input `10008`. What logical table, likely read function code, and first PDU address should you try?
   - A. Coils, function `01`, PDU address `8`.
   - B. Discrete inputs, function `02`, PDU address `8`.
   - C. Discrete inputs, function `02`, PDU address `7`.
   - D. Input registers, function `04`, PDU address `7`.

### Short Answer

1. Convert `00017` into logical table, likely read function code, and first PDU address to try.
2. Convert `10008` into logical table, likely read function code, and first PDU address to try.
3. Name two pieces of evidence that can prove what address a client actually sent.
4. Explain the difference between a wrong-but-valid value and exception `02` during address troubleshooting.

### Applied Scenario

A device manual says:

| Name | Register | Access | Type | Scale | Unit |
|---|---:|---|---|---:|---|
| Line voltage L1-L2 | `40010` | Read | unsigned 16-bit | `0.1` | V |

Your client reads a raw value of `0` when you expect about `2500`.

Tasks:

1. Write the request PDU you expected the client to send.
2. List two likely address-entry mistakes.
3. List one thing not to change until address mode is verified.

## Answer Key

1. Multiple choice: C. `40001` is usually the first holding-register item, so zero-based PDU address is `0`.
2. Multiple choice: B. Function code `03` selects holding registers; the prefix is documentation notation.
3. Multiple choice: B. `30025` is usually input register item 25, so function `04`, PDU address `24`.
4. Multiple choice: A. Off-by-one mistakes are common and should be tested before byte/word-order changes.
5. Multiple choice: B. `400001` is documentation notation; the protocol address is a 16-bit offset.
6. Multiple choice: A. `00017` is coil item 17; subtract one for the zero-based PDU, so function `01`, PDU address `16`.
7. Multiple choice: C. `10008` is discrete input item 8; subtract one for the zero-based PDU, so function `02`, PDU address `7`.

## Distractor Review Notes

| Question | Correct | Why The Distractors Are Wrong |
|---:|---|---|
| 1 | C | A sends the documentation reference, B is the one-based item number, and D confuses the prefix with the zero-based PDU address. |
| 2 | B | A is documentation notation, C is only the TCP service port, and D is transport or gateway routing context rather than table selection. |
| 3 | B | A uses the holding-register function, C sends the documented reference as a PDU address, and D uses the discrete-input function. |
| 4 | A | B changes an unrelated port, C changes byte order before proving the address, and D increases range risk instead of testing the address-base hypothesis. |
| 5 | B | A falsely excludes holding registers from TCP, C invents a one-register limit, and D treats extended documentation notation as Modbus Security behavior. |
| 6 | A | B forgets to subtract one for the zero-based PDU, C reads the coil as a discrete input, and D treats the `0` prefix as a holding register. |
| 7 | C | A puts a discrete input in the coil table, B forgets to subtract one for the zero-based PDU, and D reads the discrete input as an input register. |

Short answer:

1. `00017`: coil table, function `01` for read, first PDU address `16`. Writes would use function `05` or `15` if the coil is writable.
2. `10008`: discrete input table, function `02`, first PDU address `7`.
3. Valid evidence includes packet capture, raw client log, simulator log, request bytes, or a tool screen that displays the actual PDU address.
4. A wrong-but-valid value means the request may have reached an implemented but unintended register. Exception `02` means the server rejected the requested address or range.

Applied scenario:

1. Expected request PDU: `03 00 09 00 01`.
2. Likely mistakes: entering `10` into a zero-based address field, entering `40010` into a field that already selected holding registers, or forgetting to subtract one after removing the `4` prefix.
3. Do not change byte order, scaling, Unit Identifier, serial settings, or TCP port until address mode and requested PDU address are verified.

## Source Notes

- Official citation targets: [official-citation-map.md](https://learnmodbus.studioseventeen.io/resource/official-citation-map) rows for addressing model and zero-based PDU addresses, four primary logical data tables, public function-code categories, read coils quantity limit, read discrete inputs quantity limit, read holding registers quantity limit, read input registers quantity limit, normal response vs exception response, exception codes and exception response PDU shape, and IANA service name `mbap` on port `502` where port distractors appear.
- Tool address-entry modes are implementation-specific and still need selected tool/version evidence before learner release.

---
_Learn Modbus · learnmodbus.studioseventeen.io · Module 5/16 · v1.0.0 · Exported for offline / agent use._
