Modbus RTU vs TCP: the same command in two different envelopes

# embedded# python# iot# beginners
Modbus RTU vs TCP: the same command in two different envelopes코딩나우(하늘아래)

Equipment manuals say "supports Modbus" and leave you to work out whether that means RTU or TCP. The...

Equipment manuals say "supports Modbus" and leave you to work out whether that means RTU or TCP. The names make it sound like two protocols. It is one protocol with two envelopes.

The command you send — read these registers, write that coil — is byte-for-byte identical. Only the wrapper differs, and every practical consequence (wiring, update rate, device count, how you debug) falls out of that one difference.

The frames, side by side

RTU   [ slave addr 1B ][ function code + data  N B ][ CRC 2B ]
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TCP   [ txn id 2B ][ proto 2B ][ length 2B ][ unit id 1B ][ function code + data  N B ]
      \________________ MBAP header, 7 bytes ____________/
Enter fullscreen mode Exit fullscreen mode

The part under the carets is the PDU, and it is the same in both. RTU frames are delimited by 3.5 character times of silence and carry a CRC. TCP frames have no CRC at all — TCP already guarantees integrity, which trips people up when they go hunting for one in a packet capture.

Because the PDU is shared, so are the function codes:

Code Does Target
01 Read coils Output bits
02 Read discrete inputs Input bits (read-only)
03 Read holding registers 16-bit values — the common one
04 Read input registers 16-bit values (read-only)
05 / 06 Write single coil / register One value
15 / 16 Write multiple A contiguous block

Wiring is where the real difference lands

RTU runs over two RS-485 wires, daisy-chained. Cheap, and it reaches past a kilometre at 9600 bps. Everyone shares the line, so the master polls one device at a time.

  • Fit 120 ohm terminators at both ends. Missing ones give intermittent errors that only show up once the cable gets long.
  • Vendors label A/B (D+/D-) inconsistently. Swapping the pair is the fastest test.
  • Run a common signal ground. Ground potential differences make the link flaky.
  • Addresses are 1-247, and two devices sharing one will collide on every reply.

TCP gives each device an IP on a switch. No new cabling if the network already exists, and several clients can talk at once — in exchange for the 100 m per-segment limit and IPs to manage.

How much faster is TCP, really?

Not raw bit rate — round-trip time. RTU at 9600 bps, 8N1, is 10 bits per byte, so roughly 1 ms per byte:

request 8 B + response 9 B = 17 B      ~ 18 ms
+ inter-frame silence (3.5 chars) x2   ~  7 ms
+ device processing                    ~  5-20 ms
-----------------------------------------------
one round trip                         ~ 30-45 ms  ->  20-30 per second
Enter fullscreen mode Exit fullscreen mode

Ten slaves on that bus means two or three updates per device per second. Fine for temperature or flow; useless when you need tens of milliseconds. The same exchange on Ethernet is 1-5 ms, and devices can be polled in parallel.

The addressing trap that eats afternoons

When the manual says 40001, the number that goes on the wire is 0.

Manual says On the wire Function code
40001 0 03 holding register
40100 99 03
30001 0 04 input register
00001 0 01 coil

And when a 32-bit value spans two registers, which half comes first is device-specific. A reading that is wildly large or unexpectedly negative is almost always reversed word order, not bad arithmetic — swap the two registers and recombine.

In Python, only the client line changes

# pip install pymodbus
from pymodbus.client import ModbusSerialClient, ModbusTcpClient

# RTU - serial port
client = ModbusSerialClient(port="COM3", baudrate=9600,
                            parity="N", stopbits=1, bytesize=8)

# TCP - IP address (this line instead of the one above)
# client = ModbusTcpClient("192.168.0.50", port=502)

client.connect()
rr = client.read_holding_registers(address=0, count=2, slave=1)
print(rr.registers)
client.close()
Enter fullscreen mode Exit fullscreen mode

That is pymodbus 3.x; version 2.x used unit=1 instead of slave=1.

When nothing answers

RTU

  1. Match baud rate, parity and stop bits exactly. 9600 8N1 against 9600 8E1 gives you silence.
  2. Swap A/B.
  3. Check for duplicate slave addresses, and whether the manual is 0- or 1-based.
  4. Check the terminators and the common ground.
  5. Connect a single slave directly. One works but several fail? Wiring or termination.

TCP

  1. ping first, then check port 502 — firewalls block it regularly.
  2. Check the Unit ID. Behind a gateway it has to match the serial slave address.
  3. Check the connection limit; cheap devices accept one or two sockets and refuse the rest.
  4. Capture with Wireshark. An exception reply is the function code plus 0x80: 02 means a bad address, 03 a bad quantity.

Longer version with diagrams, plus a free robot-communication track that builds a Modbus server and client on a single PC:

What tripped you up the first time you wired up Modbus? For me it was terminators — everything worked on the bench and fell apart in the panel.