Once you've got a USB device connected and you've gotten it's device info, the next thing to do is start reading data from it. This example will illustrate how to read raw data packets from HID devices like generic USB game pads.
To use with CircuitPython, you need to first install the USB host descriptors library into the lib folder onto your CIRCUITPY drive. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, and copy the entire lib folder and the code.py file to your CIRCUITPY drive.
Your CIRCUITPY/lib folder should contain the following file:
- adafruit_usb_host_descriptors.mpy

# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries # # SPDX-License-Identifier: MIT import array import time import usb.core import adafruit_usb_host_descriptors # Set to true to print detailed information about all devices found VERBOSE_SCAN = True # indexes within the reports to ignore when determining equality. # some devices send alternating values with each report, this # allows to ignore those and focus only on bytes that are # affected by buttons. Value of [19] will ignore data at index 19. # Check your own output for values that change even when you don't # do anything on the controller and add their indexes here. IGNORE_INDEXES = [] DIR_IN = 0x80 controller = None if VERBOSE_SCAN: for device in usb.core.find(find_all=True): controller = device print("pid", hex(device.idProduct)) print("vid", hex(device.idVendor)) print("man", device.manufacturer) print("product", device.product) print("serial", device.serial_number) print("config[0]:") config_descriptor = adafruit_usb_host_descriptors.get_configuration_descriptor( device, 0 ) i = 0 while i < len(config_descriptor): descriptor_len = config_descriptor[i] descriptor_type = config_descriptor[i + 1] if descriptor_type == adafruit_usb_host_descriptors.DESC_CONFIGURATION: config_value = config_descriptor[i + 5] print(f" value {config_value:d}") elif descriptor_type == adafruit_usb_host_descriptors.DESC_INTERFACE: interface_number = config_descriptor[i + 2] interface_class = config_descriptor[i + 5] interface_subclass = config_descriptor[i + 6] print(f" interface[{interface_number:d}]") print( f" class {interface_class:02x} subclass {interface_subclass:02x}" ) elif descriptor_type == adafruit_usb_host_descriptors.DESC_ENDPOINT: endpoint_address = config_descriptor[i + 2] if endpoint_address & DIR_IN: print(f" IN {endpoint_address:02x}") else: print(f" OUT {endpoint_address:02x}") i += descriptor_len # get the first device found device = None while device is None: for d in usb.core.find(find_all=True): device = d break time.sleep(0.1) # set configuration so we can read data from it device.set_configuration() print(f"configuration set for {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) # buffer to hold 64 bytes buf = array.array("B", [0] * 64) def print_array(arr, max_index=None, fmt="hex"): """ Print the values of an array :param arr: The array to print :param max_index: The maximum index to print. None means print all. :param fmt: The format to use, either "hex" or "bin" :return: None """ out_str = "" if max_index is None or max_index >= len(arr): length = len(arr) else: length = max_index for _ in range(length): if fmt == "hex": out_str += f"{int(arr[_]):02x} " elif fmt == "bin": out_str += f"{int(arr[_]):08b} " print(out_str) def reports_equal(report_a, report_b): """ Test if two reports are equal. Accounting for any IGNORE_INDEXES :param report_a: First report data :param report_b: Second report data :return: True if the reports are equal, otherwise False. """ if report_a is None and report_b is not None or \ report_b is None and report_a is not None: return False for _ in range(len(report_a)): if IGNORE_INDEXES is not None and _ not in IGNORE_INDEXES: if report_a[_] != report_b[_]: return False return True idle_state = None prev_state = None while True: try: count = device.read(0x81, buf) # print(f"read size: {count}") except usb.core.USBTimeoutError: continue if idle_state is None: idle_state = buf[:] print("Idle state:") print_array(idle_state, max_index=count) print() if not reports_equal(buf, prev_state) and not reports_equal(buf, idle_state): print_array(buf, max_index=count) prev_state = buf[:]
Plug in a USB device to the USB A host port on the Feather and run this example code. If VERBOSE_SCAN
is True
you'll see its device info. Then the code will read data from the USB device and print it out when it changes. Once the code is running, start pressing buttons on the controller to observe the different values being printed with each button or combination.
Page last edited February 19, 2025
Text editor powered by tinymce.