Cannot init USB devices in boot.py
I tested this on a pico running MP 1.23 and the latest release of usb-device-cdc.
boot.py:
import usb.device
from usb.device.cdc import CDCInterface
IFACE_TWO = CDCInterface()
IFACE_TWO.init(timeout=0)
#usb.device.get().init(IFACE_TWO, builtin_driver=True)
main.py:
from time import sleep_ms
def main():
while True:
print(IFACE_TWO)
sleep_ms(1000)
if __name__ == "__main__": main()
Running this as-is, IFACE_TWO does show up in main.py, but uncommenting the init function causes the process to error out. This discord post suggests you can call usb.device.get.init() in boot.py, is this no longer true?
esp32: Re-initialise USB after soft reset.
Summary
Necessary if runtime USBDevice has been active on ESP32-S3 along with the default USB-CDC REPL. Without this fix, two things happen after a soft reset:
- The USB host sees the device disappear and not come back.
- Inside MicroPython, the CDC driver thinks it's still connected and active but eventually the TX FIFO fills up and significantly slows down stdout (visible on boards with a separate UART REPL).
This also fixes the init order for mp_usbd_init() so that USB is initialised after boot.py completes, the same as on other ports like rp2. This allows setting up a custom USB device in boot.py without it enumerating twice.
Testing
Note this PR needs to be cherry-picked onto #18332 or #18407 if PSRAM is enabled.
mpremote a0 run pr18332.py- where the file has the sample code from #18332- If using the USB-CDC interface for
mpremote runthen mpremote will error out as it disconnects to change USB device config. mpremote a0- Use Ctrl-C and Ctrl-B to stop the sample code and drop to an interactive REPL.
- Type Ctrl-D to soft reset.
Without fix: USB-CDC disappears along with the keyboard device, never comes back. Connecting to the UART REPL and interacting with it will eventually slow UART TX down to a trickle (quickest way to trigger is print("a"*500).)
With fix: USB-CDC disappears and re-enumerates again as plain USB-CDC device.
Second test is to put some custom usb device init code in boot.py and check that initial enumerationand soft reset work as expected there. I used:
import usb.device
from usb.device.mouse import MouseInterface
m = MouseInterface()
usb.device.get().init(m, builtin_driver=True)
Related bugs
There is still a related bug here, because calling TinyUSB tud_disconnect() on ESP32-S3 doesn't trigger any DCD_EVENT_UNPLUGGED or DCD_EVENT_BUS_RESET event. This means that the USB device driver state goes out of sync (i.e. TinyUSB thinks it's still connected).
This PR avoids this in soft reset because the host will reconfigure the USB device and the state will correct itself, but there's still a problem if Python code does something like this:
mpremote a0 run pr18332.py- (errors out as expected)
mpremote a0- Ctrl-C, Ctrl-B to get interactive REPL.
import machine; u = machine.USBDevice(); u.active(0)
In this situation the USB device disappears from the host as expected, but the issue with very slow stdout will happen as the CDC device driver still thinks it's connected and configured.
I've opened a Discussion on the TinyUSB repo to try and find out the expected behaviour: https://github.com/hathach/tinyusb/discussions/3331
This work was funded through GitHub Sponsors.
Please give some more details about this means: what exactly do you do, and what error output do you see?
A soft reset (i.e. Ctrl-D in the REPL) will still disconnect the USB port each time, because the runtime USB interface objects have to all be freed and recreated.
A hard reset (i.e. plugging the rp2 in, or calling
machine.reset()) should come up immediately with the two CDC ports.The only difference between boot.py and main.py is that the first time the device boots from a hard reset, it runs boot.py before it initialises USB. If you put the code in main.py instead of boot.py then the rp2 would first try create a device using the default USB interface, then immediately switch to the custom USB interface. This can show up on the host as connect/disconnect/connect cycle, or as an error depending on the timing.
With the init line commented out in boot.py:
mpremote fs cp boot.py :mpremote run main.pyI see the print statements I expect:
With the init line uncommented in boot.py:
mpremote fs cp boot.py :mpremote run main.pyI do not see the print statements I expect:
I get dropped back to the terminal, and connecting with
mpremote a0yields no output. Attempting to replace boot.py even after unplugging/replugging the pico also yields the above OSError; I have to drop to a shell and useos.unlink().Ah snap,
mpremote rundoes a soft reset before it runs the provided file. Because boot.py has loaded the custom USB interface, it disconnects the USB port every time (then boot.py runs and adds it back, so you end up where you started.)If you copy both boot.py and main.py to the filesystem then main.py works and you can use
mpremote a0to view the output, but you still can't usempremote runany more.I think the fix for this will be the planned work to have mpremote detect when the USB port is in a disconnect/reconnect cycle, and recover from it instead of erroring out.
Is there a timeline for when the planned work might be finished?
Not at the moment, I'm afraid.
I assume this is impacting
mpremote connectas well (also including a soft reset)? Running similar as the OP above, I'm finding that I can connect directly to REPL by connecting to its COM port - but not usingmpremote connect.Not sure if anything has changed since the last update here - though revisiting this a year later, now on MicroPython v1.27 - everything seems to be working, and I'm not sure I can find a way to make it not?
I'm seeing success with both (separately) MicroPico 4.3.4 in VS Code, and mpremote 1.27.0.
Curious if anyone else here still watching can help test / validate? Thanks!
Though for reasons I don't yet fully understand, having
usb.device.get().init(...)included as part ofboot.pyinterferes with running "MicroPico: Run current file on Pico" (micropico.run) from MicroPico in VS Code.Disabling "Micropico: No Soft Reset On Run" (
micropico.noSoftResetOnRun) seems to then resolve this.Though then, and I suspect unrelated, I get into trouble if I try using Ctrl+C to quit a loop - same as reported in https://github.com/paulober/MicroPico/issues/294 - though pretty sure this is an issue with MicroPico and not micropython.