Skip to content

Flow Control

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 uses two special byte values embedded in the data stream:

CharacterHexASCIIMeaning
XON0x11Ctrl-Q”Resume sending — I can accept data”
XOFF0x13Ctrl-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.

Enable it when opening a port:

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

Or toggle it on an open port:

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

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

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

  • 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

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.

Enable it when opening a port:

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

Or toggle it on an open port:

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

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)

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.

// 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.

ScenarioRecommendedReason
Text/ASCII protocols (AT commands, console)XON/XOFFSimple, no extra wires needed
Binary protocols (Modbus RTU, firmware transfer)RTS/CTSNo byte value conflicts
Network serial (socket://)NoneTCP handles flow control at the transport layer
High-speed transfers (115200+)RTS/CTSFaster response, less latency than XON/XOFF
3-wire cable (TX, RX, GND only)XON/XOFFRTS/CTS lines not available
RS-485 busXON/XOFF or NoneRTS is used for direction control
Unknown deviceStart with NoneAdd flow control if you see buffer overruns

All flow control options are available in two places:

At open time:

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

On an already-open port:

// 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

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.


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

ChipsetRTS/CTSDSR/DTRXON/XOFFNotes
FTDI FT232RFull support, reliable
FTDI FT2232HDual port, same quality
CP2102⚠️DTR may need jumper
CP2104Full support
CH340/CH341⚠️RTS timing may be poor
PL2303⚠️⚠️Varies by clone quality

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

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

// 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

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
// 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