From 1d9ea7f281d010954ff7071aa810afac91ee3ebe Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Oct 2025 14:34:37 +1100 Subject: [PATCH 1/6] stm32/tinyusb: Add High Speed USB controller support. Implements mapping from MICROPY_HW_USB_MAIN_DEV to TinyUSB RHPORT configuration, enabling board-specific USB PHY selection for TinyUSB stack. Adds support for HS-in-FS mode (High Speed controller running at Full Speed) which is the default for STM32 boards without external ULPI PHY. Includes additional TinyUSB source files for DWC2 host controller support. Signed-off-by: Andrew Leech --- ports/stm32/stm32_it.c | 2 +- shared/tinyusb/tusb_config.h | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ports/stm32/stm32_it.c b/ports/stm32/stm32_it.c index 3ae1980f8feb7..f528314a5e096 100644 --- a/ports/stm32/stm32_it.c +++ b/ports/stm32/stm32_it.c @@ -384,7 +384,7 @@ void USB1_OTG_HS_IRQHandler(void) { void OTG_HS_IRQHandler(void) { IRQ_ENTER(OTG_HS_IRQn); #if MICROPY_HW_TINYUSB_STACK - tud_int_handler(0); + tud_int_handler(1); // OTG_HS is always RHPORT1 on F4/F7/H7 (not N6, which uses USB1_OTG_HS_IRQHandler) #else HAL_PCD_IRQHandler(&pcd_hs_handle); #endif diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h index 8abd021548d5d..7aa12fcf89625 100644 --- a/shared/tinyusb/tusb_config.h +++ b/shared/tinyusb/tusb_config.h @@ -59,9 +59,21 @@ #define MICROPY_HW_USB_MSC_INQUIRY_REVISION_STRING "1.00" #endif -#if !defined(CFG_TUSB_RHPORT0_MODE) && !defined(CFG_TUSB_RHPORT1_MODE) +#ifndef CFG_TUSB_RHPORT0_MODE +#ifdef MICROPY_HW_TINYUSB_RHPORT0_MODE +#define CFG_TUSB_RHPORT0_MODE MICROPY_HW_TINYUSB_RHPORT0_MODE +#else #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE) #endif +#endif + +#ifndef CFG_TUSB_RHPORT1_MODE +#ifdef MICROPY_HW_TINYUSB_RHPORT1_MODE +#define CFG_TUSB_RHPORT1_MODE MICROPY_HW_TINYUSB_RHPORT1_MODE +#else +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_NONE) +#endif +#endif #if MICROPY_HW_USB_CDC #define CFG_TUD_CDC (1) From 52231c789af70dc27a67e6c0587d73c0bab551d6 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 19 Oct 2025 19:39:33 +1100 Subject: [PATCH 2/6] stm32/tinyusb: Add port-specific tusb_config.h for RHPORT configuration. Moves STM32F4/F7/H7 high-speed RHPORT mode selection out of the shared tusb_config.h and into ports/stm32/tinyusb_port/tusb_config.h, following the alif/nrf pattern. Includes py/mpconfig.h to ensure board config macros are available when TinyUSB processes the header. Signed-off-by: Andrew Leech --- ports/stm32/Makefile | 1 + ports/stm32/tinyusb_port/tusb_config.h | 51 ++++++++++++++++++++++++++ shared/tinyusb/tusb_config.h | 14 +------ 3 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 ports/stm32/tinyusb_port/tusb_config.h diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 63ed974b9b551..ae78649041f3c 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -111,6 +111,7 @@ INC += -I$(STM32LIB_HAL_ABS)/Inc INC += -I$(USBDEV_DIR)/core/inc -I$(USBDEV_DIR)/class/inc #INC += -I$(USBHOST_DIR) INC += -I$(TOP)/lib/tinyusb/src +INC += -Itinyusb_port INC += -I$(TOP)/shared/tinyusb/ INC += -Ilwip_inc diff --git a/ports/stm32/tinyusb_port/tusb_config.h b/ports/stm32/tinyusb_port/tusb_config.h new file mode 100644 index 0000000000000..4d74eec9b37b5 --- /dev/null +++ b/ports/stm32/tinyusb_port/tusb_config.h @@ -0,0 +1,51 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2026 Andrew Leech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_STM32_TINYUSB_PORT_TUSB_CONFIG_H +#define MICROPY_INCLUDED_STM32_TINYUSB_PORT_TUSB_CONFIG_H + +#include "py/mpconfig.h" + +// STM32F4/F7/H7 boards with USB_HS use OTG_HS on RHPORT1, not RHPORT0. +// Disable RHPORT0 and put RHPORT1 in device mode (HS, or FS when the +// HS controller uses the internal FS PHY via MICROPY_HW_USB_HS_IN_FS). +// Other configs are handled either by the board (e.g. N6 sets RHPORT0 +// to HS in mpconfigboard_common.h) or by the shared default in +// shared/tinyusb/tusb_config.h (RHPORT0 in FS device mode). + +// These families place OTG_HS on RHPORT1. Extend the list if a new family +// also uses OTG_HS on RHPORT1 rather than RHPORT0. +#if MICROPY_HW_USB_HS && (defined(STM32F4) || defined(STM32F7) || defined(STM32H7)) +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_NONE) +#if MICROPY_HW_USB_HS_IN_FS +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED) +#else +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#endif +#endif + +#include "shared/tinyusb/tusb_config.h" + +#endif // MICROPY_INCLUDED_STM32_TINYUSB_PORT_TUSB_CONFIG_H diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h index 7aa12fcf89625..8abd021548d5d 100644 --- a/shared/tinyusb/tusb_config.h +++ b/shared/tinyusb/tusb_config.h @@ -59,21 +59,9 @@ #define MICROPY_HW_USB_MSC_INQUIRY_REVISION_STRING "1.00" #endif -#ifndef CFG_TUSB_RHPORT0_MODE -#ifdef MICROPY_HW_TINYUSB_RHPORT0_MODE -#define CFG_TUSB_RHPORT0_MODE MICROPY_HW_TINYUSB_RHPORT0_MODE -#else +#if !defined(CFG_TUSB_RHPORT0_MODE) && !defined(CFG_TUSB_RHPORT1_MODE) #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE) #endif -#endif - -#ifndef CFG_TUSB_RHPORT1_MODE -#ifdef MICROPY_HW_TINYUSB_RHPORT1_MODE -#define CFG_TUSB_RHPORT1_MODE MICROPY_HW_TINYUSB_RHPORT1_MODE -#else -#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_NONE) -#endif -#endif #if MICROPY_HW_USB_CDC #define CFG_TUD_CDC (1) From 2d55d1d89cf9cc492dd690a7f5030e3dc5a7b339 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 28 Oct 2025 14:30:25 +1100 Subject: [PATCH 3/6] stm32/factoryreset: Add TinyUSB-specific boot.py examples. Signed-off-by: Andrew Leech --- ports/stm32/factoryreset.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/stm32/factoryreset.c b/ports/stm32/factoryreset.c index 50854a908b18e..1811ca1186f3f 100644 --- a/ports/stm32/factoryreset.c +++ b/ports/stm32/factoryreset.c @@ -44,9 +44,15 @@ static const char fresh_boot_py[] = "import pyb\r\n" "#pyb.main('main.py') # main script to run after this one\r\n" #if MICROPY_HW_ENABLE_USB +#if MICROPY_HW_TINYUSB_STACK + "#usb = machine.USBDevice()\r\n" + "#usb.builtin_driver = machine.USBDevice.BUILTIN_DEFAULT # CDC + MSC\r\n" + "#usb.active(True)\r\n" +#else "#pyb.usb_mode('VCP+MSC') # act as a serial and a storage device\r\n" "#pyb.usb_mode('VCP+HID') # act as a serial device and a mouse\r\n" #endif +#endif #if MICROPY_PY_NETWORK "#import network\r\n" "#network.country('US') # ISO 3166-1 Alpha-2 code, eg US, GB, DE, AU or XX for worldwide\r\n" From d8250363bf9f6610749f51e7b4da2cfd0e19fd20 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sat, 7 Mar 2026 07:46:16 +1100 Subject: [PATCH 4/6] shared/tinyusb: Fix CDC reconnect stall and TX FIFO. When a host closes and reopens the CDC serial port, the IN endpoint may remain stalled from a prior runtime USB disconnect (e.g. mpremote connect/disconnect cycles). Clear the stall on DTR high so the connection recovers without requiring a device reset. On DTR low (host close), flush the TX FIFO so stale data does not accumulate and block writes on the next connection. Signed-off-by: Andrew Leech --- shared/tinyusb/mp_usbd_cdc.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/shared/tinyusb/mp_usbd_cdc.c b/shared/tinyusb/mp_usbd_cdc.c index 961bdf89896ff..8018032d36cbc 100644 --- a/shared/tinyusb/mp_usbd_cdc.c +++ b/shared/tinyusb/mp_usbd_cdc.c @@ -34,6 +34,11 @@ #if MICROPY_HW_USB_CDC && MICROPY_HW_ENABLE_USBDEV && !MICROPY_EXCLUDE_SHARED_TINYUSB_USBD_CDC +// TinyUSB has no public API for endpoint stall detection/clearing; this +// private header is the intended interface for class drivers (all built-in +// TinyUSB class drivers include it for the same purpose). +#include "device/usbd_pvt.h" + static uint8_t cdc_itf_pending; // keep track of cdc interfaces which need attention to poll static int8_t cdc_connected_flush_delay = 0; @@ -176,10 +181,18 @@ void MICROPY_WRAP_TUD_CDC_LINE_STATE_CB(tud_cdc_line_state_cb)(uint8_t itf, bool #if MICROPY_HW_USB_CDC && !MICROPY_EXCLUDE_SHARED_TINYUSB_USBD_CDC if (dtr) { // A host application has started to open the cdc serial port. + // USBD_CDC_EP_IN is the IN endpoint for itf 0; only clear stall for itf 0. + if (itf == 0 && usbd_edpt_stalled(TUD_OPT_RHPORT, USBD_CDC_EP_IN)) { + usbd_edpt_clear_stall(TUD_OPT_RHPORT, USBD_CDC_EP_IN); + } // Wait a few ms for host to be ready then send tx buffer. // High speed connection SOF fires at 125us, full speed at 1ms. cdc_connected_flush_delay = (tud_speed_get() == TUSB_SPEED_HIGH) ? 128 : 16; tud_sof_cb_enable(true); + } else { + // Host has closed the cdc serial port. Discard pending TX data to + // avoid a full FIFO blocking writes on the next connection. + tud_cdc_n_write_clear(itf); } #endif #if MICROPY_HW_USB_CDC_DTR_RTS_BOOTLOADER From 2eded685f9bbf94024cb710041e973dbd317d151 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 10 Mar 2026 21:33:01 +1100 Subject: [PATCH 5/6] stm32: Match ST DFU bootloader serial number in TinyUSB. The TinyUSB serial descriptor used a raw hex dump of all 12 UID bytes in sequential order (24-char lowercase), while the legacy USB stack and ST's onboard DFU bootloader use a condensed algorithm that selects 6 bytes with two additions (12-char uppercase). This mismatch caused the device to report a different serial number depending on which USB stack was active, breaking tools that identify devices by serial (e.g. udev rules, mpremote, dfu-util). Use the ST DFU bootloader algorithm for consistency. Signed-off-by: Andrew Leech --- ports/stm32/usbd.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ports/stm32/usbd.c b/ports/stm32/usbd.c index 35275cd1bd02e..fec87c90147a7 100644 --- a/ports/stm32/usbd.c +++ b/ports/stm32/usbd.c @@ -28,15 +28,25 @@ #if MICROPY_HW_ENABLE_USBDEV && MICROPY_HW_TINYUSB_STACK +#include #include "mp_usbd.h" -#include "py/mpconfig.h" -#include "string.h" #include "mphalport.h" void mp_usbd_port_get_serial_number(char *serial_buf) { + // Use the same algorithm as the ST DFU bootloader so that the serial + // number is consistent across all USB modes. + MP_STATIC_ASSERT(12 <= MICROPY_HW_USB_DESC_STR_MAX); // 6 derived bytes x 2 hex digits + NUL + static const char hexdig[] = "0123456789ABCDEF"; uint8_t *id = (uint8_t *)MP_HAL_UNIQUE_ID_ADDRESS; - MP_STATIC_ASSERT(12 * 2 <= MICROPY_HW_USB_DESC_STR_MAX); - mp_usbd_hex_str(serial_buf, id, 12); + uint8_t bytes[] = { + id[11], (uint8_t)(id[10] + id[2]), id[9], + (uint8_t)(id[8] + id[0]), id[7], id[6], + }; + for (int i = 0; i < 6; i++) { + serial_buf[i * 2] = hexdig[bytes[i] >> 4]; + serial_buf[i * 2 + 1] = hexdig[bytes[i] & 0x0f]; + } + serial_buf[12] = '\0'; } #endif From f6345b7e961f68ad61b6ee59b241a9473e68331c Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 30 Apr 2026 20:26:30 +1000 Subject: [PATCH 6/6] stm32/extmod: Use full path for shared/tinyusb/mp_usbd.h include. Removes the need for -I$(TOP)/shared/tinyusb/ in the stm32 Makefile by using an explicit path in the two files that include mp_usbd.h outside of the shared/tinyusb/ directory itself. Signed-off-by: Andrew Leech --- extmod/machine_usb_device.c | 2 +- ports/stm32/Makefile | 1 - ports/stm32/usbd.c | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/extmod/machine_usb_device.c b/extmod/machine_usb_device.c index f6e97f42054d1..e8303ef84f769 100644 --- a/extmod/machine_usb_device.c +++ b/extmod/machine_usb_device.c @@ -28,7 +28,7 @@ #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE -#include "mp_usbd.h" +#include "shared/tinyusb/mp_usbd.h" #include "py/mperrno.h" #include "py/objstr.h" diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index ae78649041f3c..7597d23d0a3e9 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -112,7 +112,6 @@ INC += -I$(USBDEV_DIR)/core/inc -I$(USBDEV_DIR)/class/inc #INC += -I$(USBHOST_DIR) INC += -I$(TOP)/lib/tinyusb/src INC += -Itinyusb_port -INC += -I$(TOP)/shared/tinyusb/ INC += -Ilwip_inc CFLAGS += $(INC) -Wall -Wpointer-arith -Werror -Wdouble-promotion -Wfloat-conversion -std=gnu99 $(CFLAGS_EXTRA) diff --git a/ports/stm32/usbd.c b/ports/stm32/usbd.c index fec87c90147a7..d8f8b3dd551c8 100644 --- a/ports/stm32/usbd.c +++ b/ports/stm32/usbd.c @@ -29,7 +29,7 @@ #if MICROPY_HW_ENABLE_USBDEV && MICROPY_HW_TINYUSB_STACK #include -#include "mp_usbd.h" +#include "shared/tinyusb/mp_usbd.h" #include "mphalport.h" void mp_usbd_port_get_serial_number(char *serial_buf) {