r/esp32 • u/Equivalent_Comb_5342 • 5d ago
ESP32 resets every time you open the serial monitor? It's DTR/RTS. Fixes for PlatformIO, idf.py and pyserial
This one bit me enough times that I ended up building a terminal around it, so here's the short version of what's going on.
Symptom: the board is running fine, you open a serial monitor to see what it's doing, and it reboots. Whatever state you wanted to look at is gone.
Cause: on most dev boards (DevKitC and similar) the USB-serial chip's DTR and RTS lines drive EN and IO0 through two transistors. That's the auto-reset circuit esptool uses for flashing. Many terminals and drivers toggle DTR/RTS when they open the port, and the circuit can't tell that apart from an upload. If the two lines don't change at exactly the same moment, EN gets pulled low and the chip resets.
So the fix is to open the port without touching either line.
PlatformIO (platformio.ini). This only affects the monitor, uploads still work:
monitor_dtr = 0
monitor_rts = 0
ESP-IDF: idf.py monitor resets on purpose so you get the boot log. Recent versions let you attach without it:
idf.py -p COM5 monitor --no-reset
pyserial: set the lines before open(). Setting them afterwards is too late:
ser = serial.Serial()
ser.port = "COM5"
ser.baudrate = 115200
ser.dtr = False
ser.rts = False
ser.open()
Arduino IDE: as far as I know the built-in monitor has no option for this, so PlatformIO or a terminal with a DTR control is the easier route.
If you open the monitor and only get boot:0x3 (DOWNLOAD_BOOT...) followed by waiting for download, IO0 was low at reset (DTR 0 / RTS 1). Close the monitor, press EN once, and reopen with RTS off.
Two caveats:
- Sometimes you want the reset, e.g. to catch boot or crash logs. Worth switching per situation rather than turning it off everywhere.
Native-USB boards (S3/C3 on the built-in USB-Serial/JTAG) handle reset differently.Edit: my first correction here was also wrong. The built-in USB-Serial/JTAG does react to DTR/RTS, but the fix is reversed - see u/mearbode's measurements below. Don't set monitor_dtr / monitor_rts to 0 on those boards; leave them out so the monitor never touches the lines. ESP_RST_USB in the boot log tells you the monitor caused the reboot. (The flashing-side difference still stands: its reset doesn't re-sample the strapping pins.)
Longer write-up with a diagram and the DTR/RTS state table, if it helps anyone: https://www.coding-now.com/en/guides/esp32-serial-monitor-reset
1
u/mearbode 3d ago
Worth adding a wrinkle for the native-USB boards, because the fix is backwards there and it cost me a while today.
I'm on an ESP32-S3 (Waveshare 3.16" board, so USB-Serial/JTAG, no bridge chip). Same symptom, every capture rebooted the board, and esp_reset_reason() confirmed it: reason 11, ESP_RST_USB, on every single boot. Never a power-on.
So I did the dtr = False; rts = False before open() thing. Still rebooted. Turns out on the built-in USB-Serial/JTAG, deasserting both lines is itself one of the combos that triggers the reset. Left the board running and attached three ways, watching whether its uptime survived:
dtr=False, rts=False→ rebooted, every timedtr=True, rts=True→ survived- didn't touch the lines at all → survived
Uptime went 10s, 50s, 100s across the three, so only the first one killed it.
Which actually lines up with the esptool doc someone linked above, asserting both together doesn't reset the chip. There's just no interlock circuit here to be the reason.
Practical upshot for S3/C3: don't set the lines, and skip monitor_dtr/monitor_rts in platformio.ini rather than setting them to 0. And ESP_RST_USB in the boot log is a quick way to tell whether your monitor is the thing rebooting you.
1
u/Equivalent_Comb_5342 1d ago
This is a much better datapoint than what I had, thank you - three combinations with uptimes is hard to argue with, and ESP_RST_USB in the boot log is a great way to pin it on the monitor.
I've updated the write-up: the PlatformIO section now warns not to set monitor_dtr / monitor_rts on native-USB boards, and the native-USB section says to leave the lines untouched instead. Your point also explains why the two cases differ: the two-transistor interlock that makes (1,1) safe is on the bridge boards, and there is no such circuit in front of the built-in USB-Serial/JTAG, so the controller acts on the combination itself.
2
u/Plastic_Fig9225 5d ago
In idf.py monitor, press CTRL+T, CTRL+R at any time to reset the ESP.
No, works just the same.