Skip to content

Raspberry Pi Pico: Exposing USB device support #6811

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mcassaniti opened this issue Jan 29, 2021 · 32 comments
Closed

Raspberry Pi Pico: Exposing USB device support #6811

mcassaniti opened this issue Jan 29, 2021 · 32 comments
Labels

Comments

@mcassaniti
Copy link

Is it possible or a reasonable to ask about exposing USB device support in MicroPython similar to the support for the PyBoard?

Beyond that, is it reasonable to expose even more support so that custom USB devices could be created? For example, a custom U2F token running from MicroPython or an OpenPGP card implementation. I'm sure someone can come up with examples that aren't security related.

Original issue: https://github.com/raspberrypi/micropython/issues/5

@safijari
Copy link

safijari commented Feb 1, 2021

Unsure if this is the right place but anyone that lands here because they want to make a keyboard/macropad/whatever with the pico please note that you can install CircuitPython and the HID functions work fine under CircuitPython (as does the adafruit HID library). KMK is also likely to work but I haven't tested it.

@matemaciek
Copy link

matemaciek commented Feb 11, 2021

I'd love to see USB host support in MicroPython for Pi Pico, I could help in implementation.

@AngainorDev
Copy link

Also eagerly waiting for USB_HID to be available on micropython.
Is there someone actively working on this?
What would be needed?

Since this works on circuit python, I guess mainly the core usb lib needs to be ported.
I'm not familiar with differences between circuitpython and micropython, but this seems a reasonable task.
How could I help?

@LennartPiro
Copy link

@AngainorDev there is a USB implementation for the pyboard in MicroPython:

// MicroPython bindings for USB
/*
Philosophy of USB driver and Python API: pyb.usb_mode(...) configures the USB
on the board. The USB itself is not an entity, rather the interfaces are, and
can be accessed by creating objects, such as pyb.USB_VCP() and pyb.USB_HID().
We have:
pyb.usb_mode() # return the current usb mode
pyb.usb_mode(None) # disable USB
pyb.usb_mode('VCP') # enable with VCP interface
pyb.usb_mode('VCP+MSC') # enable with VCP and MSC interfaces
pyb.usb_mode('VCP+HID') # enable with VCP and HID, defaulting to mouse protocol
pyb.usb_mode('VCP+HID', vid=0xf055, pid=0x9800) # specify VID and PID
pyb.usb_mode('VCP+HID', hid=pyb.hid_mouse)
pyb.usb_mode('VCP+HID', hid=pyb.hid_keyboard)
pyb.usb_mode('VCP+HID', pid=0x1234, hid=(subclass, protocol, max_packet_len, polling_interval, report_desc))
vcp = pyb.USB_VCP() # get the VCP device for read/write
hid = pyb.USB_HID() # get the HID device for write/poll
Possible extensions:
pyb.usb_mode('host', ...)
pyb.usb_mode('OTG', ...)
pyb.usb_mode(..., port=2) # for second USB port
*/

Maybe this is a good starting point in porting it to the Pico? I'm not good enough at C programming to make much sense of it.

@AngainorDev
Copy link

@LennartPiro
I had a look at adafruit implementation as well, and it's quite different (shared lib, for all ports)
I guess the way to go here would be to follow the existing pyboard implementation, since it's also tied to the existing, working VCP interface.
I also saw some PR or Issues about using the default tinyusb lib instead of the pico sdk provided one, things lying there as well probably.

Someone with background on that low level code and USB implementation would be needed I'm afraid.

@robin7331
Copy link

Is there any update on this?

@mrkprdo
Copy link

mrkprdo commented May 3, 2021

Add me to the list here.

I just wanted to point out that one of my struggles using Circuitpython for usb hid is that it has no _thread support, which makes it difficult to perfrom concurrent functions. While Micropython does have minimal support for _thread, it will be more benificial to have usb hid device as well.

@GregWoods
Copy link

MicroPython really is the neglected younger sibling to C/C++ in the Pico world.
I struggle to see how a maker focused device could even be released without HID support. It is a huge part of the hobbyist microcontroller community. I really can't stomach going back to C, and hate switching between CircuitPython and MicroPython for every other product. Please can we have some traction on this.

@robin7331
Copy link

You might wanna look into my thread on the RPi forums
Its not an official library but it seems to work fine for HID :)

@WernerFS
Copy link

WernerFS commented Jan 10, 2022

I have been porting my MP code to CP just to findout that I need to create a new board in order to use it. CP is nice and all but I cant bear the fact that if you work on a custom board it need to be re-compiled. USB HID in MP is a must.

@tannewt
Copy link

tannewt commented Jan 10, 2022

I have been porting my MP code to CP just to findout that I need to create a new board in order to use it. CP is nice and all but I cant bear the fact that if you work on a custom board it need to be re-compiled. USB HID in MP is a must.

Custom boards often use different flash chips. A generic build will have a slow but universal setup for the flash. Using a custom build allows for an optimal flash setup for a given board.

