RS-232 handshaking — also called flow control — is how two devices agree on when to send and receive data over a serial link. Without it, a fast transmitter can flood a slow receiver, drop characters, or talk into the void when the other side isn’t ready.

The standard uses six control lines in addition to TX, RX, and ground. Each one carries a single piece of status information between the two devices, asserted by raising the line to a positive voltage (+3 to +15 V) and deasserted by dropping it to a negative voltage.

Worth noting up front: the voltage logic on control lines is the opposite of the data lines. On TX/RX, a logic 1 is negative and a logic 0 is positive. On control lines, asserted (active) is positive and deasserted (inactive) is negative. This catches people every time.

RS-232 Signal Lines & Direction (DB9) Three data lines, six control lines, one shared ground DTE Data Terminal Equipment Computer / Terminal DCE Data Communications Equipment Modem / Instrument DATA LINES TXD — pin 3 RXD — pin 2 GND — pin 5 (shared reference) CONTROL LINES — HANDSHAKING DTR — pin 4 — “Terminal ready” DSR — pin 6 — “Modem ready” RTS — pin 7 — “Ready to receive” CTS — pin 8 — “Ready for your data” DCD — pin 1 — “Carrier detected” RI — pin 9 — “Incoming call” ⚠ Voltage polarity (catches everyone): Data lines (TXD/RXD): logic 1 = negative (−3 to −15 V) · logic 0 = positive (+3 to +15 V) Control lines (the 6 above): asserted = positive (+3 to +15 V) · deasserted = negative

The six handshake lines

DTR — Data Terminal Ready

Driven by the DTE (the terminal, computer, or whatever’s acting as the “client” side). Asserted when the DTE is powered on, connected, and ready to communicate.

Think of it as: “I’m here and I’m awake.”

In practice: when you open a serial port in your terminal program, the OS typically asserts DTR. Dropping DTR is often used to signal “hang up” on a modem connection or to reset an Arduino over its USB-serial bridge.

DSR — Data Set Ready

Driven by the DCE (the modem, instrument, or “server” side). The mirror of DTR. Asserted when the DCE is powered on and ready.

Think of it as: “I’m here too.”

DTR and DSR together establish that both ends are physically present and powered before any conversation starts.

RTS — Request to Send

Driven by the DTE. In the original spec, RTS meant “I want to transmit, please grant me the line.” On modern point-to-point links, it’s reused as a flow-control signal: “I’m ready to receive data from you.”

Think of it as: “I’m ready for data.”

CTS — Clear to Send

Driven by the DCE. The response to RTS. Originally meant “you may transmit now.” On modern links: “I’m ready to receive data from you.”

Think of it as: “I’m ready for your data too.”

RTS and CTS together are what people usually mean when they say “hardware flow control.” Each side asserts its outbound line when it has buffer space available, and stops transmitting when the other side’s line goes inactive.

DCD — Data Carrier Detect

Driven by the DCE. Asserted when a modem detects a carrier signal from the remote end — i.e., when the physical connection is established.

Think of it as: “We’re connected to the other side.”

On non-modem links, DCD is often forced high by jumpering it to DSR or ignored entirely.

RI — Ring Indicator

Driven by the DCE. Asserted when an incoming call is ringing the modem.

Think of it as: “Something’s calling.”

Almost entirely a legacy modem feature. You’ll rarely use it in 2026 unless you’re maintaining old equipment.

Hardware vs. software flow control

Hardware flow control uses the physical RTS/CTS lines described above. It’s fast, reliable, and adds no overhead to the data stream — but it requires those wires to be connected and the devices on each end to honor them.

Software flow control (XON/XOFF) does the same job using two special characters in the data stream:

  • XOFF (ASCII 19, Ctrl-S): “Stop sending.”
  • XON (ASCII 17, Ctrl-Q): “OK, resume.”

The receiver injects these characters into its outbound stream to pause and resume the transmitter. It works over just three wires (TX, RX, GND), which is why it’s common in minimal cable setups. The downside: those two byte values become reserved, so XON/XOFF doesn’t work for binary data that might contain them.

Rule of thumb: Use hardware flow control when the cable supports it. Fall back to XON/XOFF only when you’re stuck with a 3-wire connection and you’re sending text.

How the handshake sequence actually works

Most resources oversimplify this. Here’s the real sequence on a typical modem-style RS-232 link:

  1. DTE powers on and asserts DTR. “I’m here.”
  2. DCE powers on and asserts DSR. “I’m here too.”
  3. DCE establishes a connection and asserts DCD. “We’re talking to the remote end.”
  4. DTE asserts RTS when it has buffer space to receive. “Send me data.”
  5. DCE asserts CTS when it has buffer space to receive. “I can take data too.”
  6. Data flows over TX and RX.
  7. Either side drops its RTS or CTS if its buffer fills up. The other side pauses.
  8. At end of session, DTE drops DTR, which the DCE interprets as “hang up.” Everything else deasserts.

On a direct point-to-point link without a modem, step 3 doesn’t apply — both ends typically just assert DTR/DSR/RTS/CTS and start talking.

Common handshaking problems

These come up constantly in real debugging work.

Data appears truncated or characters drop. Usually a flow-control mismatch. One side has hardware flow control enabled and the other doesn’t — so when the receiver’s buffer fills, the transmitter never gets the signal to pause.

The link won’t open at all. DTR or DSR isn’t being asserted. Check whether the cable carries those lines (some 3-wire cables don’t) and whether either side is configured to require them.

Data flows fine in one direction but not the other. Asymmetric handshake configuration, or a cable that cross-wires handshake lines incorrectly. Null modem cables in particular have several variants, and not all of them connect every handshake pair.

Connection drops randomly under load. Buffer overrun. Either flow control isn’t enabled, or it’s enabled but the device firmware doesn’t respond to it fast enough. Look for missing or corrupted characters near the moment of drop.

Everything works on the bench but fails in the field. Electrical noise on long cable runs. RS-232’s single-ended signaling is vulnerable to interference, and handshake lines are no exception. Consider RS-422 or RS-485 for the run, or shorten the cable.

When you don’t need handshaking

Not every RS-232 link needs flow control. If both devices are fast enough to keep up with the data rate, if the data rate is low, or if you’re just dumping logs one direction, you can skip it entirely.

In those cases, a 3-wire connection (TX, RX, GND) is enough. Most engineers also tie the unused handshake lines high on each end (loop DTR to DSR and DCD, loop RTS to CTS) so any software that expects them to be asserted doesn’t hang.

Capturing handshake events on a live link

Handshake bugs are some of the hardest serial issues to debug because they’re timing-dependent and invisible to most software — by the time your terminal program sees a problem, the actual cause was a control-line state change milliseconds earlier.

That’s where a passive tap with handshake-event timestamping matters. The EZ-Tap Pro captures every state change on every control line with microsecond timestamps, so you can see exactly when DTR dropped, exactly when CTS went inactive, and exactly which byte was in flight when it happened.

If you’re chasing an intermittent comm issue and you suspect the handshake is involved, an inline tap will find it in one capture session.

Quick reference

LineDirectionAsserted when
DTRDTE → DCETerminal is powered on and ready
DSRDCE → DTEModem/instrument is powered on and ready
RTSDTE → DCEDTE has buffer space to receive
CTSDCE → DTEDCE has buffer space to receive
DCDDCE → DTEModem detects a carrier from the remote end
RIDCE → DTEIncoming call is ringing