mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-16 18:53:29 +00:00
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.
This commit is contained in:
@@ -20,8 +20,10 @@ import (
|
|||||||
const cpuInterruptFromUSB = 10
|
const cpuInterruptFromUSB = 10
|
||||||
|
|
||||||
// flushTimeout is the maximum number of busy-wait iterations in flush().
|
// flushTimeout is the maximum number of busy-wait iterations in flush().
|
||||||
// Prevents hanging when no USB host is connected.
|
// Must be long enough for 2-3 USB frames (~3ms at 160MHz) so data gets
|
||||||
const flushTimeout = 200000
|
// through when a host is connected, but short enough that println doesn't
|
||||||
|
// freeze the application when no host is reading.
|
||||||
|
const flushTimeout = 50000
|
||||||
|
|
||||||
type USB_DEVICE struct {
|
type USB_DEVICE struct {
|
||||||
Bus *esp.USB_DEVICE_Type
|
Bus *esp.USB_DEVICE_Type
|
||||||
@@ -145,7 +147,8 @@ func (usbdev *USB_DEVICE) handleInterrupt() {
|
|||||||
func (usbdev *USB_DEVICE) WriteByte(c byte) error {
|
func (usbdev *USB_DEVICE) WriteByte(c byte) error {
|
||||||
usbdev.ensureConfigured()
|
usbdev.ensureConfigured()
|
||||||
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
||||||
// FIFO full — try flushing first, then recheck.
|
// FIFO not writable — try a short flush to nudge the hardware
|
||||||
|
// (e.g. after reset the FIFO may need WR_DONE to transition).
|
||||||
usbdev.flush()
|
usbdev.flush()
|
||||||
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
||||||
return errUSBCouldNotWriteAllData
|
return errUSBCouldNotWriteAllData
|
||||||
@@ -206,8 +209,9 @@ func (usbdev *USB_DEVICE) RTS() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush signals WR_DONE and waits (with timeout) for the hardware to
|
// flush signals WR_DONE and briefly waits for the hardware to accept more
|
||||||
// consume the data. A timeout prevents hanging when no USB host is present.
|
// data. The timeout is intentionally short so that serial output never
|
||||||
|
// stalls the application when no USB host is reading.
|
||||||
func (usbdev *USB_DEVICE) flush() {
|
func (usbdev *USB_DEVICE) flush() {
|
||||||
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
|
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
|
||||||
for i := 0; i < flushTimeout; i++ {
|
for i := 0; i < flushTimeout; i++ {
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ import (
|
|||||||
const cpuInterruptFromUSB = 8
|
const cpuInterruptFromUSB = 8
|
||||||
|
|
||||||
// flushTimeout is the maximum number of busy-wait iterations in flush().
|
// flushTimeout is the maximum number of busy-wait iterations in flush().
|
||||||
// Prevents hanging when no USB host is connected.
|
// Must be long enough for 2-3 USB frames (~3ms at 240MHz) so data gets
|
||||||
const flushTimeout = 200000
|
// through when a host is connected, but short enough that println doesn't
|
||||||
|
// freeze the application when no host is reading.
|
||||||
|
const flushTimeout = 50000
|
||||||
|
|
||||||
type USB_DEVICE struct {
|
type USB_DEVICE struct {
|
||||||
Bus *esp.USB_DEVICE_Type
|
Bus *esp.USB_DEVICE_Type
|
||||||
@@ -123,7 +125,8 @@ func (usbdev *USB_DEVICE) handleInterrupt() {
|
|||||||
func (usbdev *USB_DEVICE) WriteByte(c byte) error {
|
func (usbdev *USB_DEVICE) WriteByte(c byte) error {
|
||||||
usbdev.ensureConfigured()
|
usbdev.ensureConfigured()
|
||||||
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
||||||
// FIFO full — try flushing first, then recheck.
|
// FIFO not writable — try a short flush to nudge the hardware
|
||||||
|
// (e.g. after reset the FIFO may need WR_DONE to transition).
|
||||||
usbdev.flush()
|
usbdev.flush()
|
||||||
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
|
||||||
return errUSBCouldNotWriteAllData
|
return errUSBCouldNotWriteAllData
|
||||||
@@ -195,8 +198,9 @@ func (usbdev *USB_DEVICE) RTS() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush signals WR_DONE and waits (with timeout) for the hardware to
|
// flush signals WR_DONE and briefly waits for the hardware to accept more
|
||||||
// consume the data. A timeout prevents hanging when no USB host is present.
|
// data. The timeout is intentionally short so that serial output never
|
||||||
|
// stalls the application when no USB host is reading.
|
||||||
func (usbdev *USB_DEVICE) flush() {
|
func (usbdev *USB_DEVICE) flush() {
|
||||||
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
|
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
|
||||||
for i := 0; i < flushTimeout; i++ {
|
for i := 0; i < flushTimeout; i++ {
|
||||||
|
|||||||
Reference in New Issue
Block a user