@theboyknowsclass
Copy link

any word on this being added? (esp for pico w)

@ofirgaash1
Copy link

please, please add HID to MP

@WarpRomo
Copy link

for tha love of god, add it

@elpekenin
Copy link

elpekenin commented Aug 24, 2022

I'm planning to make a custom keyboard using RP2040's. I would like to use MP instead of CP in order to learn some low-level stuff and because CP seems too bloated with stuff I wont use.

The main problem for this project is HID, and I'm probably very under-qualified for the task, but would like to give this a try. What would need to be adapted from the STM32 one? Took a quick look at ports/stm32/usb.c and only the check for max available endpoints based on each particular chip seems to be hardware-specific.

Is TinyUSB worth?

For context, I have some experience on both C and Py (tho not using them together)

@jimmo
Copy link
Member

jimmo commented Aug 24, 2022

What would need to be adapted from the STM32 one?
Is TinyUSB worth?

@elpekenin The rp2 port uses TinyUSB, whereas the stm32 port uses a modified version of the ST usb driver. So it's probably not particularly useful looking at the stm32 implementation. Look at how the TinyUSB MSC device class is used in the rp2 code as an example of adding an additional device class.

However we may want to mirror the Python-level API from the stm32 port (which is to say setting pyb.usb_mode in boot.py, as well as the pyb.hid_mouse, pyb.hid_keyboard objects, and pyb.USB_HID class). That said, I think these need a new home that isn't the pyb module (i.e. you should be able to write the same code on all boards which don't have a pyb module). Not sure if machine is the right place though.

@elpekenin
Copy link

elpekenin commented Aug 25, 2022

@jimmo thanks for the fast reply, i will play around with the code tomorrow and the hardware should arrive soon :)
So far i had forked both MP and TUSB and made a "wrapper" repo for all project's files where they are submodules, can i remove the TUSB one? aka, I dont have to edit anything there, right?

Not sure if machine is the right place though.

About the module "design", im thinking maybe usb is the best idea, so then you can do something like usb.hid_keyboard or usb.hid_mouse and pyb.usb_mode ➡️ usb.mode

@elpekenin
Copy link

A couple of boards just arrived, will start testing soon

I've adapted noobee's work because the rp2 code has been updated since, commit on my fork. The code compiled, but before going any further:

  • If I flash a faulty firmware while testing, the bootloader will always work and I can flash again, right?
    Hoping it can't be bricked so I don't need to buy an external programmer or something
  • After taking a look at modpyb.c and usb.c I'm pretty sure my suspicion was correct and enabling/disabling the inferface just means adding a guard clause in the functions related with it, and has nothing to do with compile-time. However i don't really understand how the USB descriptor is added/removed according to the configuration.

Once I get my code working, I would of course like to make the usb module:

  • Is extmod the place to add it?
  • To handle the different hardware, should default to only allowing REPL and use #ifdefs to add other options in the MCUs that have something else implemented?

@jimmo
Copy link
Member

jimmo commented Aug 27, 2022

Hoping it can't be bricked so I don't need to buy an external programmer or something

Yes, it is almost impossible to brick a device these days, and fortunately the Pico board has an easy "boot" button to get to the uf2 bootloader. The bootloader is in ROM so you can't overwrite it by accident.

(I have never bricked a single microcontroller while working on MicroPython)

You can also use a second Pico as an external (SWD) debugger for a Pico. Not sure if that supports uploading code though.

Once I get my code working, I would of course like to make the usb module.

There's a lot of scope here, and it impacts several ports (mimxrt, samd, rp2, esp32s*, nrf and potentially stm32). Also questions about how this works with host mode, and potentially whether we want to use the tinyusb device class implementations or something more Python-based.

So, great to experiment, but I don't have any specific advice to give here yet. Anything you find out will be useful though to help figure out how to approach this.

@elpekenin
Copy link

Thanks for the detailed info, once again

My next steps will be

  1. Of course, test the (allegdly) HID-enabled firmware i made the other day and get it working
  2. Try and build a usb module which unifies all the pieces of configuration, and provides a standarized API (mode method, keyboard/mouse/consumer classes, ...)

Actually, there is no point on adding a global module, since it would just be using the underlying API. Having port-specific modules would be harmless to other ports and eaiser to maintain&test

I'm by no means a software-engineer, I'm open to ideas but this feels like a decent way of approaching the problem.
Will be sharing updates as soon as i get back working on this project in a couple of days. (perhaps spamming this issue page isn't the best place to do so, should I move the convo elsewhere?)

@mcassaniti
Copy link
Author

I'm by no means a software-engineer

I can't say I agree with you @elpekenin. This is exactly what I would consider software engineering to be all about.

perhaps spamming this issue page isn't the best place to do so, should I move the convo elsewhere?

I'd say this issue is the right place. The subscribers to the issue are the ones who are getting "spammed" and really do wish to stay up to date on anything that you can share. You might even get a few beta testers this way. The only other location I could suggest is the discussions section, but I would wait for someone to complain first.

@elpekenin
Copy link

This is exactly what I would consider software engineering to be all about.

Thanks for the encouraing words :) I meant it in a "maybe there are some design patterns I'm missing" kind-off way

I've spent several hours trying (and failing), my findings so far:
⚠️ Maybe I got my head tangled during the several tests, so take this with a grain of salt)

  • Adding the modusb_hid.c to CMakeLists.txt, even when not trying to add the module to mpconfigport.h:
    • Linux has no problem connecting to it.
    • Apparently breaks the W10 driver, and can't even connect to it with Thonny anymore - maybe just a typo/error on my code.
      Image
  • The way in which modules are added in mpconfigport.h has changed since noobee made their code, and I'm unsure how should i add it, I'll double-check soon, but pretty sure none of this worked:
/* TESTING HID */
extern const struct _mp_obj_module_t mp_module_usb_hid;
// #define MICROPY_PY_USB_HID                   (1)    //useless config flag so far, just added so i don't forget
// #define MICROPY_PY_USB_HID_INCLUDEFILE       "ports/rp2/modusb_hid.c"
// #define MICROPY_PY_USB_HID { MP_ROM_QSTR(MP_QSTR_USB_HID), MP_ROM_PTR(&mp_module_usb_hid) }, 
// #define MICROPY_PORT_BUILTIN_MODULES { MP_ROM_QSTR(MP_QSTR_USB_HID), MP_ROM_PTR(&mp_module_usb_hid) }, //noobee's way
// MP_REGISTER_MODULE(MP_QSTR_usb_hid, &mp_module_usb_hid); 
#define MICROPY_PORT_BUILTIN_MODULES { MP_OBJ_NEW_QSTR(MP_QSTR_usb_hid), (mp_obj_t)&mp_module_usb_hid },

@theboyknowsclass
Copy link

Is there anything to be gained from looking at the circuit python implementation?

@elpekenin
Copy link

Added the MP_REGISTER_MODULE line in the own modusb_hid.c file and it worked, im now trying to get drivers working on Windows :))

@elpekenin
Copy link

elpekenin commented Aug 29, 2022

It is """working""" already, you can check my branch here

I'm not sure how/why the drivers were acting weird nor how i fixed it 😅

Changes made:

  • Moved several defines to enums (im not sure i understood their meaning correctly, have some '??'s over the code)
  • Added #if CFG_TUD_HID at all my pieces of code, so it can be disabled on compile. Also added checks for stuff related with CFG_TUD_MSC and didn't have it already
  • USBD_DESC_LEN is now dynamically computed (at compile) using a multiplication thanks to the CFG flags being 1/0 (should i add some extra code to avoid problems with other values? are they already forced to be 0/1?)

TODO list:

  • Rename module to usb
  • Rename stuff to match pyb.USB_HID's API
  • Add some documentation
  • Would like to add the module from mpconfigport.h with something like
#if CFG_TUD_HID
#    define MICROPY_PY_USB_HID_INCLUDEFILE    "ports/rp2/modusb_hid.c"
#endif

But not sure how to


If you have some time, please take a moment to check if you run into driver issues (as i did yesterday):

  1. Compile my code with cd ports/rp2 && make submodules && make clean && make
  2. Test on your board with this little program
import time
import usb_hid

report = bytearray(8)

report[2] = 0x04 # register 'a' keycode
usb_hid.report(usb_hid.KEYBOARD, report) # send event

time.sleep(2)

report[2] = 0x00 # unregister 'a' keycode
usb_hid.report(usb_hid.KEYBOARD, report) # send event

And you should see a bunch fo as being printed in your screen

@CRImier
Copy link
Contributor

CRImier commented Oct 8, 2023

@elpekenin did you delete that repo? can't find it, could use some HID in a project of mine

@elpekenin
Copy link

@CRImier sorry, nuked the repo a couple months back to make GitHub see it as a fork of CircuitPython instead.

I have just pushed that branch back up:https://github.com/elpekenin/circuitpython/tree/peke-devel

However, i ended up using QMK for HID stuff, so don't expect any further work on it. Also, be aware i made very minimal testing(and it was a year ago), i have no idea how good the code is nor how easy will it merge into current repo.

@CRImier
Copy link
Contributor

CRImier commented Oct 19, 2023

@elpekenin thank you so much, tinkering with it right now, works wonders!

@fmohtadi99
Copy link

Even after around 3 years of opening this issue, there's no official update....
Actually no attention from any of 541 contributors :))

@pi-mst
Copy link

pi-mst commented Jan 7, 2024

Even after around 3 years of opening this issue, there's no official update.... Actually no attention from any of 541 contributors :))

There has been a lot of effort on this topic, see #9497.

This ticket should probably be closed since 9497 is under active development.

@LennartPiro
Copy link

USB device support was added in MicroPython v1.23.0
Issue can be closed.

@robert-hh
Copy link
Contributor

#9497 was merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests