diff --git a/examples/DualRole/Simple/device_info_max3421e/device_info_max3421e.ino b/examples/DualRole/Simple/device_info_max3421e/device_info_max3421e.ino new file mode 100644 index 00000000..37c40833 --- /dev/null +++ b/examples/DualRole/Simple/device_info_max3421e/device_info_max3421e.ino @@ -0,0 +1,242 @@ +/********************************************************************* + 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 via + * device cdc (Serial) as follows: + * 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" + +// USBHost is defined in usbh_helper.h +// USB Host using MAX3421E: SPI, CS, INT +#include "SPI.h" + +#if defined(ARDUINO_METRO_ESP32S2) +Adafruit_USBH_Host USBHost(&SPI, 15, 14); + +#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) +Adafruit_USBH_Host USBHost(&SPI, 27, 33); + +#else + +// Default CS and INT are pin 10, 9 +Adafruit_USBH_Host USBHost(&SPI, 10, 9); +#endif + +// 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 }; + +void setup() { + Serial.begin(115200); + + // init host stack on controller (rhport) 1 + // For rp2040: this is called in core1's setup1() + USBHost.begin(1); + +// while ( !Serial ) delay(10); // wait for native usb + 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'; +}