Modbus from the wire up: ASCII vs RTU vs TCP
Modbus was published by Modicon in 1979 to talk to PLCs, and almost half a century later it is still the first protocol you meet on a factory floor. The reason is brutal simplicity: a master asks, a slave answers, and everything on the wire fits in a frame you can decode by eye. No stack, no licensing, no surprises — which is exactly why every VFD, energy meter, temperature controller and PLC still ships with it.
This post walks through the three variants — ASCII, RTU and TCP — at the byte level, then looks at which physical interfaces can actually carry them.
One protocol, one data model
Before the framing differences, the part that never changes. Modbus is a request/response protocol with a single master (client) and up to 247 addressable slaves (servers). All data lives in four tables:
| Table | Access | Size | Typical use |
|---|---|---|---|
| Coils | read/write | 1 bit | relay outputs, run/stop bits |
| Discrete inputs | read-only | 1 bit | limit switches, status bits |
| Holding registers | read/write | 16 bit | setpoints, configuration |
| Input registers | read-only | 16 bit | measurements, counters |
A handful of function codes cover nearly all real traffic: 01/02 read bits, 03/04 read registers, 05/06 write a single bit/register, 15/16 write multiple. The function code plus its data is called the PDU — and the PDU is identical in ASCII, RTU and TCP. The variants differ only in what gets wrapped around it.
Figure 1 — The classic plant topology: TCP at the top for SCADA, one RS-485 RTU segment at the bottom doing the real work.
The three framings
Figure 2 — Same PDU, three different envelopes. Note what each variant uses to find the frame boundary: characters (ASCII), silence (RTU), a length field (TCP).
RTU — the workhorse
RTU sends raw binary: one address byte, the PDU, and a CRC-16. Nothing marks the start or end of a frame — the frame boundary is silence. The spec requires at least 3.5 character times of idle line between frames (t₃.₅), and any gap longer than 1.5 character times (t₁.₅) inside a frame makes it invalid. With the standard 8E1 format one character is 11 bits, so:
| Baud rate | 1 char | t₁.₅ | t₃.₅ |
|---|---|---|---|
| 9600 | 1.146 ms | 1.72 ms | 4.01 ms |
| 19200 | 0.573 ms | 0.86 ms | 2.005 ms |
| > 19200 | — | 0.75 ms (fixed) | 1.75 ms (fixed) |
Above 19200 baud the spec freezes the timers at 750 µs / 1.75 ms because the proportional values get impractically tight. This timing rule is also RTU’s sharp edge: an OS-buffered USB–RS-485 converter that pauses mid-frame will silently break framing — more on that at the end.
ASCII — the debuggable one
ASCII takes every binary byte and sends it as two hexadecimal characters, framed
between a : and a CR LF, protected by a simple LRC (two’s complement of the byte
sum). The result is twice the bytes on the wire, but two real advantages: you can
read a frame in any terminal program, and timing doesn’t matter — gaps of up to
a second between characters are legal. On a radio modem or any link with unsteady
latency, ASCII survives where RTU framing falls apart.
TCP — the same PDU in an Ethernet suit
Modbus TCP keeps the PDU and replaces everything else. A 7-byte MBAP header carries a transaction ID (so responses can be matched to requests — several may be in flight at once), a protocol ID (always 0), a byte-count length field, and a unit ID (the old slave address, still used when a gateway forwards to a serial segment behind it). There is no checksum — TCP’s own integrity checking covers it. The well-known port is 502.
Two structural differences matter in practice. First, the serial variants allow exactly one outstanding request on the bus; TCP allows many, from many clients concurrently. Second, TCP has no broadcast — serial address 0 (broadcast write) simply doesn’t translate.
One transaction under the microscope
The classic example: read 3 holding registers starting at 0x006B (register 40108) from slave 0x11.
Figure 3 — One read transaction in all three framings. The six PDU bytes
03 00 6B 00 03 (plus address) never change — only the wrapper does.
Worth noticing in the response: register contents are big-endian — high byte first. The CRC, in a small act of cruelty, goes low byte first. Mixing those two up is a rite of passage.
The 17-character ASCII version of the same request makes the cost visible: 8 binary bytes became 17 on the wire. That’s the price of debuggability.
Which wire can carry Modbus?
The spec deliberately says nothing about voltage levels. Serial Modbus needs nothing more than a UART, so it runs over anything a UART can drive:
Figure 4 — Modbus is physical-layer agnostic. The PDU survives every hop; gateways only swap the wrapper.
- TTL/CMOS UART — two MCUs on the same board can speak Modbus RTU directly. Handy when one of them is a third-party module that already exposes Modbus.
- RS-232 — point-to-point only, one device, ~15 m. Fine for a config port; wrong for a plant.
- RS-485 — the de-facto standard carrier. Differential pair, multidrop (32 unit loads per segment, more with modern fractional-load transceivers), 1200 m at moderate baud rates, excellent noise immunity. Half-duplex two-wire is the common wiring; the master must manage driver-enable turnaround.
- RS-422 — four-wire full duplex, one driver / multiple receivers; you meet it in legacy installations.
- Ethernet / Wi-Fi — Modbus TCP. Anything that moves TCP moves it.
The default serial character format is 8E1 (even parity). 8N2 is the specified fallback; plain 8N1 is everywhere in the field but technically off-spec — a classic source of “it works with my USB dongle but not with the PLC” tickets.
Choosing between them
| ASCII | RTU | TCP | |
|---|---|---|---|
| Encoding | hex text | binary | binary |
| Frame overhead | ~2.2× RTU | smallest | +7 B MBAP |
| Framing by | : … CR LF | 3.5 char silence | length field |
| Error check | LRC (weak) | CRC-16 (strong) | TCP layer |
| Timing sensitivity | none | strict | none |
| Concurrent requests | 1 | 1 | many |
| Best at | flaky links, debugging | the standard fieldbus | SCADA, IT integration |
Practical rule: RTU on the wire, TCP above it. RTU owns the device-level segments because it’s the smallest frame with the strongest checksum; TCP owns everything that touches a network. ASCII is the niche tool you’re glad exists the day you must tunnel Modbus through a half-duplex radio with 300 ms of jitter.
Field notes — where Modbus bites
- The off-by-one address. Register “40108” is addressed as 0x006B (107). The 4xxxx convention is 1-based documentation notation, the wire is 0-based. Every integrator has lost an afternoon to this.
- 32-bit values. Two consecutive registers, and the standard doesn’t define the word order. Vendors split roughly 50/50 between big- and little-word-endian; good masters have a “word swap” checkbox for a reason.
- Exception responses. A slave that can’t honor a request answers with the function code ORed with 0x80 and one exception byte (0x02 = illegal address, 0x03 = illegal value…). If your driver treats that as a timeout instead of parsing it, debugging becomes archaeology.
- USB converter buffering. A USB–RS-485 dongle hands data to the OS in 1 ms+ chunks; at high baud rates that pause exceeds t₁.₅ and shatters RTU frames. Converters with hardware buffering — or just dropping to 19200 — fix mysteries that look like “random CRC errors.”
- Driver-enable turnaround. On two-wire RS-485 the master must release the bus fast after the last stop bit, and claim it again fast enough for the reply. Transceivers with automatic direction control remove a whole class of races.
- Security is not a feature. Modbus has no authentication and no encryption — anyone on the network can write any register. Modbus TCP must live in a segmented OT network or behind a gateway; “we put the PLC on the office LAN” is an incident report waiting for its date field.