— Mathematics, Measurements & Advanced Poll-Planning

(Module 9 · Advanced & Specialised Topics)


“Why optimise? The line stops when the bus stalls.”

In many plants a Modbus segment is the critical path between a PLC and the drives, meters, or safety relays that keep product moving. Shave milliseconds from the poll cycle and you free PLC scan-time, increase historian granularity, extend radio battery life, or simply avoid race conditions that crash production. This chapter is a deep-dive laboratory: theory → formula → code → oscilloscope proof.

SectionOutcome
28.1Throughput equations for RTU & TCP, including CRC and inter-frame gaps.
28.2Baud-rate ladder — which step actually improves cycle time, when noise steals it back.
28.3Optimal poll-blocking: ❶ size ❷ alignment ❸ atomicity (FC 23) ❹ broadcast read.
28.4Concurrency & pipelining (TCP) — multi-TID window control with proofs.
28.5Event-driven & delta strategies — change-of-value, hysteresis, and exception-based polling.
28.6Real-time OS tuning on gateways (FreeRTOS/Scheduler, Linux PREEMPT_RT).
28.7Energy & radio optimisation math (LoRa/4 G) — bytes ↔ mAh.
28.8Benchmark kit: Saleae Logic + Python notebooks + Grafana dashboards.
28.9Best-practice checklist & printable cheat card.

28.1 Raw throughput mathematics

28.1.1 RTU frame timing

Tframe = (Nbytes + 2 CRC) × (StartBit + 8 Data + Parity + StopBits) / Baud
Tgap   = 3.5 × (StartBit + 8 + P + S) / Baud

For 8E1 (11 bits/char):

Tframe(ms) = 11 × (N+2) / Baud(kBd)
Tgap(ms)   = 38.5 / Baud(kBd)

Example – read 125 holding registers (8-bit Address + FC + 4 B start/qty + 2 B CRC + 2 +125×2 B response):

TX  = 1+1+2+2 = 6 B
RX  = 1+1+2+1+250+2 = 257 B
Total= 263 B
At 38 400 Bd → 11 × 263 / 38.4 ≈ 75 ms  + 2 gaps (2 ×1 ms) ≈ 77 ms

Rule-of-thumb: RTU 38 k4 + 125-word block ≈ 80 ms roundtrip.

28.1.2 TCP frame timing

Tframe = RTT + (Header_IP + Header_MBAP + Payload) / LinkRate

GigE LAN RTT ≈ 0.25 ms; TCP/IP+ETH header = 54 B; MBAP = 7 B.

Block read 125 regs → 257 B payload, total 318 B:

Wire time 318 B /1 Gb ≈ 2.5 µs — dominated by RTT & slave delay ➜ focus on pipelining.


28.2 Baud-rate ladder & diminishing returns

BaudFrame 125 regs (ms)% gain over previous
9 600303
19 200151+50 %
38 40077+49 %
57 60052+32 %
115 20026+50 %
230 400*14+46 %

⚠ At >115 kBd noise margin collapses on >100 m un-shielded RS-485; plan repeaters.


28.3 Optimal poll-blocking strategies

28.3.1 Block size selection

#regsPayload (B)Overhead(B)Efficiency
12918 %
1020969 %
60120993 %
125250996 %

60–64 registers sweet-spot on noisy lines (shorter retries) + fits some slaves’ 128-B buffer.

28.3.2 Memory alignment rules

  1. Start address divisible by 2 → word-aligned.
  2. Group tags by update frequency:
    • Fast (every scan) ⇒ bank 0.
    • Slow (once/min) ⇒ bank 3.

28.3.3 Atomic read-modify-write with FC 23

Cycle-time comparison

MethodFramesRoundtrip (38 k4)
Read (FC3) + Write (FC16)2154 ms
FC 23190 ms

Save 42 % bus time and remove race risk.

28.3.4 Broadcast read ID 0 (expert)

Use only on homogeneous sensor loops.
One frame → all slaves respond in silent mode and do not reply.
Reduces bus traffic at cost of non-confirming data.
Implementation: Master sets “direction flag” to mark broadcast value suspect until next confirmed slave reply (delta filter).


28.4 TCP concurrency & pipelining

28.4.1 Window-size algorithm

Optimal_window = ceil( Link_BW × RTT / Payload )

LAN 1 Gb, RTT 0.25 ms, payload 318 B → 1 frame in flight saturates; but latency can be hidden for multiple slaves.

28.4.2 Multi-TID pipeline code (Python asyncio)

WINDOW = 4
sem = asyncio.Semaphore(WINDOW)

async def pipereq(cli, unit, start, cnt):
    async with sem:
        return await cli.read_holding_registers(start, cnt, unit=unit)

await asyncio.gather(*(pipereq(cli, u, 0, 125) for u in range(1,9)))

Measured throughput 9 000 regs/s vs 1 800 regs/s single-flight on same network.

(Listing 28-1 provided.)


28.5 Event-driven and delta-based polling

MethodGateway logicTypical reduction
Change-of-value (CoV)Publish only ifvalue-prev
HysteresisTwo thresholds (enter/exit)Avoids chatter
Time-weighted averageSend one average over N polls95 %
Report-by-exception (limited vendor support)Slave sends unsolicited FC 4299 %

Node-RED function snippet:

if(Math.abs(msg.payload - context.last) > context.band){
   context.last = msg.payload;
   return msg;
}

28.6 Real-time OS & gateway tuning

OS / FeatureSettingEffect
Linux PREEMPT_RTisolcpus=1 for IRQ + taskset gateway processCuts max jitter 800 µs → 90 µs
FreeRTOSMove UART ISR to higher priority than TCP stackPrevents RX FIFO overflow on 115 kBd
Clock syncPTP/IEEE 1588 across gatewaysAligns sample timestamps < 1 µs

(Fig-28-1: histogram of inter-poll jitter before/after RT patch.)


28.7 Energy & radio bandwidth calculus

28.7.1 Bytes→mA-h

E_tx (mAh) = (Bytes × 8 / Baud) × I_tx (A) × 1000 / 3600

LoRa 22 dBm radio, I_tx = 120 mA, 9600 Bd, 250 B

7.4 µAh per poll.
With delta CoV reduce 90 % = 0.74 µAh ⇒ 5 yr on 3 Ah cell.

28.7.2 Radio link budget for RTU

CRC retransmissions cost ~~2 × frame bytes.
Plan for 10 % radio loss ➜ add 20 % battery margin.


28.8 Benchmark kit

  1. Saleae Logic 8 — capture RX/TX, measure Tgap.
  2. Python Jupyter workbook (/tools/benchmark.ipynb) — imports .logicdata, auto plots latency.
  3. Grafana JSON — dashboards for exception rate, throughput, jitter.

(Fig-28-2: Grafana screenshot).


28.9 Best-practice cheat card

ParameterRule-of-thumb
Block size60–125 regs or 400–800 coils
Gap timeout1.5× manufacturer spec
Baud38 k4 for ≤200 m; else 19 k2
Window (TCP)2–4 in-flight TIDs
CoV deadband1 % FS or 1 LSB, whichever larger
Retry1 (RTU), 2 (radio), back-off ×2
Jitter budget< 10 % of process dead-time

Chapter recap

  • Equations first: know your frame time, gap, RTT before touching a cable.
  • Baud alone never saves the day; block reads and pipeline windows do.
  • Change-of-value and FC 23 are hidden gems for bandwidth.
  • OS-level tuning (PREEMPT_RT, ISR priority) slashes jitter into microseconds.
  • Battery-powered or radio links demand byte-level accounting—optimise-to-mAh.

Deliverables for download

  • Listing 28-1 – async pipelined Python poller
  • benchmark.ipynb – interactive notebook to compute & visualise cycle time
  • grafana_modbus_perf.json – dashboard ready to import
  • cheat_card.pdf – laminated quick reference

Next (Chapter 29): Modbus Compliance & Conformance Testing — how to verify that every optimisation still obeys the spec, passes Modbus.org test harnesses, and plays nicely with third-party masters and slaves.

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts

Chapter 25 – Modbus ⇆ IIoT / Industry 4.0

— Bridging Brown-field Data to Cloud-Native Platforms (Module 8 · Real-World Applications & the Future of Modbus) Learning objectives Explain how Modbus’ request/response model co-exists with publish/subscribe (MQTT, AMQP) and…

Chapter 8 – Modbus TCP/IP Implementation

— Sockets, NAT, & Real-World Network Nuances (Module 3 · Modbus TCP/IP — Modbus over Modern Networks) Chapter promise Everything you must know to run Modbus at Ethernet speed reliably, deterministically,…

Chapter 9 – Modbus Gateways

— Bridging Serial & TCP/IP Worlds (Module 3 · Modbus TCP/IP) Ambition for this chapter: Build the best single source on earth for everything that happens inside a Modbus gateway—PCB…