Skip to content

esp32: build TinyUSB 0.21 from the component registry (patch 0014) - #21

Merged
bdbarnett merged 2 commits into
mainfrom
esp32-tinyusb-0.21
Sep 26, 2026
Merged

bdbarnett merged 2 commits into
mainfrom
esp32-tinyusb-0.21

Conversation

@bdbarnett

@bdbarnett bdbarnett commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Adds patch 0014, which gives the ESP32-P4 TinyUSB 0.21 and leaves every other esp32 target where it was. The P4 takes MicroPython's own v0.21.0.1-micropython1 tag. The S2 and S3 keep the cherrypick/dwc2_zlp_fix pin (0.18 plus one fix), and the targets without USB OTG still get no TinyUSB. It's the TinyUSB half of usbif#39; the usbif half is usbif#47. Merge this one first.

Why

On 0.18, the audio class re-armed the sound card's isochronous OUT endpoint from tud_task(), and on this port that runs in the MicroPython task. A busy interpreter left the endpoint unarmed, and the packets that arrived meanwhile were lost: 91 to 95 % delivered, heard as a steady tick under the music. From 0.19 the driver finishes each packet and re-arms in the USB interrupt.

Why the P4 only

espressif/tinyusb 0.21.0~2 resets an ESP32-S3 (the T-Embed, full speed) the moment the host opens its CDC port: an interrupt watchdog in the DWC2 IN-endpoint handler. The S3 stays on 0.18 until that is settled, per Brad's call. usbif#48 has the dump, the bisection and a draft report for espressif/tinyusb.

The bisection found something worth knowing before you merge. The crash is gone on MicroPython's own tag, which is 0.21.0~1 plus one commit, the fix for hathach/tinyusb#3807 ("don't flush pending data in cdcd_open"). On that tag the T-Embed opened its port and ran the sound card at 100.00 % with the interpreter busy. Moving the S2 and S3 as well is one line here plus the S3's lockfile entry. It's a separate decision, and #48 tracks it.

How the split works

idf_component.yml keeps one source for espressif/tinyusb, micropython/tinyusb-espressif, and chooses a ref per target with the component manager's matches list. The first if that matches picks the version, and a target that matches none skips the dependency, as rules did before.

It has to be one source, because a dependency can't take its source from a condition. matches sets the version only. That's why the P4 takes MicroPython's tag in the fork the port already uses, not the registry's 0.21.02. Against 0.21.02 the tag lacks only a DWC2 periodic-transfer option that is off by default and an NCM divisor change, and neither is used here. It adds the #3807 fix.

The P4's lockfile is part of the patch, and only its TinyUSB entry moves. The component manager re-solves when the manifest changes, but it keeps a locked git commit while the source is unchanged. Without the lockfile change the P4 would quietly go on building 0.18. That did happen on the first try: the build log said espressif/tinyusb (e4c0ec3…) for the P4. With the patched lockfile it says 5c5c660…. The S3 build says e4c0ec3… and links usbif's tud_audio_rx_done_post_read_cb, and the P4 build links tud_audio_rx_done_isr.

Why not patch the old driver

Patching 0.18's audio driver instead would mean backporting the ISR path, which touches usbd.c's event dispatch, the class driver table and the audio driver. A managed component is downloaded at build time, so the overlay would have to vendor a modified copy of TinyUSB. That's a fork, which is what the overlay exists to avoid.

What else changes

  • prepare-micropython.sh applies the new esp32-tinyusb profile, and the READMEs and provenance list it.
  • A behaviour change is written into the patch header. 0.21's DWC2 driver arms an isochronous OUT endpoint for the next microframe whatever its interval, so a high-speed OUT endpoint at bInterval > 1 receives nothing. usbif hit this at bInterval 4 and goes back to 1.

Tested

Both boards ran images built from this branch (overlay 63b427fc0) with usbif#47, against Windows WASAPI, 48 kHz shared, a quiet 440 Hz tone, 10 s windows.

board TinyUSB load delivered
P4 panel (high speed) v0.21.0.1-micropython1 busy 100.00 % (8,000.2 to 8,000.3 packets/s), 8 windows after stream start; pitch at I2S 440.0
T-Embed (S3, full speed) 0.18 pin idle 99.91 to 99.93 % (999.1 to 999.3 packets/s), 8 windows
T-Embed 0.18 pin busy 87.4 to 93.9 %, every window below the 99.5 % gate: #39, as expected on 0.18

On the T-Embed's 0.18 image the CDC port opens and the REPL answers. The P4 is back on its own /main.py, as the sound card.

These were measured earlier on the P4 with the registry's 0.21.0~2, which differs from the tag as described above:

delivered pitch at I2S (440 Hz sent)
0.18, interpreter busy, 48 kHz shared 90.6 to 95.5 % per 10 s window 441 to 576 Hz, unstable
0.21.0~2, interpreter busy, 48 kHz shared 100.00 % (8,000 packets/s), 10 windows 440.0 / 439.8
0.21.0~2, idle, 48 kHz shared 99.92 to 100.00 % 438.5 to 446.7 (shared-mode mixer)
0.21.0~2, interpreter busy, 44.1 kHz exclusive 99.89 to 100.00 % 440.0 ×3
0.21.0~2, interpreter busy, 48 kHz exclusive, 48 kHz wire 100.00 % after the stream-start window (99.46) 440.0 ×3

With 0.21.0~2, all 63 usbif costumes enumerated and mounted, the CDC REPL worked on the sound card's cable while it streamed, and MSC read back a FAT RAM disk. Those weren't repeated on the tag.

Not tested: ESP32-S2, and S3 boards other than the T-Embed.

The esp32 port pins espressif/tinyusb to micropython's cherry-pick branch
(0.18 plus the DWC2 OUT ZLP fix). espressif/tinyusb 0.21.0~2 is released
with that fix, and from 0.19 the audio class re-arms its isochronous OUT
endpoint in the USB interrupt instead of in tud_task(), which on this port
waits for the interpreter (usbif#39).
@bdbarnett

Copy link
Copy Markdown
Contributor Author

Don't merge yet. The LILYGO T-Embed (ESP32-S3, full speed) doesn't survive TinyUSB 0.21.0~2. It enumerates, but the moment Windows opens its CDC port (pyserial, .NET, esptool alike), the board reboots. The coredump shows an interrupt watchdog on CPU1 inside dcd_int_handler → handle_ep_irq(dir=IN) (dcd_dwc2.c:1146). An IN endpoint interrupt keeps firing, which smells like a TX-FIFO-empty interrupt that is never cleared. The same image built against the old 0.18 pin works on the same board. The P4 (high speed) runs 0.21 fine. Before this can merge, it needs either a fix or workaround for the S3, or a pin that applies only to esp32p4.

…0.18

One git source with a ref per target through the component manager's
matches list: the P4 takes MicroPython's v0.21.0.1-micropython1 tag, the
S2 and S3 keep the cherrypick/dwc2_zlp_fix pin. The P4's lockfile entry
moves with it, because a locked git commit is kept while the source is
unchanged. espressif's 0.21.0~2 resets an ESP32-S3 when the host opens
the CDC port (usbif#48).
@bdbarnett

Copy link
Copy Markdown
Contributor Author

The S3 blocker above is answered by scoping: 0.21 now goes to the P4 only and the S2/S3 keep the 0.18 pin, per Brad. Both boards were re-proven on this branch; the description has the numbers. The S3 crash is usbif#48, bisected to the missing hathach/tinyusb#3808 fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant