To connect a Raspberry Pi to an RS485 network, you must use a 3.3V-compatible transceiver like the SP3485, wired to the Pi’s primary UART pins (GPIO 14/15), with the serial console disabled in /boot/firmware/config.txt. Never use the standard 5V MAX485 module directly; its 5V logic output will permanently destroy the Pi’s 3.3V RX pin.

RS485 is the industrial standard for long-distance, noise-immune serial communication, capable of running up to 1,200 meters at lower baud rates. Because the Raspberry Pi operates at 3.3V logic and lacks native differential signaling, a TTL-to-RS485 breakout board is mandatory. Below is the complete hardware selection, wiring procedure, and Python implementation to get your Pi talking on an RS485 bus.

Hardware Spec Sheet & Transceiver Comparison

Choosing the wrong RS485 module is the most common cause of fried Raspberry Pi SoCs. The ubiquitous blue "MAX485" modules found on Amazon require a 5V VCC and output 5V logic on the RO (Receiver Output) pin. Feeding 5V into the Pi’s GPIO 15 (RXD) exceeds the absolute maximum ratings and will brick the board. Always select a 3.3V native module or use a logic level shifter.

Table 1: RS-485 Transceiver Module Comparison for 3.3V Microcontrollers
Module / IC Variant Logic Level Galvanic Isolation Max Baud Rate Est. Price (2026) Pi Compatibility
Generic MAX485 (HW-016) 5V No 2.5 Mbps $1.50 - $2.00 Unsafe (Requires BSS138 Level Shifter)
SP3485 3.3V Breakout 3.3V Native No 10 Mbps $3.00 - $4.50 Safe (Direct GPIO connection)
Waveshare RS485 CAN HAT 3.3V Native Yes (ISO1050) 1 Mbps $22.00 - $28.00 Safe (Plugs directly onto 40-pin header)
Adafruit MAX13487 (Auto-Flow) 3.3V - 5V No 16 Mbps $9.95 Safe (Auto TX/RX direction switching)
⚠️ Safety Callout: If you are working with RS485 in an industrial environment with high electromagnetic interference (EMI) or long cable runs near AC motors, spend the extra money on an isolated HAT (like the Waveshare). Ground loops between the Pi and industrial machinery can push lethal voltages back through the RS485 ground wire, destroying your Pi and connected peripherals.

Required Parts List

  • Board: Raspberry Pi 4 Model B (2GB+ RAM) or Raspberry Pi 5. (Code targets Pi 4/5 primary UART).
  • Transceiver: SP3485 3.3V RS485-to-TTL module (e.g., from LCSC or AliExpress vendors like TZT).
  • Wiring: 24 AWG stranded silicone wire or standard breadboard jumper cables.
  • Termination: 120Ω 1/4W resistor (for bus termination).

GPIO to SP3485 Pin Mapping & Wiring Steps

The Raspberry Pi exposes its primary UART on GPIO 14 (TXD) and GPIO 15 (RXD). Because RS485 is half-duplex, we use a third GPIO pin to control the Driver Enable (DE) and Receiver Enable (RE) pins on the transceiver, dictating whether the Pi is transmitting or listening.

Table 2: Raspberry Pi to SP3485 Pin Mapping
Raspberry Pi GPIO (Physical Pin) Pi Function SP3485 Module Pin Wire Color Recommendation
Pin 1 (3.3V) VCC Power VCC Red
Pin 6 (GND) Ground GND Black
Pin 8 (GPIO 14) UART TXD DI (Driver Input) Yellow
Pin 10 (GPIO 15) UART RXD RO (Receiver Output) Orange
Pin 12 (GPIO 18) Direction Control DE & RE (Jumpered together) Blue

