From 59d378ed42eaf2f7e23e102844e808ba25860303 Mon Sep 17 00:00:00 2001 From: cuicuiV5 Date: Mon, 31 Aug 2026 13:43:55 +0800 Subject: [PATCH] fix(usb): implement hardware SOF and DSTS sleep/wake auto-recovery --- src/usb/uac_microphone.cpp | 75 +++++++++++++-------- src/usb/usb_composite.cpp | 135 +++++++++++++++++++++++++++++++++++-- 2 files changed, 179 insertions(+), 31 deletions(-) diff --git a/src/usb/uac_microphone.cpp b/src/usb/uac_microphone.cpp index 999b0c3..6cf975f 100644 --- a/src/usb/uac_microphone.cpp +++ b/src/usb/uac_microphone.cpp @@ -42,7 +42,9 @@ static void uac_watchdog_task(void* arg) { uint32_t stuck_ticks = 0; while(1) { vTaskDelay(pdMS_TO_TICKS(10)); - if (s_uac_streaming) { + + // ONLY monitor if USB is fully mounted, active, NOT suspended by host, and streaming + if (tud_mounted() && !tud_suspended() && s_uac_streaming) { if (s_xfer_cb_count == last_count) { stuck_ticks += 10; if (stuck_ticks >= 50) { @@ -67,6 +69,7 @@ static void uac_watchdog_task(void* arg) { } } else { stuck_ticks = 0; + last_count = s_xfer_cb_count; } } } @@ -123,6 +126,8 @@ static void uac_driver_reset(uint8_t rhport) { (void)rhport; s_uac_streaming = false; s_uac_alt = 0; + s_xfer_cb_count = 0; + app_log("UAC", "USB Bus Reset detected -> UAC state reset"); } static uint16_t uac_driver_open(uint8_t rhport, tusb_desc_interface_t const *desc_intf, uint16_t max_len) { @@ -144,39 +149,54 @@ static bool uac_driver_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *req) { if (stage != CONTROL_STAGE_SETUP) return true; + // 1. Standard USB Requests if (req->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) { - if (req->bRequest == TUSB_REQ_SET_INTERFACE) { - uint8_t alt = (uint8_t)req->wValue; - s_uac_alt = alt; - s_uac_streaming = (alt == 1); - - // Force release endpoint to clear stuck busy flags from aborted transfers + if (req->bRequest == TUSB_REQ_SET_INTERFACE) { + uint8_t itf = (uint8_t)req->wIndex; + if (itf != s_uac_itf_as && itf != s_uac_itf_ac) { + return false; // Crucial: Let other class drivers (HID, CDC) handle their own interfaces! + } + uint8_t alt = (uint8_t)req->wValue; + s_uac_alt = alt; + s_uac_streaming = (alt == 1); + + // Force release endpoint to clear stuck busy flags from aborted transfers + usbd_edpt_close(rhport, (uint8_t)(s_uac_ep_in | 0x80)); + + if (alt == 1) { + tusb_desc_endpoint_t ep; + ep.bLength = sizeof(tusb_desc_endpoint_t); + ep.bDescriptorType = TUSB_DESC_ENDPOINT; + ep.bEndpointAddress = (uint8_t)(s_uac_ep_in | 0x80); + ep.bmAttributes.xfer = TUSB_XFER_ISOCHRONOUS; + ep.bmAttributes.sync = 1; + ep.wMaxPacketSize = 64; + ep.bInterval = 2; + usbd_edpt_open(rhport, &ep); + + // Kick off the continuous Isochronous IN chain + memset(s_tx_buf, 0, 64); + usbd_edpt_xfer(rhport, ep.bEndpointAddress, (uint8_t*)s_tx_buf, 64); + } else { usbd_edpt_close(rhport, (uint8_t)(s_uac_ep_in | 0x80)); - - if (alt == 1) { - tusb_desc_endpoint_t ep; - ep.bLength = sizeof(tusb_desc_endpoint_t); - ep.bDescriptorType = TUSB_DESC_ENDPOINT; - ep.bEndpointAddress = (uint8_t)(s_uac_ep_in | 0x80); - ep.bmAttributes.xfer = TUSB_XFER_ISOCHRONOUS; - ep.bmAttributes.sync = 1; - ep.wMaxPacketSize = 64; - ep.bInterval = 2; - usbd_edpt_open(rhport, &ep); - - // Kick off the continuous Isochronous IN chain - memset(s_tx_buf, 0, 64); - usbd_edpt_xfer(rhport, ep.bEndpointAddress, (uint8_t*)s_tx_buf, 64); - } else { - usbd_edpt_close(rhport, (uint8_t)(s_uac_ep_in | 0x80)); - } - return tud_control_status(rhport, req); + } + return tud_control_status(rhport, req); } else if (req->bRequest == TUSB_REQ_GET_INTERFACE) { + uint8_t itf = (uint8_t)req->wIndex; + if (itf != s_uac_itf_as && itf != s_uac_itf_ac) { + return false; // Crucial: Let other class drivers handle their own interfaces! + } return tud_control_xfer(rhport, req, &s_uac_alt, 1); } + return false; } + // 2. Class-Specific USB Requests if (req->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) { + uint8_t target_itf = (uint8_t)req->wIndex; + if (target_itf != s_uac_itf_ac && target_itf != s_uac_itf_as && target_itf != 0x02) { + return false; // Crucial: Let HID / CDC class drivers handle their own class requests! + } uint8_t r = req->bRequest; uint8_t cs = (uint8_t)(req->wValue >> 8); if (r == 0x81) { // GET_CUR @@ -188,8 +208,9 @@ static bool uac_driver_control_xfer_cb(uint8_t rhport, uint8_t stage, if (r == 0x82) return tud_control_xfer(rhport, req, &vol_min, 2); if (r == 0x83) return tud_control_xfer(rhport, req, &vol_max, 2); if (r == 0x84) return tud_control_xfer(rhport, req, &vol_res, 2); + return false; } - return tud_control_status(rhport, req); + return false; } static bool uac_driver_xfer_cb(uint8_t rhport, uint8_t ep_addr, diff --git a/src/usb/usb_composite.cpp b/src/usb/usb_composite.cpp index bea3284..36fe4e9 100644 --- a/src/usb/usb_composite.cpp +++ b/src/usb/usb_composite.cpp @@ -8,6 +8,9 @@ #include "USBCDC.h" #include "USBHIDKeyboard.h" #include "USBHIDConsumerControl.h" +#include "tusb.h" +#include "esp32-hal-tinyusb.h" +#include "esp_system.h" #if !ARDUINO_USB_CDC_ON_BOOT USBCDC USBSerial; @@ -17,6 +20,17 @@ static USBHIDKeyboard s_keyboard; static USBHIDConsumerControl s_consumer; static bool s_usb_ready = false; +extern "C" void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr); +extern "C" bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr); + +#include "soc/usb_struct.h" + +static bool s_hw_sleep_detected = false; +static uint32_t s_hw_sleep_start_ms = 0; +static uint16_t s_last_soffn = 0; +static uint32_t s_last_soffn_change_ms = 0; +static uint32_t s_boot_grace_until_ms = 0; + extern "C" { void usb_composite_init(void) { @@ -28,6 +42,27 @@ void usb_composite_init(void) { USB.usbClass(0xEF); USB.usbSubClass(0x02); USB.usbProtocol(0x01); // MISC_PROTOCOL_IAD + USB.usbAttributes(TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP); + + USB.onEvent([](void* arg, esp_event_base_t base, int32_t id, void* data) { + if (id == ARDUINO_USB_SUSPEND_EVENT) { + app_log("USB", "USB Suspend Event (PC Sleep/Standby)"); + } else if (id == ARDUINO_USB_RESUME_EVENT) { + app_log("USB", "USB Resume Event (PC Woke up)"); + for (uint8_t ep = 1; ep <= 4; ep++) { + usbd_edpt_clear_stall(0, (uint8_t)(ep | 0x80)); + } + s_keyboard.releaseAll(); + s_consumer.release(); + } else if (id == ARDUINO_USB_STARTED_EVENT) { + app_log("USB", "USB Started / Mounted"); + for (uint8_t ep = 1; ep <= 4; ep++) { + usbd_edpt_clear_stall(0, (uint8_t)(ep | 0x80)); + } + } else if (id == ARDUINO_USB_STOPPED_EVENT) { + app_log("USB", "USB Stopped / Bus Reset"); + } + }); #if !ARDUINO_USB_CDC_ON_BOOT USBSerial.begin(); @@ -43,6 +78,51 @@ void usb_composite_init(void) { void usb_composite_task(void) { uac_microphone_task(); + + uint32_t now = millis(); + if (s_boot_grace_until_ms == 0) { + s_boot_grace_until_ms = now + 5000; // 5s boot grace period + s_last_soffn_change_ms = now; + } + if (now < s_boot_grace_until_ms) { + return; // Don't check during initial bootup + } + + // Read hardware DWC2 register on ESP32-S3 + uint32_t dsts = USB0.dsts; + bool is_hw_suspended = (dsts & 1) != 0; // Bit 0: SuspSts (1 = Suspended) + uint16_t current_soffn = (dsts >> 8) & 0x3FFF; // Bits 8-21: Frame Number (increments every 1ms when PC is awake) + + if (current_soffn != s_last_soffn) { + s_last_soffn = current_soffn; + s_last_soffn_change_ms = now; + } + + // If SOF packets stopped for > 800ms, Windows is asleep/disconnected + bool sof_active = (now - s_last_soffn_change_ms < 800); + + // 1. Detect PC entering Sleep + if ((is_hw_suspended || !sof_active) && !s_hw_sleep_detected) { + s_hw_sleep_detected = true; + s_hw_sleep_start_ms = now; + app_log("USB_HW", "PC Sleep Detected! (SuspSts=%d, SOF stopped)", is_hw_suspended ? 1 : 0); + } + // 2. Detect PC waking up (SOF resumed after sleeping for > 2s) + else if (s_hw_sleep_detected && !is_hw_suspended && sof_active) { + if (now - s_hw_sleep_start_ms >= 2000) { + app_log("USB_HW", "PC Wakeup Detected! SOF resumed -> performing clean USB hardware re-enumeration..."); + vTaskDelay(pdMS_TO_TICKS(500)); + // Pull D+ low to signal disconnect to Windows kernel + pinMode(20, OUTPUT); + pinMode(19, OUTPUT); + digitalWrite(20, LOW); + digitalWrite(19, LOW); + vTaskDelay(pdMS_TO_TICKS(250)); + esp_restart(); + } else { + s_hw_sleep_detected = false; + } + } } static SemaphoreHandle_t s_hid_mutex = NULL; @@ -65,10 +145,29 @@ static void hid_unlock(void) { bool usb_hid_keyboard_press(uint8_t modifier, uint8_t keycode) { if (!s_usb_ready) return false; hid_lock(); + + if (s_hw_sleep_detected || (USB0.dsts & 1) != 0 || tud_suspended()) { + app_log("USB_HW", "Key pressed while PC asleep -> Sending Remote Wakeup!"); + tud_remote_wakeup(); + vTaskDelay(pdMS_TO_TICKS(15)); + } + + if (usbd_edpt_busy(0, 0x81)) { + usbd_edpt_clear_stall(0, 0x81); + } + + // 1. Direct TinyUSB driver report + hid_keyboard_report_t kbd = {0}; + kbd.modifier = modifier; + kbd.keycode[0] = keycode; + tud_hid_n_report(0, HID_REPORT_ID_KEYBOARD, &kbd, sizeof(kbd)); + + // 2. Arduino helper state update KeyReport report = {0}; report.modifiers = modifier; report.keys[0] = keycode; s_keyboard.sendReport(&report); + hid_unlock(); return true; } @@ -76,9 +175,18 @@ bool usb_hid_keyboard_press(uint8_t modifier, uint8_t keycode) { bool usb_hid_keyboard_release(void) { if (!s_usb_ready) return false; hid_lock(); + + if (usbd_edpt_busy(0, 0x81)) { + usbd_edpt_clear_stall(0, 0x81); + } + + hid_keyboard_report_t kbd = {0}; + tud_hid_n_report(0, HID_REPORT_ID_KEYBOARD, &kbd, sizeof(kbd)); + KeyReport report = {0}; // All zeroes s_keyboard.sendReport(&report); s_keyboard.releaseAll(); + hid_unlock(); return true; } @@ -94,7 +202,20 @@ bool usb_hid_keyboard_tap(uint8_t modifier, uint8_t keycode) { bool usb_hid_consumer_press(uint16_t usage_code) { if (!s_usb_ready) return false; hid_lock(); + + if (tud_suspended()) { + app_log("USB", "PC Suspended -> tud_remote_wakeup"); + tud_remote_wakeup(); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + if (usbd_edpt_busy(0, 0x81)) { + usbd_edpt_clear_stall(0, 0x81); + } + + tud_hid_n_report(0, HID_REPORT_ID_CONSUMER_CONTROL, &usage_code, 2); s_consumer.press(usage_code); + hid_unlock(); return true; } @@ -102,18 +223,24 @@ bool usb_hid_consumer_press(uint16_t usage_code) { bool usb_hid_consumer_release(void) { if (!s_usb_ready) return false; hid_lock(); + + if (usbd_edpt_busy(0, 0x81)) { + usbd_edpt_clear_stall(0, 0x81); + } + + uint16_t zero = 0; + tud_hid_n_report(0, HID_REPORT_ID_CONSUMER_CONTROL, &zero, 2); s_consumer.release(); + hid_unlock(); return true; } bool usb_hid_consumer_tap(uint16_t usage_code) { if (!s_usb_ready) return false; - hid_lock(); - s_consumer.press(usage_code); + usb_hid_consumer_press(usage_code); delay(15); - s_consumer.release(); - hid_unlock(); + usb_hid_consumer_release(); return true; }