Skip to content

Commit 53c80ee

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. Signed-off-by: Andrew Leech <[email protected]>
1 parent cd165a2 commit 53c80ee

File tree

5 files changed

+195
-30
lines changed

5 files changed

+195
-30
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+
- Custom values persist across active()/inactive() cycles
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: 45 additions & 22 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
@@ -229,7 +231,13 @@ static mp_obj_t usb_device_active(size_t n_args, const mp_obj_t *args) {
229231
mp_obj_usb_builtin_t *builtin = MP_OBJ_TO_PTR(usbd->builtin_driver);
230232
mp_usbd_update_class_state(builtin->flags);
231233
}
234+
235+
// Connect to USB host
236+
tud_connect();
232237
} else {
238+
// Disconnect from USB host first
239+
tud_disconnect();
240+
233241
// Disable all classes when deactivating
234242
mp_usbd_update_class_state(USB_BUILTIN_FLAG_NONE);
235243
}
@@ -582,22 +590,29 @@ static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
582590
// Load attribute.
583591
if (attr == MP_QSTR_builtin_driver) {
584592
dest[0] = self->builtin_driver;
593+
} else if (attr == MP_QSTR_vid) {
594+
uint16_t vid = self->custom_vid;
595+
if (vid == 0) {
596+
vid = MICROPY_HW_USB_RUNTIME_VID;
597+
}
598+
dest[0] = MP_OBJ_NEW_SMALL_INT(vid);
599+
} else if (attr == MP_QSTR_pid) {
600+
uint16_t pid = self->custom_pid;
601+
if (pid == 0) {
602+
pid = MICROPY_HW_USB_RUNTIME_PID;
603+
}
604+
dest[0] = MP_OBJ_NEW_SMALL_INT(pid);
585605
} else {
586606
// Continue lookup in locals_dict.
587607
dest[1] = MP_OBJ_SENTINEL;
588608
}
589609
} else if (dest[1] != MP_OBJ_NULL) {
590610
// Store attribute.
591611
if (attr == MP_QSTR_builtin_driver) {
592-
#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
593-
// In runtime mode, require deactivation before changing builtin_driver
612+
// Require deactivation before changing builtin_driver
594613
if (self->active) {
595-
mp_raise_OSError(MP_EINVAL); // Need to deactivate first
614+
mp_raise_OSError(MP_EINVAL);
596615
}
597-
#else
598-
// In static mode, allow changing builtin_driver when active
599-
// This will update the class state and trigger re-enumeration
600-
#endif
601616

602617
// Handle new bitfield builtin types and legacy constants
603618
uint8_t flags = 0;
@@ -621,7 +636,7 @@ static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
621636
} else if (dest[1] == MP_OBJ_FROM_PTR(&builtin_cdc_msc_obj)) {
622637
flags = USB_BUILTIN_FLAG_CDC | USB_BUILTIN_FLAG_MSC;
623638
#endif
624-
// Also handle legacy constant types for backward compatibility
639+
// Also handle legacy constant types for backward compatibility
625640
} else if (dest[1] == MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none)) {
626641
flags = USB_BUILTIN_FLAG_NONE;
627642
#if HAS_BUILTIN_DRIVERS
@@ -640,23 +655,31 @@ static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
640655
// Update the internal class state based on flags
641656
mp_usbd_update_class_state(flags);
642657

643-
#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
644-
// In static mode, if USB is active, trigger re-enumeration
645-
// to update the host with the new configuration
646-
if (self->active) {
647-
#ifndef NO_QSTR
648-
// Disconnect and reconnect to trigger enumeration with new descriptors
649-
tud_disconnect();
650-
// Small delay to ensure host recognizes disconnection
651-
mp_hal_delay_ms(100);
652-
tud_connect();
653-
#endif
654-
}
655-
#endif
656-
657658
// Store the builtin_driver (could be legacy constant or new bitfield object)
658659
self->builtin_driver = dest[1];
659660
dest[0] = MP_OBJ_NULL;
661+
} else if (attr == MP_QSTR_vid) {
662+
// Require deactivation before changing VID
663+
if (self->active) {
664+
mp_raise_OSError(MP_EINVAL);
665+
}
666+
mp_int_t vid = mp_obj_get_int(dest[1]);
667+
if (vid < 0 || vid > 0xFFFF) {
668+
mp_raise_ValueError(MP_ERROR_TEXT("VID must be 0-65535"));
669+
}
670+
self->custom_vid = (uint16_t)vid;
671+
dest[0] = MP_OBJ_NULL;
672+
} else if (attr == MP_QSTR_pid) {
673+
// Require deactivation before changing PID
674+
if (self->active) {
675+
mp_raise_OSError(MP_EINVAL);
676+
}
677+
mp_int_t pid = mp_obj_get_int(dest[1]);
678+
if (pid < 0 || pid > 0xFFFF) {
679+
mp_raise_ValueError(MP_ERROR_TEXT("PID must be 0-65535"));
680+
}
681+
self->custom_pid = (uint16_t)pid;
682+
dest[0] = MP_OBJ_NULL;
660683
}
661684
}
662685
}

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,
@@ -143,35 +143,41 @@ static const uint8_t *mp_usbd_generate_desc_cfg_unified(uint8_t flags, uint8_t *
143143
*desc++ = 0x80; // bmAttributes (bit 7 must be 1)
144144
*desc++ = USBD_MAX_POWER_MA / 2; // bMaxPower (in 2mA units)
145145

146-
// Add enabled class descriptors
146+
// Track current interface number for dynamic assignment
147+
uint8_t itf_num = 0;
148+
149+
// Add enabled class descriptors with dynamically calculated interface numbers
147150
#if CFG_TUD_CDC
148151
if (flags & USB_BUILTIN_FLAG_CDC) {
149152
const uint8_t cdc_desc[] = {
150-
TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD,
153+
TUD_CDC_DESCRIPTOR(itf_num, USBD_STR_CDC, USBD_CDC_EP_CMD,
151154
USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE)
152155
};
153156
memcpy(desc, cdc_desc, sizeof(cdc_desc));
154157
desc += sizeof(cdc_desc);
158+
itf_num += 2; // CDC uses 2 interfaces
155159
}
156160
#endif
157161

158162
#if CFG_TUD_MSC
159163
if (flags & USB_BUILTIN_FLAG_MSC) {
160164
const uint8_t msc_desc[] = {
161-
TUD_MSC_DESCRIPTOR(USBD_ITF_MSC, USBD_STR_MSC, USBD_MSC_EP_OUT, USBD_MSC_EP_IN, USBD_MSC_IN_OUT_MAX_SIZE)
165+
TUD_MSC_DESCRIPTOR(itf_num, USBD_STR_MSC, USBD_MSC_EP_OUT, USBD_MSC_EP_IN, USBD_MSC_IN_OUT_MAX_SIZE)
162166
};
163167
memcpy(desc, msc_desc, sizeof(msc_desc));
164168
desc += sizeof(msc_desc);
169+
itf_num += 1; // MSC uses 1 interface
165170
}
166171
#endif
167172

168173
#if CFG_TUD_NCM
169174
if (flags & USB_BUILTIN_FLAG_NCM) {
170175
const uint8_t ncm_desc[] = {
171-
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)
176+
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)
172177
};
173178
memcpy(desc, ncm_desc, sizeof(ncm_desc));
174179
desc += sizeof(ncm_desc);
180+
itf_num += 2; // NCM uses 2 interfaces (control + data)
175181
}
176182
#endif
177183