Step-by-Step Wiring Procedure

  1. De-energize the Pi: Disconnect the USB-C power supply before touching the GPIO header.
  2. Prepare the Transceiver: Solder a jumper wire between the DE and RE pins on the SP3485 module. This allows a single Pi GPIO pin to switch the module between transmit (HIGH) and receive (LOW) modes.
  3. Connect Power and Ground: Wire Pi Pin 1 (3.3V) to the module VCC, and Pi Pin 6 to the module GND. Do not use Pin 2 (5V); the SP3485 is a 3.3V chip.
  4. Connect UART Lines: Wire Pi TXD (Pin 8) to module DI. Wire Pi RXD (Pin 10) to module RO.
  5. Connect Direction Control: Wire Pi GPIO 18 (Pin 12) to the jumpered DE/RE pad.
  6. Wire the Bus: Connect the module’s A terminal to the RS485 network’s D+ (or A) wire, and B to D- (or B). Connect the module GND to the bus shield/ground reference.

PySerial Implementation with Error Handling

Before running Python code, you must disable the Linux serial console so it stops outputting boot logs to the UART pins. Open your terminal and edit the config file:

sudo nano /boot/firmware/config.txt

Add the following lines at the bottom to disable Bluetooth (which hijacks the primary UART on Pi 4) and enable the UART:

enable_uart=1
dtoverlay=disable-bt

Reboot the Pi. The primary UART will now be mapped to /dev/ttyAMA0.

Below is the complete, compilable Python script using PySerial and RPi.GPIO to manage the half-duplex direction switching. Install dependencies first: sudo apt install python3-serial python3-rpi.gpio.

import serial
import RPi.GPIO as GPIO
import time
import sys

# --- Pin & Port Definitions ---
DE_RE_PIN = 18          # BCM numbering for Direction Control
UART_PORT = '/dev/ttyAMA0'
BAUD_RATE = 9600

# RS485 Direction States
TRANSMIT = GPIO.HIGH
RECEIVE = GPIO.LOW

def setup_rs485():
    """Initialize GPIO and Serial Port with error handling."""
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(DE_RE_PIN, GPIO.OUT, initial=RECEIVE)
    
    try:
        ser = serial.Serial(
            port=UART_PORT,
            baudrate=BAUD_RATE,
            bytesize=serial.EIGHTBITS,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            timeout=1.0
        )
        return ser
    except serial.SerialException as e:
        print(f"Fatal Serial Error: {e}")
        sys.exit(1)

def rs485_transmit(ser, message):
    """Switch to TX mode, send data, wait for shift register to clear, switch to RX."""
    GPIO.output(DE_RE_PIN, TRANSMIT)
    time.sleep(0.01) # Allow transceiver to settle
    
    payload = (message + '\n').encode('utf-8')
    ser.write(payload)
    ser.flush() # Block until all data is written to the TX register
    
    # Calculate time needed for the last byte to physically leave the wire
    # 10 bits per byte (start + 8 data + stop) at configured baud rate
    tx_time = (len(payload) * 10) / BAUD_RATE
    time.sleep(tx_time + 0.02) 
    
    GPIO.output(DE_RE_PIN, RECEIVE)

def rs485_receive(ser):
    """Read incoming data while in RX mode."""
    if ser.in_waiting > 0:
        raw_data = ser.read(ser.in_waiting)
        return raw_data.decode('utf-8', errors='ignore').strip()
    return None

if __name__ == "__main__":
    ser_port = setup_rs485()
    print(f"RS485 Active on {UART_PORT} at {BAUD_RATE} baud. Press Ctrl+C to exit.")
    
    try:
        while True:
            # Example: Send a Modbus-style or custom poll command
            rs485_transmit(ser_port, "PING_SENSOR_01")
            
            # Listen for response
            response = rs485_receive(ser_port)
            if response:
                print(f"RX: {response}")
            
            time.sleep(1.0)
            
    except KeyboardInterrupt:
        print("\nShutting down RS485 bus...")
    finally:
        if 'ser_port' in locals() and ser_port.is_open:
            ser_port.close()
        GPIO.cleanup()

Debugging RS485: Exact Error Strings & Ranked Fixes

When your Python script fails to open the port or receives garbage data, the OS and PySerial throw specific exceptions. Here are the exact error strings and how to fix them.

