Skip to content

first commit for Super Nintendo USB controller guide #2625

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 2 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
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
74 changes: 74 additions & 0 deletions Super_Nintendo_USB_Controller/boot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# SPDX-FileCopyrightText: 2023 Robert Dale Smith for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense

import usb_hid

# This is only one example of a gamepad descriptor, and may not suit your needs.
GAMEPAD_REPORT_DESCRIPTOR = bytes((
0x05, 0x01, # USAGE_PAGE (Generic Desktop)
0x09, 0x05, # USAGE (Gamepad)
0xa1, 0x01, # COLLECTION (Application)
0x15, 0x00, # LOGICAL_MINIMUM (0)
0x25, 0x01, # LOGICAL_MAXIMUM (1)
0x35, 0x00, # PHYSICAL_MINIMUM (0)
0x45, 0x01, # PHYSICAL_MAXIMUM (1)
0x75, 0x01, # REPORT_SIZE (1)
0x95, 0x0e, # REPORT_COUNT (14)
0x05, 0x09, # USAGE_PAGE (Button)
0x19, 0x01, # USAGE_MINIMUM (Button 1)
0x29, 0x0e, # USAGE_MAXIMUM (Button 14)
0x81, 0x02, # INPUT (Data,Var,Abs)
0x95, 0x02, # REPORT_COUNT (3)
0x81, 0x01, # INPUT (Cnst,Ary,Abs)
0x05, 0x01, # USAGE_PAGE (Generic Desktop)
0x25, 0x07, # LOGICAL_MAXIMUM (7)
0x46, 0x3b, 0x01, # PHYSICAL_MAXIMUM (315)
0x75, 0x04, # REPORT_SIZE (4)
0x95, 0x01, # REPORT_COUNT (1)
0x65, 0x14, # UNIT (Eng Rot:Angular Pos)
0x09, 0x39, # USAGE (Hat switch)
0x81, 0x42, # INPUT (Data,Var,Abs,Null)
0x65, 0x00, # UNIT (None)
0x95, 0x01, # REPORT_COUNT (1)
0x81, 0x01, # INPUT (Cnst,Ary,Abs)
0x26, 0xff, 0x00, # LOGICAL_MAXIMUM (255)
0x46, 0xff, 0x00, # PHYSICAL_MAXIMUM (255)
0x09, 0x30, # USAGE (X)
0x09, 0x31, # USAGE (Y)
0x09, 0x32, # USAGE (Z)
0x09, 0x35, # USAGE (Rz)
0x75, 0x08, # REPORT_SIZE (8)
0x95, 0x04, # REPORT_COUNT (6)
0x81, 0x02, # INPUT (Data,Var,Abs)
0x06, 0x00, 0xff, # USAGE_PAGE (Vendor Specific)
0x09, 0x20, # Unknown
0x09, 0x21, # Unknown
0x09, 0x22, # Unknown
0x09, 0x23, # Unknown
0x09, 0x24, # Unknown
0x09, 0x25, # Unknown
0x09, 0x26, # Unknown
0x09, 0x27, # Unknown
0x09, 0x28, # Unknown
0x09, 0x29, # Unknown
0x09, 0x2a, # Unknown
0x09, 0x2b, # Unknown
0x95, 0x0c, # REPORT_COUNT (12)
0x81, 0x02, # INPUT (Data,Var,Abs)
0x0a, 0x21, 0x26, # Unknown
0x95, 0x08, # REPORT_COUNT (8)
0xb1, 0x02, # FEATURE (Data,Var,Abs)
0xc0 # END_COLLECTION
))

gamepad = usb_hid.Device(
report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
usage_page=0x01, # Generic Desktop Control
usage=0x05, # Gamepad
report_ids=(0,), # Descriptor uses report ID 0.
in_report_lengths=(19,), # This gamepad sends 6 bytes in its report.
out_report_lengths=(0,), # It does not receive any reports.
)