@@ -353,6 +359,23 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
353359
#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
354360

355361
const uint8_t *tud_descriptor_device_cb(void) {
362+
// Check if custom VID/PID is set (0 means use default)
363+
if (MP_STATE_VM(usbd) != MP_OBJ_NULL) {
364+
mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
365+
if (usbd->custom_vid != 0 || usbd->custom_pid != 0) {
366+
// Patch the default descriptor with custom VID/PID values.
367+
// We override whichever fields are non-zero (0 = keep default).
368+
static tusb_desc_device_t custom_desc;
369+
custom_desc = mp_usbd_builtin_desc_dev; // Copy default
370+
if (usbd->custom_vid != 0) {
371+
custom_desc.idVendor = usbd->custom_vid;
372+
}
373+
if (usbd->custom_pid != 0) {
374+
custom_desc.idProduct = usbd->custom_pid;
375+
}
376+
return (const uint8_t*)&custom_desc;
377+
}
378+
}
356379
return (const void *)&mp_usbd_builtin_desc_dev;
357380
}
358381

shared/tinyusb/mp_usbd_runtime.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,23 @@ const uint8_t *tud_descriptor_device_cb(void) {
111111
if (usbd) {
112112
result = usbd_get_buffer_in_cb(usbd->desc_dev, MP_BUFFER_READ);
113113
}
114-
return result ? result : &mp_usbd_builtin_desc_dev;
114+
115+
// If no custom descriptor, check for custom VID/PID (0 means use default)
116+
if (!result && usbd && (usbd->custom_vid != 0 || usbd->custom_pid != 0)) {
117+
// Patch the default descriptor with custom VID/PID values.
118+
// We override whichever fields are non-zero (0 = keep default).
119+
static tusb_desc_device_t custom_desc;
120+
custom_desc = mp_usbd_builtin_desc_dev; // Copy default
121+
if (usbd->custom_vid != 0) {
122+
custom_desc.idVendor = usbd->custom_vid;
123+
}
124+
if (usbd->custom_pid != 0) {
125+
custom_desc.idProduct = usbd->custom_pid;
126+
}
127+
return (const uint8_t*)&custom_desc;
128+
}
129+
130+
return result ? result : (const uint8_t*)&mp_usbd_builtin_desc_dev;
115131
}
116132

117133
const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {

0 commit comments

Comments
 (0)