Error 1: serial.serialutil.SerialException: [Errno 13] Permission denied: '/dev/ttyAMA0'

Ranked Causes & Fixes:

  1. User lacks dialout group privileges (90% of cases): The tty devices are owned by the dialout group. Fix: Run sudo usermod -a -G dialout $USER, then log out and log back in.
  2. Serial console is still active (10% of cases): The OS is holding the port open for kernel logs. Fix: Run sudo raspi-config -> Interface Options -> Serial Port -> Disable login shell, enable hardware. Alternatively, ensure console=serial0,115200 is removed from /boot/firmware/cmdline.txt.

Error 2: serial.serialutil.SerialException: [Errno 2] could not open port /dev/ttyAMA0: No such file or directory

Ranked Causes & Fixes:

  1. Bluetooth UART overlay conflict: On the Pi 4, the primary UART is routed to the Bluetooth chip by default. Fix: Add dtoverlay=disable-bt to config.txt and reboot.
  2. Using the wrong device alias: You are hardcoding ttyAMA0 but the system mapped it to ttyS0 (mini UART). Fix: Use the stable symlink /dev/serial0 in your Python code instead of hardcoding the hardware port name.

The First Three Things to Check When Hardware Fails

If the code runs without errors but you receive no data (or only garbage characters), perform these three physical checks:

  1. Verify the DE/RE Pin State: If GPIO 18 is stuck HIGH, the transceiver is locked in transmit mode and deaf to the bus. Use a multimeter to verify the DE/RE pin drops to 0V (LOW) when the Pi is supposed to be listening.
  2. Swap the A and B Bus Wires: RS485 differential signaling relies on A being the non-inverting (+) signal and B being the inverting (-) signal. Many manufacturers label these backward. If you receive garbage, swap the A and B screw terminal wires.
  3. Check for Ground Reference: RS485 is differential, but the transceivers still share a common-mode voltage range (typically -7V to +12V). If the Pi and the remote sensor have a massive ground potential difference, the signal will clip. Ensure a common ground wire runs alongside the A/B pair.

Extending the Bus: Bias Resistors and Termination

To extend this build from a simple point-to-point test into a robust, multi-drop industrial network, you must manage the physical layer of the bus. According to the Analog Devices RS-485 fundamentals guide, an unterminated or unbiased bus will float into the undefined logic region when no node is transmitting, causing the Pi to receive phantom noise bytes.

How to Simplify: Auto-Direction Modules

If you want to eliminate the GPIO 18 direction-control wiring and the time.sleep() calculations in Python, buy an auto-flow RS485 module (like the Adafruit MAX13487). These modules contain internal circuitry that automatically detects when the TX line goes active and switches the transceiver to transmit mode, reverting to receive mode instantly when the byte finishes sending. This simplifies your Python code to standard PySerial read/writes and frees up a GPIO pin.

How to Extend: Termination and Biasing

When your cable run exceeds 10 meters, or you have more than 3 nodes on the bus, signal reflections will corrupt your data. Implement the following resistor network:

  • Termination Resistor: Solder a 120Ω resistor directly across the A and B terminals at the first and last physical nodes on the daisy chain. Do not place termination resistors on nodes in the middle of the cable.
  • Bias Resistors (Fail-Safe): The Raspberry Pi (acting as the master) should provide bus biasing. Connect a 390Ω to 560Ω resistor from the A line up to VCC (3.3V), and an identical resistor from the B line down to GND. This ensures that when all nodes are in receive mode (high impedance), the bus idles in a defined "Mark" (logic 1) state, preventing the Pi UART from triggering false start-bit interrupts.

For deeper configuration details regarding Raspberry Pi UART routing and device tree overlays, consult the official Raspberry Pi Hardware Configuration Documentation. By pairing a 3.3V-safe SP3485 transceiver with proper biasing and robust PySerial error handling, your Pi will reliably integrate into legacy industrial Modbus or custom sensor networks without risking the host hardware.