; vim: ts=8 ; This library speaks the SNES protocol to the SNES controller ; Initial states: ; Data latch: low ; Data clock: high ; Serial Data: input (high-z), normally driven low ; Note: at 18MHz, 6us = 108 clock cycles ; We can probably poll the SNES controller WAY faster than it was originally ; intended to be polled. Play around with values in the burn_clock loop ; to test this out. ; (Standard) Communication Timeline ; 0us - If serial data is low, Data latch raised ; 6us - Check that Serial data is high ; 12us - Data latch dropped. Controller drives data for button B ; 18us - Data clock dropped. Read data for button B ; 24us - Data clock raised. Controller drives data for button Y ; 30us ... ; ... ; 186us - Data clock dropped. Read data for button 15 ; 192us - Data clock raised. Controller drives data for bit 16 ; 198us - Data clock remains high. Read high bit 16. (ignored) ; Data is driven HIGH if button is pressed. This may be counterintuitive. ; Drive times are read times - 6us ; Bit Time Read Button Reported ; ==== ============= =============== ; 1 18 B ; 2 30 Y ; 3 42 Select ; 4 54 Start ; 5 66 Up on joypad ; 6 78 Down on joypad ; 7 90 Left on joypad ; 8 102 Right on joypad ; 9 114 A ; 10 126 X ; 11 138 L ; 12 150 R ; 13 162 none (always high) ; 14 174 none (always high) ; 15 186 none (always high;) ; 16 198 none (always high) ; Mnemonics for some important registers and bits .equ PINC, 0x18 .equ PIN7, 0x14 .equ PIN6, 0x12 .equ PIN5, 0x11 .equ PIN4, 0x10 .equ PIN3, 0x08 .equ PIN2, 0x04 .equ PIN1, 0x02 .equ PIN0, 0x01 ; Function call to burn 99 clock cycles, including call and ret burn_clock: push r22 ldi r22, 29 burn_clock_loop: dec r22 brne burn_clock_loop pop r22 ret ; Macro to do the same thing (burns 3 * n + 4 clock cycles) .macro BURN_100_CLOCKS push r22 ; [1,2] ldi r22, 32 ; [3] dec r22 ; [4,7,10,...] brne -2 ; [5,6, 8,9,...] pop r22 ; [99,100] .endm ;extern unsigned pollSnes(void); ; This function polls the SNES controller, as described above .global pollSnes pollSnes: ;/* Register assignments for pollSnes on gcc */ ;/* Calling conventions on gcc: ; * First parameter passed in r24/r25, second in r22/23 and so on. ; * Callee must preserve r1-r17, r28/r29 ; * Result is passed in r24/r25 ; */ #define resL r24 #define resH r25 #define temp1 r22 #define temp2 r23 #define count r26 ; Serial data is PC2 ; Data latch is PC1 ; Data clock is PC0 latchhigh: sbi PINC, PIN1 ; [0] Raise data latch call burn_clock call burn_clock ; 12us ldi temp1, 8 in temp2, PINC andi temp2, PIN2; Mask out serial data pin breq unconnected cbi PINC, PIN1 ; drop data latch readbitfirst8: call burn_clock ; wait for controller to drive data ; Current time: ~18us, 30us,...102us in temp2, PINC ; Read PINC pin states ror temp2 ; Rotate PINC0 into carry flag ror temp2 ; Rotate PINC1 into carry flag ror temp2 ; Rotate PINC2 into carry flag (Controller 1 data) rol resH ; Save the current bit (in carry flag) ; To add another controller, just add another ror and ; rol into another register for return values cbi PINC, PIN0 ; drop data clock (indicating we've read this bit) call burn_clock ; Current time: ~24us, 36us,...108us sbi PINC, PIN0 ; Raise data clock dec temp1 brne readbitfirst8 ldi temp1, 8 readbitsecond8: call burn_clock ; Wait for controller to drive data ; Current time: ~114us, 126us,...198us in temp2, PINC ; Read PINC pin states ror temp2 ; Rotate PINC0 into carry flag ror temp2 ; Rotate PINC1 into carry flag ror temp2 ; Rotate PINC2 into carry flag (Controller 1 data) rol resL ; Save the current bit (in carry flag) dec temp1 breq invertvalues cbi PINC, PIN0 ; drop data clock after test to avoid dropping ; data clock after reading 16th bit call burn_clock sbi PINC, PIN0 ; raise data clock jmp readbitsecond8 ; loop invertvalues: ldi temp2, 0xff ; for xor eor resL, temp2 ; Invert for negative logic - to the SNES controller, eor resH, temp2 ; low indicates button pressed. To USB HID, the opposite. ret ; return to caller unconnected: ; Controller was disconnected clr resL ; Indicate no buttons pressed clr resH ret ; return to caller #undef resL #undef resH #undef temp1 #undef temp2 #undef count