Skip to content

Commit ce81387

Browse files
committed
extmod/machine_usb_device: Add dynamic VID/PID configuration support.
This commit adds comprehensive VID/PID (Vendor ID/Product ID) configuration support to the USB device implementation, allowing both runtime and build-time customization of USB device identification. Key features: - Runtime VID/PID properties (usb.vid, usb.pid) for dynamic configuration - Build-time VID/PID override macros (MICROPY_HW_USB_RUNTIME_VID/PID) - Dynamic descriptor patching based on custom VID/PID values - Automatic VID/PID reset on USB deactivation for clean state - Support for both runtime and static USB device modes - Validation of VID/PID ranges (1-65535) with proper error handling - Re-enumeration support in static mode for immediate VID/PID changes The implementation uses a fallback system where custom VID/PID values override runtime defaults, which in turn override compile-time defaults. This provides maximum flexibility for different deployment scenarios.
1 parent 27dac22 commit ce81387

File tree

4 files changed

+173
-28
lines changed

4 files changed

+173
-28
lines changed

docs/library/machine.USBDevice.rst

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class USBDevice -- USB Device driver
1010

1111
USBDevice provides a low-level Python API for implementing USB device functions using
1212
Python code. This includes both runtime device descriptor configuration and
13-
build-time customization of USB configuration.
13+
build-time customization of USB Vendor ID (VID) and Product ID (PID).
1414

1515
.. warning:: This low-level API assumes familiarity with the USB standard. There
1616
are high-level `usb driver modules in micropython-lib`_ which provide a
@@ -412,5 +412,95 @@ Usage Examples
412412
usb.builtin_driver = usb.BUILTIN_CDC | usb.BUILTIN_NCM
413413
usb.active(True)
414414

415+
**Custom VID/PID (Runtime Mode):**
416+
::
417+
418+
import machine
419+
420+
# Create custom device descriptor with your VID/PID
421+
custom_device_desc = bytearray([
422+
18, # bLength
423+
1, # bDescriptorType (Device)
424+
0x00, 0x02, # bcdUSB (USB 2.0)
425+
0x02, # bDeviceClass (CDC)
426+
0x00, # bDeviceSubClass
427+
0x00, # bDeviceProtocol
428+
64, # bMaxPacketSize0
429+
0x34, 0x12, # idVendor (0x1234)
430+
0x78, 0x56, # idProduct (0x5678)
431+
0x00, 0x01, # bcdDevice (1.0)
432+
1, # iManufacturer
433+
2, # iProduct
434+
3, # iSerialNumber
435+
1 # bNumConfigurations
436+
])
437+
438+
usb = machine.USBDevice()
439+
usb.active(False)
440+
usb.config(desc_dev=custom_device_desc)
441+
usb.builtin_driver = usb.BUILTIN_CDC
442+
usb.active(True)
443+
444+
Dynamic VID/PID Configuration
445+
-----------------------------
446+
447+
**Runtime VID/PID Properties (All Modes):**
448+
449+
Both runtime and non-runtime USB device modes support dynamic VID/PID
450+
configuration using simple Python properties:
451+
452+
::
453+
454+
import machine
455+
456+
usb = machine.USBDevice()
457+
usb.active(False)
458+
459+
# Set custom VID/PID dynamically
460+
usb.vid = 0x1234
461+
usb.pid = 0x5678
462+
463+
usb.builtin_driver = usb.BUILTIN_CDC
464+
usb.active(True)
465+
466+
# Check current values
467+
print(f"VID: 0x{usb.vid:04X}, PID: 0x{usb.pid:04X}")
468+
469+
# Reset to firmware defaults
470+
usb.active(False)
471+
usb.vid = 0 # 0 means use builtin default
472+
usb.pid = 0
473+
usb.active(True)
474+
475+
**Build-time VID/PID Override:**
476+
477+
For additional customization, VID/PID defaults can be set at build time
478+
using the ``MICROPY_HW_USB_RUNTIME_VID`` and ``MICROPY_HW_USB_RUNTIME_PID``
479+
macros:
480+
481+
**Override VID/PID at Build Time:**
482+
::
483+
484+
# Build with custom VID/PID
485+
make CFLAGS_EXTRA="-DMICROPY_HW_USB_RUNTIME_VID=0x1234 -DMICROPY_HW_USB_RUNTIME_PID=0x5678"
486+
487+
**Port-specific Configuration:**
488+
::
489+
490+
# In mpconfigport.h or boards/BOARD/mpconfigboard.h
491+
#define MICROPY_HW_USB_RUNTIME_VID (0x1234)
492+
#define MICROPY_HW_USB_RUNTIME_PID (0x5678)
493+
494+
This allows customizing the USB VID/PID defaults even when ``MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE``
495+
is disabled and only built-in drivers are available. If not specified, these
496+
macros default to ``MICROPY_HW_USB_VID`` and ``MICROPY_HW_USB_PID``.
497+
498+
**VID/PID Property Notes:**
499+
500+
- Properties can only be set when ``usb.active`` is ``False``
501+
- Setting VID/PID to ``0`` uses the firmware default value
502+
- Valid range is 1-65535 (0x0001-0xFFFF)
503+
- Changes are automatically reset when deactivating USB
504+
- Works in both runtime and non-runtime USB device modes
415505

