Last active
June 21, 2025 13:50
-
-
Save urish/25aa821dc23b8299ce878c7236b11489 to your computer and use it in GitHub Desktop.
gamepad_pmod_circuitpython_pio
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import usb_hid | |
| from joystick_xl.hid import create_joystick | |
| # enable default CircuitPython USB HID devices as well as JoystickXL | |
| usb_hid.enable( | |
| ( | |
| usb_hid.Device.KEYBOARD, | |
| usb_hid.Device.MOUSE, | |
| usb_hid.Device.CONSUMER_CONTROL, | |
| create_joystick(axes=2, buttons=8, hats=1), | |
| ) | |
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Tiny Tapeout Gamepad Pmod Driver for CircuitPython | |
| https://github.com/psychogenic/gamepad-pmod | |
| DATA -> GP19 (gamepad_data) –- sampled by PIO | |
| CLOCK -> GP18 (gamepad_clk) –- edge-detect inside PIO | |
| LATCH -> GP17 (gamepad_latch) –- read in plain Python | |
| Protocol: | |
| * While LATCH is low the pad shifts out 24 bits, MSB first, one bit | |
| per rising edge of CLOCK. | |
| * When LATCH goes low→high the frame is considered complete and the | |
| current 24-bit word is printed. | |
| """ | |
| import array | |
| import board | |
| import rp2pio | |
| import adafruit_pioasm | |
| from joystick_xl.joystick import Joystick | |
| # Pin mapping | |
| LATCH_GPIO = 17 | |
| CLOCK_GPIO = 18 | |
| DATA_GPIO = 19 | |
| LATCH_PIN = board.GP17 | |
| # Gamepad buttons | |
| GAMEPAD_BUTTONS = ['L', 'R', 'X', 'A', 'Right', 'Left', 'Down', 'Up', 'Start', 'Select', 'Y', 'B'] | |
| GP_L = 1 << 0 | |
| GP_R = 1 << 1 | |
| GP_X = 1 << 2 | |
| GP_A = 1 << 3 | |
| GP_RIGHT = 1 << 4 | |
| GP_LEFT = 1 << 5 | |
| GP_DOWN = 1 << 6 | |
| GP_UP = 1 << 7 | |
| GP_START = 1 << 8 | |
| GP_SELECT = 1 << 9 | |
| GP_Y = 1 << 10 | |
| GP_B = 1 << 11 | |
| # PIO program | |
| PIO_SRC = f""" | |
| .program gamepad_sniff | |
| frame_wait: | |
| wait 0 gpio {LATCH_GPIO} | |
| set x, 23 ; need 24 clock cycles | |
| mov isr, null ; clear previous data | |
| bitloop: ; ---- shift 24 bits while latch is LOW ---- | |
| wait 1 gpio {CLOCK_GPIO} ; clk ↑ | |
| mov osr, pins | |
| out null, 2 ; Discard clock / latch values | |
| in osr, 1 ; sample DATA | |
| wait 0 gpio {CLOCK_GPIO} ; clk ↓ | |
| jmp x-- bitloop ; repeat 24× | |
| ; ---------- wait for latch HIGH (end-of-frame) ------------------ | |
| wait 1 gpio {LATCH_GPIO} | |
| push ; send 24-bit word to FIFO | |
| jmp frame_wait ; capture next frame | |
| """ | |
| assembled = adafruit_pioasm.assemble(PIO_SRC) | |
| # ---------------- State machine --------------------------------------------- | |
| sm = rp2pio.StateMachine( | |
| assembled, | |
| frequency=2_000_000, # state-machine clock – plenty for ~1 MHz CLK | |
| first_in_pin=LATCH_PIN, # DATA is the one pin read by `IN PINS,1` | |
| in_pin_count=3, | |
| in_shift_right=False, # shift MSB-first | |
| ) | |
| def decode_message(value: int): | |
| result = [] | |
| for i, btn in enumerate(GAMEPAD_BUTTONS): | |
| if value & (1 << i): | |
| result.append(btn) | |
| return result | |
| def map_hat_directions(value: int): | |
| if value & GP_UP: | |
| if value & GP_LEFT: | |
| return 7 | |
| if value & GP_RIGHT: | |
| return 1 | |
| return 0 | |
| if value & GP_DOWN: | |
| if value & GP_LEFT: | |
| return 5 | |
| if value & GP_RIGHT: | |
| return 3 | |
| return 4 | |
| if value & GP_RIGHT: | |
| return 2 | |
| if value & GP_LEFT: | |
| return 6 | |
| return 8 | |
| buf = array.array('I', [0]) | |
| js = Joystick() | |
| print("Ready – waiting for frames …") | |
| while True: | |
| sm.readinto(buf) | |
| value = buf[0] | |
| js.update_button((0, value & GP_START)) | |
| js.update_button((1, value & GP_SELECT)) | |
| js.update_hat((0, map_hat_directions(value))) | |
| print(f"{value:024b} 0x{value:06X} {decode_message(value)}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment