usb host bff examples
Adding Arduino and CircuitPython USB Host BFF examples for device info and HID report
This commit is contained in:
parent
5102e9d6d9
commit
c2888cf63a
4 changed files with 379 additions and 0 deletions
|
|
@ -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';
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
23
USB_Host_BFF_Examples/CircuitPython_DeviceInfo/code.py
Normal file
23
USB_Host_BFF_Examples/CircuitPython_DeviceInfo/code.py
Normal file
|
|
@ -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)
|
||||||
49
USB_Host_BFF_Examples/CircuitPython_HIDReport/code.py
Normal file
49
USB_Host_BFF_Examples/CircuitPython_HIDReport/code.py
Normal file
|
|
@ -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(device.idVendor, device.idProduct, device.manufacturer, device.product, device.serial_number)
|
||||||
|
|
||||||
|
# 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()
|
||||||
Loading…
Reference in a new issue