Skip to main content

4 posts tagged with "Modbus TCP"

Modbus TCP/IP protocol for Ethernet-connected devices

View All Tags

EtherNet/IP Device Auto-Discovery: How Edge Gateways Identify PLCs on the Plant Floor [2026]

· 9 min read

Walk onto any modern plant floor and you'll find a patchwork of controllers — Allen-Bradley Micro800 series running EtherNet/IP, Modbus TCP devices from half a dozen vendors, maybe a legacy RTU on a serial port somewhere. The edge gateway sitting in that control cabinet needs to figure out what it's talking to, what protocol to use, and how to pull the right data — ideally without a technician manually configuring every register.

This is the device auto-discovery problem, and solving it well is the difference between a two-hour commissioning versus a two-day one.

The Discovery Sequence: Try EtherNet/IP First, Fall Back to Modbus

The most reliable approach follows a dual-protocol detection pattern. When an edge gateway powers up and finds a PLC at a known IP address, it shouldn't assume which protocol that device speaks. Instead, it runs a detection sequence:

Step 1: Attempt EtherNet/IP (CIP) Connection

EtherNet/IP uses the Common Industrial Protocol (CIP) over TCP port 44818. The gateway attempts to create a connection to a known tag — typically a device_type identifier that the PLC firmware exposes as a readable tag.

Protocol: ab-eip
Gateway: 192.168.1.100
CPU: micro800
Tag: device_type
Element Size: 2 bytes (uint16)
Element Count: 1
Timeout: 2000ms

If this connection succeeds and returns a non-zero value, the gateway knows it's talking to an EtherNet/IP device and can proceed to read the serial number components.

Step 2: If EtherNet/IP fails, try Modbus TCP

If the CIP connection returns an error (typically error code -32, indicating no route to host at the CIP layer), the gateway falls back to Modbus TCP on port 502.

For Modbus detection, the gateway reads input register 800 (address 0x300320 in the full Modbus address space — function code 4). This register holds the device type identifier by convention in many industrial equipment families.

Protocol: Modbus TCP
Port: 502
Function Code: 4 (Read Input Registers)
Start Address: 800
Register Count: 1

Step 3: Extract Serial Number

Once the device type is known, the gateway reads serial number components. Here's where things get vendor-specific. Different PLC families store their serial numbers in completely different register locations:

Device TypeProtocolMonth RegisterYear RegisterUnit Register
Micro800 PLCEtherNet/IPTag: serial_number_monthTag: serial_number_yearTag: serial_number_unit
GP Chiller (1017)Modbus TCPInput Reg 22Input Reg 23Input Reg 24
HE Chiller (1018)Modbus TCPHolding Reg 520Holding Reg 510Holding Reg 500
TS5 TCU (1021)Modbus TCPHolding Reg 1039Holding Reg 1038Holding Reg 1040

Notice the inconsistency — even within the same protocol, each device family stores its serial number in different registers, uses different function codes (input registers vs. holding registers), and sometimes the year/month/unit ordering isn't sequential in memory. This is real-world industrial automation, not a textbook.

Serial Number Encoding: Packing Identity into 32 Bits

Once you have the three components (year, month, unit number), they're packed into a single 32-bit serial number for efficient transport:

Byte 3 (bits 31-24): Year  (0x00-0xFF)
Byte 2 (bits 23-16): Month (0x00-0xFF)
Bytes 1-0 (bits 15-0): Unit Number (0x0000-0xFFFF)

This encoding allows up to 65,535 units per month per year — more than sufficient for any production line. A serial number of 0x18031A2B decodes to: year 0x18 (24), month 0x03 (March), unit 0x1A2B (6699).

Validation Matters

A serial number where the year byte is zero is invalid — it almost certainly means the PLC hasn't been properly commissioned or the register read returned garbage data. Your gateway should reject these and report a "bad serial number" status rather than silently accepting a device with identity 0x00000000.

The Configuration Lookup Pattern

Once the gateway knows the device type (e.g., type 1018 = HE Central Chiller), it needs to load the right tag configuration. The proven pattern is a directory scan:

  1. Maintain a directory of JSON configuration files (one per device type)
  2. On detection, scan the directory and match the device_type field in each JSON
  3. Load the matched configuration, which defines all tags, their data types, read intervals, and batching behavior
{
"device_type": 1018,
"version": "2.4.1",
"name": "HE Central Chiller",
"protocol": "modbus-tcp",
"plctags": [
{
"name": "supply_temp",
"id": 1,
"type": "float",
"addr": 400100,
"ecount": 2,
"interval": 5,
"compare": true
},
{
"name": "compressor_status",
"id": 2,
"type": "uint16",
"addr": 400200,
"interval": 1,
"compare": true,
"do_not_batch": true
}
]
}

Key design decisions in this configuration:

  • compare: true means only transmit when the value changes — critical for reducing bandwidth on cellular connections
  • do_not_batch: true means send immediately rather than accumulating in a batch — used for status changes and alarms that need real-time delivery
  • interval defines the polling frequency in seconds — fast-changing temperatures might be 5 seconds, while a compressor on/off status needs sub-second reads
  • ecount: 2 for floats means reading two consecutive 16-bit Modbus registers and combining them into an IEEE 754 float

Handling Modbus Address Conventions

One of the trickiest aspects of Modbus auto-discovery is the address-to-function-code mapping. Different vendors use different conventions, but the most common maps addresses to function codes like this:

Address RangeFunction CodeRegister Type
0–65536FC 1Coils (read/write bits)
100000–165536FC 2Discrete Inputs (read-only bits)
300000–365536FC 4Input Registers (read-only 16-bit)
400000–465536FC 3Holding Registers (read/write 16-bit)

When you see a configured address of 400100, the gateway strips the prefix: the actual Modbus register address sent on the wire is 100, using function code 3.

Register Grouping Optimization

Smart gateways don't read one register at a time. They scan the sorted tag list and identify contiguous address ranges that share the same function code and polling interval. These get combined into a single Modbus read request:

Tags at addresses: 400100, 400101, 400102, 400103, 400104
→ Single request: FC3, start=100, count=5

But grouping has limits. Exceeding ~50 registers per request risks timeouts, especially on Modbus RTU over slow serial links. And you can't group across function code boundaries — a tag at address 300050 (FC4) and 400050 (FC3) must be separate requests, even though they're "near" each other numerically.

Multi-Protocol Detection: The Real-World Sequence

In practice, a gateway on a plant floor often needs to detect multiple devices simultaneously — a PLC on EtherNet/IP and a temperature control unit on Modbus RTU via RS-485. The detection sequence runs in parallel:

  1. EtherNet/IP detection happens over the plant's Ethernet network — standard TCP/IP, fast, usually succeeds or fails within 2 seconds
  2. Modbus TCP detection uses the same Ethernet interface but different port (502) — also fast
  3. Modbus RTU detection happens over a serial port (/dev/ttyUSB0 or similar) — much slower, constrained by baud rate (typically 9600–115200), with byte timeouts around 50ms and response timeouts of 400ms

The serial link parameters are critical and often misconfigured:

Port: /dev/ttyUSB0
Baud Rate: 9600
Parity: None ('N')
Data Bits: 8
Stop Bits: 1
Slave Address: 1
Byte Timeout: 50ms
Response Timeout: 400ms

Getting the parity wrong is the #1 commissioning mistake with Modbus RTU. If the slave expects Even parity and the master sends None, every frame will be rejected silently — no error message, just timeouts.

Connection Resilience: The Watchdog Pattern

Discovery isn't a one-time event. Industrial connections drop — cables get unplugged during maintenance, PLCs get rebooted, network switches lose power. A robust gateway implements a multi-layer resilience strategy:

Link State Tracking: Every successful read sets the link state to "up." Any read error (timeout, connection reset, broken pipe, bad file descriptor) sets it to "down" and triggers a reconnection sequence.

Connection Error Counting: For EtherNet/IP, if you get three consecutive error-32 responses (no CIP route), stop hammering the network and wait for the next polling cycle. For Modbus, error codes like ETIMEDOUT, ECONNRESET, ECONNREFUSED, or EPIPE trigger a modbus_close() followed by reconnection on the next cycle.

Modbus Flush on Error: After a failed Modbus read, always flush the serial/TCP buffer before the next attempt. Stale response bytes from a partial read can corrupt subsequent responses.

Configuration Hot-Reload: The gateway watches its configuration files with stat(). If a file's modification time changes, it triggers a full re-initialization — destroy existing PLC tag handles, reload the JSON configuration, and re-establish all connections. This allows field engineers to update tag configurations without restarting the gateway service.

What machineCDN Brings to the Table

machineCDN's edge infrastructure handles this entire discovery and connection management lifecycle automatically. When you deploy a machineCDN gateway on the plant floor:

  • It auto-detects PLCs across EtherNet/IP and Modbus TCP/RTU simultaneously
  • It loads the correct device configuration from its library of supported equipment types
  • It manages connection resilience with automatic reconnection and buffer management
  • It optimizes Modbus reads by grouping contiguous registers and minimizing request count
  • Tag data flows through a batched delivery pipeline to the cloud, with store-and-forward buffering during connectivity gaps

For plant engineers, this means going from "cable plugged in" to "live data flowing" in minutes rather than days of manual register mapping.

Key Takeaways

  1. Always try EtherNet/IP first — it's faster and provides richer device identity information than Modbus
  2. Don't hardcode serial number locations — they vary wildly across equipment families, even from the same vendor
  3. Validate serial numbers before accepting a device — zero year values indicate bad reads
  4. Group Modbus reads by contiguous address and function code, but cap at 50 registers per request
  5. Implement connection watchdogs — industrial networks are unreliable; your gateway must recover automatically
  6. Flush after errors — stale buffer bytes from partial Modbus reads are the silent killer of data integrity

The device discovery problem isn't glamorous, but getting it right is what separates an IIoT platform that works in the lab from one that survives on a real plant floor.

Contiguous Modbus Register Reads: How to Optimize PLC Polling for Maximum Throughput [2026]

· 12 min read

If you're polling a PLC with Modbus and reading one register at a time, you're wasting 80% of your bus time on protocol overhead. Every Modbus transaction carries a fixed cost — framing bytes, CRC calculations, response timeouts, and turnaround delays — regardless of whether you're reading 1 register or 120. The math is brutal: reading 60 holding registers individually means 60 request/response cycles. Coalescing them into a single read means one cycle that returns all 60 values.

This article breaks down the mechanics of contiguous register optimization, shows you exactly how to implement it, and explains why it's the single highest-impact change you can make to your IIoT data collection architecture.

Modbus register optimization

The Hidden Cost of Naive Polling

Let's do the math on a typical Modbus RTU link at 9600 baud.

A single Modbus RTU read request (function code 03) for one holding register looks like this:

FieldBytes
Slave Address1
Function Code1
Starting Address2
Quantity of Registers2
CRC2
Request Total8

The response for a single register:

FieldBytes
Slave Address1
Function Code1
Byte Count1
Register Value2
CRC2
Response Total7

That's 15 bytes on the wire for 2 bytes of actual data — 13.3% payload efficiency.

Now add the silent interval between frames. Modbus RTU requires a minimum 3.5-character gap (roughly 4ms at 9600 baud) between transactions. Plus a typical slave response time of 5–50ms. For a conservative 20ms response delay:

  • Wire time per transaction: ~15.6ms (15 bytes × 1.04ms/byte at 9600 baud)
  • Turnaround + gap: ~24ms
  • Total per register: ~39.6ms
  • 60 registers individually: ~2,376ms (2.4 seconds!)

Now, reading those same 60 registers in one contiguous block:

  • Request: 8 bytes (unchanged)
  • Response: 1 + 1 + 1 + 120 + 2 = 125 bytes
  • Wire time: ~138.5ms
  • One turnaround: ~24ms
  • Total: ~162.5ms

That's 14.6x faster for the same data. On a serial bus where you might have multiple slave devices and hundreds of tags, this is the difference between a 1-second polling cycle and a 15-second one.

Understanding Modbus Address Spaces

Before you can coalesce reads, you need to understand that Modbus defines four distinct address spaces, each requiring a different function code:

Address RangeRegister TypeFunction CodeRead Operation
0–65,535Coils (discrete outputs)FC 01Read Coils
100,001–165,536Discrete InputsFC 02Read Discrete Inputs
300,001–365,536Input Registers (16-bit, read-only)FC 04Read Input Registers
400,001–465,536Holding Registers (16-bit, read/write)FC 03Read Holding Registers

Critical rule: you cannot coalesce reads across function codes. A request using FC 03 (holding registers) cannot include addresses that belong to FC 04 (input registers). They're physically different memory areas in the PLC. Any optimization algorithm must first partition tags by their function code, then optimize within each partition.

The address encoding convention (where 400001 maps to holding register 0) is a common source of bugs. When you see address 404002 in a tag configuration, the leading 4 indicates holding registers (FC 03), and the actual Modbus address sent on the wire is 4002. Your coalescing logic needs to strip the prefix for wire protocol but keep it for function code selection.

The Coalescing Algorithm

The core idea is simple: sort tags by address within each function code group, then merge adjacent tags into single read operations. Here's the logic:

Step 1: Sort Tags by Address

Your tag list must be ordered by Modbus address. If tags arrive in arbitrary order (as they typically do from configuration files), sort them first. This is a one-time cost at startup.

Tag: "Delivery Temp"    addr: 404002  type: float  ecount: 2
Tag: "Mold Temp" addr: 404004 type: float ecount: 2
Tag: "Return Temp" addr: 404006 type: float ecount: 2
Tag: "Flow Value" addr: 404008 type: float ecount: 2
Tag: "System Standby" addr: 404010 type: float ecount: 2

All five tags use FC 03 (holding registers). Their addresses are contiguous: 4002, 4004, 4006, 4008, 4010. Each occupies 2 registers (32-bit float = 2 × 16-bit registers).

Step 2: Walk the Sorted List and Build Read Groups

