diff --git a/USB_Host_BFF_Examples/Arduino_USB_Host_BFF_DeviceInfo/Arduino_USB_Host_BFF_DeviceInfo.ino b/USB_Host_BFF_Examples/Arduino_USB_Host_BFF_DeviceInfo/Arduino_USB_Host_BFF_DeviceInfo.ino new file mode 100644 index 000000000..f629f8873 --- /dev/null +++ b/USB_Host_BFF_Examples/Arduino_USB_Host_BFF_DeviceInfo/Arduino_USB_Host_BFF_DeviceInfo.ino @@ -0,0 +1,233 @@ +// SPDX-FileCopyrightText: 2024 Ha Thach for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example demonstrates use of both device and host, where + * - Device run on native usb controller (roothub port0) + * - Host run on MAX3421E controller (roothub port1) tested with: + * - SAMD21, SAMD51, nRF52840, ESP32S2, ESP32S3, ESP32 + * - RP2040: "pio_usb.h" must not be included, otherwise pio-usb will be used as host controller + * + * Requirements: + * - SPI instance, CS pin, INT pin are correctly configured + */ + +/* Host example will get device descriptors of attached devices and print it out: + * Device 1: ID 046d:c52f + Device Descriptor: + bLength 18 + bDescriptorType 1 + bcdUSB 0200 + bDeviceClass 0 + bDeviceSubClass 0 + bDeviceProtocol 0 + bMaxPacketSize0 8 + idVendor 0x046d + idProduct 0xc52f + bcdDevice 2200 + iManufacturer 1 Logitech + iProduct 2 USB Receiver + iSerialNumber 0 + bNumConfigurations 1 + * + */ +#include "Adafruit_TinyUSB.h" +#include "SPI.h" + +Adafruit_USBH_Host USBHost(&SPI, A1, A2); + +// Language ID: English +#define LANGUAGE_ID 0x0409 + +typedef struct { + tusb_desc_device_t desc_device; + uint16_t manufacturer[32]; + uint16_t product[48]; + uint16_t serial[16]; + bool mounted; +} dev_info_t; + +// CFG_TUH_DEVICE_MAX is defined by tusb_config header +dev_info_t dev_info[CFG_TUH_DEVICE_MAX] = { 0 }; + +//--------------------------------------------------------------------+ +// setup() & loop() +//--------------------------------------------------------------------+ +void setup() { + Serial.begin(115200); + while ( !Serial ) delay(10); // wait for native usb + Serial.println("starting usb.."); + // init host stack on controller (rhport) 1 + USBHost.begin(1); + Serial.println("usb started"); + Serial.println("TinyUSB Dual: Device Info Example with MAX3421E"); +} + +void loop() { + USBHost.task(); + Serial.flush(); +} + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +void print_device_descriptor(tuh_xfer_t *xfer); + +void utf16_to_utf8(uint16_t *temp_buf, size_t buf_len); + +void print_lsusb(void) { + bool no_device = true; + for (uint8_t daddr = 1; daddr < CFG_TUH_DEVICE_MAX + 1; daddr++) { + // TODO can use tuh_mounted(daddr), but tinyusb has an bug + // use local connected flag instead + dev_info_t *dev = &dev_info[daddr - 1]; + if (dev->mounted) { + Serial.printf("Device %u: ID %04x:%04x %s %s\r\n", daddr, + dev->desc_device.idVendor, dev->desc_device.idProduct, + (char *) dev->manufacturer, (char *) dev->product); + + no_device = false; + } + } + + if (no_device) { + Serial.println("No device connected (except hub)"); + } +} + +// Invoked when device is mounted (configured) +void tuh_mount_cb(uint8_t daddr) { + Serial.printf("Device attached, address = %d\r\n", daddr); + + dev_info_t *dev = &dev_info[daddr - 1]; + dev->mounted = true; + + // Get Device Descriptor + tuh_descriptor_get_device(daddr, &dev->desc_device, 18, print_device_descriptor, 0); +} + +/// Invoked when device is unmounted (bus reset/unplugged) +void tuh_umount_cb(uint8_t daddr) { + Serial.printf("Device removed, address = %d\r\n", daddr); + dev_info_t *dev = &dev_info[daddr - 1]; + dev->mounted = false; + + // print device summary + print_lsusb(); +} + +void print_device_descriptor(tuh_xfer_t *xfer) { + if (XFER_RESULT_SUCCESS != xfer->result) { + Serial.printf("Failed to get device descriptor\r\n"); + return; + } + + uint8_t const daddr = xfer->daddr; + dev_info_t *dev = &dev_info[daddr - 1]; + tusb_desc_device_t *desc = &dev->desc_device; + + Serial.printf("Device %u: ID %04x:%04x\r\n", daddr, desc->idVendor, desc->idProduct); + Serial.printf("Device Descriptor:\r\n"); + Serial.printf(" bLength %u\r\n" , desc->bLength); + Serial.printf(" bDescriptorType %u\r\n" , desc->bDescriptorType); + Serial.printf(" bcdUSB %04x\r\n" , desc->bcdUSB); + Serial.printf(" bDeviceClass %u\r\n" , desc->bDeviceClass); + Serial.printf(" bDeviceSubClass %u\r\n" , desc->bDeviceSubClass); + Serial.printf(" bDeviceProtocol %u\r\n" , desc->bDeviceProtocol); + Serial.printf(" bMaxPacketSize0 %u\r\n" , desc->bMaxPacketSize0); + Serial.printf(" idVendor 0x%04x\r\n" , desc->idVendor); + Serial.printf(" idProduct 0x%04x\r\n" , desc->idProduct); + Serial.printf(" bcdDevice %04x\r\n" , desc->bcdDevice); + + // Get String descriptor using Sync API + Serial.printf(" iManufacturer %u ", desc->iManufacturer); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_manufacturer_string_sync(daddr, LANGUAGE_ID, dev->manufacturer, sizeof(dev->manufacturer))) { + utf16_to_utf8(dev->manufacturer, sizeof(dev->manufacturer)); + Serial.printf((char *) dev->manufacturer); + } + Serial.printf("\r\n"); + + Serial.printf(" iProduct %u ", desc->iProduct); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_product_string_sync(daddr, LANGUAGE_ID, dev->product, sizeof(dev->product))) { + utf16_to_utf8(dev->product, sizeof(dev->product)); + Serial.printf((char *) dev->product); + } + Serial.printf("\r\n"); + + Serial.printf(" iSerialNumber %u ", desc->iSerialNumber); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_serial_string_sync(daddr, LANGUAGE_ID, dev->serial, sizeof(dev->serial))) { + utf16_to_utf8(dev->serial, sizeof(dev->serial)); + Serial.printf((char *) dev->serial); + } + Serial.printf("\r\n"); + + Serial.printf(" bNumConfigurations %u\r\n", desc->bNumConfigurations); + + // print device summary + print_lsusb(); +} + +//--------------------------------------------------------------------+ +// String Descriptor Helper +//--------------------------------------------------------------------+ + +static void _convert_utf16le_to_utf8(const uint16_t *utf16, size_t utf16_len, uint8_t *utf8, size_t utf8_len) { + // TODO: Check for runover. + (void) utf8_len; + // Get the UTF-16 length out of the data itself. + + for (size_t i = 0; i < utf16_len; i++) { + uint16_t chr = utf16[i]; + if (chr < 0x80) { + *utf8++ = chr & 0xff; + } else if (chr < 0x800) { + *utf8++ = (uint8_t) (0xC0 | (chr >> 6 & 0x1F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F)); + } else { + // TODO: Verify surrogate. + *utf8++ = (uint8_t) (0xE0 | (chr >> 12 & 0x0F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 6 & 0x3F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F)); + } + // TODO: Handle UTF-16 code points that take two entries. + } +} + +// Count how many bytes a utf-16-le encoded string will take in utf-8. +static int _count_utf8_bytes(const uint16_t *buf, size_t len) { + size_t total_bytes = 0; + for (size_t i = 0; i < len; i++) { + uint16_t chr = buf[i]; + if (chr < 0x80) { + total_bytes += 1; + } else if (chr < 0x800) { + total_bytes += 2; + } else { + total_bytes += 3; + } + // TODO: Handle UTF-16 code points that take two entries. + } + return total_bytes; +} + +void utf16_to_utf8(uint16_t *temp_buf, size_t buf_len) { + size_t utf16_len = ((temp_buf[0] & 0xff) - 2) / sizeof(uint16_t); + size_t utf8_len = _count_utf8_bytes(temp_buf + 1, utf16_len); + + _convert_utf16le_to_utf8(temp_buf + 1, utf16_len, (uint8_t *) temp_buf, buf_len); + ((uint8_t *) temp_buf)[utf8_len] = '\0'; +} diff --git a/USB_Host_BFF_Examples/Arduino_USB_Host_BFF_HIDReport/Arduino_USB_Host_BFF_HIDReport.ino b/USB_Host_BFF_Examples/Arduino_USB_Host_BFF_HIDReport/Arduino_USB_Host_BFF_HIDReport.ino new file mode 100644 index 000000000..a18f9f3d2 --- /dev/null +++ b/USB_Host_BFF_Examples/Arduino_USB_Host_BFF_HIDReport/Arduino_USB_Host_BFF_HIDReport.ino @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2024 Ha Thach for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" +#include "SPI.h" + +Adafruit_USBH_Host USBHost(&SPI, A1, A2); + +void setup() { + Serial.begin(115200); + + // init host stack on controller (rhport) 1 + USBHost.begin(1); + +// while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Dual: HID Device Report Example"); +} + +void loop() { + USBHost.task(); + Serial.flush(); +} + +extern "C" { + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. +// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough +// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, +// it will be skipped therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { + (void) desc_report; + (void) desc_len; + uint16_t vid, pid; + tuh_vid_pid_get(dev_addr, &vid, &pid); + + Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); + Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid); + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.printf("Error: cannot request to receive report\r\n"); + } +} + +// Invoked when device with hid interface is un-mounted +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { + Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); +} + +// Invoked when received report from device via interrupt endpoint +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) { + Serial.printf("HIDreport : "); + for (uint16_t i = 0; i < len; i++) { + Serial.printf("0x%02X ", report[i]); + } + Serial.println(); + // continue to request to receive report + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.printf("Error: cannot request to receive report\r\n"); + } +} + +} // extern C diff --git a/USB_Host_BFF_Examples/CircuitPython_DeviceInfo/code.py b/USB_Host_BFF_Examples/CircuitPython_DeviceInfo/code.py new file mode 100644 index 000000000..f8b984319 --- /dev/null +++ b/USB_Host_BFF_Examples/CircuitPython_DeviceInfo/code.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +"""USB Host BFF Device Info CircuitPython Example""" + +import time +import board +import max3421e +import usb + +spi = board.SPI() +cs = board.A1 +irq = board.A2 + +host_chip = max3421e.Max3421E(spi, chip_select=cs, irq=irq) + +while True: + print("Finding devices:") + for device in usb.core.find(find_all=True): + # pylint: disable=line-too-long + print(f"{device.idVendor:04x}:{device.idProduct:04x}: {device.manufacturer} {device.product}") + time.sleep(5) diff --git a/USB_Host_BFF_Examples/CircuitPython_HIDReport/code.py b/USB_Host_BFF_Examples/CircuitPython_HIDReport/code.py new file mode 100644 index 000000000..7f7011733 --- /dev/null +++ b/USB_Host_BFF_Examples/CircuitPython_HIDReport/code.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +"""USB Host BFF CircuitPython Example +Read key report from attached keyboard""" + + +import time +import array +import board +import max3421e +import usb + +spi = board.SPI() +cs = board.A1 +irq = board.A2 + +host_chip = max3421e.Max3421E(spi, chip_select=cs, irq=irq) + +device = None +vid = None +pid = None +while device is None: + for d in usb.core.find(find_all=True): + vid = d.idVendor + pid = d.idProduct + device = usb.core.find(idVendor=vid, idProduct=pid) + time.sleep(1) + +device.set_configuration() + +print(f"{device.idVendor:04x}:{device.idProduct:04x}: {device.manufacturer} {device.product}") + +# Test to see if the kernel is using the device and detach it. +if device.is_kernel_driver_active(0): + device.detach_kernel_driver(0) + +# Boot keyboards have 8 byte reports +buf = array.array("B", [0] * 8) +while True: + try: + count = device.read(0x81, buf) + # pylint: disable=broad-except + except Exception as e: + continue + for i in range(0, 8): + print(buf[i], end=" ") + print()