rp2/PICO_COMPUTER_3: add USB mass-storage host (machine.USBDrive) - #1
Open
Bryan1club wants to merge 6 commits into
Open
rp2/PICO_COMPUTER_3: add USB mass-storage host (machine.USBDrive)#1Bryan1club wants to merge 6 commits into
Bryan1club wants to merge 6 commits into
Conversation
TinyUSB's host stack already covers HID keyboard/mouse/touch (mp_usbh.c) and CDC-serial (usb_cdc.c) for this board, but never enabled the MSC (mass-storage) host class, so a USB flash drive plugged into the host port was invisible to MicroPython even though TinyUSB ships tuh_msc already. - Enable CFG_TUH_MSC / CFG_TUH_MSC_MAXLUN in tusb_config.h. - usb_msc.[ch]: TinyUSB MSC-host glue -- mount/unmount tracking plus blocking read10/write10 wrappers (TinyUSB's are callback-based; these pump tuh_task() until the SCSI command completes or times out). - machine_usbdrive.c: machine.USBDrive, a block device mountable with vfs.VfsFat, structured like machine.SDCard. Unlike the SD card (a bare SPI card polled twice a second in pcsd.py for hot-swap), USB gives a real connect/disconnect event, so USBDrive exposes on_change(fn) instead of a poll. - pcusb.py (+ _boot_board.py/manifest.py wiring): mounts /usb the same way pcsd.py mounts /sd. - USER_MANUAL.md: document the new host-port capability and /usb mount. Tested by inspection against this branch's tinyusb submodule (pinned at 3af1bec) -- no local pico-sdk/arm-gcc toolchain was available to build and flash a UF2, so this hasn't been run on real hardware yet. Happy to adjust based on how it behaves once built.
…ID input Small transport-agnostic hooks so a Bluetooth LE HID-over-GATT keyboard/ mouse (or any other non-USB input source) can feed the exact same report decoders USB keyboards/mice already use: - keyboard.inject_report(data): feeds an 8-byte HID boot keyboard report into kbd_process_report(), which has no USB dependency at all. - mouse.inject_mount(desc) / inject_report(data) / inject_umount(): feed a HID report descriptor + reports into the existing descriptor-driven mouse decoder, using a fixed USB_MOUSE_BLE_ADDR sentinel so it's never confused with a real USB mouse. usb_mouse.c's two TinyUSB-only control transfers (set/get protocol) are skipped for that sentinel address, since a non-USB source has no TinyUSB device behind it. This is groundwork for a BLE HID keyboard/mouse module (aioble- or raw bluetooth-module-based) -- not included here. No behavior change for existing USB keyboards/mice.
mp_sched_schedule() always invokes its target with exactly one argument, but usb_msc.c's notify_change() scheduled pcusb.py's _on_change() (which took zero parameters) -- every mount/unmount raised "TypeError: function takes 0 positional arguments but 1 were given" from the scheduler, which pcusb.py's own try/except didn't catch (the crash was in the callback dispatch, not inside the try block), silently killing the auto-mount. Confirmed on real hardware: the MSC layer itself was fine the whole time (a real stick enumerated correctly -- "USB drive: mounted addr=3 lun0 60620800 x 512 bytes" -- confirmed via manual `vfs.mount(vfs.VfsFat(d), "/usb")` at the REPL) -- only the automatic on-connect mount was broken. Fix: notify_change() now takes and passes through a connected bool, like every other on_change/on_usb_event callback elsewhere in this port (usb_cdc.c, mp_usbh.c) already does. pcusb.py's _on_change(connected) updated to match; it still re-checks present() rather than trusting the argument, so a stale/coalesced event can't leave the mount state wrong.
Stream audio out to a Bluetooth speaker/headset. This is a different Bluetooth transport from the board's existing BLE support (aioble, keyboard/mouse hooks) -- A2DP is Classic Bluetooth (BR/EDR), which MicroPython's `bluetooth` module has zero bindings for on any port. btaudio.c is adapted directly from BTstack's own official a2dp_source_demo.c (already vendored in lib/btstack, and already fully wired up as pico-sdk's own pico_btstack_classic CMake target -- this board just never turned it on), trimmed to source-only (no AVRCP, no stream reconfiguration) and with the demo's synthetic sine/mod audio replaced by a plain ring buffer fed from Python via write(). CMakeLists.txt adds the specific classic-profile + SBC-encoder source files directly rather than linking pico_btstack_classic, because that library pulls in its own copy of hci.c/l2cap.c -- duplicating the ones micropy_extmod_btstack already compiles for BLE. Classic and LE share ONE BTstack/HCI instance by design; hci.c/l2cap.c just need ENABLE_CLASSIC=1 on their single compilation, which target_compile_definitions on the firmware target itself achieves. Python API: btaudio.scan()/on_found()/connect()/on_connect()/ disconnect()/connected()/sample_rate()/write()/writable(). Requires bluetooth.BLE().active(True) to have already brought up the shared stack; btaudio.c never calls hci_init()/l2cap_init()/hci_power_control() itself. NOT YET TESTED ON HARDWARE -- no Bluetooth speaker in hand yet. Written carefully against BTstack's official demo and header APIs, but the SBC/AVDTP timing and buffer sizing will likely need real-world tuning.
- avdtp_configuration_sbc_t is the correct struct name in this vendored BTstack version (a2dp_source_demo.c's media_codec_configuration_sbc_t is from a newer BTstack release than what's pinned here); dropped its .reconfigure/.num_channels fields and their event getters, which don't exist in this version either and weren't used by this simplified source-only implementation anyway. - Added avdtp_acceptor.c and sdp_client.c: even a pure A2DP SOURCE role needs the AVDTP acceptor state machine (the sink can send signaling requests back) and an SDP client (to query the sink's AVDTP PSM). - ringbuf_init() doesn't exist in this port's py/ringbuf.h -- set the buf/size/iget/iput fields directly, matching usb_cdc.c's own pattern. - mp_raise_OSError_msg()/mp_raise_OSError_msg_varg() don't exist; used mp_raise_msg()/mp_raise_msg_varg() with &mp_type_OSError instead. - Shrunk the PCM feed ring buffer 16KB -> 4KB: this board's SRAM GC arena is already tuned to a documented tight minimum (see the ASSERT in memmap_rp2350/section_extra_post_platform_end.incl), and classic Bluetooth's own static buffers (AVDTP/SDP state, SBC encoder state) ate most of the slack upstream left. Revisit only alongside re-checking that assert, and only if real playback shows underruns. Now builds clean: 0 warnings, RAM 87.76%, FLASH 41.65%. Still not tested on real hardware -- no Bluetooth speaker in hand yet.
Pure Python, no firmware/C changes needed -- MicroPython's raw bluetooth module already has everything this needs (central scan/ connect, GATT client, pairing) built in. Uses the same keyboard.inject_report()/mouse.inject_report() hooks added earlier in this branch to feed BLE HID reports into the exact decoders USB keyboards/mice already use. Scans/pairs on demand (not at boot, unlike pcsd.py/pcusb.py -- pairing is a deliberate user action, not an automatic mount). Negotiates Boot Protocol Mode and subscribes to the standard Boot Keyboard/Mouse Input Report characteristics. Known v1 limitations, documented in the file: one device at a time, no persistent bonding across reboots (BTstack, used on this board, doesn't support the bonding-store IRQ events MicroPython's docs mark "NimBLE only"), boot-mode-only (no arbitrary Report-mode parsing), and mutually exclusive with aioble (both need the single bluetooth.BLE() irq() slot -- nothing else in this app uses aioble currently). NOT YET TESTED ON REAL HARDWARE -- no BLE keyboard/mouse in hand to verify against. Written against MicroPython's documented raw bluetooth API and the Bluetooth SIG's standard HOGP UUIDs; passed mpy-cross compilation (syntax-valid) but the GATT descriptor handle-range bookkeeping in particular is the most likely spot a real device's exact attribute layout could trip up.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The host USB stack already covers HID keyboard/mouse/touch (
mp_usbh.c) and CDC-serial (usb_cdc.c), butCFG_TUH_MSCwas never enabled, so a USB flash drive plugged into the host port is invisible to MicroPython even though the vendored TinyUSB already ships thetuh_mscclass driver.This adds
machine.USBDrive, a block device mountable withvfs.VfsFatexactly likemachine.SDCard, plus apcusb.pythat mounts it at/usbautomatically at boot (mirrorspcsd.py's/sd).tusb_config.h: enableCFG_TUH_MSC/CFG_TUH_MSC_MAXLUN(1 LUN — enough for the flash drives this board targets).usb_msc.[ch](new): TinyUSB MSC-host glue — mount/unmount tracking plus blockingread10/write10wrappers. TinyUSB's are callback-based; these pumptuh_task()until the SCSI command completes, the drive disappears, or a 3s timeout elapses.machine_usbdrive.c(new):machine.USBDrive, structured the same way asmachine_sdcard.c. It's actually simpler than the SD driver — USB enumeration gives a real connect/disconnect event, so instead of SD's twice-a-second poll (pcsd.py),USBDrivejust exposeson_change(fn).pcusb.py(new) +_boot_board.py/manifest.pywiring: mounts/usbat boot and on hotplug.USER_MANUAL.md: three small doc touch-ups (host-port description, pin table, storage section) so this isn't undocumented.Test plan
I don't have a local pico-sdk/arm-gcc toolchain set up, so this hasn't been built or run on real hardware — I checked it carefully against the pinned TinyUSB submodule (
3af1bec, matchingmsc_host.h's actual API) and against this branch's own conventions (usb_cdc.c/usb_cdc_mod.csplit,machine_sdcard.c's block-device shape), but a compile + hardware smoke test (plug in a stick,ls("/usb"), copy a file) is still needed. Happy to adjust based on what that turns up, or if you'd rather fold this in yourselves given you can test on real boards./usbls("/usb"), read, and write all work