— Coils, Inputs & Registers
(Module 4 · Modbus Data Model & Function Codes)
Learning objectives
By the end of this chapter you will be able to
- Name, locate, and differentiate the four canonical Modbus data tables.
- Convert between “human” address notation (e.g.
40017
) and zero-based PDU addresses (0x0010
). - Explain the read/write permissions and typical payload widths of each table.
- Detect and fix the three most common addressing mistakes (off-by-one, map overlap, 32-bit packing error).
- Design a clean, future-proof register map for a brand-new device.
10.1 Why a Data Model?
Modbus frames are payload-agnostic; the protocol doesn’t know you’re sending “motor speed” or “tank level”. Consistent semantics come from a logical model that every master and slave agrees on:
Table | Abbrev. | Purpose | Typical Real-World Item |
---|---|---|---|
Coils | 0X | Read/Write single bits | Start pump, open valve |
Discrete Inputs | 1X | Read-only bits | Limit switch, E-stop status |
Input Registers | 3X | Read-only 16-bit words | Temperature, pressure |
Holding Registers | 4X | Read/Write 16-bit words | Set-point, PID gain |
Memory model mantra “Bits live in 0-/1-tables; words live in 3-/4-tables.”
10.2 Table 0X — Coils (Read/Write Bits)
Address space: 00001 – 09999
(spec) PDU offset: 0×0000 – 0×270E
Function codes: 01 Read Coils, 05 Write Single Coil, 15 Write Multiple Coils
Field note | Detail |
---|---|
Size | 1 bit each; packed LSB-first into bytes in responses. |
Latency | Fastest write path (single‐byte toggle). |
Use-cases | Start/stop, open/close, enable/disable, heartbeat LED. |
▶ Tip Group change-of-state coils into a contiguous block so a single FC15 can toggle up to 1 968 coils in one telegram.
10.3 Table 1X — Discrete Inputs (Read-Only Bits)
Address space: 10001 – 19999
PDU offset: 0×0000 – 0×270E
Function code: 02 Read Discrete Inputs
Field note | Detail |
---|---|
Electrical origin | Usually externally wired to the slave; PLC may scan card and mirror results here. |
Polling | Read-only → safest table to burst‐poll at high frequency. |
Edge detection | Master must implement software debouncing if contact bounces faster than poll rate. |
10.4 Table 3X — Input Registers (Read-Only 16-bit Words)
Address space: 30001 – 39999
PDU offset: 0×0000 – 0×270E
Function code: 04 Read Input Registers
Field note | Detail |
---|---|
Data width | 16 bits each; devices may store sensor value raw or scaled. |
Scaling patterns | • Integer with implicit decimal (e.g. 123 → 12.3 °C)• Fixed-point (Q15 , Q8.8 )• IEEE-754 floating-point (32-bit spans two regs). |
When to choose | Measurements that the controller must not overwrite (true read-only). |
10.5 Table 4X — Holding Registers (Read/Write 16-bit Words)
Address space: 40001 – 49999
PDU offset: 0×0000 – 0×270E
Function codes: 03 Read, 06 Write Single, 16 Write Multiple, 23 Read/Write Multiple
Field note | Detail |
---|---|
Versatility | Most‐abused table – holds everything from recipe strings to 32-bit floats to packed BCD. |
Multi-word values | 32-bit / 64-bit numbers span consecutive registers. |
Read-modify-write hazard | Two masters writing overlapping blocks may clobber each other. Use FC23 where possible or allocate non-overlapping address blocks per master. |
10.6 Addressing Conventions — Zero vs One Based
10.6.1 The dual-world dilemma
Human Notation (Datasheet) | PDU Fields (Hi byte + Lo byte) |
---|---|
Coil 00001 | Start Addr = 0x0000 |
Holding Register 40017 | Start Addr = 0x0010 |
Why? The spec’s “human” table numbers start at 1, but the PDU uses zero-based offsets.
10.6.2 Conversion formulae
PDU_offset = Human_number - Table_base
Human_number = PDU_offset + Table_base
Where Table_base
is 0
, 10000
, 30000
, or 40000
depending on table.
⚠ Pitfall #1 Many PLC IDEs (e.g. Siemens TIA) already expect zero-based values; if you paste datasheet numbers directly, every read will be off by 1.
10.6.3 Prefix vs full numeric style
Style | Example | Comment |
---|---|---|
Prefixed | 4x0017 | Unambiguous, preferred in mixed systems. |
Full numeric | 40017 | Common in legacy manuals, can mislead newcomers. |
10.7 Mapping Real-World Data to Registers
Tag | Type | Scaling | Suggested Table | Reason |
---|---|---|---|---|
Motor Run/Stop | bool | — | Coil 0X | Command must be writable. |
Motor RunningFB | bool | — | Discrete Input 1X | Read-only status. |
Tank Level | float32 (cm) | IEEE-754 | Holding 4X (2 regs) | Supervisor may need to write simulated value. |
Firmware Version | ASCII(8) | 2 chars per reg | Holding 4X | Rarely changed but writable by OEM flasher. |
▶ Design tip Allocate even addresses for the first word of every multi-word value—this preserves alignment if you ever migrate to 32-bit “extended registers”.
10.8 Common Data-Packing Patterns
Width | Registers | Word Order | Byte Order | Where found |
---|---|---|---|---|
32-bit float | 2 | Big > Little (Modbus default) | Big > Little | Schneider drives |
32-bit float (“ABCD”) | 2 | Big | Big | Wago, Beckhoff |
32-bit inverted words (“BADC”) | 2 | Swapped | Big | Rockwell legacy |
32-bit unsigned int | 2 | Big | Big | Energy meters kWh |
64-bit double | 4 | Big–little pair | Big | Newer RTUs |
Byte-swap cheatsheet
# Python helper: convert two Modbus regs to float32 with possible word swap
def regs_to_float(reg_hi, reg_lo, swap=False):
import struct
words = [reg_hi, reg_lo]
if swap:
words.reverse()
raw = struct.pack('>HH', *words) # big-endian words
return struct.unpack('>f', raw)[0]
10.9 Troubleshooting Address & Data Errors
Symptom | Root cause | Quick test | Fix |
---|---|---|---|
All values zero | Off-by-one start address | Read 40000 and 40001 ; one will show expected | Subtract 1 from start register |
Value 256× too high | Byte-swap | Swap words in logic | Adjust word order toggle |
Coil write appears but resets | Writing to Discrete Input (read-only) | Check FC05 response; slave returns exception? | Use address 0X not 1X |
High word stuck at 0xFFFF | Signed/unsigned mismatch | Compare hex dump vs datasheet | Treat as signed 16-bit |
10.10 Best-Practice Register-Map Design
- Freeze the map early — downstream SCADAs will hard-code it.
- Group by poll frequency – fast-changing telemetry in one contiguous block, slow config in another.
- Avoid bit-packing inside words – modern bandwidth is cheap; clarity beats density.
- Reserve 20 % head-room at the end of each table for future tags.
- Publish a spreadsheet with: address, mnemonic, units, data-type, scaling, R/W flag, last-changed date.
(A ready-to-use Excel template is provided in Appendix A4.)
Chapter Recap
- Four tables = two bit domains (0X/1X) + two word domains (3X/4X).
- Datasheet numbers start at 1; PDUs start at 0 → watch the offset!
- Holding registers are workhorses but risky without a clear write policy.
- Multi-word data must document word + byte order to avoid endian hell.
- A disciplined register map today saves months of retro-fits tomorrow.
[Placeholders for your media team]
ID | Visual idea |
---|---|
Fig-10-1 | 4-table “memory map” heat-map graphic |
Fig-10-2 | Off-by-one conversion diagram |
Fig-10-3 | 32-bit float packing (normal vs swapped) |
Fig-10-4 | Sample best-practice register sheet screenshot |
Next up: Chapter 11 – Mastering Modbus Function Codes — The Verbs of Communication, where we’ll pair the data tables you just learned with the exact opcodes that read, write, and diagnose them.