Skip to content

adding circuitpython example for USB Host with SNES like gamepad #3015

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

Merged
merged 3 commits into from
Apr 10, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions USB_SNES_Gamepad/CircuitPython_USB_Host/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# 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[:]