/** * Gamecube controller to Nintendo 64 adapter * by Andrew Brown * Rewritten for N64 to HID by Peter Den Hartog * http://www.instructables.com/id/Use-an-Arduino-with-an-N64-controller/?ALLSTEPS * Modified for Atmega32u4 USB-HID by Jarno Lehtinen */ /** * To use, hook up the following to the Arduino Duemilanove: * Digital I/O 2: N64 serial line * All appropriate grounding and power lines */ #include "Joystick.h" //#define DEBUG //USE PIN 4 FOR DATA PIN! //USE ONLY 3.3V FOR VCC AND DATA! //TIMINGS ARE FOR 16MHz ATmega! (ATmega32u4 3.3V is 8MHz) #include "pins_arduino.h" #define N64_PIN 4 //2 #define N64_PIN_DIR DDRD // 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_HIGH DDRD &= ~0x16 //0x04 #define N64_LOW DDRD |= 0x16 //0x04 #define N64_QUERY (PIND & 0x16)//0x04) // 8 bytes of data that we get from the controller struct { // bits: 0, 0, 0, start, y, x, b, a unsigned char data1; // bits: 1, L, R, Z, Dup, Ddown, Dright, Dleft unsigned char data2; char stick_x; char stick_y; } N64_status; char N64_raw_dump[33]; // 1 received bit per byte void N64_send(unsigned char *buffer, char length); void N64_get(); void print_N64_status(); void translate_raw_data(); /** * This CRC table for repeating bytes is take from * the cube64 project * http://cia.vc/stats/project/navi-misc/cube64 */ unsigned char crc_repeating_table[] = { 0xFF, 0x14, 0xAC, 0x47, 0x59, 0xB2, 0x0A, 0xE1, 0x36, 0xDD, 0x65, 0x8E, 0x90, 0x7B, 0xC3, 0x28, 0xE8, 0x03, 0xBB, 0x50, 0x4E, 0xA5, 0x1D, 0xF6, 0x21, 0xCA, 0x72, 0x99, 0x87, 0x6C, 0xD4, 0x3F, 0xD1, 0x3A, 0x82, 0x69, 0x77, 0x9C, 0x24, 0xCF, 0x18, 0xF3, 0x4B, 0xA0, 0xBE, 0x55, 0xED, 0x06, 0xC6, 0x2D, 0x95, 0x7E, 0x60, 0x8B, 0x33, 0xD8, 0x0F, 0xE4, 0x5C, 0xB7, 0xA9, 0x42, 0xFA, 0x11, 0xA3, 0x48, 0xF0, 0x1B, 0x05, 0xEE, 0x56, 0xBD, 0x6A, 0x81, 0x39, 0xD2, 0xCC, 0x27, 0x9F, 0x74, 0xB4, 0x5F, 0xE7, 0x0C, 0x12, 0xF9, 0x41, 0xAA, 0x7D, 0x96, 0x2E, 0xC5, 0xDB, 0x30, 0x88, 0x63, 0x8D, 0x66, 0xDE, 0x35, 0x2B, 0xC0, 0x78, 0x93, 0x44, 0xAF, 0x17, 0xFC, 0xE2, 0x09, 0xB1, 0x5A, 0x9A, 0x71, 0xC9, 0x22, 0x3C, 0xD7, 0x6F, 0x84, 0x53, 0xB8, 0x00, 0xEB, 0xF5, 0x1E, 0xA6, 0x4D, 0x47, 0xAC, 0x14, 0xFF, 0xE1, 0x0A, 0xB2, 0x59, 0x8E, 0x65, 0xDD, 0x36, 0x28, 0xC3, 0x7B, 0x90, 0x50, 0xBB, 0x03, 0xE8, 0xF6, 0x1D, 0xA5, 0x4E, 0x99, 0x72, 0xCA, 0x21, 0x3F, 0xD4, 0x6C, 0x87, 0x69, 0x82, 0x3A, 0xD1, 0xCF, 0x24, 0x9C, 0x77, 0xA0, 0x4B, 0xF3, 0x18, 0x06, 0xED, 0x55, 0xBE, 0x7E, 0x95, 0x2D, 0xC6, 0xD8, 0x33, 0x8B, 0x60, 0xB7, 0x5C, 0xE4, 0x0F, 0x11, 0xFA, 0x42, 0xA9, 0x1B, 0xF0, 0x48, 0xA3, 0xBD, 0x56, 0xEE, 0x05, 0xD2, 0x39, 0x81, 0x6A, 0x74, 0x9F, 0x27, 0xCC, 0x0C, 0xE7, 0x5F, 0xB4, 0xAA, 0x41, 0xF9, 0x12, 0xC5, 0x2E, 0x96, 0x7D, 0x63, 0x88, 0x30, 0xDB, 0x35, 0xDE, 0x66, 0x8D, 0x93, 0x78, 0xC0, 0x2B, 0xFC, 0x17, 0xAF, 0x44, 0x5A, 0xB1, 0x09, 0xE2, 0x22, 0xC9, 0x71, 0x9A, 0x84, 0x6F, 0xD7, 0x3C, 0xEB, 0x00, 0xB8, 0x53, 0x4D, 0xA6, 0x1E, 0xF5 //0xFF }; void setupJoysticks() { // Communication with gamecube 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); // 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_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_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_send(command, 1); // read in data and dump it to N64_raw_dump N64_get(); interrupts(); translate_raw_data(); } void translate_raw_data() { // The get_N64_status function sloppily dumps its data 1 bit per byte // into the get_status_extended char array. It's our job to go through // that and put each piece neatly into the struct N64_status int i; memset(&N64_status, 0, sizeof(N64_status)); // line 1 // bits: A, B, Z, Start, Dup, Ddown, Dleft, Dright for (i=0; i<8; i++) { N64_status.data1 |= N64_raw_dump[i] ? (0x80 >> i) : 0; } // line 2 // bits: 0, 0, L, R, Cup, Cdown, Cleft, Cright for (i=0; i<8; i++) { N64_status.data2 |= N64_raw_dump[8+i] ? (0x80 >> i) : 0; } // line 3 // bits: joystick x value // These are 8 bit values centered at 0x80 (128) for (i=0; i<8; i++) { N64_status.stick_x |= N64_raw_dump[16+i] ? (0x80 >> i) : 0; } for (i=0; i<8; i++) { N64_status.stick_y |= N64_raw_dump[24+i] ? (0x80 >> i) : 0; } } /** * 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 N64_send(unsigned char *buffer, char length) { // 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"); N64_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"); N64_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"); N64_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"); N64_LOW; // wait 1 us, 16 cycles, then raise the line // 16-2=14 // nop block 6 asm volatile ("nop\nnop\nnop\nnop\nnop\n" "nop\nnop\nnop\nnop\nnop\n" "nop\nnop\nnop\nnop\n"); N64_HIGH; } void N64_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 = 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_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 = N64_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_QUERY) { if (!--timeout) return; } goto read_loop; } void debug() { int i; // bits: A, B, Z, Start, Dup, Ddown, Dleft, Dright // bits: 0, 0, L, R, Cup, Cdown, Cleft, Cright Serial.println(); Serial.print("Start: "); Serial.println(N64_status.data1 & 16 ? 1:0); Serial.print("Z: "); Serial.println(N64_status.data1 & 32 ? 1:0); Serial.print("B: "); Serial.println(N64_status.data1 & 64 ? 1:0); Serial.print("A: "); Serial.println(N64_status.data1 & 128 ? 1:0); Serial.print("L: "); Serial.println(N64_status.data2 & 32 ? 1:0); Serial.print("R: "); Serial.println(N64_status.data2 & 16 ? 1:0); Serial.print("Cup: "); Serial.println(N64_status.data2 & 0x08 ? 1:0); Serial.print("Cdown: "); Serial.println(N64_status.data2 & 0x04 ? 1:0); Serial.print("Cright:"); Serial.println(N64_status.data2 & 0x01 ? 1:0); Serial.print("Cleft: "); Serial.println(N64_status.data2 & 0x02 ? 1:0); Serial.print("Dup: "); Serial.println(N64_status.data1 & 0x08 ? 1:0); Serial.print("Ddown: "); Serial.println(N64_status.data1 & 0x04 ? 1:0); Serial.print("Dright:"); Serial.println(N64_status.data1 & 0x01 ? 1:0); Serial.print("Dleft: "); Serial.println(N64_status.data1 & 0x02 ? 1:0); Serial.print("Stick X:"); Serial.println(N64_status.stick_x, DEC); Serial.print("Stick Y:"); Serial.println(N64_status.stick_y, DEC); } void readJoysticks() { int i; unsigned char data, addr; // Command to send to the gamecube // The last bit is rumble, flip it to rumble // yes this does need to be inside the loop, the // array gets mutilated when it goes through N64_send unsigned char command[] = {0x01}; // don't want interrupts getting in the way noInterrupts(); // send those 3 bytes N64_send(command, 1); // read in data and dump it to N64_raw_dump N64_get(); // end of time sensitive code interrupts(); // translate the data in N64_raw_dump to something useful translate_raw_data(); } void interpretJoystickState() { Joystick.setXAxis(N64_status.stick_x); Joystick.setYAxis(-N64_status.stick_y); Joystick.setButton(0, (N64_status.data1 & 16) ); //BUTTON1 (Start) Joystick.setButton(1, (N64_status.data1 & 32) ); //BUTTON2 (Select) (Z) Joystick.setButton(2, (N64_status.data1 & 128) ); //BUTTON3 (A) Joystick.setButton(3, (N64_status.data1 & 64) ); //BUTTON4 (B) Joystick.setButton(4, (N64_status.data2 & 0x04) ); //BUTTON5 (X) (Cdown) Joystick.setButton(5, (N64_status.data2 & 0x02) ); //BUTTON6 (Y) (Cleft) Joystick.setButton(6, (N64_status.data2 & 32) ); //BUTTON7 (LB) (L) Joystick.setButton(7, (N64_status.data2 & 16) ); //BUTTON8 (RB) (R) Joystick.setButton(8, (N64_status.data2 & 0x08) ); //BUTTON9 (LT) (Cup) Joystick.setButton(9, (N64_status.data2 & 0x01) ); //BUTTON10 (RT) (Cright) /*Joystick.setXAxisRotation(180); Joystick.setYAxisRotation(180); Joystick.setHatSwitch(0,-1); Joystick.setButton(10, 0); Joystick.setButton(11, 0);*/ Joystick.setHatSwitch(0,-1); if ( (N64_status.data1 & 0x08) ) Joystick.setHatSwitch(0,0); //UP if ( (N64_status.data1 & 0x04) ) Joystick.setHatSwitch(0,180); //DOWN if ( (N64_status.data1 & 0x02) ) Joystick.setHatSwitch(0,270); //LEFT if ( (N64_status.data1 & 0x01) ) Joystick.setHatSwitch(0,90); //RIGHT if ( (N64_status.data1 & 0x08) && (N64_status.data1 & 0x01) ) Joystick.setHatSwitch(0,45); //UP-RIGHT if ( (N64_status.data1 & 0x01) && (N64_status.data1 & 0x04) ) Joystick.setHatSwitch(0,135); //RIGHT-DOWN if ( (N64_status.data1 & 0x04) && (N64_status.data1 & 0x02) ) Joystick.setHatSwitch(0,225); //DOWN-LEFT if ( (N64_status.data1 & 0x02) && (N64_status.data1 & 0x08) ) Joystick.setHatSwitch(0,315); //LEFT-UP } void setup() { #ifdef DEBUG Serial.begin(9600); #endif setupJoysticks(); Joystick.begin(false); } uint8_t oldData1 = 0xff; uint8_t oldData2 = 0xff; int8_t oldStick_x = 0; int8_t oldStick_y = 0; uint8_t flag = 0; void loop() { readJoysticks(); if (N64_status.data1 != oldData1) { oldData1 = N64_status.data1; flag = 1; } if (N64_status.data2 != oldData2) { oldData2 = N64_status.data2; flag = 1; } if (N64_status.stick_x != oldStick_x) { oldStick_x = N64_status.stick_x; flag = 1; } if (N64_status.stick_y != oldStick_y) { oldStick_y = N64_status.stick_y; flag = 1; } if (flag) { interpretJoystickState(); Joystick.sendState(); flag = 0; } delayMicroseconds(1000); #ifdef DEBUG debug(); delay(200); #endif }