diff --git a/N64Controller/N64Controller.cpp b/N64Controller/N64Controller.cpp index fba58fe..7ac2c14 100644 --- a/N64Controller/N64Controller.cpp +++ b/N64Controller/N64Controller.cpp @@ -1,41 +1,17 @@ #include "N64Controller.h" #include -#include "pins_arduino.h" - -#define NOP asm volatile ("nop") -#define NOP5 asm volatile ("nop\nnop\nnop\nnop\nnop\n") -#define NOP30 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") - -// these two macros set arduino pin 2 to input or output, which with an -// external 1K pull-up resistor to the 3.3V rail, is like pulling it high or -// low. These operations translate to 1 op code, which takes 2 cycles -#define N64_PIND_HIGH DDRD &= ~pincode -#define N64_PIND_LOW DDRD |= pincode -#define N64_PIND_QUERY (PIND & pincode) - -#define N64_PINB_HIGH DDRB &= ~pincode -#define N64_PINB_LOW DDRB |= pincode -#define N64_PINB_QUERY (PINB & pincode) N64Controller::N64Controller(unsigned char serialPin) { if(serialPin > 13) serialPin = 2; - n64_PIN = serialPin; -} - -void N64Controller::begin() { // Communication with N64 controller controller on this pin // Don't remove these lines, we don't want to push +5V to the controller - digitalWrite(n64_PIN, LOW); - pinMode(n64_PIN, INPUT); - n64_first_register = true; - switch (n64_PIN) { + digitalWrite(serialPin, LOW); + pinMode(serialPin, INPUT); + bool n64_first_register = true; + char n64_pincode; + switch (serialPin) { case 0: n64_pincode = 0x01; break; case 1: n64_pincode = 0x02; @@ -65,279 +41,20 @@ void N64Controller::begin() { case 13: n64_pincode = 0x20; n64_first_register = false; break; default: - n64_pincode = 0x04; n64_PIN = 2; + n64_pincode = 0x04; break; } - if (n64_first_register) { - N64_init_PIND(n64_pincode); + if(n64_first_register) { + interface = new N64Interface_PIND(n64_pincode); } else { - N64_init_PINB(n64_pincode); + interface = new N64Interface_PINB(n64_pincode); } } -void N64Controller::N64_init_PIND(char pincode) { - // Initialize the gamecube controller by sending it a null byte. - // This is unnecessary for a standard controller, but is required for the - // Wavebird. - unsigned char initialize = 0x00; - noInterrupts(); - N64_PIND_send(pincode, &initialize, 1); - - // Stupid routine to wait for the gamecube controller to stop - // sending its response. We don't care what it is, but we - // can't start asking for status if it's still responding - int x; - for (x=0; x<64; x++) { - // make sure the line is idle for 64 iterations, should - // be plenty. - if (!N64_PIND_QUERY) - x = 0; - } - - // Query for the gamecube controller's status. We do this - // to get the 0 point for the control stick. - unsigned char command[] = {0x01}; - N64_PIND_send(pincode, command, 1); - // read in data and dump it to N64_raw_dump - N64_PIND_get(pincode); - interrupts(); +void N64Controller::begin() { + interface->init(); } -void N64Controller::N64_init_PINB(char pincode) { - unsigned char initialize = 0x00; - noInterrupts(); - N64_PINB_send(pincode, &initialize, 1); - int x; - for (x=0; x<64; x++) { - if (!N64_PINB_QUERY) - x = 0; - } - unsigned char command[] = {0x01}; - N64_PINB_send(pincode, command, 1); - N64_PINB_get(pincode); - interrupts(); -} - -/** - * This sends the given byte sequence to the controller - * length must be at least 1 - * Oh, it destroys the buffer passed in as it writes it - */ -void N64Controller::N64_PIND_send(char pincode, unsigned char *buffer, char length) { - // Send these bytes - char bits; - - // 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"); - N64_PIND_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 - NOP5; - - asm volatile (";Setting line to high"); - N64_PIND_HIGH; - - // nop block 2 - // we'll wait only 2us to sync up with both conditions - // at the bottom of the if statement - NOP30; - - } else { - asm volatile (";Bit is a 0"); - // 0 bit - // remain low for 3us, then go high for 1us - // nop block 3 - NOP30; NOP5; NOP; - - asm volatile (";Setting line to high"); - N64_PIND_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 - NOP5; NOP; NOP; NOP; NOP; - - // 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"); - N64_PIND_LOW; - // wait 1 us, 16 cycles, then raise the line - // 16-2=14 - // nop block 6 - NOP5; NOP5; NOP; NOP; NOP; NOP; - - N64_PIND_HIGH; - -} - - -void N64Controller::N64_PINB_send(char pincode, unsigned char *buffer, char length) { - char bits; - asm volatile (";Starting outer for loop"); -outer_loop: - { - asm volatile (";Starting inner for loop"); - bits=8; -inner_loop: - { - asm volatile (";Setting line to low"); - N64_PINB_LOW; - asm volatile (";branching"); - if (*buffer >> 7) { - asm volatile (";Bit is a 1"); - NOP5; - asm volatile (";Setting line to high"); - N64_PINB_HIGH; - NOP30; - } else { - asm volatile (";Bit is a 0"); - NOP30; NOP5; NOP; - - asm volatile (";Setting line to high"); - N64_PINB_HIGH; - asm volatile ("; end of conditional branch, need to wait 1us more before next bit"); - } - asm volatile (";finishing inner loop body"); - --bits; - if (bits != 0) { - NOP5; NOP; NOP; NOP; NOP; - asm volatile (";rotating out bits"); - *buffer <<= 1; - goto inner_loop; - } - } - asm volatile (";continuing outer loop"); - --length; - if (length != 0) { - ++buffer; - goto outer_loop; - } - } - NOP; NOP; NOP; NOP; - N64_PINB_LOW; - NOP5; NOP5; NOP; NOP; NOP; NOP; - N64_PINB_HIGH; -} - - -void N64Controller::N64_PIND_get(char pincode) -{ - // 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 = N64_raw_dump; - - // 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 (N64_PIND_QUERY) { - if (!--timeout) - return; - } - // wait approx 2us and poll the line - NOP30; - *bitbin = N64_PIND_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 (!N64_PIND_QUERY) { - if (!--timeout) - return; - } - goto read_loop; - -} - - - -void N64Controller::N64_PINB_get(char pincode) -{ - asm volatile (";Starting to listen"); - unsigned char timeout; - char bitcount = 32; - char *bitbin = N64_raw_dump; -read_loop: - timeout = 0x3f; - while (N64_PINB_QUERY) { - if (!--timeout) - return; - } - NOP30; - *bitbin = N64_PINB_QUERY; - ++bitbin; - --bitcount; - if (bitcount == 0) - return; - timeout = 0x3f; - while (!N64_PINB_QUERY) { - if (!--timeout) - return; - } - goto read_loop; -} - - void N64Controller::print_N64_status() { // bits: A, B, Z, Start, Dup, Ddown, Dleft, Dright @@ -386,15 +103,8 @@ void N64Controller::print_N64_status() void N64Controller::update() { unsigned char command[] = {0x01}; - if (n64_first_register) { - noInterrupts(); - N64_PIND_send(n64_pincode, command, 1); - N64_PIND_get(n64_pincode); - interrupts(); - } else { - noInterrupts(); - N64_PINB_send(n64_pincode, command, 1); - N64_PINB_get(n64_pincode); - interrupts(); - } + noInterrupts(); + interface->send(command, 1); + interface->get(); + interrupts(); } diff --git a/N64Controller/N64Controller.h b/N64Controller/N64Controller.h index db089c8..b7f1b73 100644 --- a/N64Controller/N64Controller.h +++ b/N64Controller/N64Controller.h @@ -22,6 +22,8 @@ #ifndef N64Controller_h #define N64Controller_h +#include "N64Interface.h" + #define A_IDX 0 #define B_IDX 1 #define Z_IDX 2 @@ -46,47 +48,34 @@ class N64Controller { void update(); // then update always and get button info // consider to have a delay instead of // calling update all the time in a loop - inline bool D_up() { return (N64_raw_dump[D_UP_IDX]) > 0; }; - inline bool D_down() { return (N64_raw_dump[D_DOWN_IDX]) > 0; }; - inline bool D_left() { return (N64_raw_dump[D_LEFT_IDX]) > 0; }; - inline bool D_right() { return (N64_raw_dump[D_RIGHT_IDX]) > 0; }; - inline bool Start() { return (N64_raw_dump[START_IDX]) > 0; }; - inline bool A() { return (N64_raw_dump[A_IDX]) > 0; }; - inline bool B() { return (N64_raw_dump[B_IDX]) > 0; }; - inline bool Z() { return (N64_raw_dump[Z_IDX]) > 0; }; - inline bool L() { return (N64_raw_dump[L_IDX]) > 0; }; - inline bool R() { return (N64_raw_dump[R_IDX]) > 0; }; - inline bool C_up() { return (N64_raw_dump[C_UP_IDX]) > 0; }; - inline bool C_down() { return (N64_raw_dump[C_DOWN_IDX]) > 0; }; - inline bool C_left() { return (N64_raw_dump[C_LEFT_IDX]) > 0; }; - inline bool C_right() { return (N64_raw_dump[C_RIGHT_IDX]) > 0; }; + inline bool D_up() { return (interface->raw_dump[D_UP_IDX]) > 0; }; + inline bool D_down() { return (interface->raw_dump[D_DOWN_IDX]) > 0; }; + inline bool D_left() { return (interface->raw_dump[D_LEFT_IDX]) > 0; }; + inline bool D_right() { return (interface->raw_dump[D_RIGHT_IDX]) > 0; }; + inline bool Start() { return (interface->raw_dump[START_IDX]) > 0; }; + inline bool A() { return (interface->raw_dump[A_IDX]) > 0; }; + inline bool B() { return (interface->raw_dump[B_IDX]) > 0; }; + inline bool Z() { return (interface->raw_dump[Z_IDX]) > 0; }; + inline bool L() { return (interface->raw_dump[L_IDX]) > 0; }; + inline bool R() { return (interface->raw_dump[R_IDX]) > 0; }; + inline bool C_up() { return (interface->raw_dump[C_UP_IDX]) > 0; }; + inline bool C_down() { return (interface->raw_dump[C_DOWN_IDX]) > 0; }; + inline bool C_left() { return (interface->raw_dump[C_LEFT_IDX]) > 0; }; + inline bool C_right() { return (interface->raw_dump[C_RIGHT_IDX]) > 0; }; inline char axis_x() { return axis(X_IDX); }; inline char axis_y() { return axis(Y_IDX); }; void print_N64_status(); private: - void set_up(); - char n64_PIN; // might also be set by constructor or begin() - char n64_pincode; - bool n64_first_register; // PIN0-7: DDRD PIN8-13: DDRB - - void N64_init_PIND(char pincode); - void N64_PIND_send(char pincode, unsigned char *buffer, char length); - void N64_PIND_get(char pincode); - - void N64_init_PINB(char pincode); - void N64_PINB_send(char pincode, unsigned char *buffer, char length); - void N64_PINB_get(char pincode); + N64Interface * interface; inline char axis(int index) { char value = 0; for (char i=0; i<8; i++) { - value |= N64_raw_dump[index+i] ? (0x80 >> i) : 0; + value |= interface->raw_dump[index+i] ? (0x80 >> i) : 0; } return value; } - - char N64_raw_dump[33]; // 1 received bit per byte }; #endif diff --git a/N64Controller/N64Interface.cpp b/N64Controller/N64Interface.cpp new file mode 100644 index 0000000..c25a18a --- /dev/null +++ b/N64Controller/N64Interface.cpp @@ -0,0 +1,278 @@ +#include "N64Interface.h" + +#include +#include + +#define NOP asm volatile ("nop") +#define NOP5 asm volatile ("nop\nnop\nnop\nnop\nnop\n") +#define NOP30 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") + +// these two macros set arduino pin 2 to input or output, which with an +// external 1K pull-up resistor to the 3.3V rail, is like pulling it high or +// low. These operations translate to 1 op code, which takes 2 cycles +#define N64_PIND_HIGH DDRD &= ~pincode +#define N64_PIND_LOW DDRD |= pincode +#define N64_PIND_QUERY (PIND & pincode) + +#define N64_PINB_HIGH DDRB &= ~pincode +#define N64_PINB_LOW DDRB |= pincode +#define N64_PINB_QUERY (PINB & pincode) + +void N64Interface_PINB::init() { + unsigned char initialize = 0x00; + noInterrupts(); + send(&initialize, 1); + int x; + for (x=0; x<64; x++) { + if (!N64_PINB_QUERY) + x = 0; + } + unsigned char command[] = {0x01}; + send(command, 1); + get(); + interrupts(); +} + +void N64Interface_PINB::send(unsigned char * buffer, char length) { + char bits; + asm volatile (";Starting outer for loop"); +outer_loop: + { + asm volatile (";Starting inner for loop"); + bits=8; +inner_loop: + { + asm volatile (";Setting line to low"); + N64_PINB_LOW; + asm volatile (";branching"); + if (*buffer >> 7) { + asm volatile (";Bit is a 1"); + NOP5; + asm volatile (";Setting line to high"); + N64_PINB_HIGH; + NOP30; + } else { + asm volatile (";Bit is a 0"); + NOP30; NOP5; NOP; + + asm volatile (";Setting line to high"); + N64_PINB_HIGH; + asm volatile ("; end of conditional branch, need to wait 1us more before next bit"); + } + asm volatile (";finishing inner loop body"); + --bits; + if (bits != 0) { + NOP5; NOP; NOP; NOP; NOP; + asm volatile (";rotating out bits"); + *buffer <<= 1; + goto inner_loop; + } + } + asm volatile (";continuing outer loop"); + --length; + if (length != 0) { + ++buffer; + goto outer_loop; + } + } + NOP; NOP; NOP; NOP; + N64_PINB_LOW; + NOP5; NOP5; NOP; NOP; NOP; NOP; + N64_PINB_HIGH; +} + +void N64Interface_PINB::get() { + asm volatile (";Starting to listen"); + unsigned char timeout; + char bitcount = 32; + char *bitbin = raw_dump; +read_loop: + timeout = 0x3f; + while (N64_PINB_QUERY) { + if (!--timeout) + return; + } + NOP30; + *bitbin = N64_PINB_QUERY; + ++bitbin; + --bitcount; + if (bitcount == 0) + return; + timeout = 0x3f; + while (!N64_PINB_QUERY) { + if (!--timeout) + return; + } + goto read_loop; +} + +void N64Interface_PIND::init() { + // Initialize the gamecube controller by sending it a null byte. + // This is unnecessary for a standard controller, but is required for the + // Wavebird. + unsigned char initialize = 0x00; + noInterrupts(); + send(&initialize, 1); + + // Stupid routine to wait for the gamecube controller to stop + // sending its response. We don't care what it is, but we + // can't start asking for status if it's still responding + int x; + for (x=0; x<64; x++) { + // make sure the line is idle for 64 iterations, should + // be plenty. + if (!N64_PIND_QUERY) + x = 0; + } + + // Query for the gamecube controller's status. We do this + // to get the 0 point for the control stick. + unsigned char command[] = {0x01}; + send(command, 1); + // read in data and dump it to N64_raw_dump + get(); + interrupts(); +} + +/** + * This sends the given byte sequence to the controller + * length must be at least 1 + * Oh, it destroys the buffer passed in as it writes it + */ +void N64Interface_PIND::send(unsigned char * buffer, char length) { + // Send these bytes + char bits; + + // 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"); + N64_PIND_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 + NOP5; + + asm volatile (";Setting line to high"); + N64_PIND_HIGH; + + // nop block 2 + // we'll wait only 2us to sync up with both conditions + // at the bottom of the if statement + NOP30; + + } else { + asm volatile (";Bit is a 0"); + // 0 bit + // remain low for 3us, then go high for 1us + // nop block 3 + NOP30; NOP5; NOP; + + asm volatile (";Setting line to high"); + N64_PIND_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 + NOP5; NOP; NOP; NOP; NOP; + + // 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"); + N64_PIND_LOW; + // wait 1 us, 16 cycles, then raise the line + // 16-2=14 + // nop block 6 + NOP5; NOP5; NOP; NOP; NOP; NOP; + + N64_PIND_HIGH; +} + +void N64Interface_PIND::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 = raw_dump; + + // 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 (N64_PIND_QUERY) { + if (!--timeout) + return; + } + // wait approx 2us and poll the line + NOP30; + *bitbin = N64_PIND_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 (!N64_PIND_QUERY) { + if (!--timeout) + return; + } + goto read_loop; +} \ No newline at end of file diff --git a/N64Controller/N64Interface.h b/N64Controller/N64Interface.h new file mode 100644 index 0000000..1906c3a --- /dev/null +++ b/N64Controller/N64Interface.h @@ -0,0 +1,33 @@ +#ifndef N64Interface_h +#define N64Interface_h + +class N64Interface { +public: + virtual void init(); + virtual void send(unsigned char * buffer, char length); + virtual void get(); + + char raw_dump[33]; + +protected: + N64Interface(unsigned char pincode) : pincode(pincode) {}; + unsigned char pincode; +}; + +class N64Interface_PINB : public N64Interface { +public: + N64Interface_PINB(unsigned char pincode) : N64Interface(pincode) {}; + virtual void init(); + virtual void send(unsigned char * buffer, char length); + virtual void get(); +}; + +class N64Interface_PIND : public N64Interface { +public: + N64Interface_PIND(unsigned char pincode) : N64Interface(pincode) {}; + virtual void init(); + virtual void send(unsigned char * buffer, char length); + virtual void get(); +}; + +#endif \ No newline at end of file