├── README.md ├── library.properties └── src ├── N64Controller.cpp ├── N64Controller.h ├── N64Interface.cpp └── N64Interface.h /README.md: -------------------------------------------------------------------------------- 1 | # Arduino N64 Controller Library 2 | 3 | Based on the work in [this guide for using Nintendo controllers with Arduinos](http://www.instructables.com/id/Use-an-Arduino-with-an-N64-controller/) here comes a comfortable library for usage with, e.g., Arduino Uno and similar CPUs. For NES there is already [nespad](http://code.google.com/p/nespad/). This library uses inline assembly. Controllers can be attached to PIN 0 up to 13. But be aware that it's not written in best way possible. To use it, search for N64 in the Library Manager. Otherwise place the folder N64Controller into your `libraries` folder or download and import it as ZIP. 4 | 5 | I used it in combination with [TVout](http://code.google.com/p/arduino-tvout/) and [EEPROM](http://arduino.cc/playground/Code/EEPROMWriteAnything) for highscore saving when I modified an existing Tetris port. The port used a Simple Tetris Clone under MIT license and if you want you can see the result here which is quite nice: [N64Tetris](http://pothos.blogsport.eu/files/2012/03/N64Tetris.zip). 6 | 7 | 8 | ## Example code for library usage 9 | 10 | ```cpp 11 | #include 12 | 13 | N64Controller player1 (12); // this controller for player one is on PIN 12 14 | 15 | void setup() { 16 | player1.begin(); // Initialisation 17 | } 18 | 19 | void loop() { 20 | delay(30); 21 | player1.update(); // read key state 22 | if (player1.A() && player1.D_down() 23 | || player1.Start()) { // has no deeper meaning ;) 24 | int xaxis = player1.axis_x(); // can be negative oder positive 25 | // regarding to orientation of the analog stick 26 | } 27 | // … 28 | } 29 | ``` 30 | 31 | ## Wireing 32 | 33 | To use, hook up the following to the Arduino: 34 | 35 | * Digital I/O PIN specified as parameter (2 is fallback if you specify a number > 13): Connect to middle N64 controler PIN for serial/signal line 36 | * Grounding GND: Connect to left N64 controller PIN 37 | * Power line 3.3V: Connect to right N64 controller PIN 38 | 39 | ``` 40 | /------------\ 41 | / O O O \ 42 | | GND Signl 3.3V | 43 | |________________| 44 | ``` 45 | 46 | Maybe: connect PIN X with external 1K pull-up resistor to the 3.3V rail 47 | 48 | ## Authors 49 | 50 | * Gamecube controller to Nintendo 64 adapter by Andrew Brown 51 | * Rewritten for N64 to HID by Peter Den Hartog 52 | * Modified to be a library with selectable pins by Kai Lüke 53 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=N64Controller 2 | version=0.1.1 3 | author=Andrew Brown, Peter Den Hartog, Kai Lüke, Daniel Schaal 4 | maintainer=Kai Lüke 5 | sentence=Arduino library to connect a Nintendo 64 controller (N64 controller). 6 | paragraph= 7 | category=Device Control 8 | includes=N64Controller.h 9 | url=https://github.com/pothos/arduino-n64-controller-library 10 | architectures=avr 11 | -------------------------------------------------------------------------------- /src/N64Controller.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "N64Controller.h" 3 | #include 4 | 5 | N64Controller::N64Controller(unsigned char serialPin) { 6 | if(serialPin > 13) 7 | serialPin = 2; 8 | // Communication with N64 controller controller on this pin 9 | // Don't remove these lines, we don't want to push +5V to the controller 10 | digitalWrite(serialPin, LOW); 11 | pinMode(serialPin, INPUT); 12 | 13 | // 0-7: DDRD, 8-13: DDRB 14 | bool isDDRD = (serialPin / 8) == 0; 15 | char n64_pincode = 0x01 << (serialPin % 8); 16 | 17 | if(isDDRD) { 18 | interface = new N64Interface_PIND(n64_pincode); 19 | } else { 20 | interface = new N64Interface_PINB(n64_pincode); 21 | } 22 | } 23 | 24 | void N64Controller::begin() { 25 | interface->init(); 26 | } 27 | 28 | void N64Controller::print_N64_status() 29 | { 30 | // bits: A, B, Z, Start, Dup, Ddown, Dleft, Dright 31 | // bits: 0, 0, L, R, Cup, Cdown, Cleft, Cright 32 | Serial.println(); 33 | Serial.print("Start: "); 34 | Serial.println(Start()); 35 | 36 | Serial.print("Z: "); 37 | Serial.println(Z()); 38 | 39 | Serial.print("B: "); 40 | Serial.println(B()); 41 | 42 | Serial.print("A: "); 43 | Serial.println(A()); 44 | 45 | Serial.print("L: "); 46 | Serial.println(L()); 47 | Serial.print("R: "); 48 | Serial.println(R()); 49 | 50 | Serial.print("Cup: "); 51 | Serial.println(C_up()); 52 | Serial.print("Cdown: "); 53 | Serial.println(C_down()); 54 | Serial.print("Cright:"); 55 | Serial.println(C_right()); 56 | Serial.print("Cleft: "); 57 | Serial.println(C_left()); 58 | 59 | Serial.print("Dup: "); 60 | Serial.println(D_up()); 61 | Serial.print("Ddown: "); 62 | Serial.println(D_down()); 63 | Serial.print("Dright:"); 64 | Serial.println(D_right()); 65 | Serial.print("Dleft: "); 66 | Serial.println(D_left()); 67 | 68 | Serial.print("Stick X:"); 69 | Serial.println(axis_x(), DEC); 70 | Serial.print("Stick Y:"); 71 | Serial.println(axis_y(), DEC); 72 | } 73 | 74 | void N64Controller::update() { 75 | unsigned char command[] = {0x01}; 76 | noInterrupts(); 77 | interface->send(command, 1); 78 | interface->get(); 79 | interrupts(); 80 | } 81 | -------------------------------------------------------------------------------- /src/N64Controller.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Gamecube controller to Nintendo 64 adapter 3 | * by Andrew Brown 4 | * Rewritten for N64 to HID by Peter Den Hartog 5 | * Modified to be a library by Kai Lüke 6 | */ 7 | 8 | /** 9 | * To use, hook up the following to the Arduino: 10 | * Digital I/O 2: N64 serial line 11 | * All appropriate grounding and power lines, i.e. 12 | * GND to left N64 controller PIN, Dig.PIN2 to middle Serial/Signal, 13 | * 3.3V to right N64 PIN 14 | * /------------\ 15 | * / O O O \ 16 | * | GND Signl 3.3V | 17 | * |________________| 18 | * Maybe: connect PIN X with external 1K pull-up resistor to the 3.3V rail 19 | * Default and fallback PIN is 2 20 | */ 21 | 22 | #ifndef N64Controller_h 23 | #define N64Controller_h 24 | 25 | #include "N64Interface.h" 26 | 27 | #define A_IDX 0 28 | #define B_IDX 1 29 | #define Z_IDX 2 30 | #define START_IDX 3 31 | #define D_UP_IDX 4 32 | #define D_DOWN_IDX 5 33 | #define D_LEFT_IDX 6 34 | #define D_RIGHT_IDX 7 35 | #define L_IDX 10 36 | #define R_IDX 11 37 | #define C_UP_IDX 12 38 | #define C_DOWN_IDX 13 39 | #define C_LEFT_IDX 14 40 | #define C_RIGHT_IDX 15 41 | #define X_IDX 16 42 | #define Y_IDX 24 43 | 44 | class N64Controller { 45 | public: 46 | N64Controller(unsigned char serialPin); // first thing to call 47 | void begin(); // second thing to call 48 | void update(); // then update always and get button info 49 | // consider to have a delay instead of 50 | // calling update all the time in a loop 51 | inline bool D_up() { return (interface->raw_dump[D_UP_IDX]) > 0; }; 52 | inline bool D_down() { return (interface->raw_dump[D_DOWN_IDX]) > 0; }; 53 | inline bool D_left() { return (interface->raw_dump[D_LEFT_IDX]) > 0; }; 54 | inline bool D_right() { return (interface->raw_dump[D_RIGHT_IDX]) > 0; }; 55 | inline bool Start() { return (interface->raw_dump[START_IDX]) > 0; }; 56 | inline bool A() { return (interface->raw_dump[A_IDX]) > 0; }; 57 | inline bool B() { return (interface->raw_dump[B_IDX]) > 0; }; 58 | inline bool Z() { return (interface->raw_dump[Z_IDX]) > 0; }; 59 | inline bool L() { return (interface->raw_dump[L_IDX]) > 0; }; 60 | inline bool R() { return (interface->raw_dump[R_IDX]) > 0; }; 61 | inline bool C_up() { return (interface->raw_dump[C_UP_IDX]) > 0; }; 62 | inline bool C_down() { return (interface->raw_dump[C_DOWN_IDX]) > 0; }; 63 | inline bool C_left() { return (interface->raw_dump[C_LEFT_IDX]) > 0; }; 64 | inline bool C_right() { return (interface->raw_dump[C_RIGHT_IDX]) > 0; }; 65 | inline char axis_x() { return axis(X_IDX); }; 66 | inline char axis_y() { return axis(Y_IDX); }; 67 | 68 | void print_N64_status(); 69 | private: 70 | N64Interface * interface; 71 | 72 | inline char axis(int index) { 73 | char value = 0; 74 | for (char i=0; i<8; i++) { 75 | value |= interface->raw_dump[index+i] ? (0x80 >> i) : 0; 76 | } 77 | return value; 78 | } 79 | }; 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/N64Interface.cpp: -------------------------------------------------------------------------------- 1 | #include "N64Interface.h" 2 | 3 | #include 4 | #include 5 | 6 | #define NOP asm volatile ("nop") 7 | #define NOP5 asm volatile ("nop\nnop\nnop\nnop\nnop\n") 8 | #define NOP30 asm volatile ("nop\nnop\nnop\nnop\nnop\n" \ 9 | "nop\nnop\nnop\nnop\nnop\n" \ 10 | "nop\nnop\nnop\nnop\nnop\n" \ 11 | "nop\nnop\nnop\nnop\nnop\n" \ 12 | "nop\nnop\nnop\nnop\nnop\n" \ 13 | "nop\nnop\nnop\nnop\nnop\n") 14 | 15 | // these two macros set arduino pin 2 to input or output, which with an 16 | // external 1K pull-up resistor to the 3.3V rail, is like pulling it high or 17 | // low. These operations translate to 1 op code, which takes 2 cycles 18 | #define N64_PIND_HIGH DDRD &= ~pincode 19 | #define N64_PIND_LOW DDRD |= pincode 20 | #define N64_PIND_QUERY (PIND & pincode) 21 | 22 | #define N64_PINB_HIGH DDRB &= ~pincode 23 | #define N64_PINB_LOW DDRB |= pincode 24 | #define N64_PINB_QUERY (PINB & pincode) 25 | 26 | void N64Interface_PINB::init() { 27 | unsigned char initialize = 0x00; 28 | noInterrupts(); 29 | send(&initialize, 1); 30 | int x; 31 | for (x=0; x<64; x++) { 32 | if (!N64_PINB_QUERY) 33 | x = 0; 34 | } 35 | unsigned char command[] = {0x01}; 36 | send(command, 1); 37 | get(); 38 | interrupts(); 39 | } 40 | 41 | void N64Interface_PINB::send(unsigned char * buffer, char length) { 42 | char bits; 43 | asm volatile (";Starting outer for loop"); 44 | outer_loop: 45 | { 46 | asm volatile (";Starting inner for loop"); 47 | bits=8; 48 | inner_loop: 49 | { 50 | asm volatile (";Setting line to low"); 51 | N64_PINB_LOW; 52 | asm volatile (";branching"); 53 | if (*buffer >> 7) { 54 | asm volatile (";Bit is a 1"); 55 | NOP5; 56 | asm volatile (";Setting line to high"); 57 | N64_PINB_HIGH; 58 | NOP30; 59 | } else { 60 | asm volatile (";Bit is a 0"); 61 | NOP30; NOP5; NOP; 62 | 63 | asm volatile (";Setting line to high"); 64 | N64_PINB_HIGH; 65 | asm volatile ("; end of conditional branch, need to wait 1us more before next bit"); 66 | } 67 | asm volatile (";finishing inner loop body"); 68 | --bits; 69 | if (bits != 0) { 70 | NOP5; NOP; NOP; NOP; NOP; 71 | asm volatile (";rotating out bits"); 72 | *buffer <<= 1; 73 | goto inner_loop; 74 | } 75 | } 76 | asm volatile (";continuing outer loop"); 77 | --length; 78 | if (length != 0) { 79 | ++buffer; 80 | goto outer_loop; 81 | } 82 | } 83 | NOP; NOP; NOP; NOP; 84 | N64_PINB_LOW; 85 | NOP5; NOP5; NOP; NOP; NOP; NOP; 86 | N64_PINB_HIGH; 87 | } 88 | 89 | void N64Interface_PINB::get() { 90 | asm volatile (";Starting to listen"); 91 | unsigned char timeout; 92 | char bitcount = 32; 93 | char *bitbin = raw_dump; 94 | read_loop: 95 | timeout = 0x3f; 96 | while (N64_PINB_QUERY) { 97 | if (!--timeout) 98 | return; 99 | } 100 | NOP30; 101 | *bitbin = N64_PINB_QUERY; 102 | ++bitbin; 103 | --bitcount; 104 | if (bitcount == 0) 105 | return; 106 | timeout = 0x3f; 107 | while (!N64_PINB_QUERY) { 108 | if (!--timeout) 109 | return; 110 | } 111 | goto read_loop; 112 | } 113 | 114 | void N64Interface_PIND::init() { 115 | // Initialize the gamecube controller by sending it a null byte. 116 | // This is unnecessary for a standard controller, but is required for the 117 | // Wavebird. 118 | unsigned char initialize = 0x00; 119 | noInterrupts(); 120 | send(&initialize, 1); 121 | 122 | // Stupid routine to wait for the gamecube controller to stop 123 | // sending its response. We don't care what it is, but we 124 | // can't start asking for status if it's still responding 125 | int x; 126 | for (x=0; x<64; x++) { 127 | // make sure the line is idle for 64 iterations, should 128 | // be plenty. 129 | if (!N64_PIND_QUERY) 130 | x = 0; 131 | } 132 | 133 | // Query for the gamecube controller's status. We do this 134 | // to get the 0 point for the control stick. 135 | unsigned char command[] = {0x01}; 136 | send(command, 1); 137 | // read in data and dump it to N64_raw_dump 138 | get(); 139 | interrupts(); 140 | } 141 | 142 | /** 143 | * This sends the given byte sequence to the controller 144 | * length must be at least 1 145 | * Oh, it destroys the buffer passed in as it writes it 146 | */ 147 | void N64Interface_PIND::send(unsigned char * buffer, char length) { 148 | // Send these bytes 149 | char bits; 150 | 151 | // This routine is very carefully timed by examining the assembly output. 152 | // Do not change any statements, it could throw the timings off 153 | // 154 | // We get 16 cycles per microsecond, which should be plenty, but we need to 155 | // be conservative. Most assembly ops take 1 cycle, but a few take 2 156 | // 157 | // I use manually constructed for-loops out of gotos so I have more control 158 | // over the outputted assembly. I can insert nops where it was impossible 159 | // with a for loop 160 | 161 | asm volatile (";Starting outer for loop"); 162 | outer_loop: 163 | { 164 | asm volatile (";Starting inner for loop"); 165 | bits=8; 166 | inner_loop: 167 | { 168 | // Starting a bit, set the line low 169 | asm volatile (";Setting line to low"); 170 | N64_PIND_LOW; // 1 op, 2 cycles 171 | 172 | asm volatile (";branching"); 173 | if (*buffer >> 7) { 174 | asm volatile (";Bit is a 1"); 175 | // 1 bit 176 | // remain low for 1us, then go high for 3us 177 | // nop block 1 178 | NOP5; 179 | 180 | asm volatile (";Setting line to high"); 181 | N64_PIND_HIGH; 182 | 183 | // nop block 2 184 | // we'll wait only 2us to sync up with both conditions 185 | // at the bottom of the if statement 186 | NOP30; 187 | 188 | } else { 189 | asm volatile (";Bit is a 0"); 190 | // 0 bit 191 | // remain low for 3us, then go high for 1us 192 | // nop block 3 193 | NOP30; NOP5; NOP; 194 | 195 | asm volatile (";Setting line to high"); 196 | N64_PIND_HIGH; 197 | 198 | // wait for 1us 199 | asm volatile ("; end of conditional branch, need to wait 1us more before next bit"); 200 | 201 | } 202 | // end of the if, the line is high and needs to remain 203 | // high for exactly 16 more cycles, regardless of the previous 204 | // branch path 205 | 206 | asm volatile (";finishing inner loop body"); 207 | --bits; 208 | if (bits != 0) { 209 | // nop block 4 210 | // this block is why a for loop was impossible 211 | NOP5; NOP; NOP; NOP; NOP; 212 | 213 | // rotate bits 214 | asm volatile (";rotating out bits"); 215 | *buffer <<= 1; 216 | 217 | goto inner_loop; 218 | } // fall out of inner loop 219 | } 220 | asm volatile (";continuing outer loop"); 221 | // In this case: the inner loop exits and the outer loop iterates, 222 | // there are /exactly/ 16 cycles taken up by the necessary operations. 223 | // So no nops are needed here (that was lucky!) 224 | --length; 225 | if (length != 0) { 226 | ++buffer; 227 | goto outer_loop; 228 | } // fall out of outer loop 229 | } 230 | 231 | // send a single stop (1) bit 232 | // nop block 5 233 | asm volatile ("nop\nnop\nnop\nnop\n"); 234 | N64_PIND_LOW; 235 | // wait 1 us, 16 cycles, then raise the line 236 | // 16-2=14 237 | // nop block 6 238 | NOP5; NOP5; NOP; NOP; NOP; NOP; 239 | 240 | N64_PIND_HIGH; 241 | } 242 | 243 | void N64Interface_PIND::get() { 244 | // listen for the expected 8 bytes of data back from the controller and 245 | // blast it out to the N64_raw_dump array, one bit per byte for extra speed. 246 | // Afterwards, call translate_raw_data() to interpret the raw data and pack 247 | // it into the N64_status struct. 248 | asm volatile (";Starting to listen"); 249 | unsigned char timeout; 250 | char bitcount = 32; 251 | char *bitbin = raw_dump; 252 | 253 | // Again, using gotos here to make the assembly more predictable and 254 | // optimization easier (please don't kill me) 255 | read_loop: 256 | timeout = 0x3f; 257 | // wait for line to go low 258 | while (N64_PIND_QUERY) { 259 | if (!--timeout) 260 | return; 261 | } 262 | // wait approx 2us and poll the line 263 | NOP30; 264 | *bitbin = N64_PIND_QUERY; 265 | ++bitbin; 266 | --bitcount; 267 | if (bitcount == 0) 268 | return; 269 | 270 | // wait for line to go high again 271 | // it may already be high, so this should just drop through 272 | timeout = 0x3f; 273 | while (!N64_PIND_QUERY) { 274 | if (!--timeout) 275 | return; 276 | } 277 | goto read_loop; 278 | } -------------------------------------------------------------------------------- /src/N64Interface.h: -------------------------------------------------------------------------------- 1 | #ifndef N64Interface_h 2 | #define N64Interface_h 3 | 4 | class N64Interface { 5 | public: 6 | virtual void init(); 7 | virtual void send(unsigned char * buffer, char length); 8 | virtual void get(); 9 | 10 | char raw_dump[33]; 11 | 12 | protected: 13 | N64Interface(unsigned char pincode) : pincode(pincode) {}; 14 | unsigned char pincode; 15 | }; 16 | 17 | class N64Interface_PINB : public N64Interface { 18 | public: 19 | N64Interface_PINB(unsigned char pincode) : N64Interface(pincode) {}; 20 | virtual void init(); 21 | virtual void send(unsigned char * buffer, char length); 22 | virtual void get(); 23 | }; 24 | 25 | class N64Interface_PIND : public N64Interface { 26 | public: 27 | N64Interface_PIND(unsigned char pincode) : N64Interface(pincode) {}; 28 | virtual void init(); 29 | virtual void send(unsigned char * buffer, char length); 30 | virtual void get(); 31 | }; 32 | 33 | #endif --------------------------------------------------------------------------------