PySerial
PySerialDocs

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.

FeatureRS-232RS-485
TopologyPoint-to-pointMulti-drop bus
Max devices232 (standard), 256 (high-impedance)
Max distance~15 m~1200 m
SignalingSingle-endedDifferential
DuplexFullHalf (2-wire) or full (4-wire)

Wiring

Two-wire RS-485 (half-duplex) uses three connections:

PinNameFunction
AData- (inverting)Differential pair, negative
BData+ (non-inverting)Differential pair, positive
GNDSignal groundCommon 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

ParameterDefaultPurpose
rts_level_for_txTrueRTS state during transmission
rts_level_for_rxFalseRTS state during reception
loopbackFalseEnable RS-485 loopback mode
delay_before_txNoneSeconds to wait after asserting RTS before sending
delay_before_rxNoneSeconds 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=0

Not 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:

ChipAuto-directionNotes
FTDI FT232R + MAX485NoNeeds RTS toggling via PySerial
FTDI FT232HOptionalDepends on breakout board design
CH340 + MAX485NoCheap, works fine with RTS toggling
USB-RS485 (auto)YesBuilt-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 $USER

Log out and back in for the group change to take effect.