Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include "crc_table.h"
- #define CTRL_PIN 3
- #define CTRL_HIGH DDRD &= ~0x01
- #define CTRL_LOW DDRD |= 0x01
- #define CTRL_QUERY (PIND & 0x01)
- #define CNSL_PIN 5
- #define CNSL_HIGH DDRC &= ~0x40
- #define CNSL_LOW DDRC |= 0x40
- #define CNSL_QUERY (PINC & 0x40)
- //From console to controller
- char console_raw_dump[281];
- unsigned char console_command;
- //From controller to console
- unsigned char controller_buffer[33];
- struct state {
- char stick_x;
- char stick_y;
- // bits: 0, 0, 0, start, y, x, b, a
- unsigned char data1;
- // bits: 1, L, R, Z, Dup, Ddown, Dright, Dleft
- unsigned char data2;
- } N64_status;
- void setup()
- {
- Serial.begin(9600);
- // Communication with the N64-console on this pin
- digitalWrite(CNSL_PIN, LOW);
- pinMode(CNSL_PIN, INPUT);
- // Communication with the N64-controller on this pin
- digitalWrite(CTRL_PIN, LOW);
- pinMode(CTRL_PIN, INPUT);
- }
- void loop()
- {
- unsigned char data, addr;
- memset(controller_buffer, 0, sizeof(controller_buffer));
- noInterrupts();
- CTRL_get();
- interrupts();
- translate_raw_data();
- print_CTRL_status();
- noInterrupts();
- get_console_command();
- Serial.println(console_command, HEX);
- switch (console_command)
- {
- case 0x00:
- case 0xFF:
- // identify
- // mutilate the n64_buffer array with our status
- // we return 0x050001 to indicate we have a rumble pack
- // or 0x050002 to indicate the expansion slot is empty
- //
- // 0xFF I've seen sent from Mario 64 and Shadows of the Empire.
- // I don't know why it's different, but the controllers seem to
- // send a set of status bytes afterwards the same as 0x00, and
- // it won't work without it.
- controller_buffer[0] = 0x05;
- controller_buffer[1] = 0x00;
- controller_buffer[2] = 0x02;
- console_send(controller_buffer, 3, 0);
- //Serial.println("It was 0x00: an identify command");
- break;
- case 0x01:
- // Send to n64 the received data
- console_send(controller_buffer, 4, 0);
- //Serial.println("It was 0x01: the query command");
- break;
- case 0x02:
- // A read. If the address is 0x8000, return 32 bytes of 0x80 bytes,
- // and a CRC byte. this tells the system our attached controller
- // pack is a rumble pack
- // Assume it's a read for 0x8000, which is the only thing it should
- // be requesting anyways
- memset(controller_buffer, 0x80, 32);
- controller_buffer[32] = 0xB8; // CRC
- console_send(controller_buffer, 33, 1);
- //Serial.println("It was 0x02: the read command");
- break;
- case 0x03:
- // A write. we at least need to respond with a single CRC byte. If
- // the write was to address 0xC000 and the data was 0x01, turn on
- // rumble! All other write addresses are ignored. (but we still
- // need to return a CRC)
- // decode the first data byte (fourth overall byte), bits indexed
- // at 24 through 31
- data = 0;
- data |= (console_raw_dump[16] != 0) << 7;
- data |= (console_raw_dump[17] != 0) << 6;
- data |= (console_raw_dump[18] != 0) << 5;
- data |= (console_raw_dump[19] != 0) << 4;
- data |= (console_raw_dump[20] != 0) << 3;
- data |= (console_raw_dump[21] != 0) << 2;
- data |= (console_raw_dump[22] != 0) << 1;
- data |= (console_raw_dump[23] != 0);
- // get crc byte, invert it, as per the protocol for
- // having a memory card attached
- controller_buffer[0] = crc_repeating_table[data] ^ 0xFF;
- // send it
- console_send(controller_buffer, 1, 1);
- // end of time critical code
- // was the address the rumble latch at 0xC000?
- // decode the first half of the address, bits
- // 8 through 15
- addr = 0;
- addr |= (console_raw_dump[0] != 0) << 7;
- addr |= (console_raw_dump[1] != 0) << 6;
- addr |= (console_raw_dump[2] != 0) << 5;
- addr |= (console_raw_dump[3] != 0) << 4;
- addr |= (console_raw_dump[4] != 0) << 3;
- addr |= (console_raw_dump[5] != 0) << 2;
- addr |= (console_raw_dump[6] != 0) << 1;
- addr |= (console_raw_dump[7] != 0);
- if (addr == 0xC0) {
- // rumble = (data != 0);
- }
- // Serial.println("It was 0x03: the write command");
- //Serial.print("Addr was 0x");
- //Serial.print(addr, HEX);
- //Serial.print(" and data was 0x");
- //Serial.println(data, HEX);
- break;
- }
- interrupts();
- }
- void translate_raw_data()
- {
- memset(&N64_status, 0, sizeof(N64_status));
- for (int i = 0; i < 8; i++) {
- N64_status.data1 |= controller_buffer[i] ? (0x80 >> i) : 0;
- N64_status.data2 |= controller_buffer[8 + i] ? (0x80 >> i) : 0;
- N64_status.stick_x |= controller_buffer[16 + i] ? (0x80 >> i) : 0;
- N64_status.stick_y |= controller_buffer[24 + i] ? (0x80 >> i) : 0;
- }
- }
- void print_CTRL_status()
- {
- char out[30];
- sprintf(out, "%i%i%i%i%i%i%i%i%i%i%i%i%i%i %i %i",
- N64_status.data1&16?1:0,
- N64_status.data1&32?1:0,
- N64_status.data1&64?1:0,
- N64_status.data1&128?1:0,
- N64_status.data2&32?1:0,
- N64_status.data2&16?1:0,
- N64_status.data2&0x08?1:0,
- N64_status.data2&0x04?1:0,
- N64_status.data2&0x01?1:0,
- N64_status.data2&0x02?1:0,
- N64_status.data1&0x08?1:0,
- N64_status.data1&0x04?1:0,
- N64_status.data1&0x01?1:0,
- N64_status.data1&0x02?1:0,
- N64_status.stick_x,
- N64_status.stick_y);
- Serial.println(out);
- }
- //Send to console
- // completly copied and pasted to not mess up timings
- void console_send(unsigned char *buffer, char length, bool wide_stop)
- {
- asm volatile (";Starting N64 Send Routine");
- // Send these bytes
- char bits;
- bool bit;
- // This routine is very carefully timed by examining the assembly output.
- // Do not change any statements, it could throw the timings off
- //
- // We get 16 cycles per microsecond, which should be plenty, but we need to
- // be conservative. Most assembly ops take 1 cycle, but a few take 2
- //
- // I use manually constructed for-loops out of gotos so I have more control
- // over the outputted assembly. I can insert nops where it was impossible
- // with a for loop
- asm volatile (";Starting outer for loop");
- outer_loop:
- {
- asm volatile (";Starting inner for loop");
- bits=8;
- inner_loop:
- {
- // Starting a bit, set the line low
- asm volatile (";Setting line to low");
- CNSL_LOW; // 1 op, 2 cycles
- asm volatile (";branching");
- if (*buffer >> 7) {
- asm volatile (";Bit is a 1");
- // 1 bit
- // remain low for 1us, then go high for 3us
- // nop block 1
- asm volatile ("nop\nnop\nnop\nnop\nnop\n");
- asm volatile (";Setting line to high");
- CNSL_HIGH;
- // nop block 2
- // we'll wait only 2us to sync up with both conditions
- // at the bottom of the if statement
- asm volatile ("nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- );
- } else {
- asm volatile (";Bit is a 0");
- // 0 bit
- // remain low for 3us, then go high for 1us
- // nop block 3
- asm volatile ("nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\n");
- asm volatile (";Setting line to high");
- CNSL_HIGH;
- // wait for 1us
- asm volatile ("; end of conditional branch, need to wait 1us more before next bit");
- }
- // end of the if, the line is high and needs to remain
- // high for exactly 16 more cycles, regardless of the previous
- // branch path
- asm volatile (";finishing inner loop body");
- --bits;
- if (bits != 0) {
- // nop block 4
- // this block is why a for loop was impossible
- asm volatile ("nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\n");
- // rotate bits
- asm volatile (";rotating out bits");
- *buffer <<= 1;
- goto inner_loop;
- } // fall out of inner loop
- }
- asm volatile (";continuing outer loop");
- // In this case: the inner loop exits and the outer loop iterates,
- // there are /exactly/ 16 cycles taken up by the necessary operations.
- // So no nops are needed here (that was lucky!)
- --length;
- if (length != 0) {
- ++buffer;
- goto outer_loop;
- } // fall out of outer loop
- }
- // send a single stop (1) bit
- // nop block 5
- asm volatile ("nop\nnop\nnop\nnop\n");
- CNSL_LOW;
- // wait 1 us, 16 cycles, then raise the line
- // take another 3 off for the wide_stop check
- // 16-2-3=11
- // nop block 6
- asm volatile ("nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\n");
- if (wide_stop) {
- asm volatile (";another 1us for extra wide stop bit\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\n");
- }
- CNSL_HIGH;
- }
- void get_console_command()
- {
- int bitcount;
- char *bitbin = console_raw_dump;
- int idle_wait;
- func_top:
- console_command = 0;
- bitcount = 8;
- // wait to make sure the line is idle before
- // we begin listening
- for (idle_wait=32; idle_wait>0; --idle_wait) {
- if (!CNSL_QUERY) {
- idle_wait = 32;
- }
- }
- read_loop:
- // wait for the line to go low
- while (CNSL_QUERY){}
- // wait approx 2us and poll the line
- asm volatile (
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- );
- if (CNSL_QUERY)
- console_command |= 0x01;
- --bitcount;
- if (bitcount == 0)
- goto read_more;
- console_command <<= 1;
- // wait for line to go high again
- // I don't want this to execute if the loop is exiting, so
- // I couldn't use a traditional for-loop
- while (!CNSL_QUERY) {}
- goto read_loop;
- read_more:
- switch (console_command)
- {
- case (0x03):
- // write command
- // we expect a 2 byte address and 32 bytes of data
- bitcount = 272 + 1; // 34 bytes * 8 bits per byte
- //Serial.println("command is 0x03, write");
- break;
- case (0x02):
- // read command 0x02
- // we expect a 2 byte address
- bitcount = 16 + 1;
- //Serial.println("command is 0x02, read");
- break;
- case (0x00):
- case (0x01):
- default:
- // get the last (stop) bit
- bitcount = 1;
- break;
- //default:
- // Serial.println(consele_command, HEX);
- // goto func_top;
- }
- // make sure the line is high. Hopefully we didn't already
- // miss the high-to-low transition
- while (!CNSL_QUERY) {}
- read_loop2:
- // wait for the line to go low
- while (CNSL_QUERY){}
- // wait approx 2us and poll the line
- asm volatile (
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- );
- *bitbin = CNSL_QUERY;
- ++bitbin;
- --bitcount;
- if (bitcount == 0)
- return;
- // wait for line to go high again
- while (!CNSL_QUERY) {}
- goto read_loop2;
- }
- void CTRL_get()
- {
- // listen for the expected 8 bytes of data back from the controller and
- // blast it out to the N64_raw_dump array, one bit per byte for extra speed.
- // Afterwards, call translate_raw_data() to interpret the raw data and pack
- // it into the N64_status struct.
- asm volatile (";Starting to listen");
- unsigned char timeout;
- char bitcount = 32;
- char *bitbin = controller_buffer;
- // Again, using gotos here to make the assembly more predictable and
- // optimization easier (please don't kill me)
- read_loop:
- timeout = 0x3f;
- // wait for line to go low
- while (CTRL_QUERY) {
- if (!--timeout)
- return;
- }
- // wait approx 2us and poll the line
- asm volatile (
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- "nop\nnop\nnop\nnop\nnop\n"
- );
- *bitbin = CTRL_QUERY;
- ++bitbin;
- --bitcount;
- if (bitcount == 0)
- return;
- // wait for line to go high again
- // it may already be high, so this should just drop through
- timeout = 0x3f;
- while (!CTRL_QUERY) {
- if (!--timeout)
- return;
- }
- goto read_loop;
- }
Add Comment
Please, Sign In to add comment