SGT SGT Systems Limited
Integration & API · Protocol Reference · v1.0

Modbus TCP/RTU Notes

Field engineer reference: Modbus RTU vs TCP, function codes, register types, byte ordering, RS-485 wiring, and pymodbus examples.

Updated May 24, 2026
14 views

Modbus in one paragraph

Modbus is the lingua franca of industrial automation: a simple request/response protocol from 1979 that refuses to die because it works, it's well-documented, and every PLC and field device on Earth speaks it. SGT Systems engineers encounter Modbus on energy meters, VFDs, HVAC controllers, soft starters, gas analysers, and a thousand other devices. This document is the cheat sheet we keep open in the field.

Modbus is a strict single-master / multi-slave protocol. Only the master initiates transactions; slaves only ever respond. There is no asynchronous push from slave to master — if you want event-driven data from a Modbus device you must poll for it. This is why Modbus, despite being a "1980s protocol", remains a poor fit for high-frequency, event-driven applications and an excellent fit for cyclical telemetry.

Modbus RTU vs Modbus TCP

AspectModbus RTUModbus TCP
Physical layerRS-485 (or RS-232) serialTCP/IP over Ethernet
Framing3.5-character silence delimits framesMBAP header (7 bytes) + PDU
Error check16-bit CRCNone (TCP handles it)
Addressing1-byte slave/unit ID (1–247)IP address + unit ID (often 1 or 255)
Default portn/a (serial)502
TopologyMulti-drop bus, up to 32 nodes per segmentSwitched Ethernet, star
Typical baud rate9600 or 19200 baudEthernet line rate
ConcurrencyStrictly serial — one transaction at a timeMultiple TCP sessions can poll in parallel

The application-layer PDU (function code + data) is identical between the two. A protocol gateway is often just a transparent bridge that re-frames RTU into TCP and back. There is also a less-common variant, Modbus ASCII, which transmits hex-encoded bytes between : and CR/LF delimiters; it's roughly half as efficient as RTU and you rarely see it outside of very old legacy installations.

Function Codes

You will encounter these function codes (FCs) every day:

FCNameActs onRead/Write
01Read CoilsCoils (DO)R
02Read Discrete InputsDiscrete inputs (DI)R
03Read Holding RegistersHolding registersR
04Read Input RegistersInput registersR
05Write Single CoilCoilW
06Write Single RegisterHolding registerW
15Write Multiple CoilsCoilsW
16Write Multiple RegistersHolding registersW

Less common but worth knowing: FC 17 (Report Slave ID), FC 22 (Mask Write Register — useful for setting individual bits without race conditions), FC 23 (Read/Write Multiple — round-trip optimisation that combines a read and a write in one transaction).

Exception responses set the high bit of the function code (0x80) and add an exception code byte:

CodeNameMeaning
01Illegal FunctionSlave does not implement this function code.
02Illegal Data AddressAddress not valid on this slave.
03Illegal Data ValueValue out of range or count too large.
04Slave Device FailureInternal error in slave.
06Slave Device BusySlave is processing a long command; retry later.

Register Types

ObjectSizeDirectionRead FCWrite FCAddress range (zero-based)
Coil1 bitR/W0105 / 150–65535 (00001–09999 legacy)
Discrete Input1 bitR020–65535 (10001–19999 legacy)
Input Register16-bit wordR040–65535 (30001–39999 legacy)
Holding Register16-bit wordR/W0306 / 160–65535 (40001–49999 legacy)
Off-by-one trap. Vendor docs often quote addresses in the legacy 1-based form (e.g. "register 40001"). The wire protocol uses 0-based addressing — so 40001 maps to wire address 0, and 40100 maps to wire address 99. Most libraries (pymodbus included) want the 0-based form. Always confirm which convention the docs use.

Reading larger types: a 32-bit integer or float occupies two consecutive 16-bit registers; a 64-bit double occupies four. You issue a single FC 03 or FC 04 with count=2 (or 4) and then decode the returned bytes according to the byte/word order the device uses (see next section). A single Modbus transaction can read up to 125 holding/input registers or 2000 coils/inputs; trying to read more in one call returns exception 03.

Byte and Word Ordering

A 32-bit value (e.g. a float or an int) spans two consecutive 16-bit registers. Different vendors order those four bytes differently. There is no single "right" order — you must check the device manual or experimentally identify it.

Order nameByte sequenceAlso calledTypical vendors
Big-endianAB CDABCD, MSB-firstSchneider, many European meters
Little-endianDC BADCBA, LSB-firstRare; some Asian VFDs
Big-endian, word-swappedCD ABMid-little-endian, CDABMany US PLCs, Allen-Bradley
Little-endian, word-swappedBA DCBADCOccasionally seen on legacy gear

If a temperature reading is showing as 1.4e-41 instead of 23.5, you almost certainly have the wrong byte/word order. Try the other three permutations. A useful diagnostic: write the float 1.0 to a register, then read back as raw bytes. 1.0 as IEEE-754 single is 3F 80 00 00 — whichever order produces those bytes in order tells you the encoding.

RS-485 Wiring Notes

Modbus RTU lives on RS-485, a differential bus. Get the physical layer wrong and nothing else matters. RS-485 is half-duplex by default in Modbus deployments — drivers tri-state when not transmitting so the bus is shared.

Cable

  • Use shielded twisted pair, characteristic impedance 120 Ω. Belden 3105A or equivalent is the safe choice.
  • Run a third conductor as common reference (GND/SC) — RS-485 is differential but still needs the two devices' grounds within a few volts of each other.
  • Maximum total bus length is roughly 1200 m at 9600 baud, less at higher speeds (~600 m at 38400, ~100 m at 115200).
  • Keep cables away from VFD output cables and high-current AC runs. If you must cross them, do so at 90 degrees.

Termination

  • Install a 120 Ω resistor across A and B at each end of the bus. Two terminators total, no more.
  • Many devices have a switchable internal terminator — check it's enabled only on the two end devices.
  • An untermianted bus may work at low baud rates and short lengths and fail mysteriously when you scale up.

Biasing

  • When no device is driving the bus, A and B float and noise can be misread as data. Add pull-up on A and pull-down on B (typically 680 Ω each) at one master location.
  • Many modern converters bias internally; over-biasing causes signal asymmetry. Don't add external bias if the converter datasheet says it has internal bias.

A/B polarity

This is the single most common field problem. There is no industry-wide convention: one vendor's "A" is another vendor's "B". The reliable test:

  • With the bus idle, measure DC between A and B. The line at the more positive potential is the non-inverting line (often labelled A, sometimes D+, sometimes TX+).
  • If a device won't talk, try swapping A and B before tearing apart your config. Swap takes 10 seconds; debugging takes hours.

Topology

RS-485 is a daisy chain. Star topologies and long stubs (> 30 cm at 9600 baud) cause reflections and intermittent CRC errors. Use a "T" tap with very short legs, or daisy-chain through each device. Bus repeaters can extend reach beyond 32 nodes / 1200 m at the cost of an extra failure point.

Sample TCP read with pymodbus

from pymodbus.client import ModbusTcpClient
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.constants import Endian

client = ModbusTcpClient(host="10.20.30.40", port=502, timeout=3)
client.connect()

# Read 2 holding registers starting at address 100 from unit 1
rr = client.read_holding_registers(address=100, count=2, slave=1)
if rr.isError():
    raise RuntimeError(f"Modbus error: {rr}")

# Schneider energy meter, big-endian, word-swapped (CDAB) float
decoder = BinaryPayloadDecoder.fromRegisters(
    rr.registers,
    byteorder=Endian.BIG,
    wordorder=Endian.LITTLE,
)
power_kw = decoder.decode_32bit_float()
print(f"Active power: {power_kw:.2f} kW")

client.close()

Sample RTU read with pymodbus

from pymodbus.client import ModbusSerialClient

client = ModbusSerialClient(
    port="/dev/ttyUSB0",
    baudrate=9600,
    parity="N",     # most common; some meters need "E"
    stopbits=1,
    bytesize=8,
    timeout=1,
)
client.connect()

# Slave/unit 5, read 10 input registers starting at 0
rr = client.read_input_registers(address=0, count=10, slave=5)
if not rr.isError():
    print(rr.registers)

client.close()

Troubleshooting

CRC errors (RTU)

  • Bad wiring: missing or wrong-value termination, noisy cable run near VFDs.
  • Wrong baud rate, parity, or stop bits. Modbus RTU has no autonegotiation — both ends must match exactly.
  • Ground loops between segments at different earth potentials. Use an isolated RS-485 transceiver.
  • Too many devices on the segment — past 32 transceiver loads, signal levels collapse. Use a repeater.

Timeout / no response

  • Wrong slave ID. Try a Modbus scan across IDs 1–247.
  • A/B polarity reversed.
  • Master polling too fast — many devices need 50–100 ms inter-frame delay.
  • For TCP: firewall blocking port 502, or the device only accepts a specific unit ID (often 255).
  • Some PLCs throttle Modbus when CPU is busy with high-priority tasks; transient timeouts in those cases are normal.

Garbage values

  • Almost always byte/word ordering. See the table above.
  • Reading the wrong register type — FC 03 versus FC 04 on the same address yields completely different data.
  • Scale factor in the manual — many meters report power × 100 as an int to avoid floats.
  • Signed vs unsigned interpretation — a "negative power" of 65000 W is actually -535 W as a signed 16-bit.

Intermittent failures

  • EMI from variable frequency drives, contactors switching, lightning. Shield, ground, and route correctly.
  • Loose wire under a screw terminal — vibration over weeks loosens fasteners. Re-torque on a maintenance schedule.
  • Cellular gateway dropping the TCP session during carrier handover. Add reconnection logic with backoff.
Field tip. Carry a USB-to-RS-485 adapter and a copy of Modbus Poll (Windows) or mbpoll (Linux). Being able to query a device directly from your laptop, with the PLC out of the loop, resolves 80% of "the gateway isn't reading the meter" tickets in five minutes.

Performance Tips

  • Batch reads. Reading 50 consecutive registers in one transaction is dramatically faster than 50 individual one-register reads. Group telemetry tags by adjacency.
  • Stagger polls across devices. If you have 20 meters on one RTU bus, do not poll all of them every 1 second; you'll saturate the bus. Spread the poll cycle out.
  • Cache "nameplate" data. Serial number, firmware version, rating — read once at startup, don't poll continuously.
  • Use Modbus TCP gateways with parallel sessions only when the downstream RTU bus can handle the resulting collisions. Most cannot; the gateway serialises internally.

Need help?

For commissioning support, gateway selection, or help mapping a stubborn vendor's registers, contact the SGT Systems field engineering team at support@sgtsystems.com or via our contact page.

v1.0 · Last updated May 24, 2026 · Published May 24, 2026
© 2026 Smart Global Tech Systems Limited
• Related documentation

More from Integration & API

Browse all →