---
title: Flow Control
description: Understanding software and hardware flow control for serial communication
---

import { Aside } from '@astrojs/starlight/components';

Flow control prevents data loss when one side of a serial connection sends data faster than the other side can process it. Without flow control, the receiver's buffer fills up, and incoming bytes are silently dropped.

There are two approaches: software flow control using in-band control characters, and hardware flow control using dedicated signal lines. mcserial provides tools for enabling and managing both types -- your MCP assistant configures flow control when opening a port or via `configure_serial` on an already-open port.

## Software flow control (XON/XOFF)

Software flow control uses two special byte values embedded in the data stream:

| Character | Hex | ASCII | Meaning |
|-----------|-----|-------|---------|
| XON | `0x11` | Ctrl-Q | "Resume sending -- I can accept data" |
| XOFF | `0x13` | Ctrl-S | "Stop sending -- my buffer is full" |

When the receiver's buffer fills up, it sends XOFF to the sender. The sender pauses until it receives XON, then resumes transmission.

### Enabling XON/XOFF

Enable it when opening a port:

```json
// open_serial_port(port="/dev/ttyUSB0", baudrate=9600, xonxoff=true)
```

Or toggle it on an open port:

```json
// configure_serial(port="/dev/ttyUSB0", xonxoff=true)
{
  "success": true,
  "port": "/dev/ttyUSB0",
  "xonxoff": true,
  "rtscts": false,
  "dsrdtr": false
}
```

### Manual flow gating

The `set_flow_control` tool lets you manually send XON/XOFF signals or pause/resume your own output:

```json
// Tell the remote device to stop sending
// set_flow_control(port="/dev/ttyUSB0", input_flow=false)
{
  "success": true,
  "port": "/dev/ttyUSB0",
  "changes": {"input_flow": false},
  "flow_control_enabled": {"xonxoff": true, "rtscts": false}
}

// Tell the remote device to resume sending
// set_flow_control(port="/dev/ttyUSB0", input_flow=true)
```

- `input_flow=true` sends XON (allow the remote device to send to us)
- `input_flow=false` sends XOFF (tell the remote device to pause)
- `output_flow=true` resumes our outgoing data
- `output_flow=false` pauses our outgoing data

Flow control must already be enabled (via `xonxoff=true` or `rtscts=true`) for these signals to have effect.

### Advantages and limitations

**Advantages:**
- Works on any serial connection, including 3-wire (TX, RX, GND) cables with no additional signal lines
- Works over network serial connections (`socket://`, `rfc2217://`)
- No extra hardware required

<Aside type="caution" title="Binary data and XON/XOFF">
XON/XOFF reserves the byte values 0x11 and 0x13 as control characters. If your data stream naturally contains these values -- binary files, compressed data, encrypted payloads, protocol frames with arbitrary byte values -- the flow control logic will misinterpret data bytes as flow signals.

This causes two problems: the receiver strips the "control" bytes from your data (silent corruption), and the sender may pause unexpectedly when it encounters what it thinks is an XOFF signal.

If you are transferring binary data, use hardware flow control (RTS/CTS) instead, or disable flow control entirely and manage buffer sizes at the application layer.
</Aside>

## Hardware flow control (RTS/CTS)

Hardware flow control uses dedicated signal lines rather than in-band bytes. The most common pair is **RTS** (Request To Send) and **CTS** (Clear To Send).

When the receiver's buffer is getting full, it deasserts CTS. The sender watches CTS and pauses transmission until CTS is asserted again. Because the signaling happens on separate wires, it works with any data content -- there are no reserved byte values.

### Enabling RTS/CTS

Enable it when opening a port:

```json
// open_serial_port(port="/dev/ttyUSB0", baudrate=115200, rtscts=true)
```

Or toggle it on an open port:

```json
// configure_serial(port="/dev/ttyUSB0", rtscts=true)
{
  "success": true,
  "port": "/dev/ttyUSB0",
  "xonxoff": false,
  "rtscts": true,
  "dsrdtr": false
}
```

### Advantages and limitations

**Advantages:**
- Works with binary data -- no reserved byte values
- Faster response than XON/XOFF (hardware signal vs. byte transmission delay)
- Handled by the UART hardware, not software

**Limitations:**
- Requires a cable with RTS and CTS lines connected (some cheap cables omit them)
- Not available on 3-wire serial connections
- Not meaningful on `socket://` network connections (no physical lines to assert)

## Hardware flow control (DSR/DTR)

An alternative hardware flow control pair. DSR (Data Set Ready) and DTR (Data Terminal Ready) can serve as flow control signals when RTS/CTS is unavailable or already used for another purpose.

```json
// open_serial_port(port="/dev/ttyUSB0", baudrate=9600, dsrdtr=true)
```

DSR/DTR flow control is less common than RTS/CTS. Most devices that support hardware flow control use RTS/CTS.

<Aside type="tip" title="RS-485 and RTS">
In RS-485 mode, the RTS line is repurposed for TX direction control -- it enables or disables the bus driver, not flow control. If you need flow control on an RS-485 bus, use XON/XOFF (software flow control) or handle buffering at the application protocol level.

