Skip to content

RS-485 and Modbus

RS-485 uses a shared differential bus where multiple devices communicate over two wires. Unlike RS-232’s point-to-point model, RS-485 is half-duplex — only one device transmits at a time while all others listen. This makes it the standard for industrial protocols like Modbus RTU.

All of the operations described in this guide are performed through your MCP client. Ask the assistant to open ports, switch modes, scan for devices, and transact on the bus. The tool call notation below shows exactly what the assistant calls on your behalf.

Ports open in RS-232 mode by default. Switch to RS-485 mode before using any RS-485 tools.

// open_serial_port(port="/dev/ttyUSB0", baudrate=9600)
{
"success": true,
"port": "/dev/ttyUSB0",
"mode": "rs232",
"baudrate": 9600
}
// set_port_mode(port="/dev/ttyUSB0", mode="rs485")
{
"success": true,
"port": "/dev/ttyUSB0",
"previous_mode": "rs232",
"current_mode": "rs485",
"mode_tools": [
"set_rs485_mode",
"rs485_transact",
"rs485_scan_addresses",
"check_rs485_support"
]
}

Once in RS-485 mode, the RS-232-specific tools (get_modem_lines, pulse_line, etc.) are unavailable. Common tools like read_serial, write_serial, and configure_serial work in both modes.

If your USB-serial adapter or UART has hardware RS-485 support, set_rs485_mode configures the driver to automatically toggle the TX enable (DE/RE) line during transmission. This eliminates the need for manual RTS toggling.

// set_rs485_mode(
// port="/dev/ttyUSB0",
// enabled=true,
// rts_level_for_tx=true,
// rts_level_for_rx=false,
// delay_before_tx=0.0,
// delay_before_rx=0.0
// )
{
"success": true,
"port": "/dev/ttyUSB0",
"rs485_enabled": true,
"rts_level_for_tx": true,
"rts_level_for_rx": false,
"delay_before_tx": 0.0,
"delay_before_rx": 0.0,
"loopback": false
}

Parameters explained:

ParameterDefaultPurpose
rts_level_for_txtrueRTS state when transmitting (HIGH enables the driver on most RS-485 transceivers)
rts_level_for_rxfalseRTS state when receiving (LOW disables the driver, allows listening)
delay_before_tx0.0Seconds to wait after asserting TX enable before sending data
delay_before_rx0.0Seconds to wait after transmission before switching to receive
loopbackfalseEcho transmitted data back (for testing)

Not all USB-serial adapters support hardware RS-485. Use check_rs485_support to find out what your hardware can do before configuring.

// check_rs485_support(port="/dev/ttyUSB0")
{
"success": true,
"port": "/dev/ttyUSB0",
"driver": "ftdi_sio",
"chip": "FT232R",
"hardware_rs485": true,
"software_fallback": true,
"kernel_rs485_ioctl": true,
"notes": [
"FTDI chips have hardware RS-485 auto-direction",
"Kernel TIOCSRS485 ioctl supported"
],
"recommendation": "Use set_rs485_mode() for automatic DE/RE control"
}

This tool queries the kernel driver and USB device information to determine:

  • Which driver is loaded (ftdi_sio, cp210x, ch341, pl2303)
  • Whether the chip supports hardware DE/RE toggling
  • Whether the kernel TIOCSRS485 ioctl is available
  • A recommendation for how to proceed
ChipHardware RS-485Notes
FTDI (FT232R, FT2232, etc.)YesAutomatic direction control via driver
CP2105 / CP2108YesHardware support in these models
CP2102NoSoftware RTS control required
CH340 / CH341NoTiming may be unreliable at high baud rates
PL2303NoSoftware RTS control required
Native UART (ttyS, ttyAMA)DependsCheck if RS-485 transceiver is connected

The rs485_transact tool handles the complete send-then-receive cycle that RS-485 communication requires. It manages TX/RX turnaround timing automatically, whether your hardware supports it natively or needs software RTS toggling.

// rs485_transact(
// port="/dev/ttyUSB0",
// data="\x01\x03\x00\x00\x00\x01\x84\x0A",
// response_timeout=1.0,
// response_length=7
// )
{
"success": true,
"port": "/dev/ttyUSB0",
"bytes_sent": 8,
"data_sent": "\u0001\u0003\u0000\u0000\u0000\u0001\u0084\n",
"response": "\u0001\u0003\u0002\u0000\u0005\u0085\u0085",
"response_bytes": 7,
"response_hex": "01030200058585",
"hardware_rs485": true
}

