Skip to content

Commit 0ee5d1d

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. Files modified: - extmod/machine_usb_device.c: VID/PID properties and validation logic - shared/tinyusb/mp_usbd.h: VID/PID structure fields and runtime macros - shared/tinyusb/mp_usbd_descriptor.c: Dynamic descriptor patching for VID/PID - docs/library/machine.USBDevice.rst: Comprehensive VID/PID documentation
1 parent 3cd6d3b commit 0ee5d1d

File tree

4 files changed

+187
-3
lines changed

4 files changed

+187
-3
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: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ static mp_obj_t usb_device_make_new(const mp_obj_type_t *type, size_t n_args, si
109109
o->builtin_driver = MP_OBJ_FROM_PTR(&builtin_none_obj);
110110
}
111111
#endif
112+
o->custom_vid = 0; // 0 = use builtin default
113+
o->custom_pid = 0; // 0 = use builtin default
112114

113115
#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
114116
// Initialize runtime-only fields
@@ -232,6 +234,9 @@ static mp_obj_t usb_device_active(size_t n_args, const mp_obj_t *args) {
232234
} else {
233235
// Disable all classes when deactivating
234236
mp_usbd_update_class_state(USB_BUILTIN_FLAG_NONE);
237+
// Reset custom VID/PID for clean state
238+
usbd->custom_vid = 0;
239+
usbd->custom_pid = 0;
235240
}
236241
#endif
237242
}
@@ -582,6 +587,18 @@ static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
582587
// Load attribute.
583588
if (attr == MP_QSTR_builtin_driver) {
584589
dest[0] = self->builtin_driver;
590+
} else if (attr == MP_QSTR_vid) {
591+
uint16_t vid = self->custom_vid;
592+
if (vid == 0) {
593+
vid = MICROPY_HW_USB_RUNTIME_VID;
594+
}
595+
dest[0] = MP_OBJ_NEW_SMALL_INT(vid);
596+
} else if (attr == MP_QSTR_pid) {
597+
uint16_t pid = self->custom_pid;
598+
if (pid == 0) {
599+
pid = MICROPY_HW_USB_RUNTIME_PID;
600+
}
601+
dest[0] = MP_OBJ_NEW_SMALL_INT(pid);
585602
} else {
586603
// Continue lookup in locals_dict.
587604
dest[1] = MP_OBJ_SENTINEL;
@@ -657,6 +674,54 @@ static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
657674
// Store the builtin_driver (could be legacy constant or new bitfield object)
658675
self->builtin_driver = dest[1];
659676
dest[0] = MP_OBJ_NULL;
677+
} else if (attr == MP_QSTR_vid) {
678+
#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
679+
// In runtime mode, require deactivation before changing VID
680+
if (self->active) {
681+
mp_raise_OSError(MP_EINVAL); // Need to deactivate first
682+
}
683+
#endif
684+
mp_int_t vid = mp_obj_get_int(dest[1]);
685+
if (vid < 0 || vid > 0xFFFF) {
686+
mp_raise_ValueError(MP_ERROR_TEXT("VID must be 0-65535"));
687+
}
688+
self->custom_vid = (uint16_t)vid;
689+
690+
#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
691+
// In static mode, if USB is active, trigger re-enumeration
692+
if (self->active) {
693+
#ifndef NO_QSTR
694+
tud_disconnect();
695+
mp_hal_delay_ms(100);
696+
tud_connect();
697+
#endif
698+
}
699+
#endif
700+
dest[0] = MP_OBJ_NULL;
701+
} else if (attr == MP_QSTR_pid) {
702+
#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
703+
// In runtime mode, require deactivation before changing PID
704+
if (self->active) {
705+
mp_raise_OSError(MP_EINVAL); // Need to deactivate first
706+
}
707+
#endif
708+
mp_int_t pid = mp_obj_get_int(dest[1]);
709+
if (pid < 0 || pid > 0xFFFF) {
710+
mp_raise_ValueError(MP_ERROR_TEXT("PID must be 0-65535"));
711+
}
712+
self->custom_pid = (uint16_t)pid;
713+
714+
#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
715+
// In static mode, if USB is active, trigger re-enumeration
716+
if (self->active) {
717+
#ifndef NO_QSTR
718+
tud_disconnect();
719+
mp_hal_delay_ms(100);
720+
tud_connect();
721+
#endif
722+
}
723+
#endif
724+
dest[0] = MP_OBJ_NULL;
660725
}
661726
}
662727
}

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: 18 additions & 2 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,
@@ -353,6 +353,22 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
353353
#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
354354

355355
const uint8_t *tud_descriptor_device_cb(void) {
356+
// Check if custom VID/PID is set
357+
if (MP_STATE_VM(usbd) != MP_OBJ_NULL) {
358+
mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
359+
if (usbd->custom_vid != 0 || usbd->custom_pid != 0) {
360+
// Create custom descriptor with overridden VID/PID
361+
static tusb_desc_device_t custom_desc;
362+
custom_desc = mp_usbd_builtin_desc_dev; // Copy default
363+
if (usbd->custom_vid != 0) {
364+
custom_desc.idVendor = usbd->custom_vid;
365+
}
366+
if (usbd->custom_pid != 0) {
367+
custom_desc.idProduct = usbd->custom_pid;
368+
}
369+
return (const uint8_t*)&custom_desc;
370+
}
371+
}
356372
return (const void *)&mp_usbd_builtin_desc_dev;
357373
}
358374

0 commit comments

Comments
 (0)