Communication
PLC Serial Communication: RS232, RS485, Modbus RTU/TCP & Communication Layers Explained
Understand PLC serial communication from the physical wire up — RS232 vs RS485, TX/RX data flow, Modbus RTU vs TCP framing, and the 7-layer OSI model mapped to industrial protocols.
The physical layer: RS232 and RS485
Before any protocol can run, the bits have to become voltages on copper. RS232 and RS485 define that part: how ones and zeros travel down the wire, how far they can go and how many devices can share it.
The two standards as roads
If RS232 is a country lane between two houses, RS485 is a highway that a whole street of devices can join:
| Concept | RS232 | RS485 |
|---|---|---|
| Road type | Single-lane country road | Multi-lane highway |
| Distance | ~15 meters max | Up to 1200 meters |
| Passengers | 1 driver, 1 passenger (point-to-point) | Up to 32 devices on one bus |
| Speed vs distance | Fast but short | Fast AND long |
| Noise immunity | Low (single-ended) | High (differential signaling) |
TX and RX
Every serial connection has two basic signals. TX (transmit) carries data leaving the device, and RX (receive) carries data arriving at it.
Cross TX to RX
One device's TX has to land on the other device's RX, with the grounds joined. Get this wrong and you see no response at all:
Device A Device B
┌──────┐ ┌──────┐
│ TX ─┼──────────┼─ RX │
│ RX ─┼──────────┼─ TX │
│ GND ─┼──────────┼─ GND │
└──────┘ └──────┘
TX always connects to RX (crossover)
Never TX-to-TX or RX-to-RX!
RS232 signal levels
RS232 is single-ended: each signal is measured against ground, and the logic levels are inverted, so a negative voltage is a 1:
Logic 1 (Mark): -3V to -15V
Logic 0 (Space): +3V to +15V
+15V ┤ ┌───┐ ┌───┐
│ │ │ │ │ ← Logic 0
+3V ┤─ ─ ─│─ ─│─ ─ ─│─ ─│─ ─ ─
0V ┤ │ │ │ │
-3V ┤─ ─ ─│─ ─│─ ─ ─│─ ─│─ ─ ─
│ ────┘ └─────┘ └──── ← Logic 1
-15V ┤
RS485 differential signaling
RS485 sends each bit as the voltage difference between two wires, A and B. Noise that hits both wires equally cancels out at the receiver:
┌─── Line A (D+) ───┐
Device ─────┤ ├───── Device
└─── Line B (D-) ───┘
Logic 1: A > B (differential voltage > +200mV)
Logic 0: A < B (differential voltage < -200mV)
Noise hits BOTH lines equally → receiver subtracts it out!
This is why RS485 works over 1200m in noisy factories.
RS232 or RS485: side by side
| Feature | RS232 | RS485 |
|---|---|---|
| Signaling | Single-ended | Differential |
| Max cable length | 15m (50 ft) | 1200m (4000 ft) |
| Max devices | 2 (point-to-point) | 32 (multidrop bus) |
| Topology | Point-to-point | Bus / daisy-chain |
| Common use | PC-to-PLC debug, HMI | Fieldbus, sensor networks |
| Connector | DB9 / DB25 | Screw terminals (A, B, GND) |
| Typical baud rates | 9600–115200 | 9600–921600 |
| Industrial rating | Office/lab | Factory floor |
When to use each
RS232 → Use when:
✓ Short distance (< 15m)
✓ Only 2 devices talking
✓ Programming port / HMI connection
✓ Legacy equipment
RS485 → Use when:
✓ Long distance or noisy environment
✓ Multiple devices on one cable
✓ Modbus RTU network
✓ Industrial sensor bus
Modbus: what the bytes mean
Modbus is a protocol. RS232 and RS485 move the bytes; Modbus defines what those bytes mean. If the serial line is the postal truck, Modbus is the format of the letter inside it.
Modbus RTU vs Modbus TCP
| Feature | Modbus RTU | Modbus TCP |
|---|---|---|
| Transport | RS232 / RS485 serial | Ethernet (TCP/IP) |
| Framing | Binary with CRC-16 | TCP packet with MBAP header |
| Addressing | Station ID (1–247) | IP address + Unit ID |
| Speed | Up to 115200 baud | 10/100 Mbps Ethernet |
| Max devices | 247 addresses; 32 unit loads per RS-485 segment without repeaters | Unlimited (network limit) |
| Error check | CRC-16 checksum | TCP built-in + optional |
| Cable | 2-wire (RS485) or 3-wire (RS232) | Cat5/Cat6 Ethernet |
Modbus RTU frame structure
An RTU frame is the slave ID, the function code, the data and a CRC-16. A gap of 3.5 character times of silence on the line marks the end of a frame.
┌──────────┬───────────┬──────────────┬──────────┐
│ Slave ID │ Function │ Data │ CRC-16 │
│ (1 byte) │ (1 byte) │ (N bytes) │ (2 bytes)│
└──────────┴───────────┴──────────────┴──────────┘
│ │ │ │
│ │ │ └─ Error detection
│ │ └─ Register addresses & values
│ └─ Read (03), Write Single (06), Write Multi (16)
└─ Which device responds (1–247)
Frame gap: 3.5 character times of silence = end of frame
Modbus TCP frame (MBAP header)
Modbus TCP drops the CRC and puts a 7-byte MBAP header in front of the function code. The Unit ID in that header plays the part of the RTU slave ID:
┌──────────────┬────────────┬────────┬─────────┬───────────┬──────────┐
│ Transaction │ Protocol │ Length │ Unit ID │ Function │ Data │
│ ID (2 bytes) │ ID (2 bytes│(2 bytes│(1 byte) │ (1 byte) │(N bytes) │
│ │ = 0x0000) │ │ │ │ │
└──────────────┴────────────┴────────┴─────────┴───────────┴──────────┘
│
└─ Same as RTU slave ID
No CRC needed — TCP handles error detection!
Common Modbus function codes
| Code | Name | Description |
|---|---|---|
| 01 | Read Coils | Read digital outputs (ON/OFF) |
| 02 | Read Discrete Inputs | Read digital inputs |
| 03 | Read Holding Registers | Read analog/config values (most common) |
| 04 | Read Input Registers | Read analog input values |
| 05 | Write Single Coil | Set one digital output |
| 06 | Write Single Register | Set one register value |
| 15 | Write Multiple Coils | Set multiple digital outputs |
| 16 | Write Multiple Registers | Set multiple register values |
The OSI model for industrial communication
The OSI model is why you need both RS485 and Modbus: RS485 sits at the physical layer and Modbus at the application layer. Each of the seven layers has its own job:
Layer 7 │ APPLICATION │ Modbus commands, read/write registers
────────┤──────────────┤─────────────────────────────────────
Layer 6 │ PRESENTATION │ Data encoding (big-endian, ASCII vs RTU)
────────┤──────────────┤─────────────────────────────────────
Layer 5 │ SESSION │ Request/response pairing, timeouts
────────┤──────────────┤─────────────────────────────────────
Layer 4 │ TRANSPORT │ TCP (reliable) or UDP (fast)
────────┤──────────────┤─────────────────────────────────────
Layer 3 │ NETWORK │ IP addressing, routing between subnets
────────┤──────────────┤─────────────────────────────────────
Layer 2 │ DATA LINK │ Framing, MAC addresses, CRC checks
────────┤──────────────┤─────────────────────────────────────
Layer 1 │ PHYSICAL │ RS232/RS485 voltages, cables, connectors
Where industrial protocols sit in the model
Protocol │ Layer 1 │ Layer 2-4 │ Layer 7
──────────────────┤──────────────┤──────────────────┤──────────────
Modbus RTU │ RS232/RS485 │ Serial framing │ Modbus
Modbus TCP │ Ethernet │ TCP/IP │ Modbus
EtherNet/IP │ Ethernet │ TCP/UDP + IP │ CIP
PROFINET │ Ethernet │ TCP/UDP + RT │ PROFINET IO
PROFIBUS DP │ RS485 │ Token passing │ PROFIBUS
EtherCAT │ Ethernet │ On-the-fly │ CoE/SoE
OPC UA │ Ethernet │ TCP/IP │ OPC UA Binary
Modbus RTU master in Structured Text
The program below configures the serial port, then runs a state machine: wait for the 500 ms poll interval, read 10 holding registers with function 03, give up after three failed attempts (error or 2 s timeout), and after a good read write 5 registers back with function 16:
PROGRAM ModbusRTUMaster
VAR
// Serial port configuration
SerialConfig : COM_CONFIG;
// Modbus request
mbRequest : MODBUS_REQUEST;
mbSend : BOOL := FALSE;
mbBusy : BOOL;
mbDone : BOOL;
mbError : BOOL;
// Data buffers
HoldingRegs : ARRAY[0..9] OF WORD; // Read from slave
WriteRegs : ARRAY[0..4] OF WORD; // Write to slave
// Communication state machine
CommState : INT := 0;
PollTimer : TON;
TimeoutTimer : TON;
RetryCount : INT := 0;
MAX_RETRIES : INT := 3;
END_VAR
// ─── Serial Port Setup (done once) ───
SerialConfig.Port := 1; // COM1
SerialConfig.BaudRate := 19200; // Match slave setting
SerialConfig.DataBits := 8;
SerialConfig.StopBits := 1;
SerialConfig.Parity := 'E'; // Even parity (Modbus standard)
SerialConfig.Timeout := T#1S; // 1 second timeout
// ─── Communication State Machine ───
CASE CommState OF
0: // IDLE — Wait for poll interval
PollTimer(IN := TRUE, PT := T#500MS);
IF PollTimer.Q THEN
PollTimer(IN := FALSE);
CommState := 10;
END_IF;
10: // SEND READ REQUEST
mbRequest.SlaveID := 1; // Target device address
mbRequest.FunctionCode := 3; // Read Holding Registers
mbRequest.StartAddress := 40001; // First register
mbRequest.Quantity := 10; // Read 10 registers
mbSend := TRUE;
TimeoutTimer(IN := FALSE);
CommState := 20;
20: // WAIT FOR RESPONSE
TimeoutTimer(IN := TRUE, PT := T#2S);
IF mbDone THEN
// Success — copy data
HoldingRegs := mbRequest.ResponseData;
RetryCount := 0;
CommState := 30; // Next: write
ELSIF mbError OR TimeoutTimer.Q THEN
// Failed — retry logic
RetryCount := RetryCount + 1;
IF RetryCount >= MAX_RETRIES THEN
// Log alarm, skip to next cycle
RetryCount := 0;
CommState := 0;
ELSE
CommState := 10; // Retry
END_IF;
END_IF;
30: // SEND WRITE REQUEST
mbRequest.SlaveID := 1;
mbRequest.FunctionCode := 16; // Write Multiple Registers
mbRequest.StartAddress := 40101;
mbRequest.Quantity := 5;
mbRequest.WriteData := WriteRegs;
mbSend := TRUE;
CommState := 40;
40: // WAIT FOR WRITE CONFIRMATION
TimeoutTimer(IN := TRUE, PT := T#2S);
IF mbDone OR mbError OR TimeoutTimer.Q THEN
CommState := 0; // Back to idle
END_IF;
END_CASE;
Modbus TCP client in Structured Text
The TCP client builds a request with a fresh transaction ID each time, reads 16 holding registers from a remote I/O module on port 502, and keeps counters for packets sent, received and lost. It only accepts a response whose transaction ID matches the request:
PROGRAM ModbusTCPClient
VAR
// TCP connection
tcpConnect : TCP_CONNECT;
tcpConnected : BOOL;
ServerIP : STRING := '192.168.1.100';
ServerPort : UINT := 502; // Standard Modbus TCP port
// Modbus TCP transaction
TransactionID : UINT := 0;
mbTCPRequest : MODBUS_TCP_REQUEST;
// Read data from remote I/O module
RemoteInputs : ARRAY[0..15] OF WORD;
RemoteOutputs : ARRAY[0..7] OF WORD;
// Diagnostics
CommOK : BOOL;
ResponseTime : TIME;
RequestTimestamp : TIME;
PacketsSent : UDINT;
PacketsReceived : UDINT;
PacketsLost : UDINT;
END_VAR
// ─── Build Modbus TCP Request ───
TransactionID := TransactionID + 1;
mbTCPRequest.TransactionID := TransactionID;
mbTCPRequest.ProtocolID := 0; // Always 0 for Modbus
mbTCPRequest.UnitID := 1; // Slave/unit identifier
mbTCPRequest.FunctionCode := 3; // Read Holding Registers
mbTCPRequest.StartAddress := 0;
mbTCPRequest.Quantity := 16;
// ─── Send and Track ───
IF tcpConnected THEN
RequestTimestamp := TIME();
// ... send mbTCPRequest over TCP socket ...
PacketsSent := PacketsSent + 1;
END_IF;
// ─── Response Handling ───
IF ResponseReceived THEN
ResponseTime := TIME() - RequestTimestamp;
PacketsReceived := PacketsReceived + 1;
CommOK := TRUE;
// Validate transaction ID matches
IF Response.TransactionID = TransactionID THEN
RemoteInputs := Response.Data;
END_IF;
ELSIF ResponseTimeout THEN
PacketsLost := PacketsLost + 1;
CommOK := FALSE;
END_IF;
Common communication errors and fixes
| Symptom | Likely Cause | Fix |
|---|---|---|
| No response at all | TX/RX wired wrong | Swap TX and RX wires |
| Garbled data | Baud rate mismatch | Match all devices to same baud |
| CRC errors | Parity mismatch | Set all devices to same parity (Even/None) |
| Intermittent timeout | Cable too long for RS232 | Switch to RS485 for >15m |
| Multiple slaves conflict | Same address on bus | Assign unique IDs 1–247 |
| Modbus TCP refused | Wrong port or firewall | Use port 502, check firewall rules |
| Data swapped (byte order) | Endianness mismatch | Check big-endian vs little-endian |
Choosing by distance
Start with the distance to the device. Under 15 m with a single device (a debug or HMI link), RS232 is fine. For up to 32 sensors or VFDs on one cable, use RS485 with Modbus RTU. For SCADA and MES connections, use Ethernet with Modbus TCP:
┌─────────────────┐
│ How far is the │
│ device? │
└────────┬────────┘
│
┌───── < 15m ──────┐──── > 15m ────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ RS232 │ │ RS485 │ │ Ethernet│
│ OK │ │ Modbus │ │ Modbus │
│ │ │ RTU │ │ TCP │
└─────────┘ └─────────┘ └─────────┘
│ │ │
1 device Up to 32 Unlimited
Debug/HMI Sensors/VFDs SCADA/MES
Commissioning checklist
This is the error table above, in the order you would check it on site: