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
| Aspect | Modbus RTU | Modbus TCP |
|---|---|---|
| Physical layer | RS-485 (or RS-232) serial | TCP/IP over Ethernet |
| Framing | 3.5-character silence delimits frames | MBAP header (7 bytes) + PDU |
| Error check | 16-bit CRC | None (TCP handles it) |
| Addressing | 1-byte slave/unit ID (1–247) | IP address + unit ID (often 1 or 255) |
| Default port | n/a (serial) | 502 |
| Topology | Multi-drop bus, up to 32 nodes per segment | Switched Ethernet, star |
| Typical baud rate | 9600 or 19200 baud | Ethernet line rate |
| Concurrency | Strictly serial — one transaction at a time | Multiple 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:
| FC | Name | Acts on | Read/Write |
|---|---|---|---|
| 01 | Read Coils | Coils (DO) | R |
| 02 | Read Discrete Inputs | Discrete inputs (DI) | R |
| 03 | Read Holding Registers | Holding registers | R |
| 04 | Read Input Registers | Input registers | R |
| 05 | Write Single Coil | Coil | W |
| 06 | Write Single Register | Holding register | W |
| 15 | Write Multiple Coils | Coils | W |
| 16 | Write Multiple Registers | Holding registers | W |
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:
| Code | Name | Meaning |
|---|---|---|
| 01 | Illegal Function | Slave does not implement this function code. |
| 02 | Illegal Data Address | Address not valid on this slave. |
| 03 | Illegal Data Value | Value out of range or count too large. |
| 04 | Slave Device Failure | Internal error in slave. |
| 06 | Slave Device Busy | Slave is processing a long command; retry later. |
Register Types
| Object | Size | Direction | Read FC | Write FC | Address range (zero-based) |
|---|---|---|---|---|---|
| Coil | 1 bit | R/W | 01 | 05 / 15 | 0–65535 (00001–09999 legacy) |
| Discrete Input | 1 bit | R | 02 | — | 0–65535 (10001–19999 legacy) |
| Input Register | 16-bit word | R | 04 | — | 0–65535 (30001–39999 legacy) |
| Holding Register | 16-bit word | R/W | 03 | 06 / 16 | 0–65535 (40001–49999 legacy) |
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 name | Byte sequence | Also called | Typical vendors |
|---|---|---|---|
| Big-endian | AB CD | ABCD, MSB-first | Schneider, many European meters |
| Little-endian | DC BA | DCBA, LSB-first | Rare; some Asian VFDs |
| Big-endian, word-swapped | CD AB | Mid-little-endian, CDAB | Many US PLCs, Allen-Bradley |
| Little-endian, word-swapped | BA DC | BADC | Occasionally 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 × 100as 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.
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.