From f13faa6aba8997fbdfb2f401cdae53af48f2f2ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 18 Sep 2026 14:46:43 +0000 Subject: [PATCH] fix(esp32s3): bound the SerialConsole idle sleep on hardware USB CDC (#11901) HWCDC::isPlugged() is a SOF watchdog that reads false transiently while USB is connected and working. runOnce() answered that with a 20 s sleep, and nothing wakes the thread on RX, so host traffic sat in the CDC RX ring and reached the API as a burst. Cap the sleep at 250 ms, the rate readStream() already idles at. IS_USB_SERIAL only tested ARDUINO_USB_CDC_ON_BOOT, so ARDUINO_USB_MODE=0 boards ran the same check against a USB-Serial/JTAG peripheral that is not attached to the PHY and never sees a SOF. Gate the check on IS_USB_HWCDC. Measured on tlora-t3s3-v1, 900 s of 1 Hz ToRadio/FromRadio round trips: before 12 stalls, rtt_max 19.96 s, console asleep 26.4% of wall time. After 0 stalls, rtt_max 0.147 s, p50 unchanged at 0.028 s. Fixes #11864 --- src/SerialConsole.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 24141be28..fb40bf891 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -16,6 +16,11 @@ #ifdef SERIAL_HAS_ON_RECEIVE #undef SERIAL_HAS_ON_RECEIVE #endif +// Port is HWCDC only in hardware USB-Serial/JTAG mode. With ARDUINO_USB_MODE=0 it is TinyUSB +// USBCDC, the PHY is routed away from the USJ peripheral, and isPlugged() would never see a SOF. +#if defined(ARDUINO_USB_MODE) && ARDUINO_USB_MODE +#define IS_USB_HWCDC +#endif #include "HWCDC.h" #endif @@ -130,8 +135,10 @@ int32_t SerialConsole::runOnce() if (hasPendingOutput()) return delay < 25 ? delay : 25; // 0 continues a budget slice; else short-poll TX drain return Port.available() ? delay : INT32_MAX; -#elif defined(IS_USB_SERIAL) - return HWCDC::isPlugged() ? delay : (1000 * 20); +#elif defined(IS_USB_HWCDC) + // isPlugged() is a SOF watchdog that flaps false while USB is fine (#11864), and nothing wakes + // this thread on RX, so cap the idle sleep at the rate readStream() already idles at. + return HWCDC::isPlugged() ? delay : 250; #else return delay; #endif