Skip to content

Commit 5239baf

Browse files
committed
adding circuitpython example for USB Host with SNES like gamepad
1 parent 071c95b commit 5239baf

File tree

1 file changed

+193
-0
lines changed
  • USB_SNES_Gamepad/CircuitPython_USB_Host

1 file changed

+193
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
import array
5+
import time
6+
import usb.core
7+
import adafruit_usb_host_descriptors
8+
9+
# Set to true to print detailed information about all devices found
10+
VERBOSE_SCAN = True
11+
12+
BTN_DPAD_UPDOWN_INDEX = 1
13+
BTN_DPAD_RIGHTLEFT_INDEX = 0
14+
BTN_ABXY_INDEX = 5
15+
BTN_OTHER_INDEX = 6
16+
17+
DIR_IN = 0x80
18+
controller = None
19+
20+
if VERBOSE_SCAN:
21+
for device in usb.core.find(find_all=True):
22+
controller = device
23+
print("pid", hex(device.idProduct))
24+
print("vid", hex(device.idVendor))
25+
print("man", device.manufacturer)
26+
print("product", device.product)
27+
print("serial", device.serial_number)
28+
print("config[0]:")
29+
config_descriptor = adafruit_usb_host_descriptors.get_configuration_descriptor(
30+
device, 0
31+
)
32+
33+
i = 0
34+
while i < len(config_descriptor):
35+
descriptor_len = config_descriptor[i]
36+
descriptor_type = config_descriptor[i + 1]
37+
if descriptor_type == adafruit_usb_host_descriptors.DESC_CONFIGURATION:
38+
config_value = config_descriptor[i + 5]
39+
print(f" value {config_value:d}")
40+
elif descriptor_type == adafruit_usb_host_descriptors.DESC_INTERFACE:
41+
interface_number = config_descriptor[i + 2]
42+
interface_class = config_descriptor[i + 5]
43+
interface_subclass = config_descriptor[i + 6]
44+
print(f" interface[{interface_number:d}]")
45+
print(
46+
f" class {interface_class:02x} subclass {interface_subclass:02x}"
47+
)
48+
elif descriptor_type == adafruit_usb_host_descriptors.DESC_ENDPOINT:
49+
endpoint_address = config_descriptor[i + 2]
50+
if endpoint_address & DIR_IN:
51+
print(f" IN {endpoint_address:02x}")
52+
else:
53+
print(f" OUT {endpoint_address:02x}")
54+
i += descriptor_len
55+
56+
# get the first device found
57+
device = None
58+
while device is None:
59+
for d in usb.core.find(find_all=True):
60+
device = d
61+
break
62+
time.sleep(0.1)
63+
64+
# set configuration so we can read data from it
65+
device.set_configuration()
66+
print(
67+
f"configuration set for {device.manufacturer}, {device.product}, {device.serial_number}"
68+
)
69+
70+
# Test to see if the kernel is using the device and detach it.
71+
if device.is_kernel_driver_active(0):
72+
device.detach_kernel_driver(0)
73+
74+
# buffer to hold 64 bytes
75+
buf = array.array("B", [0] * 64)
76+
77+
78+
def print_array(arr, max_index=None, fmt="hex"):
79+
"""
80+
Print the values of an array
81+
:param arr: The array to print
82+
:param max_index: The maximum index to print. None means print all.
83+
:param fmt: The format to use, either "hex" or "bin"
84+
:return: None
85+
"""
86+
out_str = ""
87+
if max_index is None or max_index >= len(arr):
88+
length = len(arr)
89+
else:
90+
length = max_index
91+
92+
for _ in range(length):
93+
if fmt == "hex":
94+
out_str += f"{int(arr[_]):02x} "
95+
elif fmt == "bin":
96+
out_str += f"{int(arr[_]):08b} "
97+
print(out_str)
98+
99+
100+
def reports_equal(report_a, report_b, check_length=None):
101+
"""
102+
Test if two reports are equal. Accounting for any IGNORE_INDEXES
103+
104+
:param report_a: First report data
105+
:param report_b: Second report data
106+
:return: True if the reports are equal, otherwise False.
107+
"""
108+
if (
109+
report_a is None
110+
and report_b is not None
111+
or report_b is None
112+
and report_a is not None
113+
):
114+
return False
115+
116+
length = len(report_a) if check_length is None else check_length
117+
for _ in range(length):
118+
if report_a[_] != report_b[_]:
119+
return False
120+
return True
121+
122+
123+
def differing_indexes(report_a, report_b):
124+
if (
125+
report_a is None
126+
and report_b is not None
127+
or report_b is None
128+
and report_a is not None
129+
):
130+
return None
131+
indexes = []
132+
for _ in range(len(report_a)):
133+
if report_a[_] != report_b[_]:
134+
indexes.append(_)
135+
return indexes
136+
137+
138+
idle_state = None
139+
prev_state = None
140+
141+
while True:
142+
try:
143+
count = device.read(0x81, buf)
144+
# print(f"read size: {count}")
145+
except usb.core.USBTimeoutError:
146+
continue
147+
148+
if idle_state is None:
149+
idle_state = buf[:]
150+
print("Idle state:")
151+
print_array(idle_state[:8], max_index=count)
152+
print()
153+
154+
if not reports_equal(buf, prev_state, 8) and not reports_equal(buf, idle_state, 8):
155+
# print_array(buf, max_index=count)
156+
# diff_indexes = differing_indexes(idle_state, buf)
157+
# print(f"diff indexes: {diff_indexes}")
158+
159+
# for idx in diff_indexes:
160+
# print(f"{idx}: {idle_state[idx]} -> {buf[idx]}")
161+
# print_array(buf)
162+
163+
if buf[BTN_DPAD_UPDOWN_INDEX] == 0x0:
164+
print("D-Pad UP pressed")
165+
elif buf[BTN_DPAD_UPDOWN_INDEX] == 0xFF:
166+
print("D-Pad DOWN pressed")
167+
168+
if buf[BTN_DPAD_RIGHTLEFT_INDEX] == 0:
169+
print("D-Pad LEFT pressed")
170+
elif buf[BTN_DPAD_RIGHTLEFT_INDEX] == 0xFF:
171+
print("D-Pad RIGHT pressed")
172+
173+
if buf[BTN_ABXY_INDEX] == 0x2F:
174+
print("A pressed")
175+
elif buf[BTN_ABXY_INDEX] == 0x4F:
176+
print("B pressed")
177+
elif buf[BTN_ABXY_INDEX] == 0x1F:
178+
print("X pressed")
179+
elif buf[BTN_ABXY_INDEX] == 0x8F:
180+
print("Y pressed")
181+
182+
if buf[BTN_OTHER_INDEX] == 0x01:
183+
print("L shoulder pressed")
184+
elif buf[BTN_OTHER_INDEX] == 0x02:
185+
print("R shoulder pressed")
186+
elif buf[BTN_OTHER_INDEX] == 0x10:
187+
print("SELECT pressed")
188+
elif buf[BTN_OTHER_INDEX] == 0x20:
189+
print("START pressed")
190+
191+
print_array(buf[:8])
192+
193+
prev_state = buf[:]

0 commit comments

Comments
 (0)