RS-485
Configure RS-485 half-duplex communication with PySerial. Set up RS485Settings, RTS toggling, and wiring for multi-drop serial networks.
PySerial supports RS-485 mode through serial.rs485.RS485Settings. This configures the kernel driver to toggle RTS automatically for half-duplex transmit/receive switching.
RS-485 Basics
RS-485 is a differential signaling standard for multi-drop serial networks. Unlike RS-232 (point-to-point, short range), RS-485 supports up to 32 devices on a single bus at distances up to 1200 meters.
| Feature | RS-232 | RS-485 |
|---|---|---|
| Topology | Point-to-point | Multi-drop bus |
| Max devices | 2 | 32 (standard), 256 (high-impedance) |
| Max distance | ~15 m | ~1200 m |
| Signaling | Single-ended | Differential |
| Duplex | Full | Half (2-wire) or full (4-wire) |
Wiring
Two-wire RS-485 (half-duplex) uses three connections:
| Pin | Name | Function |
|---|---|---|
| A | Data- (inverting) | Differential pair, negative |
| B | Data+ (non-inverting) | Differential pair, positive |
| GND | Signal ground | Common reference |
Connect A to A, B to B, and GND to GND between all devices. Add 120-ohm termination resistors at both ends of the bus for runs over a few meters.
Enable RS-485 Mode
import serial
import serial.rs485
ser = serial.Serial("/dev/ttyUSB0", 9600, timeout=1)
ser.rs485_mode = serial.rs485.RS485Settings(
rts_level_for_tx=True,
rts_level_for_rx=False,
delay_before_tx=0.0,
delay_before_rx=0.0,
)
ser.write(b"\x01\x03\x00\x00\x00\x0A\xC5\xCD") # Modbus query example
response = ser.read(25)
print(response.hex())
ser.close()When rs485_mode is set, the driver asserts RTS before transmitting and de-asserts it after. Your code just calls write() and read() normally.
Log RS-485 bus data automatically
TofuPilot records test results from your PySerial scripts, tracks pass/fail rates across RS-485 connected devices, and generates compliance reports. Free to start.
RS485Settings Parameters
| Parameter | Default | Purpose |
|---|---|---|
rts_level_for_tx | True | RTS state during transmission |
rts_level_for_rx | False | RTS state during reception |
loopback | False | Enable RS-485 loopback mode |
delay_before_tx | None | Seconds to wait after asserting RTS before sending |
delay_before_rx | None | Seconds to wait after sending before de-asserting RTS |
RTS Timing
Some RS-485 adapters need time to switch direction. If you're dropping the first byte of a response or the last byte of a transmission, adjust the delays:
import serial
import serial.rs485
ser = serial.Serial("/dev/ttyUSB0", 9600, timeout=1)
ser.rs485_mode = serial.rs485.RS485Settings(
rts_level_for_tx=True,
rts_level_for_rx=False,
delay_before_tx=0.002, # 2 ms before transmitting
delay_before_rx=0.002, # 2 ms after transmitting, before receiving
)Start with 1-2 ms and increase if you still get garbled data. Most USB-to-RS485 adapters work with zero delay.
Platform Support
Linux
Linux has native RS-485 support through ioctl. PySerial uses this when available. You can also configure RS-485 mode system-wide with rs485conf:
# Check current RS-485 settings
rs485conf /dev/ttyS1
# Enable RS-485 with RTS-on-send
rs485conf /dev/ttyS1 --rs485=1 --rts-on-send=1 --rts-after-send=0Not all UART drivers support rs485conf. Built-in UARTs (e.g. on Raspberry Pi, BeagleBone) usually do. USB adapters rely on PySerial's software RTS toggling.
Windows and macOS
No native RS-485 kernel support. PySerial toggles RTS in software, which works but has higher latency. For time-critical applications on these platforms, use a USB-to-RS485 adapter with built-in direction control (auto-direction adapters with TXE pin).
USB-to-RS485 Adapters
Common adapter chips and their behavior:
| Chip | Auto-direction | Notes |
|---|---|---|
| FTDI FT232R + MAX485 | No | Needs RTS toggling via PySerial |
| FTDI FT232H | Optional | Depends on breakout board design |
| CH340 + MAX485 | No | Cheap, works fine with RTS toggling |
| USB-RS485 (auto) | Yes | Built-in direction control, no RTS needed |
With auto-direction adapters, you don't need rs485_mode at all. Just open the port and read/write normally. The adapter handles TX/RX switching in hardware.
Multi-Device Polling
RS-485 buses commonly use a master-slave (polling) pattern. The master sends a request with a device address, and only the addressed device responds.
import serial
import serial.rs485
import struct
def poll_device(ser, address, register, count):
"""Send a Modbus-style read request and return raw response bytes."""
# Build request: address + function(0x03) + register + count
request = struct.pack(">BBHH", address, 0x03, register, count)
# Append CRC (simplified, use pymodbus for production)
crc = 0xFFFF
for byte in request:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
request += struct.pack("<H", crc)
ser.write(request)
# Response: address(1) + function(1) + byte_count(1) + data(2*count) + crc(2)
expected_len = 3 + 2 * count + 2
return ser.read(expected_len)
ser = serial.Serial("/dev/ttyUSB0", 9600, timeout=0.5)
ser.rs485_mode = serial.rs485.RS485Settings()
for addr in range(1, 5):
resp = poll_device(ser, addr, 0x0000, 2)
if resp:
print(f"Device {addr}: {resp.hex()}")
else:
print(f"Device {addr}: no response")
ser.close()For real Modbus work, use the pymodbus library instead of computing CRCs manually. See the Modbus RTU page.
Troubleshooting
No response from devices. Check A/B wiring (swapped A and B is the most common mistake). Some devices label them differently (D+ vs Data+).
Garbled first/last bytes. Increase delay_before_tx or delay_before_rx. The adapter may not switch direction fast enough.
Works at low baud, fails at high baud. Add termination resistors (120 ohm between A and B) at both ends of the bus. Long cables without termination cause reflections at higher speeds.
Permission denied on Linux. Add your user to the dialout group:
sudo usermod -aG dialout $USERLog out and back in for the group change to take effect.