usb_hid.enable((gamepad,))
128 changes: 128 additions & 0 deletions Super_Nintendo_USB_Controller/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# SPDX-FileCopyrightText: 2023 Robert Dale Smith for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# Simple Super Nintendo controller to standard USB HID gamepad with DirectInput button mapping.
# Tested on KB2040

import time
import board
import digitalio
import usb_hid

# Update the SNES Controller pins based on your input
LATCH_PIN = board.D6
CLOCK_PIN = board.D5
DATA_PIN = board.D7

# Set up pins for SNES Controller
latch = digitalio.DigitalInOut(LATCH_PIN)
latch.direction = digitalio.Direction.OUTPUT

clock = digitalio.DigitalInOut(CLOCK_PIN)
clock.direction = digitalio.Direction.OUTPUT

data = digitalio.DigitalInOut(DATA_PIN)
data.direction = digitalio.Direction.INPUT
data.pull = digitalio.Pull.UP # pull-up as a default

# SNES to USB button mapping
buttonmap = {
"B": (0, 0, 0x2), # Button 1
"Y": (1, 0, 0x1), # Button 3
"Select": (2, 1, 0x01), # Button 9
"Start": (3, 1, 0x02), # Button 10
"Up": (4, 0, 0), # D-pad North
"Down": (5, 0, 0), # D-pad South
"Left": (6, 0, 0), # D-pad East
"Right": (7, 0, 0), # D-pad West
"A": (8, 0, 0x4), # Button 2
"X": (9, 0, 0x8), # Button 4
"L": (10, 0, 0x10), # Button 5
"R": (11, 0, 0x20) # Button 6
}

# D-pad buttons to hat-switch value
dpad_state = {
"Up": 0,
"Down": 0,
"Left": 0,
"Right": 0,
}

hat_map = {
(0, 0, 0, 0): 0x08, # Released
(1, 0, 0, 0): 0x00, # N
(1, 1, 0, 0): 0x01, # NE
(0, 1, 0, 0): 0x02, # E
(0, 1, 0, 1): 0x03, # SE
(0, 0, 0, 1): 0x04, # S
(0, 0, 1, 1): 0x05, # SW
(0, 0, 1, 0): 0x06, # W
(1, 0, 1, 0): 0x07, # NW
}

def read_snes_controller():
data_bits = []
latch.value = True
time.sleep(0.000012) # 12µs
latch.value = False

for _ in range(16):
time.sleep(0.000006) # Wait 6µs
data_bits.append(data.value)

clock.value = True
time.sleep(0.000006) # 6µs
clock.value = False
time.sleep(0.000006) # 6µs

return data_bits

# Find the gamepad device in the usb_hid devices
gamepad_device = None
for device in usb_hid.devices:
if device.usage_page == 0x01 and device.usage == 0x05:
gamepad_device = device
break

if gamepad_device is not None:
print("Gamepad device found!")
else:
print("Gamepad device not found.")

report = bytearray(19)
report[2] = 0x08 # default released hat switch value
prev_report = bytearray(report)

while True:
button_state = read_snes_controller()
all_buttons = list(buttonmap.keys())

for idx, button in enumerate(all_buttons):
index, byte_index, button_value = buttonmap[button]
is_pressed = not button_state [index] # True if button is pressed

if button in dpad_state: # If it's a direction button
dpad_state[button] = 1 if is_pressed else 0
else:
if is_pressed:
report[byte_index] |= button_value
else: # not pressed, reset button state
report[byte_index] &= ~button_value

# SOCD (up priority and neutral horizontal)
if (dpad_state["Up"] and dpad_state["Down"]):
dpad_state["Down"] = 0
if (dpad_state["Left"] and dpad_state["Right"]):
dpad_state["Left"] = 0
dpad_state["Right"] = 0

# Extract the dpad_state to a tuple and get the corresponding hat value
dpad_tuple = (dpad_state["Up"], dpad_state["Right"], dpad_state["Left"], dpad_state["Down"])
report[2] = hat_map[dpad_tuple] # Assuming report[2] is the byte for the hat switch

if prev_report != report:
gamepad_device.send_report(report)
prev_report[:] = report

time.sleep(0.1)