416506
.. _usb driver modules in micropython-lib: https://github.com/micropython/micropython-lib/tree/master/micropython/usb#readme

extmod/machine_usb_device.c

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ static mp_obj_t usb_device_make_new(const mp_obj_type_t *type, size_t n_args, si
105105
o->builtin_driver = MP_OBJ_FROM_PTR(&builtin_none_obj);
106106
}
107107
#endif
108+
o->custom_vid = 0; // 0 = use builtin default
109+
o->custom_pid = 0; // 0 = use builtin default
108110

109111
#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
110112
// Initialize runtime-only fields
@@ -228,6 +230,8 @@ static mp_obj_t usb_device_active(size_t n_args, const mp_obj_t *args) {
228230
} else {
229231
// Disable all classes when deactivating
230232
mp_usbd_update_class_state(USB_BUILTIN_FLAG_NONE);
233+
// Note: custom_vid/pid are NOT reset here - they persist
234+
// across deactivation so users can set them before activating
231235
}
232236
#endif
233237
}
@@ -578,22 +582,29 @@ static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
578582
// Load attribute.
579583
if (attr == MP_QSTR_builtin_driver) {
580584
dest[0] = self->builtin_driver;
585+
} else if (attr == MP_QSTR_vid) {
586+
uint16_t vid = self->custom_vid;
587+
if (vid == 0) {
588+
vid = MICROPY_HW_USB_RUNTIME_VID;
589+
}
590+
dest[0] = MP_OBJ_NEW_SMALL_INT(vid);
591+
} else if (attr == MP_QSTR_pid) {
592+
uint16_t pid = self->custom_pid;
593+
if (pid == 0) {
594+
pid = MICROPY_HW_USB_RUNTIME_PID;
595+
}
596+
dest[0] = MP_OBJ_NEW_SMALL_INT(pid);
581597
} else {
582598
// Continue lookup in locals_dict.
583599
dest[1] = MP_OBJ_SENTINEL;
584600
}
585601
} else if (dest[1] != MP_OBJ_NULL) {
586602
// Store attribute.
587603
if (attr == MP_QSTR_builtin_driver) {
588-
#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
589-
// In runtime mode, require deactivation before changing builtin_driver
604+
// Require deactivation before changing builtin_driver in both modes
590605
if (self->active) {
591-
mp_raise_OSError(MP_EINVAL); // Need to deactivate first
606+
mp_raise_OSError(MP_EINVAL);
592607
}
593-
#else
594-
// In static mode, allow changing builtin_driver when active
595-
// This will update the class state and trigger re-enumeration
596-
#endif
597608

598609
// Handle new bitfield builtin types and legacy constants
599610
uint8_t flags = 0;
@@ -636,23 +647,31 @@ static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
636647
// Update the internal class state based on flags
637648
mp_usbd_update_class_state(flags);
638649

639-
#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
640-
// In static mode, if USB is active, trigger re-enumeration
641-
// to update the host with the new configuration
642-
if (self->active) {
643-
#ifndef NO_QSTR
644-
// Disconnect and reconnect to trigger enumeration with new descriptors
645-
tud_disconnect();
646-
// Small delay to ensure host recognizes disconnection
647-
mp_hal_delay_ms(100);
648-
tud_connect();
649-
#endif
650-
}
651-
#endif
652-
653650
// Store the builtin_driver (could be legacy constant or new bitfield object)
654651
self->builtin_driver = dest[1];
655652
dest[0] = MP_OBJ_NULL;
653+
} else if (attr == MP_QSTR_vid) {
654+
// Require deactivation before changing VID in both modes
655+
if (self->active) {
656+
mp_raise_OSError(MP_EINVAL);
657+
}
658+
mp_int_t vid = mp_obj_get_int(dest[1]);
659+
if (vid < 0 || vid > 0xFFFF) {
660+
mp_raise_ValueError(MP_ERROR_TEXT("VID must be 0-65535"));
661+
}
662+
self->custom_vid = (uint16_t)vid;
663+
dest[0] = MP_OBJ_NULL;
664+
} else if (attr == MP_QSTR_pid) {
665+
// Require deactivation before changing PID in both modes
666+
if (self->active) {
667+
mp_raise_OSError(MP_EINVAL);
668+
}
669+
mp_int_t pid = mp_obj_get_int(dest[1]);
670+
if (pid < 0 || pid > 0xFFFF) {
671+
mp_raise_ValueError(MP_ERROR_TEXT("PID must be 0-65535"));
672+
}
673+
self->custom_pid = (uint16_t)pid;
674+
dest[0] = MP_OBJ_NULL;
656675
}
657676
}
658677
}

