From 3dd8073c290c077f17ffdee17a019763ad82604d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 29 Aug 2025 08:06:27 +1000 Subject: [PATCH] tools/mpremote: Workaround ESP DTR/RTS reset quirk at disconnect time. The problem with ESP board spurious reset happens at disconnect time on Windows (clearing DTR before RTS triggers a reset). Previous workarounds tried to detect possible ESP boards and apply the correct DTR and RTS settings when opening the port. Instead, we can manually clear RTS before closing the port and thereby avoid the reset issue. Opening the port can keep the default behaviour (RTS & DTR both set). close() is called from a finally block in the mpremote main module (via do_disconnect()) - so this should always happen provided the Python process isn't terminated by the OS. One additional workaround is needed to prevent a spurious reset first time a Silicon Labs CP210x-based ESP board is opened by mpremote after enumeration. Signed-off-by: Angus Gratton --- tools/mpremote/mpremote/transport_serial.py | 60 +++++++++------------ 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index a26183680c..eccdd4c1ea 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -42,31 +42,8 @@ from errno import EPERM from .console import VT_ENABLED from .transport import TransportError, TransportExecError, Transport -VID_ESPRESSIF = 0x303A # Espressif Incorporated -PID_ESPRESSIF_SERIAL_JTAG = 0x1001 # Serial/JTAG peripheral of ESP32-S3,C3,C6 - -def has_espressif_dtr_quirk(devicename): - """ESP8266 and ESP32 dev boards use the DTR and RTS lines to trigger reset & - reset into bootloader mode. This can causes spurious reset issues on Windows. - - Apply the quirk to any USB/Serial chip on Windows that isn't using the - Microsoft CDC-ACM driver, or to the integrated Espressif Serial/JTAG device. - - Don't apply it to Espressif boards running TinyUSB, as TinyUSB uses DTR - to determine if the CDC port is open (and there's no spurious reset issue). - """ - portinfo = list(serial.tools.list_ports.grep(devicename)) # type: ignore - if not portinfo: - return False - - def port_attr(name): - return getattr(portinfo[0], name, None) - - return (port_attr("vid"), port_attr("pid")) == ( - VID_ESPRESSIF, - PID_ESPRESSIF_SERIAL_JTAG, - ) or port_attr("manufacturer") != "Microsoft" +VID_SILICON_LABS = 0x10C4 class SerialTransport(Transport): @@ -90,19 +67,27 @@ class SerialTransport(Transport): delayed = False for attempt in range(wait + 1): try: - if device.startswith("rfc2217://"): - self.serial = serial.serial_for_url(device, **serial_kwargs) - elif os.name == "nt": - self.serial = serial.Serial(**serial_kwargs) - self.serial.port = device - if has_espressif_dtr_quirk(device): - # DTR False: to avoid using the reset button will hang the MCU in bootloader mode - # RTS False: to prevent pulses on rts on serial.close() that would POWERON_RESET an ESPxx - self.serial.dtr = False # DTR False = gpio0 High = Normal boot - self.serial.rts = False # RTS False = EN High = MCU enabled + self.serial = serial.serial_for_url(device, do_not_open=True, **serial_kwargs) + if os.name == "nt": + portinfo = list(serial.tools.list_ports.grep(device)) # type: ignore + if portinfo and getattr(portinfo[0], "vid", None) == VID_SILICON_LABS: + # Silicon Labs CP210x driver on Windows has a quirk + # where after a power on reset it will set DTR and RTS + # at different times when the port is opened (it doesn't + # happen on subsequent openings). + # + # To avoid issues with spurious reset on Espressif boards we clear DTR and RTS, + # open the port, and then set them in an order which prevents triggering a reset. + self.serial.dtr = False + self.serial.rts = False + self.serial.open() + self.serial.dtr = True + self.serial.rts = True + + # On all other host/driver combinations we keep the default + # behaviour (pyserial will set DTR and RTS automatically on open) + if not self.serial.isOpen(): self.serial.open() - else: - self.serial = serial.Serial(device, **serial_kwargs) break except OSError: if wait == 0: @@ -121,6 +106,9 @@ class SerialTransport(Transport): print("") def close(self): + # ESP Windows quirk: Prevent target from resetting when Windows clears DTR before RTS + self.serial.rts = False + self.serial.dtr = False self.serial.close() def read_until(