diff --git a/components/usb_device/README.md b/components/usb_device/README.md index a6d44ae51..42c4dbacb 100644 --- a/components/usb_device/README.md +++ b/components/usb_device/README.md @@ -70,7 +70,9 @@ for back-compatibility. - **Sequential allocation** of interfaces / endpoints / strings with an endpoint-budget check (error via `std::error_code` if exceeded). - **Configurable identity**: VID, PID, manufacturer / product / serial / interface - strings. + strings, plus the descriptor details some hosts check: `bcd_device` + (device release), `max_power_ma` (bMaxPower, clamped to 500 mA and rounded up + to the next 2 mA unit) and `remote_wakeup`. - **Idiomatic espp**: no exceptions; `initialize()` reports failures via `std::error_code`. - **Safe marshaling**: the TinyUSB RX callbacks (TinyUSB task context) are drained @@ -85,6 +87,11 @@ Composite CDC + vendor/WebUSB device (both interfaces carry the same raw stream) espp::UsbDevice::Config cfg; cfg.vid = 0x1209; // pid.codes VID (ODrive uses this) cfg.pid = 0x0d32; // ODrive-like PID +// optional descriptor details (defaults: 0x0100, 100 mA, remote wakeup on); +// e.g. a Nintendo Switch expects a Pro Controller to report 0x0210 and 500 mA +cfg.bcd_device = 0x0100; +cfg.max_power_ma = 100; // clamped to 500, rounded up to a 2 mA unit +cfg.remote_wakeup = true; espp::UsbDevice::CdcFunction cdc; cdc.on_receive = [&](std::span data) { /* serial rx */ }; diff --git a/components/usb_device/include/usb_device.hpp b/components/usb_device/include/usb_device.hpp index 107401d55..0240a674c 100644 --- a/components/usb_device/include/usb_device.hpp +++ b/components/usb_device/include/usb_device.hpp @@ -229,6 +229,14 @@ class UsbDevice : public BaseComponent { std::string manufacturer{"espp"}; /**< Manufacturer string descriptor. */ std::string product{"espp USB Device"}; /**< Product string descriptor. */ std::string serial_number{"000000000001"}; /**< Serial number string descriptor. */ + // Descriptor details some hosts check (e.g. a Switch expects a Pro Controller + // to report bcdDevice 0x0210 and 500 mA). + uint16_t bcd_device{0x0100}; /**< bcdDevice (device release, BCD) in the device descriptor. + Ignored for an XInput-only device (which reports the Xbox + 360 value). */ + uint16_t max_power_ma{100}; /**< bMaxPower in the configuration descriptor, in mA; clamped + to 500 and rounded up to the next 2 mA unit. */ + bool remote_wakeup{true}; /**< Advertise remote wakeup in the configuration attributes. */ std::optional cdc{}; /**< Enable a CDC-ACM function. */ std::optional vendor{}; /**< Enable a vendor-specific / WebUSB function. */ diff --git a/components/usb_device/src/usb_device.cpp b/components/usb_device/src/usb_device.cpp index f8001377e..84c211b4e 100644 --- a/components/usb_device/src/usb_device.cpp +++ b/components/usb_device/src/usb_device.cpp @@ -1,5 +1,6 @@ #include "usb_device.hpp" +#include #include #include #include @@ -888,7 +889,7 @@ bool UsbDevice::initialize(std::error_code &ec) { impl_->device_desc.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE; impl_->device_desc.idVendor = xinput_only ? config_.xinput->vid : config_.vid; impl_->device_desc.idProduct = xinput_only ? config_.xinput->pid : config_.pid; - impl_->device_desc.bcdDevice = xinput_only ? espp::xinput::kDefaultBcdDevice : 0x0100; + impl_->device_desc.bcdDevice = xinput_only ? espp::xinput::kDefaultBcdDevice : config_.bcd_device; impl_->device_desc.iManufacturer = 0x01; impl_->device_desc.iProduct = 0x02; impl_->device_desc.iSerialNumber = 0x03; @@ -927,10 +928,24 @@ bool UsbDevice::initialize(std::error_code &ec) { desc.clear(); auto append = [&](const uint8_t *p, size_t n) { desc.insert(desc.end(), p, p + n); }; { - const uint8_t hdr[] = { - // config number, interface count, string index, total length, attribute, power (mA) - TUD_CONFIG_DESCRIPTOR(1, itf_count, 0, total_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, - 100), + // The 9-byte configuration descriptor header, written out explicitly + // (TUD_CONFIG_DESCRIPTOR's arithmetic on runtime values is a narrowing + // conversion inside a braced initializer). bMaxPower is in 2 mA units: + // clamp to the USB 2.0 maximum (500 mA) and round UP so an odd request is + // never under-reported (1 mA -> 2 mA). + const uint16_t power_ma = std::min(config_.max_power_ma, 500); + const uint8_t attributes = static_cast( + TU_BIT(7) | (config_.remote_wakeup ? TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP : 0)); + const uint8_t hdr[9] = { + 9, // bLength + static_cast(TUSB_DESC_CONFIGURATION), // bDescriptorType + static_cast(total_len & 0xFF), // wTotalLength (LE) + static_cast(total_len >> 8), + static_cast(itf_count), // bNumInterfaces + 1, // bConfigurationValue + 0, // iConfiguration + attributes, // bmAttributes + static_cast((power_ma + 1) / 2), // bMaxPower (2 mA units) }; append(hdr, sizeof(hdr)); } diff --git a/doc/en/buses/usb_cdc.rst b/doc/en/buses/usb_cdc.rst index 2d318a749..f78c0416b 100644 --- a/doc/en/buses/usb_cdc.rst +++ b/doc/en/buses/usb_cdc.rst @@ -59,7 +59,10 @@ Features - WebUSB: BOS descriptor + WebUSB URL descriptor + MS OS 2.0 descriptor for driverless browser access, with a configurable landing-page URL - Sequential interface / endpoint / string allocation with an endpoint-budget check -- Configurable VID, PID, and manufacturer / product / serial / interface strings +- Configurable VID, PID, and manufacturer / product / serial / interface strings, + plus the descriptor details some hosts check: ``bcd_device`` (device release), + ``max_power_ma`` (bMaxPower, clamped to 500 mA and rounded up to the next 2 mA + unit) and ``remote_wakeup`` - No exceptions; ``initialize()`` reports failures via ``std::error_code`` - Safely marshals the TinyUSB RX callbacks (TinyUSB task context) into per-function user callbacks @@ -75,6 +78,11 @@ stream: espp::UsbDevice::Config cfg; cfg.vid = 0x1209; // pid.codes VID (ODrive uses this) cfg.pid = 0x0d32; // ODrive-like PID + // optional descriptor details (defaults: 0x0100, 100 mA, remote wakeup on); + // e.g. a Nintendo Switch expects a Pro Controller to report 0x0210 and 500 mA + cfg.bcd_device = 0x0100; + cfg.max_power_ma = 100; // clamped to 500, rounded up to a 2 mA unit + cfg.remote_wakeup = true; espp::UsbDevice::CdcFunction cdc; cdc.on_receive = [&](std::span data) { /* handle serial rx */ };