Skip to content

USB Host Issues Using Gamepads #10243

Open
@FoamyGuy

Description

@FoamyGuy

CircuitPython version and board name

Not working:
Adafruit CircuitPython 10.0.0-alpha.2 on 2025-04-04; Adafruit Feather RP2040 USB Host with rp2040
Adafruit CircuitPython 10.0.0-alpha.2 on 2025-04-04; Adafruit Metro RP2350 with rp2350b
Adafruit CircuitPython 9.2.7 on 2025-04-01; Adafruit Metro RP2350 with rp2350b

Working:
Adafruit CircuitPython 9.2.7 on 2025-04-01; Adafruit Feather RP2040 USB Host with rp2040

Code/REPL

# 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[:]

Behavior

Under 9.2.7 on the Feather RP2040 USB Host it can successfully find the device during a scan and read data from the device when the buttons on the gamepad are pressed.

Under 10.0.0-alpha.2 on the Feather RP2040 USB Host it does not find the device during the scan and cannot read data from it.

Under 9.2.7 and 10.0.0-alpha.2 on the Metro RP2350 it does not find the device during the scan and cannot read data from it.

Description

No response

Additional information

Most testing done with this gamepad: https://www.adafruit.com/product/6285

As an additional data point I also have a generic PS3 controller that I used for the guide page here: https://learn.adafruit.com/adafruit-feather-rp2040-with-usb-type-a-host/usb-host-read-data This controller does work on the Feather RP2040 USB Host under 10.0.0-alpha.2, but does not work on the Metro RP2350 under either 9.2.7 or 10.0.0-alpha.2

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions