Skip to content

Instantly share code, notes, and snippets.

@LeeMartin77
Created June 5, 2020 20:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LeeMartin77/ff9db9eaed0d54a8bbdc3c987226ec98 to your computer and use it in GitHub Desktop.
Save LeeMartin77/ff9db9eaed0d54a8bbdc3c987226ec98 to your computer and use it in GitHub Desktop.
N64 to USB GamepadConverter with a ATmega32U4 board
// 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