— Efficient Binary Communication
(Module 2 · Modbus Serial Protocols — The Original Workhorses)
Goal of this chapter Make you 100 % confident with every bit, byte, gap, and CRC that flows across a Modbus RTU bus so you can design, capture, decode, and optimise real-world networks without trial-and-error.
Quick Navigation
§ | Topic | Depth Markers |
---|---|---|
5.1 | Why choose RTU? | Context & pros/cons |
5.2 | Frame anatomy | Detailed field-by-field tables |
5.3 | Timing law | T1.5 & T3.5 mathematics, oscilloscope patterns |
5.4 | CRC-16 explained | Polynomial theory → byte-code |
5.5 | Worked hex walkthrough | From request to normal & exception replies |
5.6 | Throughput maths | Max poll rate vs baud, byte-stuffing trade-off |
5.7 | Robust design patterns | Buffer sizing, exception handling, grouping reads |
5.8 | Field diagnostics & pitfalls | “Ghost frames”, bus contention, false CRCs |
(Diagram placeholders appear as [Fig-5-x]; code blocks as Listing x.)
5.1 Why Choose RTU?
Attribute | RTU | ASCII | TCP |
---|---|---|---|
Efficiency | 11 bits/byte binary | 22 ASCII hex chars for same data | Framed in TCP/IP |
Overhead | ≈ 40 % of ASCII | 2× more bytes per register | Ethernet header 38 B |
Human readability | Low | High | Medium (Wireshark) |
Timing critical | Yes | Yes but looser | No (handled by sockets) |
Rule of thumb If you control the wire (RS-485) and need maximum nodes/km/baud, always pick RTU.
5.2 RTU Frame Anatomy — Byte-by-Byte
Order | Field | Length | Description | Endianness |
---|---|---|---|---|
① | Slave Address | 1 B | 1 – 247 (0 = broadcast) | — |
② | Function Code | 1 B | 01–06, 15, 16, 23… | — |
③ | PDU Data | N B | Start addr, qty, payload | Big-endian words, little-endian bytes |
④ | CRC-16 | 2 B | Lo byte first, then Hi | Little-endian |
[Fig-5-1] Two-row timeline: TX bytes (client) top, RX bytes (server) bottom; CRC highlighted.
▶ Tip Always transmit CRC-Lo before CRC-Hi. Mixing order causes “CRC error” even though bytes are mathematically correct.
5.2.1 Data Field Layout for Popular Function Codes
FC | Name | Request Data Bytes | Response Data Bytes |
---|---|---|---|
03 | Read Holding Regs | StartHi StartLo QtyHi QtyLo | ByteCnt (RegHi RegLo)… |
06 | Write Single Reg | RegHi RegLo ValHi ValLo | Echo of request |
16 | Write Multiple Regs | StartHi StartLo QtyHi QtyLo ByteCnt (data…) | Echo start+qty |
(Detailed PDU diagrams per FC in Appendix A2.)
5.3 Timing Law — The T1.5 / T3.5 Doctrine
RTU has no start/stop tokens; silence gates each frame.
Symbol | Formula | Example @ 9600 bit/s | Example @ 115200 bit/s |
---|---|---|---|
Tchar | 11 bits ÷ Baud | 1.146 ms | 95.6 µs |
T1.5 | 1.5 × Tchar | 1.72 ms | 143 µs |
T3.5 | 3.5 × Tchar | 4.01 ms | 335 µs |
If the idle gap ≥ T3.5, a receiver must treat the next byte as the start of a new frame.
[Fig-5-2] Oscilloscope screenshot showing 9600-bit/s telegram, highlight 3.5-char gap.
⚠ Pitfall PC COM ports with FIFO buffers often block-burst bytes, hiding the T3.5 gap. Use Win32 Serial API
COMMTIMEOUTS
or Linuxtermios VMIN/VTIME
to guarantee flush.
5.3.1 Framing State Machine (Pseudo-code)
if idle_time ≥ T3.5:
state = COLLECT
frame.clear()
on_byte(b):
frame.append(b)
if idle_time > T1.5:
error("Inter-char gap") # discard
5.4 CRC-16 — Math From First Principles
Polynomial: 0xA001 (reverse of 0x8005). Initial value: 0xFFFF.
5.4.1 Algorithm in 6 Steps
- Init CRC = 0xFFFF.
- XOR CRC low-byte with incoming byte.
- For 8 bits:
3 a. If (CRC & 0x0001) ⇒ CRC = (CRC >> 1) ⊕ 0xA001
3 b. else CRC >>= 1 - Repeat step 2–3 for every byte in Addr…Data.
- Final CRC Lo = CRC & 0xFF, CRC Hi = CRC >> 8.
- Append to frame Lo first.
Listing 1 Python (no tables):
def crc16(data: bytes) -> bytes:
crc = 0xFFFF
for ch in data:
crc ^= ch
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return bytes([crc & 0xFF, crc >> 8])
▶ Bench-test Compare against value
0xC5C4
for the sample frame01 03 00 00 00 06
.
[Fig-5-3] Bit-level animation: LSB shift and XOR tap.
5.5 Full Hex Walk-Through
5.5.1 Request — Read 6 Holding Regs From Slave 17
Field | Hex | Meaning |
---|---|---|
Address | 11 | 0x11 = 17 |
Function | 03 | Read Holding Registers |
Start | 00 00 | 40001 (0-based) |
Qty | 00 06 | 6 regs |
CRC | C5 C4 | ← calculated |
Raw: 11 03 00 00 00 06 C5 C4
5.5.2 Normal Response (6 Regs)
11 03 0C 00 64 00 C8 01 2C 01 90 02 58 6F E1
Byte-count 0x0C = 12 data bytes.
5.5.3 Exception Response (Illegal Address)
11 83 02 C0 F1
Function 0x03 → 0x83; Exception 02.
Exception | Typical root cause |
---|---|
01 Illegal Function | Slave firmware doesn’t support FC |
02 Illegal Address | Register outside map |
03 Illegal Value | Quantity=0 or too large |
04 Slave Failure | Internal self-test error |
5.6 Throughput Mathematics
5.6.1 Frame Length Formula
Lbytes=1_Addr+1_FC+N_Data+2_CRCL_{\text{bytes}} = 1\_{\text{Addr}} + 1\_{\text{FC}} + N\_{\text{Data}} + 2\_{\text{CRC}}
Total time (worst-case including gaps): Ttotal=(Lbytes×11+3.5×11)/BaudT_\text{total} = (L_{\text{bytes}} \times 11 + 3.5 \times 11) / Baud
Example: Read 60 regs (FC03, data cost = 4 request + 125 reply bytes) @ 38 400 Bd → ≈ 41 ms round-trip → 1 450 regs/s.
5.6.2 Optimisation Levers
- Group registers into max 125 per poll.
- Use higher baud; most modern transceivers stable at 115 200 Bd across < 300 m with Cat-5e.
- Cut turn-around delay in slave firmware (timer between last RX and first TX).
- Limit exception retries; 3 faulty slaves can halve bus capacity.
5.7 Robust Implementation Patterns
Layer | Pattern | Benefit |
---|---|---|
Application | Idempotent writes – compare reg before write | Avoids needless traffic |
Transport | Sliding transaction IDs (TCP) | Overlap requests without race conditions |
Serial driver | DMA-driven TX-Enable | Eliminates jitter > 1 bit |
Scheduler | Least-recently-polled first | Fairness across 100+ slaves |
Error recovery | Exponential backoff per slave | Sick node ≠ bus paralysis |
5.8 Field Diagnostics & Classic Pitfalls
Symptom | Likely cause | Quick test |
---|---|---|
Random CRC errors | Incorrect bias / termination | Scope idle line: V_AB must be > +200 mV |
Slave answers wrong FC | Byte mis-alignment (missing start) | Check T3.5 gap @ master TX |
Frames echoed to self | TX-Enable stuck high (fail-safe) | Measure DE pin; should drop after last stop-bit |
“No response” after 32 nodes | Bus loading (32 UL limit) | Switch to 1/8 UL transceivers or segment with repeater |
⚠ Ghost frames Noise on unterminated stubs often mimics Mark→Space transition → slave sees phantom start; fix layout before rewriting code.
Key Takeaways
- RTU = fastest, leanest Modbus flavour; byte-dense frames plus CRC-16 integrity.
- T1.5 / T3.5 gaps are protocol law—calculate from baud, verify on scope.
- Little-endian CRC, big-endian data; never swap them.
- Throughput = baud × grouping factor − overhead; measure, then optimise.
- 80 % of “CRC errors” trace back to wiring & timing, not code.
Assets to Produce Before Publication
ID | Visual | Notes |
---|---|---|
Fig-5-1 | RTU frame colour-coded | Addr / FC / Data / CRC |
Fig-5-2 | Scope timing 9600 Bd | Mark 1.5 & 3.5 char |
Fig-5-3 | CRC bit-shift animation | Educational gif |
Fig-5-4 | Throughput vs baud graph | 9.6 k, 19.2 k, 115 k |
Up Next
Chapter 6 — Modbus ASCII: Human-Readable Communication will show how the same PDU survives dial-up modems, radio links, and teletype consoles—handy when you must debug with nothing but a serial terminal.