fix(usb): implement hardware SOF and DSTS sleep/wake auto-recovery

This commit is contained in:
cuicuiV5 committed 2026-08-31 13:43:55 +08:00
1 parent 44415fa442
commit 59d378ed42
2 files changed
+179 -31

No files matched your search

+48 -27
View File
@@ -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,
+131 -4
View File
@@ -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;
}