fix: USB serial logs now flow on cold+warm boot without jiggle (#2034)

The "logs only flow if you unplug and replug the USB cable at the right
moment" symptom traced to two interacting problems with the ESP32-C3 USB
Serial/JTAG controller (HWCDC):

1. Serial.begin was gated on gpio.isUsbConnected(). That check sampled
USB state at one specific microsecond during boot. If USB enumeration on
the host hadn't completed by that moment (common after a reset that
auto- reconnects a moment later), Serial was never initialized and
stayed dead until the next boot where the timing happened to win.

2. HWCDC writes block for up to the configured TX timeout (default 250
ms) when the host has the port open but isn't actively draining — a
state the macOS USB CDC stack enters intermittently after reconnect. The
firmware then appears to hang on logging until a USB unplug+replug
cycles the peripheral and flushes the TX FIFO.

Fix: move the Serial init to the very top of setup() with a 250 ms stall
before Serial.begin (lets the USB peripheral power-on and host
enumeration complete on cold boot), and call logSerial.setTxTimeoutMs(0)
so writes drop bytes harmlessly when the host is slow instead of
stalling the firmware. Both warm reboot and cold power-on now produce
logs immediately.

Did you use AI tools to help write this code? partial
This commit is contained in:
Jeremy Klein
2026-05-18 22:26:25 -04:00
committed by GitHub
parent df53faab91
commit a525606d7f
+15 -10
View File
@@ -261,6 +261,21 @@ void setupDisplayAndFonts() {
void setup() {
t1 = millis();
#ifdef ENABLE_SERIAL_LOG
// Earliest possible Serial setup. The 250 ms stall before begin() lets the
// USB Serial/JTAG peripheral finish power-on and lets the host complete USB
// enumeration before we touch the CDC state — otherwise cold boot races
// and the host has to be physically replugged for logs to flow. Warm reboot
// worked without the delay because USB was already enumerated.
//
// setTxTimeoutMs(0) makes writes non-blocking — the HWCDC TX FIFO drops
// bytes harmlessly if the host isn't actively draining, instead of blocking
// for the default 250 ms per write and chaining into a firmware hang.
delay(250);
Serial.begin(115200);
logSerial.setTxTimeoutMs(0);
#endif
HalSystem::begin();
// Read-and-clear so a panic later in setup() doesn't loop into silent reboot.
@@ -276,16 +291,6 @@ void setup() {
halTiltSensor.begin();
halClock.begin();
#ifdef ENABLE_SERIAL_LOG
if (gpio.isUsbConnected()) {
Serial.begin(115200);
const unsigned long start = millis();
while (!Serial && (millis() - start) < 500) {
delay(10);
}
}
#endif
LOG_INF("MAIN", "Hardware detect: %s", gpio.deviceIsX3() ? "X3" : "X4");
// SD Card Initialization