Skip to content

USB Host Issues Using Gamepads #10243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
FoamyGuy opened this issue Apr 10, 2025 · 11 comments
Open

USB Host Issues Using Gamepads #10243

FoamyGuy opened this issue Apr 10, 2025 · 11 comments

Comments

@FoamyGuy
Copy link
Collaborator

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

@FoamyGuy FoamyGuy added the bug label Apr 10, 2025
@FoamyGuy
Copy link
Collaborator Author

Tested with the Fruit Jam next and got some more positive results there.

Adafruit CircuitPython 9.2.7 on 2025-04-01; Adafruit Fruit Jam with rp2350b
Adafruit CircuitPython 10.0.0-alpha.2 on 2025-04-04; Adafruit Fruit Jam with rp2350b

Both find the gamepad and are able to read data from it.

I'm going to try swapping to a known good USB A breakout. All of the Metro testing was done with a new one that I haven't used previously. I have a few that I've used successfully with mice and keyboards on Metro so they are known good, I'll try one of them to attempt to rule out a cable issue causing the Metro RP2350 results.

@FoamyGuy
Copy link
Collaborator Author

I tried with a different USB Host cable breakout on the Metro RP2350 and confirmed it still is unable to find the SNES like gamepad under both 9.2.7 and 10.0.0-alpha.2

@FoamyGuy
Copy link
Collaborator Author

On the Metro RP2350 the gamepad is able to be found and have data read from it when it is connected via a CH334F USB hub (https://www.adafruit.com/product/5999). All testing prior to this comment was performed with the USB jack breakout (https://www.adafruit.com/product/4449) connected directly to the Metro.

On the Metro RP2350 under both CircuitPython 9.2.7 and 10.0.0-alpna.2 the gamepad does work if it's connected thru a CH334F hub, but not when connected directly.

@tannewt tannewt modified the milestones: 10.0.0, 10.x.x Apr 10, 2025
@tannewt
Copy link
Member

tannewt commented Apr 10, 2025

@hathach Want to take a look at this? Maybe a low speed device that doesn't work when directly connected.

@hathach
Copy link
Member

hathach commented Apr 14, 2025

Yeah possibly, please try to update pio-usb 0.7.1 which includes this fix sekigon-gonnoc/Pico-PIO-USB#176 for direct low speed attach

@FoamyGuy
Copy link
Collaborator Author

I tried today with pico-usb 0.7.1 on Metro RP2350 with the controller directly connected to the metro USB host pins, it is still not finding the device during the scan on that version as well.

@hathach
Copy link
Member

hathach commented Apr 14, 2025

then it is probably race condition of some kind, I will try to order the gamepad along with mouse that cause repeated hub connection issue as well for troubleshooting. Quick question, does it work now with rp2040 ?

@FoamyGuy
Copy link
Collaborator Author

I tried with a build for the Feather RP2040 USB Host using pio-usb 0.7.1. I am still seeing the same behavior on that version as with circuitpython 10.x on that device. No devices found with scan and unable to read data.

@FoamyGuy
Copy link
Collaborator Author

I tested this today on a Feather RP2040 USB Host with the gamepad connected thru a CH334F USB hub and it does work that way, same as the Metro RP2350.

@hathach
Copy link
Member

hathach commented Apr 16, 2025

gamepad is in shipping, it may take a few days. Will test/troubleshoot this once I got this on my hands.

@hathach
Copy link
Member

hathach commented Apr 24, 2025

I am able to reproduce the issue, the gamepad somehow return 0 bytes in the 1st get device descriptor. Not sure why, I am in the middle of improving/enhancing host/hub driver. Will try to troubleshoot this as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants