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
This commit is contained in:
Thomas Göttgens authored and GitHub committed 2026-09-18 14:46:43 +00:00
1 parent d96c690a91
commit f13faa6aba
1 file changed
+9 -2
+9 -2
View File
@@ -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