shared/tinyusb/mp_usbd.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ static inline void mp_usbd_init_tud(void) {
9797
#endif
9898
}
9999

100+
// Allow runtime override of VID/PID defaults
101+
#ifndef MICROPY_HW_USB_RUNTIME_VID
102+
#define MICROPY_HW_USB_RUNTIME_VID MICROPY_HW_USB_VID
103+
#endif
104+
#ifndef MICROPY_HW_USB_RUNTIME_PID
105+
#define MICROPY_HW_USB_RUNTIME_PID MICROPY_HW_USB_PID
106+
#endif
107+
100108
// Individual USB class flags for bitfield operations
101109
#define USB_BUILTIN_FLAG_NONE 0x00
102110
#define USB_BUILTIN_FLAG_CDC 0x01
@@ -148,6 +156,9 @@ typedef struct {
148156
bool active; // Has the user set the USB device active?
149157
bool trigger; // Has the user requested the active state change (or re-activate)?
150158

159+
uint16_t custom_vid; // Custom VID (0 = use builtin default)
160+
uint16_t custom_pid; // Custom PID (0 = use builtin default)
161+
151162

152163
// Temporary pointers for xfer data in progress on each endpoint
153164
// Ensuring they aren't garbage collected until the xfer completes
@@ -172,6 +183,8 @@ typedef struct {
172183
mp_obj_base_t base;
173184
mp_obj_t builtin_driver; // Points to one of mp_type_usb_device_builtin_nnn
174185
bool active; // Has the user set the USB device active?
186+
uint16_t custom_vid; // Custom VID (0 = use builtin default)
187+
uint16_t custom_pid; // Custom PID (0 = use builtin default)
175188
} mp_obj_usb_device_t;
176189

177190
static inline void mp_usbd_init(void) {

shared/tinyusb/mp_usbd_descriptor.c

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ const tusb_desc_device_t mp_usbd_builtin_desc_dev = {
4646
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
4747
.bDeviceProtocol = MISC_PROTOCOL_IAD,
4848
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
49-
.idVendor = MICROPY_HW_USB_VID,
50-
.idProduct = MICROPY_HW_USB_PID,
49+
.idVendor = MICROPY_HW_USB_RUNTIME_VID,
50+
.idProduct = MICROPY_HW_USB_RUNTIME_PID,
5151
.bcdDevice = 0x0100,
5252
.iManufacturer = USBD_STR_MANUF,
5353
.iProduct = USBD_STR_PRODUCT,
@@ -141,35 +141,41 @@ static const uint8_t *mp_usbd_generate_desc_cfg_unified(uint8_t flags, uint8_t *
141141
*desc++ = 0x80; // bmAttributes (bit 7 must be 1)
142142
*desc++ = USBD_MAX_POWER_MA / 2; // bMaxPower (in 2mA units)
143143

144-
// Add enabled class descriptors
144+
// Track current interface number for dynamic assignment
145+
uint8_t itf_num = 0;
146+
147+
// Add enabled class descriptors with dynamically calculated interface numbers
145148
#if CFG_TUD_CDC
146149
if (flags & USB_BUILTIN_FLAG_CDC) {
147150
const uint8_t cdc_desc[] = {
148-
TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD,
151+
TUD_CDC_DESCRIPTOR(itf_num, USBD_STR_CDC, USBD_CDC_EP_CMD,
149152
USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE)
150153
};
151154
memcpy(desc, cdc_desc, sizeof(cdc_desc));
152155
desc += sizeof(cdc_desc);
156+
itf_num += 2; // CDC uses 2 interfaces
153157
}
154158
#endif
155159

156160
#if CFG_TUD_MSC
157161
if (flags & USB_BUILTIN_FLAG_MSC) {
158162
const uint8_t msc_desc[] = {
159-
TUD_MSC_DESCRIPTOR(USBD_ITF_MSC, USBD_STR_MSC, USBD_MSC_EP_OUT, USBD_MSC_EP_IN, USBD_MSC_IN_OUT_MAX_SIZE)
163+
TUD_MSC_DESCRIPTOR(itf_num, USBD_STR_MSC, USBD_MSC_EP_OUT, USBD_MSC_EP_IN, USBD_MSC_IN_OUT_MAX_SIZE)
160164
};
161165
memcpy(desc, msc_desc, sizeof(msc_desc));
162166
desc += sizeof(msc_desc);
167+
itf_num += 1; // MSC uses 1 interface
163168
}
164169
#endif
165170

166171
#if CFG_TUD_NCM
167172
if (flags & USB_BUILTIN_FLAG_NCM) {
168173
const uint8_t ncm_desc[] = {
169-
TUD_CDC_NCM_DESCRIPTOR(USBD_ITF_NET, USBD_STR_NET, USBD_STR_NET_MAC, USBD_NET_EP_CMD, 64, USBD_NET_EP_OUT, USBD_NET_EP_IN, USBD_NET_IN_OUT_MAX_SIZE, CFG_TUD_NET_MTU)
174+
TUD_CDC_NCM_DESCRIPTOR(itf_num, USBD_STR_NET, USBD_STR_NET_MAC, USBD_NET_EP_CMD, 64, USBD_NET_EP_OUT, USBD_NET_EP_IN, USBD_NET_IN_OUT_MAX_SIZE, CFG_TUD_NET_MTU)
170175
};
171176
memcpy(desc, ncm_desc, sizeof(ncm_desc));
172177
desc += sizeof(ncm_desc);
178+
itf_num += 2; // NCM uses 2 interfaces (control + data)
173179
}
174180
#endif
175181

@@ -345,6 +351,23 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
345351
#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
346352

347353
const uint8_t *tud_descriptor_device_cb(void) {
354+
// Check if custom VID/PID is set (0 means use default)
355+
if (MP_STATE_VM(usbd) != MP_OBJ_NULL) {
356+
mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
357+
if (usbd->custom_vid != 0 || usbd->custom_pid != 0) {
358+
// Patch the default descriptor with custom VID/PID values.
359+
// We override whichever fields are non-zero (0 = keep default).
360+
static tusb_desc_device_t custom_desc;
361+
custom_desc = mp_usbd_builtin_desc_dev; // Copy default
362+
if (usbd->custom_vid != 0) {
363+
custom_desc.idVendor = usbd->custom_vid;
364+
}
365+
if (usbd->custom_pid != 0) {
366+
custom_desc.idProduct = usbd->custom_pid;
367+
}
368+
return (const uint8_t*)&custom_desc;
369+
}
370+
}
348371
return (const void *)&mp_usbd_builtin_desc_dev;
349372
}
350373

0 commit comments

Comments
 (0)