— The Verbs of Communication
(Module 4 · Modbus Data Model & Function Codes)
Learning objectives
By the end of this chapter you will be able to …
- Enumerate every public Modbus function code (FC01 – FC24, FC43) and know its legal data tables.
- Decode request and response PDUs byte-by-byte for all high-volume codes (FC01/02/03/04/05/06/15/16/23).
- Recognise diagnostic and file-access op-codes (FC07/08/20/21) and decide when not to use them.
- Implement best-practice polling strategies that slash bandwidth by 60 % and avoid “write-clobber” race conditions.
- Troubleshoot exception frames; map each exception code to a probable root cause and field fix.
11.1 Why function codes matter
A Modbus message has only two semantic fields: Function Code + Data.
The code is the verb; it declares what the master wants the slave to do with the data addressed in Chapter 10.
Concept lens Data tables = nouns; Function code = verb.
e.g. “Read (verb) 16 Holding Registers (noun) starting at 40001.”
11.2 Function-code landscape
Block | FC | Verb | Target table | Supported by |
---|---|---|---|---|
Bit Read | 01 | Read Coils | 0X (RW bits) | 99 % |
02 | Read Discrete Inputs | 1X (RO bits) | 95 % | |
Bit Write | 05 | Write Single Coil | 0X | 95 % |
15 (0Fh) | Write Multiple Coils | 0X | 90 % | |
Word Read | 03 | Read Holding Regs | 4X | 100 % |
04 | Read Input Regs | 3X | 98 % | |
Word Write | 06 | Write Single Reg | 4X | 98 % |
16 (10h) | Write Multiple Regs | 4X | 95 % | |
Combo | 23 (17h) | Read/Write Multi-Reg | 4X | 70 % |
Diagnostics | 07 | Read Exception Status | n/a | PLC-class |
08 | Diagnostics (various sub-functions) | n/a | Rare | |
File / Mask | 20 | Read File Record | File space | PLC, RTU |
21 | Write File Record | — | ||
Encapsulated | 43 | MEI (Encap. Interface) | Device ID / CANopen map | Newer slaves |
(Figure 11-1 placeholder: “Function-code universe” mind-map.)
11.3 Deep dive: core public function codes
11.3.1 FC 01 – Read Coils (bits RW)
Field | Request | Response |
---|---|---|
Addr Hi/Lo | Start coil offset | echoes |
Qty Hi/Lo | # coils (1 – 2 000) | echoes |
— | — | ByteCnt then packed bits (LSB = first coil) |
Bandwidth tip – always read coils in groups of 8/16/32 so the packed byte boundary matches polling block; avoids padding.
11.3.2 FC 03 – Read Holding Registers
Byte | Request | Example |
---|---|---|
0-1 | Start Addr Hi/Lo | 0x00 0x10 (40017 human) |
2-3 | Quantity Hi/Lo | 0x00 0x06 (6 regs) |
Response – ByteCount = Qty × 2, then 2 × Qty data bytes.
Off-by-one reminder – start address is zero-based offset into 4X table.
11.3.3 FC 15 (0Fh) – Write Multiple Coils
Payload formula:
ByteCnt = ⌈Qty / 8⌉
FrameLen = 1 Addr 1 FC 2 Start 2 Qty 1 ByteCnt N(Bytes) 2 CRC
Write masks inside payload bit-pack; unused trailing bits must be zero.
11.3.4 FC 23 (17h) – Read/Write Multiple Registers
One frame, two operations.
- Request PDU contains read start/qty + write start/qty + write bytes.
- Response PDU returns only read section.
Use when you must read-modify-write without race risk (e.g. HMI adjusting PID but needing current value first).
(Listing 1: Python pymodbus
FC23 call.)
11.4 Diagnostic & rare codes (overview)
FC | Sub-func | Purpose | Replaced by |
---|---|---|---|
07 | — | Return coil & DI summary in one byte | Vendor-specific heartbeat |
08 | 00 | Echo loopback | ICMP ping in TCP world |
08 | 04 | Force listen-only mode | Not advisable; can brick slave |
20/21 | — | PLC file record ops | Ethernet/IP, S7comm |
43/14 | Read Device ID | Returns model/serial in ASCII | Good for asset mgmt |
Most modern OT stacks ignore 07/08; FC43/14 is worth enabling for automatic asset inventory.
11.5 Exception Responses Refresher
(See Chapter 13 for full table.)
Code | Typical trigger | Field remedy |
---|---|---|
01 Illegal Function | Master polled FC05 but slave is RO sensor | Change poll list |
02 Illegal Data Address | Offset beyond map | Fix register map |
03 Illegal Data Value | Qty = 0 or > spec | Break burst into smaller chunks |
06 Slave Busy | PLC executing flash update | Increase retry back-off |
0B Gateway Target Fail | Serial slave offline | Check RS-485 t-line |
Exception frames consume far less bandwidth than timeouts; pollers should handle them gracefully (no 5 s reconnect penalty).
11.6 Polling & write strategies
Scenario | Best FC | Block size | Why |
---|---|---|---|
HMI screen – 50 coils scattered | 01 (coils) + map them contiguous | 64 | One frame ≈ 10 ms |
Historian – log 1 000 analogs | 03 (holding) or 04 (input) | 125 per frame (max) | Lowest overhead |
Set 10 set-points atomically | 23 Read/Write | ≤ 10 | Avoid racing another master |
Toggle single output | 05 | 1 | Quick, deterministic |
Bandwidth rule
Cycle time ≈ Frames × (Header + Payload) ÷ link speed + TCP/Rtu gap.
Aggregating registers is the #1 lever.
11.7 Implementation patterns
Client side
for block in grouped_registers(table=4X, width<=125):
rsp = read(block)
if rsp.exception:
log_and_skip
Server side
switch(func_code) {
case 0x03: read_holding(); break;
case 0x10: write_multi_holding(); break;
default: return_exception(ILLEGAL_FUNC);
}
Security filter – drop FC05/06/15/16 if station is “read-only” zone.
11.8 Troubleshooting cheatsheet
Symptom | Likely mis-used FC | Fix |
---|---|---|
Only first register updates | Master used FC06 on multi-word float | Use FC16 |
Slave returns exception 01 | Master polling unsupported FC24 (mask write) | Disable mask write or upgrade slave |
Data flips every poll | Two masters both FC16 same regs | Split address space or implement token |
11.9 Best-practice summary
- Read big, write small. Group reads up to 125 regs; keep writes minimal.
- Prefer FC23 for atomic R/W scenarios to eliminate race conditions.
- Know the table ↔ FC matrix – never write to 3X or 1X.
- Trap exceptions; they’re free diagnostics compared with timeouts.
- Document every FC allowed on a device – simplest firewall is an allow-list.
Placeholder assets
ID | Idea |
---|---|
Fig-11-1 | Mind-map of all function codes grouped by category |
Fig-11-2 | Side-by-side FC03 normal vs exception frames |
Listing 1 | Python FC23 example |
Table-11-A | Quick-ref cheat table (FC, hex, table, R/W) |
Next chapter: Chapter 12 — Handling Data in Modbus: Endianness, Floats & Advanced Types, where we’ll deep-dive into 32-bit floats, 64-bit doubles, BCD, string packing, and bit-level access tricks.