Skip to content

stm32/tinyusb: Add High-Speed USB support and fix CDC/VBUS issues.#18933

Merged
dpgeorge merged 5 commits into
micropython:masterfrom
andrewleech:stm32_tinyusb_hs_clean
May 7, 2026
Merged

stm32/tinyusb: Add High-Speed USB support and fix CDC/VBUS issues.#18933
dpgeorge merged 5 commits into
micropython:masterfrom
andrewleech:stm32_tinyusb_hs_clean

Conversation

@andrewleech
Copy link
Copy Markdown
Contributor

@andrewleech andrewleech commented Mar 16, 2026

Summary

The STM32 port's TinyUSB integration only supports the FS controller. Boards with a HS controller (PYBD_SF6, STM32F429DISC, OLIMEX_H407, or ULPI-based designs) cannot use TinyUSB — the RHPORT configuration is hardcoded to RHPORT 0 / full-speed only.

This PR adds proper RHPORT mode selection in tusb_config.h based on MICROPY_HW_USB_HS and MICROPY_HW_USB_HS_IN_FS board config, routes HS IRQ handlers to the correct RHPORT (including STM32N6 which maps HS to RHPORT 0), and adds VBUS sensing configuration for the HS-in-FS path on STM32F4/F7 (mirroring the existing FS path, with support for both VBDEN and legacy NOVBUSSENS register variants).

