import rp2
from machine import Pin
import time

#----------------------------------------------------------------
# Gamepad Pmod hardware simulator (using the RP2040 PIO peripheral)
# Copyright (C) 2025, Tiny Tapeout LTD
# SPDX-License-Identifier: Apache-2.0
# Author: Uri Shaked
#
# This program uses:
#   - an OUT pin (data) that is driven by the OUT instruction
#   - two sideset pins: the first will be used for the clock (clk),
#     the second for the latch (latch).
#
# (At 1 MHz SM frequency, the bit‐transmission takes 1 + 24×2 + 2 = 51 cycles.
# With delay count = 1600, the full cycle is about (51+1600)=1651 µs ≈ 600 Hz.)
#----------------------------------------------------------------

@rp2.asm_pio(
    out_init=(rp2.PIO.OUT_LOW, ),           # data pin starts low
    in_shiftdir=rp2.PIO.SHIFT_RIGHT,
    out_shiftdir=rp2.PIO.SHIFT_RIGHT,
    sideset_init=(rp2.PIO.OUT_LOW, ) * 2,   # default: clk=0, latch=0
)
def gamepad_tx():
    pull()                              # Block until a word is in FIFO; load OSR.
    mov(x, osr)                         # Save OSR to X (so we have a “cached” value).
    set(y, 25)                           
    in_(y, 26)                          # ISR <- 1600 (0x640)
    wrap_target()
    pull(noblock)                       # If a new word is waiting, update OSR.
    mov(x, osr)                         # Save OSR to X (so we have a “cached” value).
    # --- Bit shifting loop (24 bits) ---
    set(y, 23)             .side(0b00)  # Initialize Y counter; normal state.
    label("bit_loop")
    out(pins, 1)           .side(0b00)  # Shift one bit (data on OUT pin) with clock low.
    jmp(y_dec, "bit_loop") .side(0b10)  # Data valid on clock's rising edge.
    # --- Latch pulse ---
    set(pins, 0)           .side(0b01) 
    nop()                  .side(0b00)
    # --- Delay loop ---
    mov(y, isr)                         # ISR is 1600, giving us ~1/600 period at 1 MHz.
    label("delay_loop")
    jmp(y_dec, "delay_loop")
    wrap()



class Gamepad:
    def __init__(self, data_pin, latch_pin, clk_pin, sm_id=0, freq=1000000):
        """
        Initialize the Gamepad interface.
        
        Arguments:
          data_pin : the GPIO number for the data output.
          clk_pin  : the GPIO number for the clock output (first sideset pin).
          latch_pin: the GPIO number for the latch output (second sideset pin).
          sm_id    : which PIO state machine to use (default 0).
          freq     : state machine clock frequency (default 1 MHz, giving a ~500 kHz bit clock).
        """
        assert clk_pin == latch_pin + 1

        # Initialize the pins.
        self.data_pin = Pin(data_pin, Pin.OUT)
        self.clk_pin = Pin(clk_pin, Pin.OUT)
        self.latch_pin = Pin(latch_pin, Pin.OUT)
        
        # 0xffffff means both controllers are disconnected
        self._last_value = 0xffffff

        # Create the state machine.
        # The state machine is configured with:
        #  - out_base set to the data pin.
        #  - sideset_base set to the clock pin (latch will be the next pin).
        self.sm = rp2.StateMachine(sm_id, gamepad_tx,
                                   freq=freq,
                                   out_base=self.data_pin,
                                   sideset_base=self.latch_pin)
        self.running = False

    def start(self):
        """
        Start the PIO state machine.
        """
        if not self.running:
            self.sm.active(1)
            self.sm.put(self._last_value)
            self.running = True

    def stop(self):
        """
        Disable the PIO machine.
        """
        self.sm.active(0)
        self.running = False

    def send(self, value: int):
        """
        Set a new 24-bit value to transmit.
        """
        self.sm.put(value)
        self._last_value = value

#----------------------------------------------------------------
# Example usage
#----------------------------------------------------------------
if __name__ == "__main__":
    # Create a Gamepad instance using (for example) GPIO0 for data,
    # GPIO1 for clock, and GPIO2 for latch.
    gp = Gamepad(latch_pin=17, clk_pin=18, data_pin=19, sm_id=0, freq=1000000)
    
    # Set an initial 24-bit value (here, 0xABCDEF).
    print("Sending:", hex(0xabcdef))
    gp.send(0xABCDEF)
    
    # Start the state machine.
    gp.start()
    
    try:
        i = 0
        while True:
            time.sleep(0.2)
            print("Sending:", hex(1 << i))
            gp.send(1 << i)
            i += 1
            if i == 24:
                i = 0
    except KeyboardInterrupt:
        gp.stop()
        print("Gamepad stopped.")
BOOTSELLED1239USBRaspberryPiPico©2020RP2-8020/21P64M15.00TTT
D0D1D2D3D4D5D6D7GNDLOGIC