micropython-lib icon indicating copy to clipboard operation
micropython-lib copied to clipboard

usbd: Add USB device drivers implemented in Python.

Open projectgus opened this issue 2 years ago • 47 comments

Support writing custom USB interfaces in Python, to extend USB device functionality.

These modules rely on the machine.USBD() object implemented on the MicroPython side, which is a thin C wrapper around TinyUSB's "application" device class driver. That work is in https://github.com/micropython/micropython/pull/9497 and is currently supported on rp2 port, only.

How to test/use this PR

As this is a Draft in active development, using it requires some messing about with PR branches, custom config, etc:

  1. Check out the matching PR for micropython and then check out this PR into the lib/micropython-lib submodule directory before building a firmware.
  2. Edit ports/rp2/mpconfigport.h and check the value of MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE from (0) to (1)`.
  3. Optionally can also change MICROPY_HW_USB_CDC to (0) and MICROPY_HW_ENABLE_UART_REPL to (1) if you plan to have the REPL on the hardware serial port, and not have the built-in TinyUSB CDC serial port.
  4. Strongly recommend also editing ports/rp2/boards/manifest.py and add the line require("usbd") to freeze the usbd package. Developing this way is a bit awkward (have to fully reflash each time), however it has the advantage of decoupling the flashing method fully from any USB-CDC interface.

Alternative to step 4, if not freezing usdbd then it is possible to install the package directly from this branch using mip (this still requires the custom MicroPython build from steps 1 & 2):

$ mpremote connect /dev/ttyUSB0 mip install --index https://projectgus.github.io/micropython-lib/mip/feature/usbd_python usbd

Recommend against using mpremote mount to mount the parent directory of usbd. If using USB-CDC then this crashes when reenumerating the USB device to add new interfaces. If using 115200bps hardware UART then it works but it's miserably slow.

Example Code

See the usbd/examples subdirectory for some simple example programs.

These aren't installed with the package but can be run as (for example) mpremote run hid_keypad_example.py.

(Note: because any USB serial connection will close on reenumeration, printed output from this point will only be visible if outputting stdout to traditional UART, semihosting, etc.)

Debugging Tips

See the micropython PR https://github.com/micropython/micropython/pull/9497

TODOs

  • [ ] Set up the Python package structure properly, split out example code to examples directory.
  • [ ] Support boot.py integration, rather than requiring the Python code to add new interfaces and then make the host re-enumerate the device.
  • [ ] Test on host operating systems other than Linux (!)
  • [x] Test removing the "static" USB interfaces entirely and having MicroPython as a 100% "Python" USB device.
  • [x] Provide a way for code to be run when a device interface is activated by the host after enumeration (callback, event, etc).
  • [x] Memory optimize the descriptor APIs, they will need to allocate a "descriptor buffer" and write into it, not allocate each time.
  • [x] Evaluate whether the control transfer API is OK.
    • It currently allocates a tuple for request but probably these should be individual function parameters to save the allocation.
    • It's modelled on the TinyUSB API which is a bit fiddly to work with from Python, possibly the wrapper can be refactored.
  • [ ] AsyncUSBInterface class with helper coroutine to submit USB transfers.
  • [x] Implement a sample CDC-ACM serial port interface class (not to replace the current one, but to add a second custom serial port).
  • [x] Implement a sample USB MIDI interface class.
  • [x] Implement some additional common HID types.
  • [ ] Shrink the code size. Haven't optimised for size very much, yet.
  • [ ] See if we can provide a host helper program for generating HID Report Descriptors (or point to one that already exists). I tried implementing this in MicroPython so it builds the Report Descriptor on the device, but it takes up too much space.
  • [ ] Decide on approach to numeric constants. This PR currently has some named numeric constants in device.py, and in one place (endpoint_descriptor()) it experiments with allowing simple "magic strings" in place of numeric values for readability and ease of use. However it may be desirable to remove all of these and require callers to pass in their own numeric values, instead.
  • [ ] Documentation.

This work was funded through GitHub Sponsors.

projectgus avatar Oct 26 '22 06:10 projectgus

Hi - I'm trying to try this out, but I can't import device or hid. I am building on a rp2, in WSL, and used these instructions - I guess I am missing something here?

cd ~
git clone https://github.com/projectgus/micropython -b feature/usbd_python
cd micropython
sudo apt-get install build-essential libffi-dev git pkg-config

cd mpy-cross
make

cd ..
cd port/rp2
make submodules
cmake .
make

It creates a .uf2 and Thonny lets me run command, but import device and import hid don't work. Sorry if I'm missing something obvious.

paulhamsh avatar Nov 01 '22 20:11 paulhamsh

@paulhamsh Have you copied device.py and hid.py from this PR to the filesystem?

jimmo avatar Nov 01 '22 21:11 jimmo

@paulhamsh Have you copied device.py and hid.py from this PR to the filesystem?

Oops - thank you. I thought that, as they were part of micropython-lib, somehow they were part of the build. I've copied them manually and it works well. Amazing! Thank you.

paulhamsh avatar Nov 01 '22 22:11 paulhamsh

@paulhamsh Right, sorry - I haven't properly integrated these with the micropython-lib build yet (and they probably won't be in the default config, to save size). Glad it's working well - it's still at an early stage so any feedback, requests for different APIs, etc are very welcome!

projectgus avatar Nov 01 '22 22:11 projectgus

@paulhamsh Hey, I saw in a notification that you were working on https://github.com/paulhamsh/Micropython-Midi-Device but it's not working yet. Your code looks like it should work to me at a glance, and I had MIDI on my list of things to implement anyway, so I'll try to take a look at it soon as well!

projectgus avatar Nov 03 '22 23:11 projectgus

Ah – I had a problem in getting the descriptors to look right, but I put in some debugging and fixed that.

So now it looks like a good set of descriptors, but doesn’t really work for midi.

If I put in the first empty Audio interface, nothing works.

If I leave it as just the MIDI Interface, it worked for a few times (a single message made it, or some in bulk, like they got suck somewhere).

This is under Windows 10 using MixiOx to look for midi messages.

paulhamsh avatar Nov 04 '22 07:11 paulhamsh

I think you're the first person to use this driver code on Windows, so there are many potential bugs that you might have found! :). I'll hopefully get to have a play with MIDI (maybe on Windows) myself, next week.

projectgus avatar Nov 04 '22 09:11 projectgus

It works now – I think I send a bad midi message! Not with Midi OX, but it does work with MidiView and Reaper on Win 11.

But also not with the Audio interface in place, just the single Midi Interface – which is odd because that is part of the spec.

From: Angus Gratton @.> Sent: 04 November 2022 09:35 To: micropython/micropython-lib @.> Cc: Paul Hamshere @.>; Mention @.> Subject: Re: [micropython/micropython-lib] usbd: Add USB device drivers implemented in Python. (PR #558)

I think you're the first person to use this driver code on Windows, so there are many potential bugs that you might have found! :). I'll hopefully get to have a play with MIDI (maybe on Windows) myself, next week.

— Reply to this email directly, view it on GitHub https://github.com/micropython/micropython-lib/pull/558#issuecomment-1303173741 , or unsubscribe https://github.com/notifications/unsubscribe-auth/ABJQ424VLGMRPMSDHYPDNGDWGTKC3ANCNFSM6AAAAAAROUS2SU . You are receiving this because you were mentioned. https://github.com/notifications/beacon/ABJQ4275TUNRPCT234WFN2TWGTKC3A5CNFSM6AAAAAAROUS2SWWGG33NNVSW45C7OR4XAZNMJFZXG5LFINXW23LFNZ2KUY3PNVWWK3TUL5UWJTSNVTNG2.gif Message ID: @.*** @.***> >

paulhamsh avatar Nov 04 '22 09:11 paulhamsh

Midi send mostly works – but I can’t work out how to use the callbacks on submit_xfer.

This doesn’t seem to work – it crashes on the first receive_data - just calling it to prime the transfer, not even actually receiving any data.

    def receive_data_callback(ep_addr, result, xferred_bytes):
        self.got_data = True
        return True

    def receive_data(self):
        data = bytearray(64)
        return self.submit_xfer(0x03, data, receive_data_callback)

And if I add a callback to send, the same happens. Any ideas?

UPDATE: I think I needed to add self.receive_data_callback() FURTHER UPDATE: There is something wrong with the receiving part. MidiOx doesn't work because it sends a midi message when it receives one. I have tried PocketMIDI and it crashes when it sends a midi message. I've changed the code to set up a receive transfer each time one concludes but it doesn't help. So I must have done something wrong.

    def receive_data_callback(ep_addr, result, xferred_bytes):
        self.got_data = True
        self.rx_data_store = self.rx_data
        self.submit_xfer(0x03, self.rx_data, self.receive_data_callback)       

    def start_receive_data(self):
        self.submit_xfer(0x03, self.rx_data, self.receive_data_callback)

Now it works - missing 'self' in the callback

    def receive_data_callback(self, ep_addr, result, xferred_bytes):
        self.got_data = True
        self.rx_data_store = self.rx_data
        self.submit_xfer(0x03, self.rx_data, self.receive_data_callback)       

    def start_receive_data(self):
        self.submit_xfer(0x03, self.rx_data, self.receive_data_callback)

paulhamsh avatar Nov 04 '22 14:11 paulhamsh

Hi - my Midi code works fine now - my Python obviously isn't up to it, too many 'self's missing. Thanks for your help! https://github.com/paulhamsh/Micropython-Midi-Device

paulhamsh avatar Nov 08 '22 09:11 paulhamsh

@paulhamsh That's great! I hope it's useful. Debugging is definitely the most fiddly part of this, especially if you don't have the REPL moved off of the default USB-CDC port! I scratched my head a number of times before I got a robust way to always see errors, and then things got smoother! I'll try to include some debugging tips when I write up the docs for this.

I see you've MIT licensed your midi.py file (thanks!) Would you happy for me to use that as a basis for inclusion in this PR?

projectgus avatar Nov 08 '22 22:11 projectgus

@paulhamsh That's great! I hope it's useful. Debugging is definitely the most fiddly part of this, especially if you don't have the REPL moved off of the default USB-CDC port! I scratched my head a number of times before I got a robust way to always see errors, and then things got smoother! I'll try to include some debugging tips when I write up the docs for this.

I see you've MIT licensed your midi.py file (thanks!) Would you happy for me to use that as a basis for inclusi

@paulhamsh That's great! I hope it's useful. Debugging is definitely the most fiddly part of this, especially if you don't have the REPL moved off of the default USB-CDC port! I scratched my head a number of times before I got a robust way to always see errors, and then things got smoother! I'll try to include some debugging tips when I write up the docs for this.

I see you've MIT licensed your midi.py file (thanks!) Would you happy for me to use that as a basis for inclusion in this PR?

Yes of course! Happy for you to share it!!

paulhamsh avatar Nov 08 '22 22:11 paulhamsh

I scratched my head a number of times before I got a robust way to always see errors, and then things got smoother! I'll try to include some debugging tips when I write up the docs for this.

@projectgus t I'm struggling with similar issues I guess ;) I wanted the REPL log to UART0 or 1, so I tried os.dupterm(machine.UART(0)) and os.dupterm(machine.UART(1)) but without success. The first one just freezes the REPL on USB, the second one succeeds but I did not see any output on the serial console on UART1.

I also tried enabling MICROPY_HW_ENABLE_UART_REPL and recompiled, with same outcome.

Can you share a few details on your debug setup? Did I overlook something obvious here? Thanks.

hoihu avatar Apr 24 '23 07:04 hoihu

I wanted the REPL log to UART0 or 1, so I tried os.dupterm(machine.UART(0)) and os.dupterm(machine.UART(1)) but without success. The first one just freezes the REPL on USB, the second one succeeds but I did not see any output on the serial console on UART1.

never mind, it was too late during that evening I guess, I had the wrong pins connected. REPL works now via UART. Sorry for the noise

hoihu avatar Apr 24 '23 19:04 hoihu

I am trying to get serial data off of my pico and stumbled upon these changes and they look fantastical - I want to create a second usb serial device, which leaves the existing serial connection intact. Is this possible with these changes, even with reenumeration?

barafael avatar May 22 '23 12:05 barafael

@barafael Sorry I didn't reply to you earlier about this. I've had almost no time to work on MP lately, but I've dedicated time from July so the momentum should pick up on this PR quite soon.

I know @hoihu started working on CDC serial support on their fork, there is a branch here with a commit: https://github.com/hoihu/micropython-lib/commits/feat-cdc-python - I haven't tested it, it looks like it would enumerate but there's still a bunch of work before it can be used the same as the default USB serial port.

So, unfortunately I don't know of an easy way to get >1 USB-CDC serial port for now - either with or without these changes. Best I can offer is: watch this space!

projectgus avatar Jun 08 '23 05:06 projectgus

but there's still a bunch of work before it can be used the same as the default USB serial port.

Had some time left today and I pushed an update with a basic example on how to use it. https://github.com/hoihu/micropython-lib/blob/feat-cdc-python/micropython/usbd/cdc_example.py

But as @projectgus said, it's currently still far away from anything. Especially the read() function should be using a ringbuffer or similar. At least it's possible to do some basic rx/tx over that interface now..

hoihu avatar Jun 08 '23 12:06 hoihu

@hoihu does this sound like it's going to be needed? https://github.com/micropython/micropython/pull/9458

andrewleech avatar Jun 08 '23 20:06 andrewleech

Just for the benefit of anyone else looking at this and wanting to borrow code; I've got a HID keypad and basic USB mass storage in my fork. I've created a draft PR to make the changes easy to find at projectgus/micropython-lib#1 .

turmoni avatar Jun 28 '23 20:06 turmoni

I've successfully built the UF2 but after copying the files and running mpremote exec "import hid_test.py" I'm getting this:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "hid_test.py", line 2, in <module>
  File "device.py", line 7, in <module>
ImportError: can't perform relative import

Which is referring to this line in device.py: from .utils import split_bmRequestType

Am I missing something? 🧐

robin7331 avatar Jul 04 '23 09:07 robin7331

@robin7331 Maybe the usbd package hasn't been included properly. Can you please give details about what "copying the files" means in this context? Without the package directory and __init__.py file, the relative imports won't work.

projectgus avatar Jul 10 '23 04:07 projectgus

Rebased this PR and the linked micropython PR, and incorporated some fixes for issues noticed by @turmoni and @hoihu . EP_IN_FLAG is now correctly defined as the right constant.

@turmoni I've implemented a possible alternative fix in this branch for the _always_cb workaround in your PR. There was a race where the callback function wasn't added before submit_transfer was called, so a very rapid xfer completion could fire before there was a callback set. This might not be enough to solve the recursion depth crash you were seeing if the host sends back a lot of rapid responses though, I haven't tested your MSC class with it. It turns out that there are some cases where the current implementation requires usbd_task() to recurse. Doing every device callback via micropython.schedule() might turn out to be the path of least resistance.

(EDIT: Sorry @turmoni I just recalled you'd documented this race in detail over on https://github.com/micropython/micropython/pull/9497#issuecomment-1603376458 and I'd missed it as I was busy with other things. There might be more to this, but also there might not be!)

projectgus avatar Jul 10 '23 08:07 projectgus

@turmoni Regarding the issues you mentioned:

The inability to STALL endpoints

Now implemented as USBInterface.set_ep_stall() & USBInterface.get_ep_stall(). Have tested these work "on the wire" but not in a context where they matter for protocol correctness (like MSC).

Control transfer callbacks not firing for anything other than the first interface that's added

Do you remember which transfers you weren't seeing interface callbacks for?

Not really seeing a way to handle input on the control endpoint

This was possible but fiddly. I've adapted the keypad example you wrote to use control transfers for SET_REPORT and added it here.

Am currently trying to decide if it's worth building a more intuitive interface for control transfers with data, or to leave it like this.

projectgus avatar Jul 26 '23 07:07 projectgus

Thanks, I'll have a look at these at some point!

Do you remember which transfers you weren't seeing interface callbacks for?

It was all of them - on a device using a HID keypad and a mass storage device, whichever interface I registered first was the only one to have the control transfer callbacks invoked, e.g.:

kp = hidkeypad.KeypadInterface()
ms = msc.MSCInterface(<...>)
ud.add_interface(kp)
ud.add_interface(ms)
ud.reenumerate()

Would fire the control callback in KeypadInterface but not MSCInterface, whilst:

kp = hidkeypad.KeypadInterface()
ms = msc.MSCInterface(<...>)
ud.add_interface(ms)
ud.add_interface(kp)
ud.reenumerate()

would fire the control callbacks for MSCInterface but not KeypadInterface. Having HID followed by MSC happens to work out somewhat well because OSs will just try SCSI on the OUT/IN endpoints if they don't get a response to the max LUN request.

I did try to track this down in the C code, and couldn't understand why it wasn't firing. It's been a few weeks now so I'm a bit fuzzy, but it seemed like it wasn't being handled on the C side, even though it looked like it should have been.

turmoni avatar Jul 26 '23 17:07 turmoni

I'm new to this can someone upload an uf2 file with this change please?

akshaykg avatar Aug 21 '23 12:08 akshaykg

@akshaykg Sorry, I thought I'd replied to you already about this, my mistake. At this point this PR is pretty rough. It's really only useful if you're planning to develop it directly, rather than use it in something. Once it's a bit further along, I'll look at whether we can distribute some "early access" binary builds somewhere.

projectgus avatar Oct 05 '23 01:10 projectgus

Have rebased (to pick up pre-commit, woo!) and committed a USB CDC serial driver here, along with an example (REPL via os.dupterm()) and a performance test. Thanks to @hoihu who developed the initial CDC driver, and @linted who contributed some fixes to it.

Performance is... underwhelming. Reads seem to be 33% to 50% of the throughput of Native USB-CDC, "verified" writes are about 50% and "unverified" about 20%... I'd hoped we'd be closer to 50% of native performance all around :disappointed:.

Complete performance test logs
❯ python -u ./cdc_rate_benchmark.py /dev/ttyUSB0 /dev/ttyACM1 1 | tee cdc_rate_usbd.log
REPL on /dev/ttyUSB0
data on /dev/ttyACM1
USB VCP 1
READ: bufsize=256, read 32768 bytes in 191.68 msec = 166.95 kibytes/sec = 1.37 MBits/sec
READ: bufsize=512, read 32768 bytes in 193.90 msec = 165.03 kibytes/sec = 1.35 MBits/sec
READ: bufsize=1024, read 65536 bytes in 393.46 msec = 162.66 kibytes/sec = 1.33 MBits/sec
READ: bufsize=2048, read 131072 bytes in 775.69 msec = 165.01 kibytes/sec = 1.35 MBits/sec
READ: bufsize=4096, read 262144 bytes in 1552.44 msec = 164.90 kibytes/sec = 1.35 MBits/sec
READ: bufsize=8192, read 524288 bytes in 3116.02 msec = 164.31 kibytes/sec = 1.35 MBits/sec
READ: bufsize=16384, read 1048576 bytes in 6214.78 msec = 164.77 kibytes/sec = 1.35 MBits/sec
WRITE: verified=1, bufsize=128, wrote 4096 bytes in 518.10 msec = 7.72 kibytes/sec = 0.06 MBits/sec
WRITE: verified=1, bufsize=256, wrote 4096 bytes in 257.01 msec = 15.56 kibytes/sec = 0.13 MBits/sec
WRITE: verified=1, bufsize=512, wrote 8192 bytes in 513.80 msec = 15.57 kibytes/sec = 0.13 MBits/sec
WRITE: verified=1, bufsize=1024, wrote 16384 bytes in 770.36 msec = 20.77 kibytes/sec = 0.17 MBits/sec
WRITE: verified=1, bufsize=2048, wrote 32768 bytes in 1527.01 msec = 20.96 kibytes/sec = 0.17 MBits/sec
WRITE: verified=1, bufsize=4096, wrote 65536 bytes in 2804.35 msec = 22.82 kibytes/sec = 0.19 MBits/sec
WRITE: verified=1, bufsize=8192, wrote 524288 bytes in 21591.40 msec = 23.71 kibytes/sec = 0.19 MBits/sec
WRITE: verified=0, bufsize=128, wrote 4096 bytes in 503.86 msec = 7.94 kibytes/sec = 0.07 MBits/sec
WRITE: verified=0, bufsize=256, wrote 4096 bytes in 258.68 msec = 15.46 kibytes/sec = 0.13 MBits/sec
WRITE: verified=0, bufsize=512, wrote 8192 bytes in 418.61 msec = 19.11 kibytes/sec = 0.16 MBits/sec
WRITE: verified=0, bufsize=1024, wrote 16384 bytes in 519.21 msec = 30.82 kibytes/sec = 0.25 MBits/sec
WRITE: verified=0, bufsize=2048, wrote 32768 bytes in 1011.07 msec = 31.65 kibytes/sec = 0.26 MBits/sec
WRITE: verified=0, bufsize=4096, wrote 65536 bytes in 1780.98 msec = 35.94 kibytes/sec = 0.29 MBits/sec
WRITE: verified=0, bufsize=8192, wrote 524288 bytes in 13202.22 msec = 38.78 kibytes/sec = 0.32 MBits/sec

❯ python -u ./cdc_rate_benchmark.py /dev/ttyACM1 | tee cdc_native.log
REPL and data on /dev/ttyACM1
READ: bufsize=256, read 32768 bytes in 83.63 msec = 382.62 kibytes/sec = 3.13 MBits/sec
READ: bufsize=512, read 32768 bytes in 94.43 msec = 338.88 kibytes/sec = 2.78 MBits/sec
READ: bufsize=1024, read 65536 bytes in 163.04 msec = 392.53 kibytes/sec = 3.22 MBits/sec
READ: bufsize=2048, read 131072 bytes in 301.30 msec = 424.82 kibytes/sec = 3.48 MBits/sec
READ: bufsize=4096, read 262144 bytes in 502.04 msec = 509.92 kibytes/sec = 4.18 MBits/sec
READ: bufsize=8192, read 524288 bytes in 1123.43 msec = 455.75 kibytes/sec = 3.73 MBits/sec
READ: bufsize=16384, read 1048576 bytes in 2211.46 msec = 463.04 kibytes/sec = 3.79 MBits/sec
WRITE: verified=1, bufsize=128, wrote 4096 bytes in 119.59 msec = 33.45 kibytes/sec = 0.27 MBits/sec
WRITE: verified=1, bufsize=256, wrote 4096 bytes in 103.98 msec = 38.47 kibytes/sec = 0.32 MBits/sec
WRITE: verified=1, bufsize=512, wrote 8192 bytes in 194.74 msec = 41.08 kibytes/sec = 0.34 MBits/sec
WRITE: verified=1, bufsize=1024, wrote 16384 bytes in 375.83 msec = 42.57 kibytes/sec = 0.35 MBits/sec
WRITE: verified=1, bufsize=2048, wrote 32768 bytes in 739.20 msec = 43.29 kibytes/sec = 0.35 MBits/sec
WRITE: verified=1, bufsize=4096, wrote 65536 bytes in 1467.83 msec = 43.60 kibytes/sec = 0.36 MBits/sec
WRITE: verified=1, bufsize=8192, wrote 524288 bytes in 11674.63 msec = 43.86 kibytes/sec = 0.36 MBits/sec
WRITE: verified=0, bufsize=128, wrote 4096 bytes in 52.36 msec = 76.39 kibytes/sec = 0.63 MBits/sec
WRITE: verified=0, bufsize=256, wrote 4096 bytes in 38.10 msec = 104.98 kibytes/sec = 0.86 MBits/sec
WRITE: verified=0, bufsize=512, wrote 8192 bytes in 64.13 msec = 124.75 kibytes/sec = 1.02 MBits/sec
WRITE: verified=0, bufsize=1024, wrote 16384 bytes in 113.82 msec = 140.58 kibytes/sec = 1.15 MBits/sec
WRITE: verified=0, bufsize=2048, wrote 32768 bytes in 217.15 msec = 147.37 kibytes/sec = 1.21 MBits/sec
WRITE: verified=0, bufsize=4096, wrote 65536 bytes in 424.91 msec = 150.62 kibytes/sec = 1.23 MBits/sec
WRITE: verified=0, bufsize=8192, wrote 524288 bytes in 3340.08 msec = 153.29 kibytes/sec = 1.26 MBits/sec
WRITE: verified=0, bufsize=9999, wrote 639936 bytes in 4056.14 msec = 154.07 kibytes/sec = 1.26 MBits/sec

I think the slow "verified" performance on Native really tells the story, the less Python code executing for each USB transfer then the better.

The client currently uses a pure Python "utils.Buffer" class which is designed around TinyUSB's buffer interface requirements. I'm going to re-test incorporating https://github.com/micropython/micropython/pull/12487, and also maybe look at whether "Buffer" should be implemented in C: possibly as a wrapper around TinyUSB's tu_fifo struct (which is already compiled into the binary, and tightly integrates with TinyUSB's APIs.)

projectgus avatar Oct 05 '23 02:10 projectgus

awesome👍

Performance is... underwhelming.

IMO that is already pretty good and more than I would have hoped for! Depends obviously on the use-case, but there will be many applications where speed is not the most critical thing.

Looking forward to the buffer optimisations though😉

hoihu avatar Oct 05 '23 10:10 hoihu

Finding this very useful, but (perhaps showing my inexperience) I am having trouble sending shift, ctrl, etc through the HID interface. I can, for example send the 'a' key, and I can send the shift key followed by an 'a'; I just get lowercase 'a's typed either way, although I can leave the system thinking shift is down, so other keyboards send 'A'. I'm sure I need to send another byte with flags, but I can't make that work without just crashing the device.

DrKendall332 avatar Oct 15 '23 13:10 DrKendall332

Have pushed some changes which include breaking interface changes for lower level internal USB interfaces, to reduce resource consumption:

  • Where previously a USBInterface subclass implemented get_itf_descriptor and get_endpoint_descriptors, now it's only necessary to implement one function descriptor_config_cb. See the bundled USB class drivers for examples of what this needs to do.
  • A single USBInterface object can now represent more than one USB Interface. This is something of a "principle of least surprise" naming violation, but it makes it easier to create a driver that requires multiple USB interfaces grouped together (i.e. MIDI).
  • USBInterface.handle_*_control_xfer callback functions' request parameter is no longer a tuple, it's a memoryview to the request bytes and the callee needs to manually unpack it. See handle_interface_control_xfer in hid.py for an example of how to work with this.

There are also some small performance improvements, but it's not significantly better than the numbers posted above.

projectgus avatar Oct 18 '23 05:10 projectgus