Additional issues found and fixed during testing (each is a separate commit and can be split into its own PR if preferred):

  • CDC reconnect stall: When a host closes and reopens the CDC serial port (e.g. repeated mpremote connect/disconnect cycles), the IN endpoint can remain stalled from a prior runtime USB disconnect. The device becomes unresponsive until reset. Fixed by clearing endpoint stall on DTR high. Additionally, on DTR low (host close) the TX FIFO is now flushed so stale data from a previous session does not accumulate and block writes on the next connection.

  • DFU bootloader serial number mismatch: The TinyUSB serial descriptor uses a raw hex dump of all 12 UID bytes (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 causes the device to report a different serial number depending on which USB stack is active, breaking tools that identify devices by serial (udev rules, mpremote, dfu-util). Fixed by using the ST DFU bootloader algorithm.

  • TinyUSB-specific boot.py template: The factory reset boot.py template references pyb.usb_mode() which does not exist on the TinyUSB stack. A TinyUSB-specific template is added that uses machine.USBDevice with BUILTIN_DEFAULT.

Testing

Built with CFLAGS_EXTRA=-DMICROPY_HW_TINYUSB_STACK=1 for:

  • PYBD_SF6 (F7, HS-in-FS) — flashed and verified CDC serial, machine.USBDevice API, filesystem access
  • OPENMV_N6 (N6, HS-in-FS) — flashed and verified CDC serial, machine.USBDevice API, filesystem access
  • STM32F429DISC (F4, HS-in-FS) — build only
  • OLIMEX_H407 (F4, HS-in-FS) — build only
  • NUCLEO_H563ZI (H5, FS only) — build only, regression check

ULPI boards (STM32H747I_DISCO) are tested on hardware as part of the original development in #18303 — this branch is split out from that work.

CDC serial throughput (OPENMV_N6, HS link at 480 Mbit/s)

Compared TinyUSB vs legacy USB stack using tests/serial_test.py methodology.

Read (device → host):

bufsize TinyUSB KB/s Legacy KB/s Ratio
256 5198 816 6.4x
512 5490 817 6.7x
1024 6050 811 7.5x
2048 7560 817 9.3x
4096 9448 811 11.6x
8192 8422 815 10.3x
16384 8086 787 10.3x

Write (host → device):

bufsize TinyUSB KB/s Legacy KB/s Ratio
256 186 464 0.4x
512 218 592 0.4x
1024 193 671 0.3x
2048 185 733 0.3x
4096 184 763 0.2x
8192 178 786 0.2x
16384 179 800 0.2x

TinyUSB read is 6-12x faster. Write is 2.5-4.5x slower due to a pre-existing limitation in the TinyUSB CDC stdin path — mp_hal_stdin_rx_chr() reads byte-by-byte through a 512-byte ringbuffer, causing backpressure on the USB OUT endpoint. This affects all TinyUSB ports, not just HS, and is being tracked separately.

Trade-offs and Alternatives

The RHPORT configuration uses #ifndef guards so boards like STM32N6 that pre-define CFG_TUSB_RHPORT0_MODE in mpconfigboard_common.h pass through unchanged. This avoids needing per-family #if chains but means any future HS-capable family (e.g. STM32U5) needs its own board-level override.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 16, 2026

Code size report:

Reference:  docs/develop/porting: Update session log for example port. [9f396bb]
Comparison: stm32/extmod: Use full path for shared/tinyusb/mp_usbd.h include. [merge of f6345b7]
  mpy-cross:    +0 +0.000% 
   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
      esp32:    +0 +0.000% ESP32_GENERIC
     mimxrt:   +48 +0.012% TEENSY40
        rp2:   +32 +0.003% RPI_PICO_W
       samd:   +28 +0.010% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.47%. Comparing base (72222a6) to head (d2e0d20).
⚠️ Report is 25 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #18933   +/-   ##
=======================================
  Coverage   98.47%   98.47%           
=======================================
  Files         176      176           
  Lines       22831    22831           
=======================================
  Hits        22483    22483           
  Misses        348      348           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kwagyeman
Copy link
Copy Markdown
Contributor

That 4KB buffer size offers great performance.

@andrewleech
Copy link
Copy Markdown
Contributor Author

@kwagyeman yeah initially I thought there was something really wrong with my tests showing it as way too fast for a board without the ULPI - I didn't actually realize the N6 has HS built in! maybe it's not so bad once you figure out how to program it...
I've started a follow up branch addressing the stdin speed issue but there's more refinement / cleanup needed there.

@projectgus projectgus self-requested a review March 26, 2026 03:04
@andrewleech andrewleech force-pushed the stm32_tinyusb_hs_clean branch from 4c3e95f to 1e59002 Compare March 27, 2026 19:35
@kwagyeman kwagyeman moved this to Backlog in OpenMV Features Apr 9, 2026
@kwagyeman
Copy link
Copy Markdown
Contributor

@andrewleech - I added this to our work queue list.

@andrewleech
Copy link
Copy Markdown
Contributor Author

I ended up with some "general tinyusb correctness" commits in here too that aren't strictly related to HS support, I can split them into separate PR's if preferred though testing worked better with them altogether

Copy link
Copy Markdown
Member

@dpgeorge dpgeorge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, thanks!

Just a comment about putting the config elsewhere in stm32.

Comment thread shared/tinyusb/tusb_config.h Outdated

#if !defined(CFG_TUSB_RHPORT0_MODE) && !defined(CFG_TUSB_RHPORT1_MODE)
#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE)
// Configure RHPORT modes based on board USB PHY configuration.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is highly stm32 port specific. I think the way it should be handled is how alif and nrf ports do it: they have a custom tusb_config.h file that includes this common one, and overrides/adds additional config options as needed by the port.

To make that work, I think you need to remove the INC += -I$(TOP)/shared/tinyusb/ from stm32's Makefile.

Maybe put the new file in as ports/stm32/tinyusb_port/tusb_config.h following alif, then add INC += -Itinyusb_port to the Makefile.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - moved to ports/stm32/tinyusb_port/tusb_config.h following the alif pattern. The shared file is restored to its original default.

@iabdalkader
Copy link
Copy Markdown
Contributor

iabdalkader commented Apr 16, 2026

FWIW I've been seeing plenty of errors on MacOS/M4, which eventually lead to a bus reset, which in turn leads to an assert in TinyUSB (due to race condition, I think). I was trying to figure out if it was our firmware causing this when I saw this PR. I could cherry-pick this and see if any fixes here fix the issue. Errors look like:

  # Endpoint 0x82 aborts leading up to the crash
  17:29:29.849  endpoint 0x82: aborting 16 requests
  17:29:32.711  endpoint 0x82: aborting 16 requests
  17:29:33.022  endpoint 0x82: aborting 16 requests

  # Then 0x81 transaction errors fire ~70 times in 130ms
  17:30:05.083  endpoint 0x81: status 0xe00002ed (transaction error): 0 bytes
  17:30:05.084  endpoint 0x81: status 0xe00002ed (transaction error): 0 bytes
  17:30:05.084  endpoint 0x81: status 0xe00002ed (transaction error): 0 bytes
    ... (~70 more, every ~1ms) ...
  17:30:05.131  endpoint 0x81: status 0xe00002ed (transaction error): 0 bytes

  # Host gives up
  17:30:05.132  hardware connection lost
  17:30:05.140  detected termination of interfaceNub
  17:30:05.143  Unmounting /Volumes/PYBFLASH

This might have been a hub issue, I tested again yesterday and didn't see any errors.

@andrewleech andrewleech force-pushed the stm32_tinyusb_hs_clean branch from 1e59002 to a60b5c5 Compare April 17, 2026 21:35
Comment thread ports/stm32/tinyusb_port/tusb_config.h Outdated
* The MIT License (MIT)
*
* Copyright (c) 2022 Blake W. Garner
* Copyright (c) 2022 Ibrahim Abdelkader <iabdalkader@openmv.io>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who is Blake Garner??

I think this copyright can just be your name.

Copy link
Copy Markdown
Contributor Author

@andrewleech andrewleech Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, sorry about that. The AI tool fabricated the name rather than looking up the actual author. Copyright is now correct.

I've got a "private review" workflow for code reviewing via draft prs on my fork before pushing upstream which has helped me catch most of these weird issues, but follow up changes on an existing PR don't have that workflow. I'll have to figure out a better phone-based review workflow sorry.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Andrew's secret alias, revealed!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I engineered a solution for myself: https://github.com/andrewleech/serve-review

Comment thread ports/stm32/Makefile Outdated
Comment thread ports/stm32/stm32_it.c Outdated
IRQ_ENTER(USB1_OTG_HS_IRQn);
#if MICROPY_HW_TINYUSB_STACK
tud_int_handler(0);
tud_int_handler(TUD_OPT_RHPORT);
Copy link
Copy Markdown
Contributor

@iabdalkader iabdalkader Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file is missing USB2_OTG_HS_IRQHandler *for N6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this should just be:

void USB1_OTG_HS_IRQHandler(void) {
    #if MICROPY_HW_TINYUSB_STACK
    tud_int_handler(0);

void USB2_OTG_HS_IRQHandler(void) {
    #if MICROPY_HW_TINYUSB_STACK
    tud_int_handler(1);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added USB2_OTG_HS_IRQHandler with hardcoded rhport 1, and changed USB1 to hardcoded 0. Also kept TUD_OPT_RHPORT for the non-N6 OTG_HS_IRQHandler since F4/F7/H7 only have one HS controller.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated - I've removed USB2_OTG_HS_IRQHandler again on reflection. USB2_OTG_HS_IRQn is never enabled in usbd_conf.c for N6 and TinyUSB is configured with RHPORT0, so dispatching to tud_int_handler(1) would hit an uninitialised port. If N6 boards need USB2 with TinyUSB then usbd_conf.c would need to initialise RHPORT1 too, at which point the handler belongs alongside that work. Happy to re-add it if there's a specific N6 config I'm missing.

@andrewleech andrewleech force-pushed the stm32_tinyusb_hs_clean branch 2 times, most recently from 105a90a to 2fae783 Compare April 20, 2026 09:01
Copy link
Copy Markdown
Contributor

@projectgus projectgus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @andrewleech,

Thanks for implementing this! I have some minor comments, but overall it looks good to me.

Some of the USB class drivers may need some additional optimisation now we're up against 480Mbps line rates. 😁

Comment thread ports/stm32/tinyusb_port/tusb_config.h Outdated
Comment thread ports/stm32/tinyusb_port/tusb_config.h
Comment thread ports/stm32/Makefile Outdated
@andrewleech andrewleech force-pushed the stm32_tinyusb_hs_clean branch 2 times, most recently from 2e5b8dc to f6345b7 Compare May 2, 2026 09:48
Copy link
Copy Markdown
Contributor

@projectgus projectgus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good to me, thanks @andrewleech!

@projectgus projectgus added this to the release-1.29.0 milestone May 6, 2026
@dpgeorge dpgeorge moved this from Backlog to In progress in OpenMV Features May 7, 2026
pi-anl added 5 commits May 8, 2026 02:19
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.

Thi STM32F4/F7/H7 high-speed RHPORT mode selection is placed in
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 <andrew.leech@planetinnovation.com.au>
Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
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 <andrew.leech@planetinnovation.com.au>
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 <andrew.leech@planetinnovation.com.au>
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 <andrew.leech@planetinnovation.com.au>
@dpgeorge dpgeorge force-pushed the stm32_tinyusb_hs_clean branch from f6345b7 to d2e0d20 Compare May 7, 2026 16:23
@dpgeorge dpgeorge merged commit d2e0d20 into micropython:master May 7, 2026
69 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Done in OpenMV Features May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants