View difference between Paste ID: XmjGYSnJ and zfLNcx9q
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
}