---
title: RS-232 Communication
description: Working with modem control lines, break signals, and device resets
---

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

RS-232 is the default mode when you open a port with mcserial. It provides point-to-point serial communication with modem control lines -- the signals that let you detect device presence, manage hardware flow control, and trigger resets.

All of the operations described in this guide are performed through your MCP client -- ask the assistant to open ports, read modem lines, pulse reset signals, and so on. The tool call notation below shows exactly what the assistant calls on your behalf.

Every port opens in RS-232 mode automatically. No additional configuration is needed unless you want to switch to RS-485.

## Modem control lines

RS-232 defines six modem control lines. Four are **inputs** (read from the remote device) and two are **outputs** (set by you).

### Input lines

These reflect the state of the remote device. Read them with `get_modem_lines`:

| Line | Full Name | What it means |
|------|-----------|---------------|
| CTS | Clear To Send | Remote device is ready to receive data |
| DSR | Data Set Ready | Remote device is powered and present |
| RI | Ring Indicator | Incoming call (modems) or attention signal |
| CD | Carrier Detect | Active connection established |

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

### Output lines

These are signals you send to the remote device. Set them with `set_modem_lines`:

| Line | Full Name | Common uses |
|------|-----------|-------------|
| RTS | Request To Send | Hardware flow control, TX enable for RS-485 converters |
| DTR | Data Terminal Ready | Signal our presence, trigger device reset |

```json
// set_modem_lines(port="/dev/ttyUSB0", rts=true, dtr=true)
{
  "success": true,
  "port": "/dev/ttyUSB0",
  "rts": true,
  "dtr": true
}
```

You can set one or both lines in a single call. Pass `null` (omit the parameter) to leave a line unchanged.

## Device reset sequences

Many development boards use DTR or RTS to trigger a hardware reset. The `pulse_line` tool handles the timing for you.

### Arduino reset

Arduino boards use DTR to reset the microcontroller. When DTR goes low, the reset capacitor pulls the RESET pin low briefly, rebooting the chip.

```json
// pulse_line(port="/dev/ttyUSB0", line="dtr", duration_ms=100, active_low=true)
{
  "success": true,
  "port": "/dev/ttyUSB0",
  "line": "dtr",
  "duration_ms": 100,
  "active_low": true
}
```

The `active_low=true` parameter (the default) means the line pulses LOW then returns HIGH -- exactly what Arduino expects.

<Aside type="tip" title="ESP32 bootloader entry">
ESP32 and ESP8266 boards use a more complex sequence involving both DTR and RTS to enter the bootloader. The typical sequence is:

1. Set DTR low, RTS high (hold EN high, pull GPIO0 low)
2. Set DTR high, RTS low (release GPIO0, pull EN low to reset)
3. Release both lines

You can achieve this with two `set_modem_lines` calls with a short delay between them, or use the `pulse_line` tool for each line in sequence. The exact timing depends on the board's RC circuit.
</Aside>

### Practical example: connect to Arduino, reset, read boot output

<Steps>

1. **Open the serial port**

   Open the port at 115200 baud (common for Arduino). If you are unsure of the baud rate, omit it and mcserial will attempt auto-detection.

   ```json
   // open_serial_port(port="/dev/ttyACM0", baudrate=115200)
   {
     "success": true,
     "port": "/dev/ttyACM0",
     "mode": "rs232",
     "baudrate": 115200,
     "resource_uri": "serial:///dev/ttyACM0/data"
   }
   ```

2. **Flush any stale data in the buffer**

   Clear out anything sitting in the receive buffer from before the reset.

   ```json
   // flush_serial(port="/dev/ttyACM0")
   {
     "success": true,
     "port": "/dev/ttyACM0",
     "flushed_input": true,
     "flushed_output": true
   }
   ```

3. **Pulse DTR to reset the Arduino**

   A 100ms DTR pulse is enough for any Arduino board.

   ```json
   // pulse_line(port="/dev/ttyACM0", line="dtr", duration_ms=100, active_low=true)
   {
     "success": true,
     "port": "/dev/ttyACM0",
     "line": "dtr",
     "duration_ms": 100,
     "active_low": true
   }
   ```

4. **Read the bootloader and startup output**

   After reset, the Arduino bootloader prints a brief message, then your sketch starts. Read multiple lines to capture everything.

   ```json
   // read_serial_lines(port="/dev/ttyACM0", max_lines=20)
   {
     "success": true,
     "lines": [
       "",
       "Sketch starting...",
       "Initializing sensors...",
       "Ready."
     ],
     "count": 4,
     "bytes_read": 52,
     "port": "/dev/ttyACM0"
   }
   ```

5. **Interact with the running sketch**

   Send commands and read responses as needed.

   ```json
   // write_serial(port="/dev/ttyACM0", data="STATUS\r\n")
   {
     "success": true,
     "bytes_written": 8,
     "port": "/dev/ttyACM0"
   }
   ```

</Steps>

## Break signals

A break signal is a sustained LOW state on the TX line that lasts longer than a normal character frame. Some devices use break signals to enter configuration mode, trigger a reset, or synchronize communication.

mcserial provides two ways to send breaks:

### send_break -- timed pulse

`send_break` holds the line LOW for a fixed duration, then releases it. This is the most common usage.

```json
// send_break(port="/dev/ttyUSB0", duration_ms=250)
{
  "success": true,
  "port": "/dev/ttyUSB0",
  "duration_ms": 250
}
```

The default duration is 250ms, which is long enough for most devices. The valid range is 1ms to 5000ms.

### set_break_condition -- sustained hold

`set_break_condition` holds the TX line LOW indefinitely until you explicitly release it. Use this when a protocol requires a sustained break state.

```json
// Assert break (hold TX low)
// set_break_condition(port="/dev/ttyUSB0", enabled=true)
{
  "success": true,
  "port": "/dev/ttyUSB0",
  "break_condition": true
}

// Release break (return to normal)
// set_break_condition(port="/dev/ttyUSB0", enabled=false)
{
  "success": true,
  "port": "/dev/ttyUSB0",
  "break_condition": false
}
```

The current break state is also reported in the `get_modem_lines` response under `break_condition`.

<Aside type="note">
Both `send_break` and `set_break_condition` require RS-232 mode (the default). If you have switched to RS-485 mode, switch back with `set_port_mode(port, "rs232")` first.
</Aside>

## Checking line states

You can verify the state of all modem lines at any time with `get_modem_lines`. This is useful for:

- **Detecting device presence** -- check DSR before attempting communication
- **Debugging connections** -- verify CTS is asserted if hardware flow control is in use
- **Monitoring** -- poll RI or CD for incoming events on modem-style devices

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

In this example, CTS is asserted (the device can receive data) but DSR is low (the device may not be powered or connected). This is a common state for USB-serial adapters that loop CTS back internally but do not connect DSR.