Starting from the first tag, accumulate subsequent tags into the same group as long as:

  1. Same function code — The address prefix maps to the same Modbus command
  2. Contiguous addresses — The next tag's address equals the current head address plus accumulated register count
  3. Same polling interval — Tags with different intervals should be in separate groups (a 1-second tag shouldn't force a 60-second tag to be read every second)
  4. Register count limit — Modbus protocol limits a single read to 125 registers (for FC 03/04) or 2000 coils (for FC 01/02). In practice, keeping it under 50–120 registers per read improves reliability on noisy links

When any condition fails, finalize the current group, issue the read, and start a new group with the current tag as head.

Step 3: Dispatch the Coalesced Read

For our five temperature tags:

Single coalesced read:
Function Code: 03
Starting Address: 4002
Quantity: 10 registers (5 tags × 2 registers each)

One transaction returns all 10 registers. The response buffer contains the raw bytes in order — you then walk through the buffer, extracting values according to each tag's type and element count.

Step 4: Unpack Values from the Response Buffer

This is where data types matter. The response is a flat array of 16-bit words. For each tag in the group, you consume the correct number of words:

  • uint16/int16: 1 word, direct assignment
  • uint32/int32: 2 words, combine as (word[1] << 16) | word[0] (check your PLC's byte order!)
  • float32: 2 words, requires IEEE 754 reconstruction — modbus_get_float() in libmodbus or manual byte swapping
  • bool/int8/uint8: 1 word, mask with & 0xFF
Response buffer: [w0, w1, w2, w3, w4, w5, w6, w7, w8, w9]
|-------| |-------| |-------| |-------| |-------|
Tag 0 Tag 1 Tag 2 Tag 3 Tag 4
float float float float float

Handling Gaps in the Address Space

Real-world tag configurations rarely have perfectly contiguous addresses. You'll encounter gaps:

Tag A: addr 404000, ecount 2
Tag B: addr 404004, ecount 2 ← gap of 2 registers at 404002
Tag C: addr 404006, ecount 2

You have two choices:

  1. Read through the gap — Issue one read from 4000 to 4007 (8 registers), and simply ignore the 2 garbage registers at offset 2–3. This is usually optimal if the gap is small (< 10 registers). The cost of reading extra registers is almost zero.

  2. Split into separate reads — If the gap is large (say, 50+ registers), two smaller reads are more efficient than one bloated read full of data you'll discard.

A good heuristic: if the gap is less than the per-transaction overhead expressed in registers, read through it. At 9600 baud, a transaction costs ~24ms of overhead, equivalent to reading about 12 extra registers. So gaps under 12 registers should be read through.

Handling Mixed Polling Intervals

Not all tags need the same update rate. Temperature setpoints might only need reading every 60 seconds, while pump status flags need 1-second updates. Your coalescing algorithm must handle this.

The approach: partition by interval before coalescing by address. Tags with interval: 1 form one pool, tags with interval: 60 form another. Within each pool, apply address-based coalescing normally.

During each polling cycle, check whether enough time has elapsed since a tag's last read. If a tag isn't due for reading, skip it — but this means breaking the contiguous chain:

Cycle at T=30s:
Tag A (interval: 1s) → READ addr: 404000
Tag B (interval: 60s) → SKIP addr: 404002 ← breaks contiguity
Tag C (interval: 1s) → READ addr: 404004

Tags A and C can't be coalesced because Tag B sits between them and isn't being read. The algorithm must detect the break and issue two separate reads.

Optimization: If Tag B is cheap to read (1–2 registers), consider reading it anyway and discarding the result. The overhead of an extra 2 registers in a contiguous block is far less than the overhead of a separate transaction.

Modbus RTU vs TCP: Different Optimization Priorities

Modbus RTU (Serial)

  • Bottleneck: Baud rate and turnaround time
  • Priority: Minimize transaction count at all costs
  • Flush between reads: Always flush the serial buffer before starting a new poll cycle to clear any stale or corrupted data
  • Retry logic: Implement 2–3 retries per read with short delays — serial links are noisy, but a retry is still cheaper than dropping data
  • Response timeout: Configure carefully. Too short (< 50ms) causes false timeouts; too long (> 500ms) kills throughput. 100–200ms is typical for most PLCs
  • Byte/character timeout: Set to ~5ms at 9600 baud. This detects mid-frame breaks

Modbus TCP

  • Bottleneck: Connection management, not bandwidth
  • Priority: Keep connection alive and reuse it
  • Connection recovery: Detect ETIMEDOUT, ECONNRESET, ECONNREFUSED, EPIPE, and EBADF — these all mean the connection is dead and needs reconnecting
  • No inter-frame gaps: TCP handles framing, so back-to-back transactions are fine
  • Default port: 502, but some PLCs use non-standard ports — make this configurable

Real-World Configuration Example

Here's a practical tag configuration for a temperature control unit using Modbus RTU, with 32 holding registers read as IEEE 754 floats:

{
"protocol": "modbus-rtu",
"batch_timeout": 60,
"link": {
"port": "/dev/rs232",
"base_addr": 1,
"baud": 9600,
"parity": "N",
"data_bits": 8,
"stop_bits": 2,
"byte_timeout_ms": 5,
"response_timeout_ms": 200
},
"tags": [
{"name": "Delivery Temp", "addr": 404002, "type": "float", "ecount": 2, "interval": 60},
{"name": "Mold Temp", "addr": 404004, "type": "float", "ecount": 2, "interval": 60},
{"name": "Return Temp", "addr": 404006, "type": "float", "ecount": 2, "interval": 60},
{"name": "Flow Value", "addr": 404008, "type": "float", "ecount": 2, "interval": 60},
{"name": "Heater Output %", "addr": 404054, "type": "float", "ecount": 2, "interval": 60},
{"name": "Cooling Output %", "addr": 404056, "type": "float", "ecount": 2, "interval": 60},
{"name": "Pump Status", "addr": 404058, "type": "float", "ecount": 2, "interval": 1, "immediate": true},
{"name": "Heater Status", "addr": 404060, "type": "float", "ecount": 2, "interval": 1, "immediate": true},
{"name": "Vent Status", "addr": 404062, "type": "float", "ecount": 2, "interval": 1, "immediate": true}
]
}

The coalescing algorithm would produce:

  • Group 1 (interval: 60s): Read 404002–404009 → 4 tags, 8 registers, one read
  • Group 2 (interval: 60s): Read 404054–404057 → 2 tags, 4 registers, one read (non-contiguous with Group 1, separate read)
  • Group 3 (interval: 1s): Read 404058–404063 → 3 tags, 6 registers, one read

Three transactions instead of nine. At 9600 baud, that saves ~240ms per polling cycle — which adds up to minutes of saved bus time per hour.

Common Pitfalls

1. Ignoring Element Count

A float tag occupies 2 registers, not 1. If your coalescing logic treats every tag as 1 register, your contiguity check will be wrong and you'll read corrupt data. Always use addr + elem_count when calculating the next expected address.

2. Byte Order Confusion

Different PLCs use different byte orders for 32-bit values. Some use big-endian word order (ABCD), others use little-endian (DCBA), and some use mid-endian (BADC or CDAB). If your float values come back as NaN or astronomically wrong numbers, byte order is almost certainly the issue. Test with a known value (like a temperature setpoint you can verify on the HMI) and adjust.

3. Not Handling Read Failures Gracefully

When a coalesced read fails, the entire group fails. Don't panic — just flush the serial buffer, log the error, and retry up to 3 times. If the error is a connection-level failure (timeout, connection reset), close and reopen the Modbus context rather than hammering a dead link.

4. Exceeding the Register Limit

The Modbus specification allows up to 125 registers per read (FC 03/04) and 2000 coils (FC 01/02). In practice, many PLCs choke at lower limits — some only handle 50–60 registers per request reliably. Cap your coalesced read size at a conservative number (50 is safe for virtually all PLCs) and test with your specific hardware.

5. Mixing Immediate and Batched Tags

Some tags (like alarm states, emergency stops, or pump status changes) need to be sent to the cloud immediately — not held in a batch for 60 seconds. These "do not batch" tags should be delivered to the MQTT layer as soon as they're read, bypassing the batching pipeline. But they can still participate in coalesced reads; the immediate/batched distinction is about delivery, not reading.

How machineCDN Handles This

The machineCDN edge daemon implements all of these optimizations natively. Tags are automatically sorted by address at configuration load time, coalesced into contiguous read groups respecting function code boundaries and interval constraints, and read with retry logic tuned for industrial serial links. Both Modbus RTU and Modbus TCP are supported with automatic protocol detection — the daemon probes the PLC at startup to determine the device type, serial number, and communication protocol before configuring the polling loop.

The result: a single edge gateway on a Teltonika RUT9 industrial router can poll hundreds of tags from multiple devices with sub-second cycle times, even on 9600 baud serial links.

Conclusion

Contiguous register optimization is not optional for production IIoT deployments. The performance difference between naive per-register polling and properly coalesced reads is an order of magnitude. The algorithm is straightforward — sort by address, group by function code and interval, cap at the register limit, and handle gaps intelligently. Get this right, and your serial bus goes from bottleneck to barely loaded.

The tags are just data points. How you read them determines whether your IIoT system is a science project or production infrastructure.

Modbus RTU vs Modbus TCP: A Practical Comparison for Plant Engineers [2026]

· 12 min read

Modbus RTU vs TCP comparison

If you've spent time on the factory floor, you've touched Modbus. It's the lingua franca of industrial automation — older than most engineers who use it, yet still embedded in nearly every PLC, VFD, sensor, and temperature controller shipping today.

But "Modbus" isn't one protocol. It's two very different beasts that happen to share a register model. Understanding when to use Modbus RTU over RS-485 serial versus Modbus TCP over Ethernet isn't academic — it directly impacts your polling throughput, your wiring costs, your alarm response times, and whether your edge gateway can actually keep up with your machines.

This guide breaks down both protocols at the wire level, compares real-world performance, and gives you a decision framework for your next deployment.

The Frame: What Actually Goes On The Wire

Modbus RTU Frame Structure

Modbus RTU (Remote Terminal Unit) sends binary data over a serial connection — typically RS-485, sometimes RS-232 for point-to-point. The frame is compact:

[Slave Address: 1 byte] [Function Code: 1 byte] [Data: N bytes] [CRC-16: 2 bytes]

A typical "read holding registers" request looks like this on the wire:

01 03 00 00 00 0A C5 CD
│ │ │ │ └── CRC-16 (little-endian)
│ │ │ └──────── Quantity: 10 registers
│ │ └────────────── Starting address: 0x0000
│ └───────────────── Function code: 0x03 (Read Holding Registers)
└──────────────────── Slave address: 1

That's 8 bytes for the request. The response carries 20 bytes of register data plus 5 bytes of overhead — 25 bytes total. Clean. Efficient. Zero wasted bandwidth.

The silent interval problem: RTU uses timing to delimit frames. A gap of 3.5 character times (approximately 3.6ms at 9600 baud) signals the end of one frame. This means:

  • You cannot have pauses inside a frame
  • Multitasking operating systems (Linux, Windows) can introduce jitter that corrupts framing
  • At 9600 baud, one character takes ~1.04ms, so the inter-frame gap is ~3.6ms
  • At 19200 baud, the gap shrinks to ~1.8ms — tighter timing requirements

Modbus TCP Frame Structure

Modbus TCP wraps the same function codes in a TCP/IP packet with an MBAP (Modbus Application Protocol) header:

[Transaction ID: 2 bytes] [Protocol ID: 2 bytes] [Length: 2 bytes] [Unit ID: 1 byte] [Function Code: 1 byte] [Data: N bytes]

The same read request becomes:

00 01 00 00 00 06 01 03 00 00 00 0A
│ │ │ │ │ │ └── Quantity: 10 registers
│ │ │ │ │ └──────── Starting address: 0x0000
│ │ │ │ └─────────── Function code: 0x03
│ │ │ └────────────── Unit ID: 1
│ │ └──────────────────── Remaining bytes: 6
│ └────────────────────────── Protocol ID: 0x0000 (Modbus)
└──────────────────────────────── Transaction ID: 0x0001

Key difference: No CRC. TCP handles error detection at the transport layer. The Transaction ID is huge — it lets you pipeline multiple requests without waiting for responses, something RTU physically cannot do.

Serial Configuration: Getting the Basics Right

When you configure a Modbus RTU connection, you're setting up a serial port. The classic configuration that works with most PLCs:

ParameterTypical ValueNotes
Baud Rate9600Some devices support 19200, 38400, even 115200
Data Bits8Almost universally 8
ParityNoneSome devices default to Even — check documentation
Stop Bits1Use 2 when parity is None (per Modbus spec, though 1 works for most devices)
Byte Timeout4msTime between individual bytes within a frame
Response Timeout100msMaximum wait for slave response

The byte timeout and response timeout are where most deployment issues hide. Set the byte timeout too low on a noisy RS-485 bus and you'll get fragmented frames. Set the response timeout too high and your polling cycle slows to a crawl when a device goes offline.

Real-world rule: On a clean RS-485 bus with less than 100 meters of cable, 4ms byte timeout and 100ms response timeout works reliably. Add 20ms to the response timeout for every 100 meters of additional cable, and double both values if you're running near VFDs or welding equipment.

Modbus TCP: Port 502 and What Lives Behind It

Modbus TCP devices listen on port 502 by default. When you configure a gateway to talk to a PLC over TCP, you're specifying:

  • IP address of the PLC or protocol converter
  • TCP port (502 is standard)
  • Unit ID (equivalent to the slave address — matters when a single IP serves multiple logical devices)

The connection lifecycle matters more than most engineers realize:

  1. TCP handshake: ~1ms on a local network, but can spike to 50ms+ through managed switches with port security
  2. Keep-alive: Modbus TCP doesn't define keep-alive. Some PLCs will drop idle connections after 30-60 seconds
  3. Connection pooling: A well-designed gateway maintains persistent connections rather than reconnecting per poll cycle

The Unit ID trap: When you have a Modbus TCP-to-RTU bridge (common when retrofitting serial devices onto Ethernet), the Unit ID maps to the RTU slave address on the serial side. If you set Unit ID to 0 or 255, many bridges interpret this as "send to all devices" — which can cause chaos on a shared RS-485 bus.

Performance: Real Numbers, Not Spec Sheet Fantasy

Here's what actually matters — how fast can you poll data?

Modbus RTU at 9600 Baud

Reading 10 holding registers from a single device:

  • Request frame: 8 bytes → 8.3ms
  • Slave processing time: 2-10ms (PLC-dependent)
  • Response frame: 25 bytes → 26ms
  • Inter-frame gap: 3.6ms × 2 = 7.2ms
  • Total per device: ~45-55ms

With 10 devices on an RS-485 bus, one complete poll cycle takes 450-550ms. That's roughly 2 polls per second — acceptable for temperature monitoring, too slow for motion control.

Bumping to 19200 baud cuts transmission time in half, getting you to ~30ms per device or about 3.3 polls per second across 10 devices.

Modbus TCP on 100Mbps Ethernet

The same 10-register read over TCP:

  • Request frame: 12 bytes (+ TCP overhead) → under 1ms
  • Slave processing time: 2-10ms
  • Response frame: 29 bytes → under 1ms
  • TCP ACK overhead: ~0.5ms
  • Total per device: ~5-15ms

But here's where TCP shines: pipelining. With the Transaction ID, you can fire 10 requests without waiting for responses. A well-optimized gateway can poll 10 devices in 15-25ms total — nearly 40-60 polls per second.

The Contiguous Register Advantage

Whether RTU or TCP, reading contiguous registers in a single request is dramatically faster than individual reads. Reading 50 contiguous registers costs roughly the same as reading 1 register — the overhead is in the framing, not the data payload.

If your PLC stores related data in registers 40001-40050, read them all in one Function Code 03 request. If the data is scattered across registers 40001, 40200, 40500, and 41000, you need four separate requests — four times the overhead.

Smart IIoT platforms like machineCDN optimize this automatically, grouping contiguous register reads into batch requests that minimize round-trips to the PLC.

Function Codes: The Ones That Actually Matter

The Modbus spec defines 20+ function codes, but in practice you'll use five:

CodeNameUse Case
0x01Read CoilsDigital outputs (on/off states)
0x02Read Discrete InputsDigital inputs (sensor contacts)
0x03Read Holding RegistersThe workhorse — analog values, setpoints, configuration
0x04Read Input RegistersRead-only process values (some PLCs put sensor data here)
0x06Write Single RegisterSending commands or setpoints to the PLC

The register type confusion: Modbus defines four data spaces — coils (1-bit R/W), discrete inputs (1-bit RO), holding registers (16-bit R/W), and input registers (16-bit RO). Different PLC manufacturers map data differently. A temperature reading might be in holding register 40001 on one brand and input register 30001 on another. Always check the PLC's register map.

Error Handling: Where Deployments Break

RTU Error Detection

RTU uses CRC-16 (polynomial 0xA001). If a single bit flips during transmission — common on electrically noisy factory floors — the CRC fails and the master discards the frame. The master then retries, burning another 45ms+.

Common RTU error scenarios:

  • No response (timeout): Device is offline, wrong slave address, or cable broken. The master waits for the full response timeout before moving on.
  • CRC mismatch: Electrical noise. Check cable shielding, termination resistors (120Ω at each end of the RS-485 bus), and distance from high-power equipment.
  • Exception response: The slave responds with function code + 0x80, indicating an error (illegal address, illegal data value, slave device failure). This is actually good — it means the device is alive and communicating.

TCP Error Handling

TCP's built-in retry and checksum mechanisms handle bit errors transparently. Your Modbus TCP errors are typically:

  • Connection refused: Device is down or port 502 is blocked
  • Connection timeout: Network issue, VLAN misconfiguration, or firewall
  • No response on established connection: PLC is overloaded or has crashed — the TCP connection stays open but the application layer is dead

The zombie connection problem: A PLC might crash while the TCP connection remains technically open (no FIN packet sent). Your gateway keeps sending requests into the void, timing out on each one. Implement application-level heartbeats — if you don't get a valid Modbus response within 3 consecutive poll cycles, tear down the connection and reconnect.

Wiring and Physical Layer Considerations

RS-485 for RTU

  • Max cable length: 1,200 meters (4,000 feet) at 9600 baud
  • Max devices per bus: 32 (standard drivers) or 256 (with high-impedance receivers)
  • Topology: Multi-drop bus (daisy-chain, NOT star)
  • Termination: 120Ω resistors at both ends of the bus
  • Cable: Shielded twisted pair (STP), 24 AWG minimum

The star topology trap: RS-485 is designed for daisy-chain (bus) topology. Running cables from a central hub to each device in a star pattern creates reflections that corrupt signals. If your plant layout forces star wiring, use an RS-485 hub/repeater at the center.

Ethernet for TCP

  • Max cable length: 100 meters per segment (Cat5e/Cat6)
  • Devices: Limited only by switch capacity and IP addressing
  • Topology: Star (standard Ethernet)
  • Switches: Use industrial-rated managed switches. Consumer switches will die in a factory environment within months.

When to Use Each Protocol

Choose Modbus RTU when:

  • Connecting to legacy devices that only have serial ports
  • Cable runs are long (200m+) and Ethernet infrastructure doesn't exist
  • You need simplicity — two wires, no switches, no IP configuration
  • Budget is tight and the device count is low (under 10 per bus)
  • Temperature controllers, VFDs, and simple sensors with RS-485 ports

Choose Modbus TCP when:

  • You need high poll rates (>5 Hz per device)
  • Connecting 10+ devices at one location
  • Ethernet infrastructure already exists
  • You want to pipeline requests for maximum throughput
  • Remote access or cloud connectivity is needed (TCP routes through firewalls more easily)
  • The PLC supports it (most modern PLCs do)

The hybrid reality: Most IIoT deployments end up with both. A Modbus TCP-capable PLC talks to the edge gateway over Ethernet while older serial devices connect through an RS-485 port on the same gateway. Platforms like machineCDN handle this natively — the edge gateway manages both protocol stacks and normalizes the data into a unified model before it leaves the plant floor.

Configuration Pitfalls That Will Waste Your Time

  1. Baud rate mismatch: Every device on an RTU bus must use the same baud rate. One device at 19200 on a 9600 bus will generate garbage that confuses everything.

  2. Duplicate slave addresses: Two devices with the same address on the same RS-485 bus will both try to respond simultaneously, corrupting each other's frames.

  3. Polling too fast: If your poll interval is shorter than the total round-trip time for all devices, requests will pile up and timeouts cascade. Calculate your minimum cycle time before setting the poll interval.

  4. Byte ordering (endianness): A 32-bit float spanning two 16-bit Modbus registers can be arranged as Big-Endian (AB CD), Little-Endian (CD AB), Big-Endian byte-swapped (BA DC), or Little-Endian byte-swapped (DC BA). The spec doesn't mandate an order. Each manufacturer chooses their own. Test with known values before assuming.

  5. Register addressing: Some documentation uses 0-based addressing (register 0 = first register), others use 1-based (register 1 = first register), and some use Modbus convention addressing (40001 = first holding register). Off-by-one errors here will give you data from the wrong register — and the values might look plausible enough to be dangerous.

Scaling Factors and Unit Conversion

PLCs store numbers as integers — typically 16-bit signed or unsigned values. A temperature of 72.5°F might be stored as:

  • 7250 with an implicit scale factor of ÷100
  • 725 with a scale factor of ÷10
  • 73 rounded to the nearest integer

The register map documentation should specify the scale factor, but many don't. When you see register values like 7250, 1472, or 2840, you need to figure out the engineering units.

Temperature conversions are common in multi-vendor environments:

  • Fahrenheit to Celsius: (F - 32) × 5/9
  • Weight (lbs to kg): lbs ÷ 2.205
  • Pressure (PSI to kPa): PSI ÷ 0.145
  • Length (feet to meters): ft ÷ 3.281

A robust IIoT platform handles these conversions at the edge, storing normalized SI values in the cloud regardless of what the PLC natively reports.

Conclusion: The Protocol Doesn't Matter as Much as the Architecture

Modbus RTU and Modbus TCP are both viable for modern IIoT deployments. The protocol choice is a physical-layer decision — what ports does the equipment have, how far away is it, and how fast do you need data?

The real challenge is what happens after the data leaves the register: normalizing values from heterogeneous equipment, handling connectivity loss gracefully, batching telemetry for efficient cloud delivery, and turning raw register data into actionable insights.

Whether your machines speak RTU over serial or TCP over Ethernet, the goal is the same — get reliable, normalized data off the plant floor and into the hands of engineers who can act on it.


machineCDN connects to both Modbus RTU and Modbus TCP devices through its edge gateway, handling protocol translation, data normalization, and store-and-forward buffering automatically. Learn how it works →

Modbus TCP vs RTU: A Practical Guide for Plant Engineers [2026]

· 14 min read
MachineCDN Team
Industrial IoT Experts

Modbus TCP vs RTU

Modbus has been the lingua franca of industrial automation for over four decades. Despite the rise of OPC-UA, MQTT, and EtherNet/IP, Modbus remains the most widely deployed protocol on factory floors worldwide. If you're connecting PLCs, chillers, temperature controllers, or blenders to any kind of monitoring or cloud platform, you will encounter Modbus — guaranteed.

But Modbus comes in two flavors that behave very differently at the wire level: Modbus RTU (serial) and Modbus TCP (Ethernet). Choosing the wrong one — or misconfiguring either — is the single most common source of data collection failures in IIoT deployments.

This guide covers the real differences that matter when you're wiring up a plant, not textbook definitions.