]> git.zarvox.org Git - usbsnes.git/blob - snes.S
Functional code for SNES port 2.
[usbsnes.git] / snes.S
1 ; vim: ts=8
2 ; This library speaks the SNES protocol to the SNES controller
3
4 ; Initial states:
5 ; Data latch: low
6 ; Data clock: high
7 ; Serial Data: input (high-z), normally driven low
8
9 ; Note: at 18MHz, 6us = 108 clock cycles
10 ; We can probably poll the SNES controller WAY faster than it was originally
11 ; intended to be polled.  Play around with values in the burn_clock loop
12 ; to test this out.
13
14 ; (Standard) Communication Timeline
15 ; 0us   - If serial data is low, Data latch raised
16 ; 6us   - Check that Serial data is high
17 ; 12us  - Data latch dropped.  Controller drives data for button B
18 ; 18us  - Data clock dropped.  Read data for button B
19 ; 24us  - Data clock raised.   Controller drives data for button Y
20 ; 30us ...
21 ; ...
22 ; 186us - Data clock dropped.  Read data for button 15
23 ; 192us - Data clock raised.  Controller drives data for bit 16
24 ; 198us - Data clock remains high. Read high bit 16. (ignored)
25
26 ; Data is driven HIGH if button is pressed.  This may be counterintuitive.
27
28 ; Drive times are read times - 6us
29
30 ; Bit     Time Read       Button Reported
31 ; ====   =============    ===============
32 ; 1          18           B
33 ; 2          30           Y
34 ; 3          42           Select
35 ; 4          54           Start
36 ; 5          66           Up on joypad
37 ; 6          78           Down on joypad
38 ; 7          90           Left on joypad
39 ; 8         102           Right on joypad
40
41 ; 9         114           A
42 ; 10        126           X
43 ; 11        138           L
44 ; 12        150           R
45 ; 13        162           none (always high)
46 ; 14        174           none (always high)
47 ; 15        186           none (always high;)
48 ; 16        198           none (always high)
49
50 ; Mnemonics for some important registers and bits
51 .equ PINC, 0x18
52
53 .equ PIN7, 0x14
54 .equ PIN6, 0x12
55 .equ PIN5, 0x11
56 .equ PIN4, 0x10
57 .equ PIN3, 0x08
58 .equ PIN2, 0x04
59 .equ PIN1, 0x02
60 .equ PIN0, 0x01
61
62
63
64 ; Function call to burn 99 clock cycles, including call and ret
65 burn_clock:
66         push r22
67         ldi r22, 29
68 burn_clock_loop:
69         dec r22
70         brne burn_clock_loop
71         pop r22
72         ret
73
74 ; Macro to do the same thing (burns 3 * n + 4 clock cycles)
75 .macro BURN_100_CLOCKS
76         push r22 ;      [1,2]
77         ldi r22, 32 ;   [3]
78         dec r22 ;       [4,7,10,...]
79         brne -2 ;       [5,6, 8,9,...]
80         pop r22 ;       [99,100]
81 .endm
82
83
84 ;extern unsigned pollSnes(void);
85 ; This function polls the SNES controller, as described above
86 .global pollSnes
87 pollSnes:
88 ;/* Register assignments for pollSnes on gcc */
89 ;/* Calling conventions on gcc:
90 ; * First parameter passed in r24/r25, second in r22/23 and so on.
91 ; * Callee must preserve r1-r17, r28/r29
92 ; * Result is passed in r24/r25
93 ; */
94 #define resL     r24
95 #define resH     r25
96 #define temp1    r22
97 #define temp2    r23
98 #define count    r26
99
100 ; Serial data is PC2
101 ; Data latch is PC1
102 ; Data clock is PC0
103
104 latchhigh:
105         sbi PINC, PIN1  ; [0] Raise data latch
106         call burn_clock
107         call burn_clock ; 12us
108
109         ldi temp1, 8
110         in temp2, PINC
111         andi temp2, PIN2;  Mask out serial data pin
112         breq unconnected
113
114         cbi PINC, PIN1  ; drop data latch
115 readbitfirst8:
116         call burn_clock ; wait for controller to drive data
117
118                         ; Current time: ~18us, 30us,...102us
119         in temp2, PINC  ; Read PINC pin states
120         ror temp2       ; Rotate PINC0 into carry flag
121         ror temp2       ; Rotate PINC1 into carry flag
122         ror temp2       ; Rotate PINC2 into carry flag (Controller 1 data)
123         rol resH        ; Save the current bit (in carry flag)
124                         ;   To add another controller, just add another ror and
125                         ;   rol into another register for return values
126         cbi PINC, PIN0  ; drop data clock (indicating we've read this bit)
127         call burn_clock
128
129                         ; Current time: ~24us, 36us,...108us
130         sbi PINC, PIN0  ; Raise data clock
131         dec temp1
132         brne readbitfirst8
133         ldi temp1, 8
134 readbitsecond8:
135         call burn_clock ; Wait for controller to drive data
136
137                         ; Current time: ~114us, 126us,...198us
138         in temp2, PINC  ; Read PINC pin states
139         ror temp2       ; Rotate PINC0 into carry flag
140         ror temp2       ; Rotate PINC1 into carry flag
141         ror temp2       ; Rotate PINC2 into carry flag (Controller 1 data)
142         rol resL        ; Save the current bit (in carry flag)
143         dec temp1
144         breq invertvalues
145         cbi PINC, PIN0  ; drop data clock after test to avoid dropping
146                         ; data clock after reading 16th bit
147         call burn_clock
148
149
150         sbi PINC, PIN0  ; raise data clock
151         jmp readbitsecond8 ; loop
152
153 invertvalues:
154         ldi temp2, 0xff ; for xor
155         eor resL, temp2 ; Invert for negative logic - to the SNES controller,
156         eor resH, temp2 ; low indicates button pressed.  To USB HID, the opposite.
157         ret             ; return to caller
158
159 unconnected:            ; Controller was disconnected
160         clr resL        ; Indicate no buttons pressed
161         clr resH
162         ret             ; return to caller
163
164
165 #undef resL
166 #undef resH
167 #undef temp1
168 #undef temp2
169 #undef count
170