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 | } |