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 63ed974b9b551..7597d23d0a3e9 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -111,7 +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 += -I$(TOP)/shared/tinyusb/ +INC += -Itinyusb_port INC += -Ilwip_inc CFLAGS += $(INC) -Wall -Wpointer-arith -Werror -Wdouble-promotion -Wfloat-conversion -std=gnu99 $(CFLAGS_EXTRA) 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" 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/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/ports/stm32/usbd.c b/ports/stm32/usbd.c index 35275cd1bd02e..d8f8b3dd551c8 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 "mp_usbd.h" -#include "py/mpconfig.h" -#include "string.h" +#include +#include "shared/tinyusb/mp_usbd.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 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