When hardware RS-485 is not configured, rs485_transact falls back to manual RTS control:

  1. Asserts RTS HIGH (enable driver / TX mode)
  2. Sends the data and waits for transmission to complete
  3. Drops RTS LOW (disable driver / RX mode)
  4. Waits for the turnaround delay
  5. Reads the response

The turnaround_delay parameter (default 5ms) controls the pause between sending and listening. Increase it for slow devices or long cable runs.

rs485_scan_addresses probes a range of addresses and reports which ones respond. This is the fastest way to discover what is connected to a bus.

// rs485_scan_addresses(
// port="/dev/ttyUSB0",
// start_address=1,
// end_address=30,
// probe_template="{addr:02x}03000001",
// response_timeout=0.1
// )
{
"success": true,
"port": "/dev/ttyUSB0",
"addresses_scanned": 30,
"devices_found": 3,
"responding_addresses": [
{"address": 1, "response_length": 7, "response_hex": "01030200058585"},
{"address": 5, "response_length": 7, "response_hex": "05030200124478"},
{"address": 16, "response_length": 7, "response_hex": "10030200009930"}
],
"hardware_rs485": true
}

The probe_template uses Python’s string formatting. The placeholder {addr} is replaced with the current address being scanned. The default template {addr:02x}03000001 formats the address as a two-character hex string followed by a Modbus “read holding register” frame.

This example walks through a complete Modbus RTU session: discovering devices, then reading a holding register.

  1. Open the port and switch to RS-485 mode

    Modbus RTU typically runs at 9600 baud with 8N1 framing.

    // open_serial_port(port="/dev/ttyUSB0", baudrate=9600)
    // set_port_mode(port="/dev/ttyUSB0", mode="rs485")
  2. Configure hardware RS-485 (if supported)

    If your adapter supports hardware direction control, enable it. If not, rs485_transact handles RTS toggling in software.

    // set_rs485_mode(port="/dev/ttyUSB0", enabled=true, rts_level_for_tx=true, rts_level_for_rx=false)
  3. Scan the bus for responding devices

    Scan addresses 1 through 247 (the Modbus valid range). Use a short timeout per address to keep the scan fast.

    // rs485_scan_addresses(
    // port="/dev/ttyUSB0",
    // start_address=1,
    // end_address=247,
    // response_timeout=0.1
    // )
    {
    "success": true,
    "addresses_scanned": 247,
    "devices_found": 2,
    "responding_addresses": [
    {"address": 1, "response_length": 7, "response_hex": "01030200058585"},
    {"address": 10, "response_length": 7, "response_hex": "0a030200003071"}
    ]
    }
  4. Read a holding register from a device

    Construct a Modbus RTU “Read Holding Registers” frame for address 1, register 0, quantity 1. The frame format is: [address][function 0x03][start_hi][start_lo][qty_hi][qty_lo][CRC_lo][CRC_hi].

    // rs485_transact(
    // port="/dev/ttyUSB0",
    // data="\x01\x03\x00\x00\x00\x01\x84\x0A",
    // response_length=7,
    // encoding="latin-1"
    // )
    {
    "success": true,
    "bytes_sent": 8,
    "response_hex": "01030200058585",
    "response_bytes": 7,
    "hardware_rs485": true
    }

    The response 01 03 02 00 05 85 85 decodes as: address 1, function 3, 2 bytes of data, value 0x0005 (decimal 5), followed by a CRC.

  5. Close the port when finished

    // close_serial_port(port="/dev/ttyUSB0")

The probe_template parameter in rs485_scan_addresses is a Python format string. It supports the {addr} placeholder with standard format specifiers:

TemplateAddress 16 becomesUse case
{addr:02x}030000011003000001Modbus-style hex address prefix
{addr:d}16Decimal address prefix
{addr:c}(ASCII char 16)Single-byte binary address

If the template does not contain {addr, the raw address byte is prepended automatically. This means a template of 03000001 with address 16 sends byte 0x10 followed by 03000001.

When check_rs485_support reports that your adapter lacks hardware RS-485, mcserial uses software emulation:

  • Before transmitting, RTS is asserted HIGH to enable the transceiver’s driver
  • After transmission completes, RTS is dropped LOW to switch back to receive mode
  • A configurable turnaround delay prevents missed response bytes

Software emulation works reliably at baud rates up to about 115200. At higher speeds, the timing of the RTS toggle may not be precise enough, causing the first byte of a response to be corrupted or missed. If you need high-speed RS-485, use an adapter with hardware support (FTDI-based adapters are widely available and well-supported).