SHOW:
|
|
- or go back to the newest paste.
1 | #include "crc_table.h" | |
2 | ||
3 | #define CTRL_PIN 3 | |
4 | #define CTRL_HIGH DDRD &= ~0x01 | |
5 | #define CTRL_LOW DDRD |= 0x01 | |
6 | #define CTRL_QUERY (PIND & 0x01) | |
7 | ||
8 | #define CNSL_PIN 5 | |
9 | #define CNSL_HIGH DDRC &= ~0x40 | |
10 | #define CNSL_LOW DDRC |= 0x40 | |
11 | #define CNSL_QUERY (PINC & 0x40) | |
12 | ||
13 | //From console to controller | |
14 | char console_raw_dump[281]; | |
15 | unsigned char console_command; | |
16 | ||
17 | //From controller to console | |
18 | unsigned char controller_buffer[33]; | |
19 | ||
20 | struct state { | |
21 | char stick_x; | |
22 | char stick_y; | |
23 | // bits: 0, 0, 0, start, y, x, b, a | |
24 | unsigned char data1; | |
25 | // bits: 1, L, R, Z, Dup, Ddown, Dright, Dleft | |
26 | unsigned char data2; | |
27 | } N64_status; | |
28 | ||
29 | void setup() | |
30 | { | |
31 | Serial.begin(9600); | |
32 | ||
33 | // Communication with the N64-console on this pin | |
34 | digitalWrite(CNSL_PIN, LOW); | |
35 | pinMode(CNSL_PIN, INPUT); | |
36 | ||
37 | // Communication with the N64-controller on this pin | |
38 | digitalWrite(CTRL_PIN, LOW); | |
39 | pinMode(CTRL_PIN, INPUT); | |
40 | } | |
41 | ||
42 | void loop() | |
43 | { | |
44 | unsigned char data, addr; | |
45 | memset(controller_buffer, 0, sizeof(controller_buffer)); | |
46 | ||
47 | noInterrupts(); | |
48 | CTRL_get(); | |
49 | interrupts(); | |
50 | translate_raw_data(); | |
51 | print_CTRL_status(); | |
52 | ||
53 | noInterrupts(); | |
54 | ||
55 | get_console_command(); | |
56 | Serial.println(console_command, HEX); | |
57 | switch (console_command) | |
58 | { | |
59 | case 0x00: | |
60 | case 0xFF: | |
61 | // identify | |
62 | // mutilate the n64_buffer array with our status | |
63 | // we return 0x050001 to indicate we have a rumble pack | |
64 | // or 0x050002 to indicate the expansion slot is empty | |
65 | // | |
66 | // 0xFF I've seen sent from Mario 64 and Shadows of the Empire. | |
67 | // I don't know why it's different, but the controllers seem to | |
68 | // send a set of status bytes afterwards the same as 0x00, and | |
69 | // it won't work without it. | |
70 | controller_buffer[0] = 0x05; | |
71 | controller_buffer[1] = 0x00; | |
72 | controller_buffer[2] = 0x02; | |
73 | ||
74 | console_send(controller_buffer, 3, 0); | |
75 | ||
76 | //Serial.println("It was 0x00: an identify command"); | |
77 | break; | |
78 | case 0x01: | |
79 | // Send to n64 the received data | |
80 | ||
81 | console_send(controller_buffer, 4, 0); | |
82 | //Serial.println("It was 0x01: the query command"); | |
83 | break; | |
84 | case 0x02: | |
85 | // A read. If the address is 0x8000, return 32 bytes of 0x80 bytes, | |
86 | // and a CRC byte. this tells the system our attached controller | |
87 | // pack is a rumble pack | |
88 | ||
89 | // Assume it's a read for 0x8000, which is the only thing it should | |
90 | // be requesting anyways | |
91 | memset(controller_buffer, 0x80, 32); | |
92 | controller_buffer[32] = 0xB8; // CRC | |
93 | ||
94 | console_send(controller_buffer, 33, 1); | |
95 | ||
96 | //Serial.println("It was 0x02: the read command"); | |
97 | break; | |
98 | case 0x03: | |
99 | // A write. we at least need to respond with a single CRC byte. If | |
100 | // the write was to address 0xC000 and the data was 0x01, turn on | |
101 | // rumble! All other write addresses are ignored. (but we still | |
102 | // need to return a CRC) | |
103 | ||
104 | // decode the first data byte (fourth overall byte), bits indexed | |
105 | // at 24 through 31 | |
106 | data = 0; | |
107 | data |= (console_raw_dump[16] != 0) << 7; | |
108 | data |= (console_raw_dump[17] != 0) << 6; | |
109 | data |= (console_raw_dump[18] != 0) << 5; | |
110 | data |= (console_raw_dump[19] != 0) << 4; | |
111 | data |= (console_raw_dump[20] != 0) << 3; | |
112 | data |= (console_raw_dump[21] != 0) << 2; | |
113 | data |= (console_raw_dump[22] != 0) << 1; | |
114 | data |= (console_raw_dump[23] != 0); | |
115 | ||
116 | // get crc byte, invert it, as per the protocol for | |
117 | // having a memory card attached | |
118 | controller_buffer[0] = crc_repeating_table[data] ^ 0xFF; | |
119 | ||
120 | // send it | |
121 | console_send(controller_buffer, 1, 1); | |
122 | ||
123 | // end of time critical code | |
124 | // was the address the rumble latch at 0xC000? | |
125 | // decode the first half of the address, bits | |
126 | // 8 through 15 | |
127 | addr = 0; | |
128 | addr |= (console_raw_dump[0] != 0) << 7; | |
129 | addr |= (console_raw_dump[1] != 0) << 6; | |
130 | addr |= (console_raw_dump[2] != 0) << 5; | |
131 | addr |= (console_raw_dump[3] != 0) << 4; | |
132 | addr |= (console_raw_dump[4] != 0) << 3; | |
133 | addr |= (console_raw_dump[5] != 0) << 2; | |
134 | addr |= (console_raw_dump[6] != 0) << 1; | |
135 | addr |= (console_raw_dump[7] != 0); | |
136 | ||
137 | if (addr == 0xC0) { | |
138 | // rumble = (data != 0); | |
139 | } | |
140 | ||
141 | // Serial.println("It was 0x03: the write command"); | |
142 | //Serial.print("Addr was 0x"); | |
143 | //Serial.print(addr, HEX); | |
144 | //Serial.print(" and data was 0x"); | |
145 | //Serial.println(data, HEX); | |
146 | break; | |
147 | ||
148 | } | |
149 | ||
150 | interrupts(); | |
151 | } | |
152 | ||
153 | void translate_raw_data() | |
154 | { | |
155 | memset(&N64_status, 0, sizeof(N64_status)); | |
156 | for (int i = 0; i < 8; i++) { | |
157 | N64_status.data1 |= controller_buffer[i] ? (0x80 >> i) : 0; | |
158 | N64_status.data2 |= controller_buffer[8 + i] ? (0x80 >> i) : 0; | |
159 | N64_status.stick_x |= controller_buffer[16 + i] ? (0x80 >> i) : 0; | |
160 | N64_status.stick_y |= controller_buffer[24 + i] ? (0x80 >> i) : 0; | |
161 | } | |
162 | } | |
163 | ||
164 | void print_CTRL_status() | |
165 | { | |
166 | char out[30]; | |
167 | sprintf(out, "%i%i%i%i%i%i%i%i%i%i%i%i%i%i %i %i", | |
168 | N64_status.data1&16?1:0, | |
169 | N64_status.data1&32?1:0, | |
170 | N64_status.data1&64?1:0, | |
171 | N64_status.data1&128?1:0, | |
172 | N64_status.data2&32?1:0, | |
173 | N64_status.data2&16?1:0, | |
174 | N64_status.data2&0x08?1:0, | |
175 | N64_status.data2&0x04?1:0, | |
176 | N64_status.data2&0x01?1:0, | |
177 | N64_status.data2&0x02?1:0, | |
178 | N64_status.data1&0x08?1:0, | |
179 | N64_status.data1&0x04?1:0, | |
180 | N64_status.data1&0x01?1:0, | |
181 | N64_status.data1&0x02?1:0, | |
182 | N64_status.stick_x, | |
183 | N64_status.stick_y); | |
184 | Serial.println(out); | |
185 | } | |
186 | ||
187 | //Send to console | |
188 | // completly copied and pasted to not mess up timings | |
189 | void console_send(unsigned char *buffer, char length, bool wide_stop) | |
190 | { | |
191 | asm volatile (";Starting N64 Send Routine"); | |
192 | // Send these bytes | |
193 | char bits; | |
194 | ||
195 | bool bit; | |
196 | ||
197 | // This routine is very carefully timed by examining the assembly output. | |
198 | // Do not change any statements, it could throw the timings off | |
199 | // | |
200 | // We get 16 cycles per microsecond, which should be plenty, but we need to | |
201 | // be conservative. Most assembly ops take 1 cycle, but a few take 2 | |
202 | // | |
203 | // I use manually constructed for-loops out of gotos so I have more control | |
204 | // over the outputted assembly. I can insert nops where it was impossible | |
205 | // with a for loop | |
206 | ||
207 | asm volatile (";Starting outer for loop"); | |
208 | outer_loop: | |
209 | { | |
210 | asm volatile (";Starting inner for loop"); | |
211 | bits=8; | |
212 | inner_loop: | |
213 | { | |
214 | // Starting a bit, set the line low | |
215 | asm volatile (";Setting line to low"); | |
216 | CNSL_LOW; // 1 op, 2 cycles | |
217 | ||
218 | asm volatile (";branching"); | |
219 | if (*buffer >> 7) { | |
220 | asm volatile (";Bit is a 1"); | |
221 | // 1 bit | |
222 | // remain low for 1us, then go high for 3us | |
223 | // nop block 1 | |
224 | asm volatile ("nop\nnop\nnop\nnop\nnop\n"); | |
225 | ||
226 | asm volatile (";Setting line to high"); | |
227 | CNSL_HIGH; | |
228 | ||
229 | // nop block 2 | |
230 | // we'll wait only 2us to sync up with both conditions | |
231 | // at the bottom of the if statement | |
232 | asm volatile ("nop\nnop\nnop\nnop\nnop\n" | |
233 | "nop\nnop\nnop\nnop\nnop\n" | |
234 | "nop\nnop\nnop\nnop\nnop\n" | |
235 | "nop\nnop\nnop\nnop\nnop\n" | |
236 | "nop\nnop\nnop\nnop\nnop\n" | |
237 | "nop\nnop\nnop\nnop\nnop\n" | |
238 | ); | |
239 | ||
240 | } else { | |
241 | asm volatile (";Bit is a 0"); | |
242 | // 0 bit | |
243 | // remain low for 3us, then go high for 1us | |
244 | // nop block 3 | |
245 | asm volatile ("nop\nnop\nnop\nnop\nnop\n" | |
246 | "nop\nnop\nnop\nnop\nnop\n" | |
247 | "nop\nnop\nnop\nnop\nnop\n" | |
248 | "nop\nnop\nnop\nnop\nnop\n" | |
249 | "nop\nnop\nnop\nnop\nnop\n" | |
250 | "nop\nnop\nnop\nnop\nnop\n" | |
251 | "nop\nnop\nnop\nnop\nnop\n" | |
252 | "nop\n"); | |
253 | ||
254 | asm volatile (";Setting line to high"); | |
255 | CNSL_HIGH; | |
256 | ||
257 | // wait for 1us | |
258 | asm volatile ("; end of conditional branch, need to wait 1us more before next bit"); | |
259 | ||
260 | } | |
261 | // end of the if, the line is high and needs to remain | |
262 | // high for exactly 16 more cycles, regardless of the previous | |
263 | // branch path | |
264 | ||
265 | asm volatile (";finishing inner loop body"); | |
266 | --bits; | |
267 | if (bits != 0) { | |
268 | // nop block 4 | |
269 | // this block is why a for loop was impossible | |
270 | asm volatile ("nop\nnop\nnop\nnop\nnop\n" | |
271 | "nop\nnop\nnop\nnop\n"); | |
272 | // rotate bits | |
273 | asm volatile (";rotating out bits"); | |
274 | *buffer <<= 1; | |
275 | ||
276 | goto inner_loop; | |
277 | } // fall out of inner loop | |
278 | } | |
279 | asm volatile (";continuing outer loop"); | |
280 | // In this case: the inner loop exits and the outer loop iterates, | |
281 | // there are /exactly/ 16 cycles taken up by the necessary operations. | |
282 | // So no nops are needed here (that was lucky!) | |
283 | --length; | |
284 | if (length != 0) { | |
285 | ++buffer; | |
286 | goto outer_loop; | |
287 | } // fall out of outer loop | |
288 | } | |
289 | ||
290 | // send a single stop (1) bit | |
291 | // nop block 5 | |
292 | asm volatile ("nop\nnop\nnop\nnop\n"); | |
293 | CNSL_LOW; | |
294 | // wait 1 us, 16 cycles, then raise the line | |
295 | // take another 3 off for the wide_stop check | |
296 | // 16-2-3=11 | |
297 | // nop block 6 | |
298 | asm volatile ("nop\nnop\nnop\nnop\nnop\n" | |
299 | "nop\nnop\nnop\nnop\nnop\n" | |
300 | "nop\n"); | |
301 | if (wide_stop) { | |
302 | asm volatile (";another 1us for extra wide stop bit\n" | |
303 | "nop\nnop\nnop\nnop\nnop\n" | |
304 | "nop\nnop\nnop\nnop\nnop\n" | |
305 | "nop\nnop\nnop\nnop\n"); | |
306 | } | |
307 | ||
308 | CNSL_HIGH; | |
309 | } | |
310 | ||
311 | void get_console_command() | |
312 | { | |
313 | ||
314 | int bitcount; | |
315 | char *bitbin = console_raw_dump; | |
316 | int idle_wait; | |
317 | ||
318 | func_top: | |
319 | console_command = 0; | |
320 | ||
321 | bitcount = 8; | |
322 | ||
323 | // wait to make sure the line is idle before | |
324 | // we begin listening | |
325 | for (idle_wait=32; idle_wait>0; --idle_wait) { | |
326 | if (!CNSL_QUERY) { | |
327 | idle_wait = 32; | |
328 | } | |
329 | } | |
330 | ||
331 | read_loop: | |
332 | // wait for the line to go low | |
333 | while (CNSL_QUERY){} | |
334 | ||
335 | // wait approx 2us and poll the line | |
336 | asm volatile ( | |
337 | "nop\nnop\nnop\nnop\nnop\n" | |
338 | "nop\nnop\nnop\nnop\nnop\n" | |
339 | "nop\nnop\nnop\nnop\nnop\n" | |
340 | "nop\nnop\nnop\nnop\nnop\n" | |
341 | "nop\nnop\nnop\nnop\nnop\n" | |
342 | "nop\nnop\nnop\nnop\nnop\n" | |
343 | ); | |
344 | if (CNSL_QUERY) | |
345 | console_command |= 0x01; | |
346 | ||
347 | --bitcount; | |
348 | if (bitcount == 0) | |
349 | goto read_more; | |
350 | ||
351 | console_command <<= 1; | |
352 | ||
353 | // wait for line to go high again | |
354 | // I don't want this to execute if the loop is exiting, so | |
355 | // I couldn't use a traditional for-loop | |
356 | while (!CNSL_QUERY) {} | |
357 | goto read_loop; | |
358 | ||
359 | read_more: | |
360 | switch (console_command) | |
361 | { | |
362 | case (0x03): | |
363 | // write command | |
364 | // we expect a 2 byte address and 32 bytes of data | |
365 | bitcount = 272 + 1; // 34 bytes * 8 bits per byte | |
366 | //Serial.println("command is 0x03, write"); | |
367 | break; | |
368 | case (0x02): | |
369 | // read command 0x02 | |
370 | // we expect a 2 byte address | |
371 | bitcount = 16 + 1; | |
372 | //Serial.println("command is 0x02, read"); | |
373 | break; | |
374 | case (0x00): | |
375 | case (0x01): | |
376 | default: | |
377 | // get the last (stop) bit | |
378 | bitcount = 1; | |
379 | break; | |
380 | //default: | |
381 | // Serial.println(consele_command, HEX); | |
382 | // goto func_top; | |
383 | } | |
384 | ||
385 | // make sure the line is high. Hopefully we didn't already | |
386 | // miss the high-to-low transition | |
387 | while (!CNSL_QUERY) {} | |
388 | read_loop2: | |
389 | // wait for the line to go low | |
390 | while (CNSL_QUERY){} | |
391 | ||
392 | // wait approx 2us and poll the line | |
393 | asm volatile ( | |
394 | "nop\nnop\nnop\nnop\nnop\n" | |
395 | "nop\nnop\nnop\nnop\nnop\n" | |
396 | "nop\nnop\nnop\nnop\nnop\n" | |
397 | "nop\nnop\nnop\nnop\nnop\n" | |
398 | "nop\nnop\nnop\nnop\nnop\n" | |
399 | "nop\nnop\nnop\nnop\nnop\n" | |
400 | ); | |
401 | *bitbin = CNSL_QUERY; | |
402 | ++bitbin; | |
403 | --bitcount; | |
404 | if (bitcount == 0) | |
405 | return; | |
406 | ||
407 | // wait for line to go high again | |
408 | while (!CNSL_QUERY) {} | |
409 | goto read_loop2; | |
410 | } | |
411 | ||
412 | void CTRL_get() | |
413 | { | |
414 | // listen for the expected 8 bytes of data back from the controller and | |
415 | // blast it out to the N64_raw_dump array, one bit per byte for extra speed. | |
416 | // Afterwards, call translate_raw_data() to interpret the raw data and pack | |
417 | // it into the N64_status struct. | |
418 | asm volatile (";Starting to listen"); | |
419 | unsigned char timeout; | |
420 | char bitcount = 32; | |
421 | char *bitbin = controller_buffer; | |
422 | ||
423 | // Again, using gotos here to make the assembly more predictable and | |
424 | // optimization easier (please don't kill me) | |
425 | read_loop: | |
426 | timeout = 0x3f; | |
427 | // wait for line to go low | |
428 | while (CTRL_QUERY) { | |
429 | if (!--timeout) | |
430 | return; | |
431 | } | |
432 | // wait approx 2us and poll the line | |
433 | asm volatile ( | |
434 | "nop\nnop\nnop\nnop\nnop\n" | |
435 | "nop\nnop\nnop\nnop\nnop\n" | |
436 | "nop\nnop\nnop\nnop\nnop\n" | |
437 | "nop\nnop\nnop\nnop\nnop\n" | |
438 | "nop\nnop\nnop\nnop\nnop\n" | |
439 | "nop\nnop\nnop\nnop\nnop\n" | |
440 | ); | |
441 | *bitbin = CTRL_QUERY; | |
442 | ++bitbin; | |
443 | --bitcount; | |
444 | if (bitcount == 0) | |
445 | return; | |
446 | ||
447 | // wait for line to go high again | |
448 | // it may already be high, so this should just drop through | |
449 | timeout = 0x3f; | |
450 | while (!CTRL_QUERY) { | |
451 | if (!--timeout) | |
452 | return; | |
453 | } | |
454 | goto read_loop; | |
455 | ||
456 | } |