Modbus RTU Over Serial: How It Works on RS-485
Modbus is the lingua franca of industrial automation. Walk into any plant, solar farm, or building management room and the PLCs, VFDs, energy meters, and temperature controllers on the bus are probably speaking Modbus RTU over an RS-485 serial line. The protocol has not changed since 1979, and that is exactly why it is still everywhere: it is simple, deterministic, and cheap to implement in firmware. For background on the underlying electrical standard, see our guide to serial communication protocols.
The trade-off for that simplicity is that Modbus RTU is unforgiving. There is no start-of-frame byte, no length field, and no built-in retransmission. A device recognizes a frame by listening for silence on the bus. A single miscalculated CRC byte, a too-short inter-frame gap, or a parity mismatch and the frame is gone. If you are commissioning a system or debugging one that has stopped talking, you need to understand what is actually on the wire.
This guide breaks Modbus RTU down to the byte level: the frame structure, the CRC-16 algorithm, function codes, timing rules, and how to read the raw bytes from a macOS serial terminal.
Modbus RTU vs ASCII vs TCP
The Modbus family has three variants you will encounter in the field. They share the same logical model (coils, discrete inputs, holding registers, input registers) but differ in how frames are encoded and transported.
- Modbus RTU is the binary serial form. Bytes go on the wire as 8-bit values, framed by silence, protected by a 2-byte CRC-16. This is what runs over RS-485 in the vast majority of installations.
- Modbus ASCII is the text serial form. Each byte is sent as two ASCII hexadecimal characters (0-9, A-F), frames begin with
:and end with CRLF, and the checksum is a single-byte longitudinal redundancy check (LRC). ASCII is roughly half the throughput of RTU and is mostly found on legacy equipment. - Modbus TCP wraps the same request/response PDU in a TCP segment with a 7-byte MBAP header. There is no CRC (TCP already guarantees delivery) and no inter-frame timing. Modbus TCP typically runs on port 502 over Ethernet.
The rest of this article focuses on RTU, because that is what you debug with a serial terminal in your hand.
The RTU Frame on the Wire
A Modbus RTU frame has exactly four parts, in this order:
- Slave Address (1 byte) — the destination device, range 1 to 247. Address 0 is the broadcast address: every slave acts on the request and none replies. Addresses 248 to 255 are reserved.
- Function Code (1 byte) — the operation the slave should perform. Valid public codes are 1 to 127. If the slave rejects the request (bad address, illegal value) it echoes the function code back with the high bit set (an exception response).
- Data (variable length) — the request or response payload. For a read, this contains the start address and quantity; for a write, the values to write.
- CRC-16 (2 bytes, little-endian) — cyclic redundancy check over the address, function code, and data. The low byte is transmitted first, then the high byte.
There is no start byte. A slave knows a new frame has begun when it sees the first byte after a silent period, and it knows the frame has ended when the silence resumes. This is what makes timing so important on RTU.
CRC-16: Polynomial 0xA001
Modbus RTU uses CRC-16 with the polynomial 0xA001, which is the bit-reversed form of the standard CRC-16 polynomial 0x8005. The algorithm is initialized to 0xFFFF and processes each byte least-significant-bit first:
crc = 0xFFFF; for each byte: crc ^= byte; for i in 0..7: if (crc & 1) crc = (crc >> 1) ^ 0xA001; else crc >>= 1;
After every byte has been processed, the resulting 16-bit value is appended to the frame with the low byte first. This little-endian ordering catches a particular class of errors where the byte order itself is swapped, and it is a common implementation bug: a CRC computed correctly but appended in the wrong byte order will fail validation on every legitimate slave.
Inter-Frame Timing: 3.5 Character Times
Because there is no delimiter byte, RTU relies on bus silence to mark frame boundaries. The Modbus over Serial Line specification defines two timing values:
- t3.5 — at least 3.5 character times of silence delimit the end of one frame and the start of the next. A character on the wire is 11 bits (1 start + 8 data + 1 parity + 1 stop, or 1 start + 8 data + 2 stop bits). At 9600 baud that is roughly 4.0 ms; at 19200 baud it is about 2.0 ms.
- t1.5 — 1.5 character times of silence between bytes inside a single frame. If a longer gap arrives mid-frame, the receiver discards the partial frame.
At baud rates above 19200, the calculated character times become impractically small and the spec mandates fixed values: t3.5 = 1.75 ms and t1.5 = 750 µs. This is also why some USB-to-RS-485 adapters struggle at 115200 baud: their latency budget eats into the inter-frame gap, and the master starts the next frame before the slave has finished parsing the previous one.
This is the single most common reason a Modbus RTU bus "works at 9600 but falls apart at 115200." It is not the cable; it is the timing.
Function Codes Over Serial
Every Modbus operation is identified by a 1-byte function code. The codes you will see on almost every industrial bus:
- 0x01 Read Coils — read one or more 1-bit coil values (output booleans).
- 0x02 Read Discrete Inputs — read 1-bit input-only values.
- 0x03 Read Holding Registers — read 16-bit read/write registers. This is the workhorse of Modbus: setpoints, measured values, configuration parameters.
- 0x04 Read Input Registers — read 16-bit input-only registers (e.g. counters).
- 0x05 Write Single Coil — write one coil (ON or OFF).
- 0x06 Write Single Register — write one holding register.
- 0x0F Write Multiple Coils — write a block of coils.
- 0x10 Write Multiple Registers — write a block of holding registers.
If the slave cannot execute the request (register does not exist, value out of range, device in the wrong state) it returns an exception response: function code ORed with 0x80, followed by a 1-byte exception code. Function code 0x83 with exception code 0x02 means "illegal data address" and is what you see when you try to read a register that the device does not actually expose.
A Worked Example: Read Holding Register 40001
Let's walk through a complete request/response on the wire. We want to read one holding register from slave 1, starting at the documented address 40001. In Modbus 5-digit addressing, the leading "4" identifies the register type (holding) and the remaining digits are 1-indexed, so 40001 maps to the zero-indexed register address 0x0000. The quantity is one register: 0x0001.
The Request
01 03 00 00 00 01 0A 84
0x01— Slave Address (1)0x03— Function Code (Read Holding Registers)0x00 0x00— Start Address (register 0)0x00 0x01— Quantity (1 register)0x0A 0x84— CRC-16 little-endian (low byte 0x0A, then high byte 0x84, CRC value 0x0A84)
The Response
Assuming register 40001 contains the value 0 (0x0000), the slave responds:
01 03 02 00 00 B8 44
0x01— Slave Address (echoes the request)0x03— Function Code (echoes the request)0x02— Byte Count (2 bytes of data, since one 16-bit register is two bytes on the wire)0x00 0x00— Register value (0)0xB8 0x44— CRC-16 little-endian (low byte 0xB8, then high byte 0x44, CRC value 0x44B8)
If you can read those two hex strings and verify the CRCs in your head, you can debug any Modbus RTU bus. Most engineers cannot, which is exactly why a serial terminal with protocol-aware analysis is valuable.
Serial Port Configuration
The serial parameters must match on master and slave. The dominant Modbus RTU configuration is 8N1: 8 data bits, no parity, 1 stop bit. Other combinations you will encounter:
- 8E1 (8 data, even parity, 1 stop) — common on older equipment from the 1990s when parity was a meaningful extra check.
- 8N2 (8 data, no parity, 2 stop bits) — sometimes used as a workaround for slaves that need extra time between characters.
- 8O1 (8 data, odd parity, 1 stop) — rare, but some inherited installations insist on it.
Baud rates in order of prevalence: 9600 (the de facto default), 19200, 38400, and 115200 (fast, but the inter-frame gap is tight and some adapters cannot keep up). A bus is only as fast as its slowest device; mixing 9600-only energy meters with 115200-capable PLCs means the whole segment runs at 9600.
Master/Slave on RS-485: Half-Duplex
RS-485 is a half-duplex electrical standard: one differential pair carries both directions, but only one device drives it at a time. A Modbus RTU bus is therefore strictly request/response. The master sends, then releases the line, then listens. The addressed slave waits a brief turn-around delay (typically 1 to 100 ms, often configurable), drives the line with its response, and releases it again. The master then issues the next request.
This has two practical consequences. First, there is no collision detection: if the master starts transmitting before the slave has finished, both signals collide and the frame is corrupted. Second, the master must tri-state its driver fast enough that the bus is released for the slave's response.USB-to-RS-485 adapters with automatic direction control handle this in hardware; adapters with an explicit DE/RE pin require software control and tight timing. If you see your own request echoed back in the response window, the adapter is not releasing the line.
Reading Modbus RTU from a macOS Serial Terminal
With a USB-to-RS-485 adapter connected to the bus, here is what the raw byte stream looks like in a hex view. For a deeper dive into the adapter side of this workflow, see our USB-to-RS485 guide for macOS.
01 03 00 00 00 01 0A 84 -- master request
01 03 02 00 00 B8 44 -- slave response
Without protocol analysis, that is all you see: 8 hex bytes, then 7 hex bytes, then nothing until the next poll. A protocol-aware terminal annotates the same stream:
- Request to slave 0x01: FC03 Read Holding Registers, address 0x0000, quantity 1, CRC 0x0A84 (valid)
- Response from slave 0x01: FC03, 2 data bytes, value 0x0000, CRC 0x44B8 (valid)
That annotation is the difference between staring at hex and actually debugging the bus. The built-in screen command on macOS cannot do this; it shows ASCII only, which is unreadable for binary Modbus. For more on the tooling landscape, see our survey of serial terminals for macOS.
Comparison: Modbus RTU vs ASCII vs TCP
Common Errors and What They Mean
- CRC error on every frame. The slave rejects the request entirely and stays silent, so you see a timeout at the master. Cause is almost always noise on the bus, wrong baud rate, or wrong byte order in the appended CRC. Verify baud first.
- Timeout, no response at all. Either the slave address is wrong, the slave is not on the bus, the cable is broken, or two slaves are both driving the line (bus contention). Try a broadcast (address 0) read to see if anything responds.
- Garbled or partial frames. The inter-frame gap is too small, the master is queueing requests faster than the slave can process them, or there are reflections from missing termination. Slow the poll rate; if frames clean up, you have a timing problem.
- Echo of your own request in the response window. The RS-485 adapter is not releasing the driver after transmission. With a DE/RE-pin adapter, the turn-off timing in your master software is too slow. Switch to an adapter with automatic direction control.
- Exception response (FC | 0x80). The slave received a valid frame but rejected the operation. Exception 0x01 is illegal function (the device does not support that FC), 0x02 is illegal data address (register does not exist), 0x03 is illegal data value (out of range).
If you want to test master logic without risking real hardware, see our guide to running a Modbus simulator on macOS. For a broader comparison of where Modbus fits against other industrial protocols, see our industrial protocol comparison.
Frequently Asked Questions
What is the difference between Modbus RTU and Modbus ASCII over serial?
Modbus RTU uses compact binary frames with a CRC-16 checksum, sending each byte as 8 bits on the wire. Modbus ASCII represents every byte as two ASCII hex characters (0-9, A-F) wrapped between a colon start character and a CRLF end delimiter, with a simpler LRC checksum. RTU is roughly twice as fast as ASCII for the same payload and is the dominant choice on RS-485; ASCII is easier to read on a plain-text terminal but rarely worth the bandwidth cost.
How is the CRC-16 calculated in Modbus RTU?
Modbus RTU uses CRC-16 with the polynomial 0xA001 (the bit-reversed form of 0x8005), initialized to 0xFFFF. The CRC is computed over the slave address, function code, and data bytes, then appended little-endian: the low byte is sent first, then the high byte. The receiver recomputes the CRC over the incoming bytes and compares it to the appended value; any mismatch means the frame is discarded as corrupted.
What is the 3.5 character time inter-frame gap in Modbus RTU?
Modbus RTU has no explicit delimiter byte, so frames are separated by silence on the bus. A silent interval of at least 3.5 character times marks the end of one frame and the start of the next. At or above 19200 baud the Modbus specification recommends a fixed 1.75 ms gap, because the calculated 3.5-character time becomes impractically small. Without this inter-frame gap, slaves cannot tell where one frame ends and the next begins.
Do I need parity if Modbus RTU already uses CRC-16?
Practically, no. CRC-16 catches far more errors than a single parity bit, so 8N1 (8 data bits, no parity, 1 stop bit) is the de facto Modbus RTU default. Some legacy installations use 8E1 or 8N2 for compatibility with older equipment, but parity adds little protection when the CRC is mandatory and present on every frame. Match the parity setting to whatever the slave device documents.
Serial Terminal for macOS
MacTools Serial Terminal shows Modbus RTU frames annotated in real time, validates CRC-16 automatically, and logs every byte with millisecond timestamps. User-space drivers for Apple Silicon. $9.99 one-time.
Get MacTools Serial TerminalRelated: Full SCADA System
Need continuous monitoring with dashboards, alarms, and trending across all your devices? Voltrus SCADA supports Modbus, OPC-UA, Siemens S7, Allen-Bradley, DNP3, BACnet, MQTT, and more. Lifetime license from $249.