The Feather RP2040 USB Host and Metro RP2350 devices both support USB Host and can be used with this game controller.
Feather RP2040 USB Host Wiring
Connecting to the Feather RP2040 USB Host requires plugging in the USB game controller to the Feather's Host port.
Metro RP2350 USB Host Wiring
Connecting to the Metro RP2350 USB Host port requires soldering pins to the broken out USB Host connections as shown in this guide page. Make the following connections between the Metro USB Host pins and USB Host breakout cable.
- GND to Black wire
- D+ to Green wire
- D- to White wire
- 5V to Red red
Then connect the USB Game Controller to the USB Host port on the breakout cable.
Fruit Jam Mini Computer
Connecting to the Fruit Jam requires plugging in the USB game controller to one of the Fruit Jam's USB Host ports.
As of CircuitPython version 10.0.0-beta.3, the wired USB keyboard works on CircuitPython when connected directly to the USB Host pins or port. Prior releases of CircuitPython required connecting through a USB hub such as the CH334F.
Demo Code
Connect the USB game controller while the microcontroller is unplugged from power. Only power up the microcontroller once the USB host connections are made securely.
Press the 'Download Project Bundle' button below to download a zip file containing the demo code. Connect your computer to your Feather or Metro via a known good data+power USB cable. Copy code.py and the required libraries to the CIRCUITPY drive on your device which appears when the board is connected to your computer via good USB cable.
The code will scan for connected USB devices, printing out information about each one it finds.
Then it will start a loop reading from the first device it found and printing messages when each of the gamepad buttons are pressed. Check the serial console to see the output.
# 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
BTN_DPAD_UPDOWN_INDEX = 1
BTN_DPAD_RIGHTLEFT_INDEX = 0
BTN_ABXY_INDEX = 5
BTN_OTHER_INDEX = 6
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, check_length=None):
"""
Test if two reports are equal. If check_length is provided then
check for equality in only the first check_length number of bytes.
:param report_a: First report data
:param report_b: Second report data
:param check_length: How many bytes to check
: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
length = len(report_a) if check_length is None else check_length
for _ in range(length):
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[:8], max_index=count)
print()
if not reports_equal(buf, prev_state, 8) and not reports_equal(buf, idle_state, 8):
if buf[BTN_DPAD_UPDOWN_INDEX] == 0x0:
print("D-Pad UP pressed")
elif buf[BTN_DPAD_UPDOWN_INDEX] == 0xFF:
print("D-Pad DOWN pressed")
if buf[BTN_DPAD_RIGHTLEFT_INDEX] == 0:
print("D-Pad LEFT pressed")
elif buf[BTN_DPAD_RIGHTLEFT_INDEX] == 0xFF:
print("D-Pad RIGHT pressed")
if buf[BTN_ABXY_INDEX] == 0x2F:
print("A pressed")
elif buf[BTN_ABXY_INDEX] == 0x4F:
print("B pressed")
elif buf[BTN_ABXY_INDEX] == 0x1F:
print("X pressed")
elif buf[BTN_ABXY_INDEX] == 0x8F:
print("Y pressed")
if buf[BTN_OTHER_INDEX] == 0x01:
print("L shoulder pressed")
elif buf[BTN_OTHER_INDEX] == 0x02:
print("R shoulder pressed")
elif buf[BTN_OTHER_INDEX] == 0x10:
print("SELECT pressed")
elif buf[BTN_OTHER_INDEX] == 0x20:
print("START pressed")
# print_array(buf[:8])
prev_state = buf[:]
The Controller is Not Acting Right
The program presented works for the generic SNES controller sold by Adafruit. Controllers from other sources may have key mappings a bit different from the Adafruit controller. This will help you adjust the code to your controller.
Near the top of the program are the following constants:
BTN_DPAD_UPDOWN_INDEX = 1 BTN_DPAD_RIGHTLEFT_INDEX = 0 BTN_ABXY_INDEX = 5 BTN_OTHER_INDEX = 6
These are the values obtained with the Adafruit controller. You can change the numbers to the ones output by your controller. For example a customer stated they changed BTN_DPAD_UPDOWN_INDEX to 4 from 1 and BTN_DPAD_RIGHTLEFT_INDEX to 3 from 0.
How do you find the right numbers?
- Trial and error based on the output of the program (shown above)
- You can use a HID key mapper website. https://joypad.ai/ is one such site (not tried).
If you are positive you bought your controller from Adafruit and it's not mapped right, you can post to the Adafruit forums for assistance.
Page last edited October 14, 2025
Text editor powered by tinymce.