/********************************************************************************** Nintendo 64 Controller Test for Arduino Mega Author: sanni Date: 2016-04-15 Version: V2 OLED lib: http://www.rinkydinkelectronics.com/library.php?id=79 Thanks to: Andrew Brown/Peter Den Hartog - N64 send/get functions **********************************************************************************/ #include extern uint8_t SmallFont[]; // define LCD pins OLED myOLED(SDA, SCL, 8); //define LED pin int ledPin = 10; // These two macros toggle the eepDataPin/ControllerDataPin between input and output // External 1K pull-up resistor from eepDataPin to VCC required // 0x10 = 00010000 -> Port H Pin 4 #define N64_HIGH DDRH &= ~0x10 #define N64_LOW DDRH |= 0x10 // Read the current state(0/1) of the eepDataPin #define N64_QUERY (PINH & 0x10) // received Controller data char N64_raw_dump[33]; // 1 received bit per byte String rawStr = ""; // above char array read into a string struct { char stick_x; char stick_y; } N64_status; // on which screens do we start int startscreen = 0; int mode = 0; int test = 1; //stings that hold the buttons String button = "N/A"; String lastbutton = "N/A"; //name of the current displayed result String anastick = ""; // Graph int xax = 22 + 24; // midpoint x int yax = 24; // midpoint y int zax = 24; // size // variables to display test data of different sticks int upx = 0; int upy = 0; int uprightx = 0; int uprighty = 0; int rightx = 0; int righty = 0; int downrightx = 0; int downrighty = 0; int downx = 0; int downy = 0; int downleftx = 0; int downlefty = 0; int leftx = 0; int lefty = 0; int upleftx = 0; int uplefty = 0; // variables to save test data int bupx = 0; int bupy = 0; int buprightx = 0; int buprighty = 0; int brightx = 0; int brighty = 0; int bdownrightx = 0; int bdownrighty = 0; int bdownx = 0; int bdowny = 0; int bdownleftx = 0; int bdownlefty = 0; int bleftx = 0; int blefty = 0; int bupleftx = 0; int buplefty = 0; int results = 0; void N64_send(unsigned char *buffer, char length); void N64_get(); void setup() { // Communication with controller on this pin // Don't remove these lines, we don't want to push +5V to the controller // Output a low signal PORTH &= ~(1 << 4); // Set Controller Data Pin(PH4) to Input DDRH &= ~(1 << 4); // Led pinMode(ledPin, OUTPUT); // OLED myOLED.begin(); myOLED.setFont(SmallFont); } // 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 get_button() { // 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(); // 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)); // 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; } // read char array N64_raw_dump into string rawStr rawStr = ""; for (i = 0; i < 16; i++) { rawStr = rawStr + String(N64_raw_dump[i], DEC); } // Buttons (A,B,Z,S,DU,DD,DL,DR,0,0,L,R,CU,CD,CL,CR) if (rawStr.substring(0, 16) == "0000000000000000") { lastbutton = button; button = "Press a button"; digitalWrite(ledPin, LOW); } else { digitalWrite(ledPin, HIGH); for (int i = 0; i < 16; i++) { // seems to be 16, 8 or 4 depending on what pin is used if (N64_raw_dump[i] == 16) { switch (i) { case 7: button = "D-Right"; break; case 6: button = "D-Left"; break; case 5: button = "D-Down"; break; case 4: button = "D-Up"; break; case 3: button = "START"; break; case 2: button = "Z"; break; case 1: button = "B"; break; case 0: button = "A"; break; case 15: button = "C-Right"; break; case 14: button = "C-Left"; break; case 13: button = "C-Down"; break; case 12: button = "C-Up"; break; case 11: button = "R"; break; case 10: button = "L"; break; } } } } } void printSTR(String st, int x, int y) { char buf[st.length() + 1]; st.toCharArray(buf, st.length() + 1); myOLED.print(buf, x, y); } void nextscreen() { if (button == "Press a button" && lastbutton == "START") { // reset button lastbutton = "N/A"; myOLED.clrScr(); if (startscreen != 4) startscreen = startscreen + 1; else { startscreen = 1; test = 1; } } } void loop() { // Get Button and analog stick get_button(); switch (startscreen) { case 0: // Logo Screen { myOLED.print("ControllerTest", CENTER, 8); myOLED.print("V1.0", CENTER, 18); myOLED.drawLine(22 + 0, 28, 22 + 84, 28); myOLED.print("2013 sanni", CENTER, 32); myOLED.update(); delay(1500); startscreen = 1; myOLED.clrScr(); break; } case 1: { myOLED.print("Button Test", CENTER, 0); myOLED.drawLine(22 + 0, 10, 22 + 84, 10); // Print Button printSTR(" " + button + " ", CENTER, 20); // Print Stick X Value String stickx = String("X: " + String(N64_status.stick_x, DEC) + " "); printSTR(stickx, 22 + 0, 38); // Print Stick Y Value String sticky = String("Y: " + String(N64_status.stick_y, DEC) + " "); printSTR(sticky, 22 + 42, 38); //Update LCD myOLED.update(); // go to next screen nextscreen(); break; } case 2: { myOLED.print("Range", 22 + 52, 5); myOLED.print("Test", 22 + 52, 15); myOLED.drawRect(22 + 50, 0, 22 + 83, 25); // Print Stick X Value String stickx = String("X:" + String(N64_status.stick_x, DEC) + " "); printSTR(stickx, 22 + 50, 28); // Print Stick Y Value String sticky = String("Y:" + String(N64_status.stick_y, DEC) + " "); printSTR(sticky, 22 + 50, 38); // Draw Axis myOLED.drawLine(xax - zax, yax, xax + zax, yax); myOLED.drawLine(xax, yax - zax, xax, yax + zax); myOLED.clrPixel(xax, yax - 80 / 4); myOLED.clrPixel(xax, yax + 80 / 4); myOLED.clrPixel(xax + 80 / 4, yax); myOLED.clrPixel(xax - 80 / 4, yax); //Draw Analog Stick if (mode == 1) { myOLED.setPixel(xax + N64_status.stick_x / 4, yax - N64_status.stick_y / 4); //Update LCD myOLED.update(); } else { myOLED.drawCircle(xax + N64_status.stick_x / 4, yax - N64_status.stick_y / 4, 2); //Update LCD myOLED.update(); myOLED.clrScr(); } // switch mode if (button == "Press a button" && lastbutton == "Z") { if (mode == 0) { mode = 1; myOLED.clrScr(); } else { mode = 0; myOLED.clrScr(); } } // go to next screen nextscreen(); break; } case 3: { myOLED.print("Skipping Test", CENTER, 0); myOLED.drawLine(22 + 0, 10, 22 + 83, 10); myOLED.drawRect(22 + 0, 20, 22 + 83, 44); if (N64_status.stick_x > 0) myOLED.drawLine(22 + N64_status.stick_x, 20, 22 + N64_status.stick_x, 44); //Update LCD myOLED.update(); if (button == "Press a button" && lastbutton == "Z") { // reset button lastbutton = "N/A"; myOLED.clrScr(); } // go to next screen nextscreen(); break; } case 4: { switch ( test ) { case 0: // Display results { switch (results) { case 0: { anastick = "YOURS"; upx = bupx; upy = bupy; uprightx = buprightx; uprighty = buprighty; rightx = brightx; righty = brighty; downrightx = bdownrightx; downrighty = bdownrighty; downx = bdownx; downy = bdowny; downleftx = bdownleftx; downlefty = bdownlefty; leftx = bleftx; lefty = blefty; upleftx = bupleftx; uplefty = buplefty; if (button == "Press a button" && lastbutton == "A") { // reset button lastbutton = "N/A"; results = 1; } break; } case 1: { anastick = "ORIG"; upx = 1; upy = 84; uprightx = 67; uprighty = 68; rightx = 83; righty = -2; downrightx = 67; downrighty = -69; downx = 3; downy = -85; downleftx = -69; downlefty = -70; leftx = -85; lefty = 0; upleftx = -68; uplefty = 68; if (button == "Press a button" && lastbutton == "A") { // reset button lastbutton = "N/A"; results = 0; } break; } } //results myOLED.clrScr(); printSTR(anastick, 22 + 50, 0); myOLED.print("U:", 22 + 50, 10); myOLED.printNumI(upy, RIGHT, 10); myOLED.print("D:", 22 + 50, 20); myOLED.printNumI(downy, RIGHT, 20); myOLED.print("L:", 22 + 50, 30); myOLED.printNumI(leftx, RIGHT, 30); myOLED.print("R:", 22 + 50, 40); myOLED.printNumI(rightx, RIGHT, 40); myOLED.drawLine(xax + upx / 4, yax - upy / 4, xax + uprightx / 4, yax - uprighty / 4); myOLED.drawLine(xax + uprightx / 4, yax - uprighty / 4, xax + rightx / 4, yax - righty / 4); myOLED.drawLine(xax + rightx / 4, yax - righty / 4, xax + downrightx / 4, yax - downrighty / 4); myOLED.drawLine(xax + downrightx / 4, yax - downrighty / 4, xax + downx / 4, yax - downy / 4); myOLED.drawLine(xax + downx / 4, yax - downy / 4, xax + downleftx / 4, yax - downlefty / 4); myOLED.drawLine(xax + downleftx / 4, yax - downlefty / 4, xax + leftx / 4, yax - lefty / 4); myOLED.drawLine(xax + leftx / 4, yax - lefty / 4, xax + upleftx / 4, yax - uplefty / 4); myOLED.drawLine(xax + upleftx / 4, yax - uplefty / 4, xax + upx / 4, yax - upy / 4); myOLED.setPixel(xax, yax); //Update LCD myOLED.update(); break; } //display results case 1:// +y Up { myOLED.print("Hold Stick Up", CENTER, 18); myOLED.print("then press A", CENTER, 28); //myOLED.drawBitmap(110, 60, ana1); if (button == "Press a button" && lastbutton == "A") { bupx = N64_status.stick_x; bupy = N64_status.stick_y; // reset button lastbutton = "N/A"; myOLED.clrScr(); test = 2; } break; } case 2:// +y+x Up-Right { myOLED.print("Up-Right", CENTER, 22 ); //myOLED.drawBitmap(110, 60, ana2); if (button == "Press a button" && lastbutton == "A") { buprightx = N64_status.stick_x; buprighty = N64_status.stick_y; test = 3; // reset button lastbutton = "N/A"; myOLED.clrScr(); } break; } case 3:// +x Right { myOLED.print("Right", CENTER, 22 ); //myOLED.drawBitmap(110, 60, ana3); if (button == "Press a button" && lastbutton == "A") { brightx = N64_status.stick_x; brighty = N64_status.stick_y; test = 4; // reset button lastbutton = "N/A"; myOLED.clrScr(); } break; } case 4:// -y+x Down-Right { myOLED.print("Down-Right", CENTER, 22 ); //myOLED.drawBitmap(110, 60, ana4); if (button == "Press a button" && lastbutton == "A") { bdownrightx = N64_status.stick_x; bdownrighty = N64_status.stick_y; test = 5; // reset button lastbutton = "N/A"; myOLED.clrScr(); } break; } case 5:// -y Down { myOLED.print("Down", CENTER, 22 ); //myOLED.drawBitmap(110, 60, ana5); if (button == "Press a button" && lastbutton == "A") { bdownx = N64_status.stick_x; bdowny = N64_status.stick_y; test = 6; // reset button lastbutton = "N/A"; myOLED.clrScr(); } break; } case 6:// -y-x Down-Left { myOLED.print("Down-Left", CENTER, 22 ); //myOLED.drawBitmap(110, 60, ana6); if (button == "Press a button" && lastbutton == "A") { bdownleftx = N64_status.stick_x; bdownlefty = N64_status.stick_y; test = 7; // reset button lastbutton = "N/A"; myOLED.clrScr(); } break; } case 7:// -x Left { myOLED.print("Left", CENTER, 22 ); //myOLED.drawBitmap(110, 60, ana7); if (button == "Press a button" && lastbutton == "A") { bleftx = N64_status.stick_x; blefty = N64_status.stick_y; test = 8; // reset button lastbutton = "N/A"; myOLED.clrScr(); } break; } case 8:// +y+x Up-Left { myOLED.print("Up-Left", CENTER, 22); //myOLED.drawBitmap(110, 60, ana8); if (button == "Press a button" && lastbutton == "A") { bupleftx = N64_status.stick_x; buplefty = N64_status.stick_y; test = 0; // reset button lastbutton = "N/A"; myOLED.clrScr(); } break; } } if (test != 0) { myOLED.print("Benchmark", CENTER, 0); myOLED.drawLine(22 + 0, 9, 22 + 83, 9); } myOLED.update(); // go to next screen nextscreen(); break; } } }