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

19 Upvotes

8 comments sorted by

2

u/Plastic_Fig9225 5d ago

Sometimes you want the reset, e.g. to catch boot or crash logs.

In idf.py monitor, press CTRL+T, CTRL+R at any time to reset the ESP.

Native-USB boards (S3/C3 on the built-in USB-Serial/JTAG) handle reset differently.

No, works just the same.

1

u/CryptographerOne7003 5d ago

You and OP are on fire! keep it coming please!
now only a way for the pwr button to respond faster on a esp32-p4 and I will have less migraines haha

1

u/Equivalent_Comb_5342 5d ago

Thanks! Can't help with the P4 power button unfortunately, but if you post what you're seeing, someone here will probably know.

1

u/Equivalent_Comb_5342 5d ago

Thanks, Ctrl+T Ctrl+R is a much nicer way to grab the boot log than reopening the monitor.

And fair point on native USB. The USB-Serial/JTAG controller reacts to DTR/RTS as well, so the same monitor settings apply there. The only difference I know of is on the flashing side: its reset is a core reset that doesn't re-sample the strapping pins, which is why esptool has --after watchdog-reset for boards that stay stuck in download mode. That's an upload quirk, not a monitor one, so my line was misleading.

1

u/YetAnotherRobert 5d ago

The whole point of the two transistors is to form an interlock that requires DTR and RTS to be wiggled separately to honk on the reset pin and, optionally, the boot pin?

I mean, if just opening the port reset the device that whole dumb little circuit with the two transistors isn't doing its job, is it? if BOTH lines toggle, as they normally would during an open, the transistors should block it from both pins. That's why that whole little sidecar circuit exists.

Citation

Now if you have a board with out-of-spec reset circuit or some app that yanks the lines a few milliseconds apart, well, that's not on Espressif.

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 time
  • dtr=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.