Created
June 5, 2020 20:42
-
-
Save LeeMartin77/ff9db9eaed0d54a8bbdc3c987226ec98 to your computer and use it in GitHub Desktop.
N64 to USB GamepadConverter with a ATmega32U4 board
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// using the fantastic https://github.com/MHeironimus/ArduinoJoystickLibrary | |
// Based on the soulders of giants: | |
// Gamecube controller to Nintendo 64 adapter by Andrew Brown | |
// http://www.cs.duke.edu/~brownan/n642gc.html | |
// N64 to HID by Peter Den Hartog: | |
// https://www.instructables.com/id/Use-an-Arduino-with-an-N64-controller/ | |
// N64 Controller Tester by sanni | |
// https://github.com/sanni/controllertest | |
#include <pins_arduino.h> | |
//NOTE: This pin (4) maps to register 0x16 on my particular board. | |
//Your board will be different. Test the registister to save much frustration. | |
#define N64_PIN 4 | |
#define N64_PIN_DIR DDRD | |
// these two macros set arduino pin 2 to input or output, which is like | |
// pulling it high or low. These operations translate to 1 op code, | |
// which takes 2 cycles | |
#define N64_HIGH DDRD &= ~0x16 | |
#define N64_LOW DDRD |= 0x16 | |
#define N64_QUERY (PIND & 0x16) | |
// 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 translate_raw_data(); | |
#include <Joystick.h> | |
int modifierPin = 16; | |
// Create Joystick | |
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD, | |
15, 0, // Button Count, Hat Switch Count | |
true, true, false, // X and Y, but no Z Axis | |
false, false, false, // No Rx, Ry, or Rz | |
false, false, // No rudder or throttle | |
false, false, false); // No accelerator, brake, or steering | |
void setup() { | |
// Communication with 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); | |
// Set Range Values | |
// While the joystick uses an 8 bit integer for it's value, | |
// it never seems to exceed 85 - YMMV with controllers | |
Joystick.setXAxisRange(-85, 84); | |
Joystick.setYAxisRange(-85, 84); | |
//I have an additional pin mapped for a "Modifier" for emustation | |
//You should be able to remove it if you don't need it. | |
pinMode(modifierPin, INPUT); | |
Joystick.begin(false); | |
} | |
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 loop() { | |
// Command to send to the N64 Controller | |
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(); | |
//Take our status and convert it to the joypad | |
//Start: | |
Joystick.setButton(0, N64_status.data1 & 16 ? 1:0); | |
//Z: | |
Joystick.setButton(1, N64_status.data1 & 32 ? 1:0); | |
//B: | |
Joystick.setButton(2, N64_status.data1 & 64 ? 1:0); | |
//A: | |
Joystick.setButton(3, N64_status.data1 & 128 ? 1:0); | |
//L: | |
Joystick.setButton(4, N64_status.data2 & 32 ? 1:0); | |
//R: | |
Joystick.setButton(5, N64_status.data2 & 16 ? 1:0); | |
//Cup: | |
Joystick.setButton(6, N64_status.data2 & 0x08 ? 1:0); | |
//Cdown: | |
Joystick.setButton(7, N64_status.data2 & 0x04 ? 1:0); | |
//Cright: | |
Joystick.setButton(8, N64_status.data2 & 0x01 ? 1:0); | |
//Cleft: | |
Joystick.setButton(9, N64_status.data2 & 0x02 ? 1:0); | |
//Dup: | |
Joystick.setButton(10, N64_status.data1 & 0x08 ? 1:0); | |
//Ddown: | |
Joystick.setButton(11, N64_status.data1 & 0x04 ? 1:0); | |
//Dright: | |
Joystick.setButton(12, N64_status.data1 & 0x01 ? 1:0); | |
//Dleft: | |
Joystick.setButton(13, N64_status.data1 & 0x02 ? 1:0); | |
Joystick.setXAxis(N64_status.stick_x); | |
//Data for the Y axis seems to come out inverted | |
Joystick.setYAxis(N64_status.stick_y * -1); | |
//Modifier: | |
Joystick.setButton(14, digitalRead(modifierPin)); | |
Joystick.sendState(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment