From b2df89c417841a7db18120fb40e1dee96cf71865 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 30 Apr 2024 11:33:39 +1000 Subject: [PATCH] examples/usb: Add a very simple USBDevice example with host. Signed-off-by: Damien George --- examples/usb/usb_simple_device.py | 144 ++++++++++++++++++++++++++ examples/usb/usb_simple_host_pyusb.py | 47 +++++++++ 2 files changed, 191 insertions(+) create mode 100644 examples/usb/usb_simple_device.py create mode 100755 examples/usb/usb_simple_host_pyusb.py diff --git a/examples/usb/usb_simple_device.py b/examples/usb/usb_simple_device.py new file mode 100644 index 000000000..0b0921f54 --- /dev/null +++ b/examples/usb/usb_simple_device.py @@ -0,0 +1,144 @@ +# Implementation of a very simple, custom USB device in Python. The device has an +# IN and OUT endpoint, accepts up to 64 bytes of data on the OUT endpoint, and echos +# that data back to the IN endpoint. +# +# To run, just execute this file on a device with machine.USBDevice support. The device +# will then change to the custom USB device. +# +# For example, use `mpremote` (the `--no-follow` option starts the script running +# without waiting for a response, because there won't be a response after the device +# changes its USB mode): +# +# $ mpremote run --no-follow usb_simple_device.py +# +# Then, run the host side of the example on your PC using: +# +# $ sudo python usb_simple_host_pyusb.py +# +# You'll need to have `pyusb` installed via `pip install pyusb` to run the host PC code. +# And `sudo` is most likely needed to access the custom USB device. +# +# Once you have finished running the code, you will need to reset or unplug the USB +# device to stop it. + +import machine + +# VID and PID of the USB device. +VID = 0xF055 +PID = 0x9999 + +# USB endpoints used by the device. +EP_OUT = 0x01 +EP_IN = 0x81 + +# USB device descriptor. +_desc_dev = bytes( + [ + 0x12, # bLength + 0x01, # bDescriptorType: Device + 0x00, + 0x02, # USB version: 2.00 + 0xFF, # bDeviceClass: vendor + 0x00, # bDeviceSubClass + 0x01, # bDeviceProtocol + 0x40, # bMaxPacketSize + VID & 0xFF, + VID >> 8 & 0xFF, # VID + PID & 0xFF, + PID >> 8 & 0xFF, # PID + 0x00, + 0x01, # bcdDevice: 1.00 + 0x11, # iManufacturer + 0x12, # iProduct + 0x13, # iSerialNumber + 0x01, # bNumConfigurations: 1 + ] +) + +# USB configuration descriptor. +_desc_cfg = bytes( + [ + # Configuration Descriptor. + 0x09, # bLength + 0x02, # bDescriptorType: configuration + 0x20, + 0x00, # wTotalLength: 32 + 0x01, # bNumInterfaces + 0x01, # bConfigurationValue + 0x14, # iConfiguration + 0xA0, # bmAttributes + 0x96, # bMaxPower + # Interface Descriptor. + 0x09, # bLength + 0x04, # bDescriptorType: interface + 0x00, # bInterfaceNumber + 0x00, # bAlternateSetting + 0x02, # bNumEndpoints + 0xFF, # bInterfaceClass + 0x03, # bInterfaceSubClass + 0x00, # bInterfaceProtocol + 0x15, # iInterface + # Endpoint IN1. + 0x07, # bLength + 0x05, # bDescriptorType: endpoint + EP_IN, # bEndpointAddress + 0x03, # bmAttributes: interrupt + 0x40, + 0x00, # wMaxPacketSize + 0x0A, # bInterval + # Endpoint OUT1. + 0x07, # bLength + 0x05, # bDescriptorType: endpoint + EP_OUT, # bEndpointAddress + 0x02, # bmAttributes: bulk + 0x40, + 0x00, # wMaxPacketSize + 0x00, # bInterval + ] +) + +# USB strings. +_desc_strs = { + 0x11: b"iManufacturer", + 0x12: b"iProduct", + 0x13: b"iSerial", + 0x14: b"iInterface", + 0x15: b"iInterface", + 0x16: b"Extra information", +} + + +# USB callback for when our custom USB interface is opened by the host. +def _open_itf_cb(interface_desc_view): + print("_open_itf_cb", bytes(interface_desc_view)) + # Prepare to receive first data packet on the OUT endpoint. + usbd.submit_xfer(EP_OUT, usbd_buf) + + +# USB callback for when a data transfer (IN or OUT) has completed. +def _xfer_cb(ep_addr, result, xferred_bytes): + print("_xfer_cb", ep_addr, result, xferred_bytes) + if ep_addr == EP_OUT: + # Received data packet from the host, print it out. + print(usbd_buf) + # And then echo the data back to the host. + usbd.submit_xfer(EP_IN, memoryview(usbd_buf)[:xferred_bytes]) + elif ep_addr == EP_IN: + # Host got our data, prepare to receive the next data packet. + usbd.submit_xfer(EP_OUT, usbd_buf) + + +# USB data buffer, for IN and OUT transfers. +usbd_buf = bytearray(64) + +# Switch the USB device to our custom USB driver. +usbd = machine.USBDevice() +usbd.builtin_driver = usbd.BUILTIN_NONE +usbd.config( + desc_dev=_desc_dev, + desc_cfg=_desc_cfg, + desc_strs=_desc_strs, + open_itf_cb=_open_itf_cb, + xfer_cb=_xfer_cb, +) +usbd.active(1) diff --git a/examples/usb/usb_simple_host_pyusb.py b/examples/usb/usb_simple_host_pyusb.py new file mode 100755 index 000000000..d8ac2dd9c --- /dev/null +++ b/examples/usb/usb_simple_host_pyusb.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# +# Host side of the `usb_simple_device.py` example. This must be run using standard +# Python on a PC. See further instructions in `usb_simple_device.py`. + +import sys +import usb.core +import usb.util + +# VID and PID of the custom USB device. +VID = 0xF055 +PID = 0x9999 + +# USB endpoints used by the device. +EP_OUT = 0x01 +EP_IN = 0x81 + + +def main(): + # Search for the custom USB device by VID/PID. + dev = usb.core.find(idVendor=VID, idProduct=PID) + + if dev is None: + print("No USB device found") + sys.exit(1) + + # Claim the USB device. + usb.util.claim_interface(dev, 0) + + # Read the device's strings. + for i in range(0x11, 0x17): + print(f"str{i}:", usb.util.get_string(dev, i)) + + # Test writing to the device. + ret = dev.write(EP_OUT, b"01234567", timeout=1000) + print(ret) + + # Test reading from the device. + print(dev.read(EP_IN, 64)) + + # Release the USB device. + usb.util.release_interface(dev, 0) + usb.util.dispose_resources(dev) + + +if __name__ == "__main__": + main()