Skip to content

Commit eba6e7a

Browse files
committed
machine/esp32s3,esp32c3: make USB Serial/JTAG writes non-blocking when FIFO is full
When no USB host is reading (e.g. board not connected to a serial monitor), WriteByte and Write would spin for up to 200k iterations per byte waiting for the FIFO to drain. This stalled the entire application, freezing unrelated peripherals like I2C displays. Reduce flushTimeout from 200,000 to 50,000 iterations (~3ms) which is enough for 2-3 USB frames when a host is connected, but short enough that serial output won't freeze the application when no host is reading. Serial output is best-effort; callers like putchar already ignore write errors. Applies to both ESP32-S3 and ESP32-C3 which share the same USB Serial/JTAG controller design.
1 parent f4a6e03 commit eba6e7a

2 files changed

Lines changed: 18 additions & 10 deletions

File tree

src/machine/machine_esp32c3_usb.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
const cpuInterruptFromUSB = 10
2121

2222
// flushTimeout is the maximum number of busy-wait iterations in flush().
23-
// Prevents hanging when no USB host is connected.
24-
const flushTimeout = 200000
23+
// Must be long enough for 2-3 USB frames (~3ms at 160MHz) so data gets
24+
// through when a host is connected, but short enough that println doesn't
25+
// freeze the application when no host is reading.
26+
const flushTimeout = 50000
2527

2628
type USB_DEVICE struct {
2729
Bus *esp.USB_DEVICE_Type
@@ -145,7 +147,8 @@ func (usbdev *USB_DEVICE) handleInterrupt() {
145147
func (usbdev *USB_DEVICE) WriteByte(c byte) error {
146148
usbdev.ensureConfigured()
147149
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
148-
// FIFO full — try flushing first, then recheck.
150+
// FIFO not writable — try a short flush to nudge the hardware
151+
// (e.g. after reset the FIFO may need WR_DONE to transition).
149152
usbdev.flush()
150153
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
151154
return errUSBCouldNotWriteAllData
@@ -206,8 +209,9 @@ func (usbdev *USB_DEVICE) RTS() bool {
206209
return false
207210
}
208211

209-
// flush signals WR_DONE and waits (with timeout) for the hardware to
210-
// consume the data. A timeout prevents hanging when no USB host is present.
212+
// flush signals WR_DONE and briefly waits for the hardware to accept more
213+
// data. The timeout is intentionally short so that serial output never
214+
// stalls the application when no USB host is reading.
211215
func (usbdev *USB_DEVICE) flush() {
212216
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
213217
for i := 0; i < flushTimeout; i++ {

src/machine/machine_esp32xx_usb.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
const cpuInterruptFromUSB = 8
2121

2222
// flushTimeout is the maximum number of busy-wait iterations in flush().
23-
// Prevents hanging when no USB host is connected.
24-
const flushTimeout = 200000
23+
// Must be long enough for 2-3 USB frames (~3ms at 240MHz) so data gets
24+
// through when a host is connected, but short enough that println doesn't
25+
// freeze the application when no host is reading.
26+
const flushTimeout = 50000
2527

2628
type USB_DEVICE struct {
2729
Bus *esp.USB_DEVICE_Type
@@ -123,7 +125,8 @@ func (usbdev *USB_DEVICE) handleInterrupt() {
123125
func (usbdev *USB_DEVICE) WriteByte(c byte) error {
124126
usbdev.ensureConfigured()
125127
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
126-
// FIFO full — try flushing first, then recheck.
128+
// FIFO not writable — try a short flush to nudge the hardware
129+
// (e.g. after reset the FIFO may need WR_DONE to transition).
127130
usbdev.flush()
128131
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
129132
return errUSBCouldNotWriteAllData
@@ -195,8 +198,9 @@ func (usbdev *USB_DEVICE) RTS() bool {
195198
return false
196199
}
197200

198-
// flush signals WR_DONE and waits (with timeout) for the hardware to
199-
// consume the data. A timeout prevents hanging when no USB host is present.
201+
// flush signals WR_DONE and briefly waits for the hardware to accept more
202+
// data. The timeout is intentionally short so that serial output never
203+
// stalls the application when no USB host is reading.
200204
func (usbdev *USB_DEVICE) flush() {
201205
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
202206
for i := 0; i < flushTimeout; i++ {

0 commit comments

Comments
 (0)