Do not enable `rtscts=true` on a port that is in RS-485 mode. The RTS line toggling for flow control would interfere with the direction control toggling, causing data corruption on the bus.
</Aside>

## Choosing the right flow control

| Scenario | Recommended | Reason |
|----------|-------------|--------|
| Text/ASCII protocols (AT commands, console) | XON/XOFF | Simple, no extra wires needed |
| Binary protocols (Modbus RTU, firmware transfer) | RTS/CTS | No byte value conflicts |
| Network serial (`socket://`) | None | TCP handles flow control at the transport layer |
| High-speed transfers (115200+) | RTS/CTS | Faster response, less latency than XON/XOFF |
| 3-wire cable (TX, RX, GND only) | XON/XOFF | RTS/CTS lines not available |
| RS-485 bus | XON/XOFF or None | RTS is used for direction control |
| Unknown device | Start with None | Add flow control if you see buffer overruns |

## Flow control in mcserial

All flow control options are available in two places:

**At open time:**

```json
// open_serial_port(
//   port="/dev/ttyUSB0",
//   baudrate=9600,
//   xonxoff=false,
//   rtscts=false,
//   dsrdtr=false
// )
```

**On an already-open port:**

```json
// configure_serial(
//   port="/dev/ttyUSB0",
//   xonxoff=true,
//   rtscts=false,
//   dsrdtr=false
// )
```

You can enable multiple flow control methods simultaneously, though this is unusual. The most common configurations are:

- **No flow control** (all false) -- suitable when you control the data rate, or when using protocols that handle their own flow management
- **XON/XOFF only** -- text terminals, console access, simple ASCII protocols
- **RTS/CTS only** -- binary transfers, high-speed communication, GPS receivers

### Detecting flow control problems

If you suspect flow control issues, look for these symptoms:

- **Missing data in the middle of a transfer**: the receiver's buffer overflowed and bytes were dropped. Enable flow control.
- **Data stops flowing and never resumes**: the sender received XOFF but never got XON (or CTS was deasserted and never reasserted). Check cable connections and remote device state.
- **Corrupted binary data with 0x11 or 0x13 bytes missing**: XON/XOFF is enabled on a binary stream. Switch to RTS/CTS or disable flow control.

Use `get_connection_status` to check the current flow control configuration and modem line states for all open ports.

---

## USB Adapter Compatibility

Not all USB-to-serial adapters support all flow control features. The chipset determines what's available:

| Chipset | RTS/CTS | DSR/DTR | XON/XOFF | Notes |
|---------|---------|---------|----------|-------|
| FTDI FT232R | ✅ | ✅ | ✅ | Full support, reliable |
| FTDI FT2232H | ✅ | ✅ | ✅ | Dual port, same quality |
| CP2102 | ✅ | ⚠️ | ✅ | DTR may need jumper |
| CP2104 | ✅ | ✅ | ✅ | Full support |
| CH340/CH341 | ⚠️ | ❌ | ✅ | RTS timing may be poor |
| PL2303 | ⚠️ | ⚠️ | ✅ | Varies by clone quality |

**Legend:** ✅ = reliable, ⚠️ = works but may have issues, ❌ = not supported or unreliable

<Aside type="caution" title="Cheap adapters and flow control">
Budget CH340 and PL2303 clone adapters often have poor flow control timing or don't implement hardware handshaking correctly. If you experience:

- **Dropped data at high baud rates** — the adapter isn't responding to CTS fast enough
- **RTS/CTS not affecting behavior** — lines may not be physically connected

Consider upgrading to FTDI-based adapters for critical applications requiring flow control.
</Aside>

### Debugging with modem lines

Use `get_modem_lines` to verify flow control lines are behaving as expected:

```json
// get_modem_lines(port="/dev/ttyUSB0")
{
  "success": true,
  "input_lines": {
    "cts": false,
    "dsr": false,
    "ri": false,
    "cd": false
  },
  "output_lines": {
    "rts": true,
    "dtr": true
  }
}
```

If CTS is always false when you expect it to be asserted:

1. **Check the cable** — CTS may not be wired through
2. **Check the remote device** — it may not be asserting CTS
3. **Check the adapter** — cheap adapters may not report CTS accurately

### Deadlock scenarios

Flow control can cause communication to stall if both ends wait on each other:

**XON/XOFF deadlock:**
1. You send XOFF (buffer full)
2. Remote stops sending
3. Your code processes data and clears buffer
4. You forget to send XON
5. Remote waits forever

**RTS/CTS deadlock:**
1. You deassert RTS (not ready to receive)
2. Remote stops sending and waits for RTS
3. Your code waits for data
4. Nothing happens

**Prevention:**

- Always re-enable flow after processing (XON or reassert RTS)
- Use timeouts to detect stalls
- Check `get_modem_lines` to diagnose which side is blocking

```json
// If data stops flowing, check line states
// get_modem_lines(port="/dev/ttyUSB0")

// If CTS is false, the remote is blocking our sends
// If we've deasserted RTS, the remote is waiting for us
```
