Skip to content

Instantly share code, notes, and snippets.

@cleure
Last active April 21, 2021 13:28
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save cleure/5820004 to your computer and use it in GitHub Desktop.
Save cleure/5820004 to your computer and use it in GitHub Desktop.
More advanced NES / SNES to USB adapter, using a Teensy 2.0 board. Controller type can now be auto-detected, and there's an option to translate button presses to either USB Joystick or USB Keyboard events.
#include <stdint.h>
// GPIO pins used for connected gamepad
#define CLOCK 21
#define LATCH 20
#define DATA 19
#define DEVICE_TYPE_AUTO 0
#define DEVICE_TYPE_NES 1
#define DEVICE_TYPE_SNES 2
#define DEVICE_METHOD_JOYSTICK 0
#define DEVICE_METHOD_KEYBOARD 1
enum {
NES_BUTTON_A,
NES_BUTTON_B,
NES_BUTTON_SELECT,
NES_BUTTON_START,
NES_DPAD_UP,
NES_DPAD_DOWN,
NES_DPAD_LEFT,
NES_DPAD_RIGHT
} NES_BUTTON_MAP;
enum {
SNES_BUTTON_B,
SNES_BUTTON_Y,
SNES_BUTTON_SELECT,
SNES_BUTTON_START,
SNES_DPAD_UP,
SNES_DPAD_DOWN,
SNES_DPAD_LEFT,
SNES_DPAD_RIGHT,
SNES_BUTTON_A,
SNES_BUTTON_X,
SNES_BUTTON_L,
SNES_BUTTON_R,
} SNES_BUTTON_MAP;
uint8_t BUTTON_STATE[16];
uint8_t DEVICE_TYPE = DEVICE_TYPE_AUTO; // Device Type (configurable)
uint8_t DEVICE_METHOD = DEVICE_METHOD_JOYSTICK; // Device Method (configurable)
const int16_t NES_JOYSTICK_MAP[4][2] = {
{NES_BUTTON_A, 1},
{NES_BUTTON_B, 2},
{NES_BUTTON_SELECT, 7},
{NES_BUTTON_START, 8},
};
// NES Keyboard Map (configurable)
const int16_t NES_KEYBOARD_MAP[8][2] = {
{NES_BUTTON_A, KEY_X},
{NES_BUTTON_B, KEY_Z},
{NES_BUTTON_SELECT, KEY_A},
{NES_BUTTON_START, KEY_S},
{NES_DPAD_UP, KEY_UP},
{NES_DPAD_DOWN, KEY_DOWN},
{NES_DPAD_LEFT, KEY_LEFT},
{NES_DPAD_RIGHT, KEY_RIGHT},
};
const int16_t SNES_JOYSTICK_MAP[8][2] = {
{SNES_BUTTON_A, 1},
{SNES_BUTTON_B, 2},
{SNES_BUTTON_X, 3},
{SNES_BUTTON_Y, 4},
{SNES_BUTTON_L, 5},
{SNES_BUTTON_R, 6},
{SNES_BUTTON_SELECT, 7},
{SNES_BUTTON_START, 8},
};
// SNES Keyboard Map (configurable)
const int16_t SNES_KEYBOARD_MAP[16][2] = {
{SNES_BUTTON_A, KEY_X},
{SNES_BUTTON_B, KEY_Z},
{SNES_BUTTON_X, KEY_D},
{SNES_BUTTON_Y, KEY_F},
{SNES_BUTTON_L, KEY_C},
{SNES_BUTTON_R, KEY_V},
{SNES_BUTTON_SELECT, KEY_A},
{SNES_BUTTON_START, KEY_S},
{SNES_DPAD_UP, KEY_UP},
{SNES_DPAD_DOWN, KEY_DOWN},
{SNES_DPAD_LEFT, KEY_LEFT},
{SNES_DPAD_RIGHT, KEY_RIGHT},
};
void (*PROCESS_INPUT_FN)(void);
// Delay for approx 100ns. Assumes 16Mhz AtMega CPU
#define delay100ns()
__asm__ __volatile__ ("nop\n\t");\
__asm__ __volatile__ ("nop\n\t");
#define SendCmdTakeSample(clock_pin, latch_pin)\
digitalWrite(clock_pin, HIGH);\
digitalWrite(latch_pin, HIGH);\
delay100ns();\
digitalWrite(latch_pin, LOW);
#define SendCmdSendData(clock_pin)\
delay100ns();\
digitalWrite(clock_pin, LOW);
#define SendCmdShiftOut(clock_pin)\
delay100ns();\
digitalWrite(clock_pin, HIGH);
void ReadInput(uint8_t *data, int num)
{
int i;
SendCmdTakeSample(CLOCK, LATCH);
// Read output
for (i = 0; i < num; i++) {
SendCmdSendData(CLOCK);
// 1 = Off, 0 = On... Bit must be inverted
data[i] = (~digitalRead(DATA)) & 0x1;
SendCmdShiftOut(CLOCK);
}
}
int GetHatState()
{
/*
0
UP
315 45
270 LT RT 90
225 135
DN
180
*/
uint8_t x, y;
const static int16_t dpad_lookup[4][4] = {
{ -1, 270, 90, -1},
{ 0, 315, 45, -1},
{180, 225, 135, -1},
{ -1, -1, -1, -1}
};
y = BUTTON_STATE[NES_DPAD_UP] | (BUTTON_STATE[NES_DPAD_DOWN] << 1);
x = BUTTON_STATE[NES_DPAD_LEFT] | (BUTTON_STATE[NES_DPAD_RIGHT] << 1);
return dpad_lookup[y][x];
}
void ProcessInputNES()
{
int i, hat0;
ReadInput((uint8_t *)&BUTTON_STATE, 8);
hat0 = GetHatState();
for (i = 0; i < 4; i++) {
Joystick.button(
NES_JOYSTICK_MAP[i][1],
BUTTON_STATE[NES_JOYSTICK_MAP[i][0]]);
}
Joystick.hat(hat0);
}
void ProcessInputNESUsingKeyboard()
{
int i;
ReadInput((uint8_t *)&BUTTON_STATE, 8);
for (i = 0; i < 8; i++) {
if (BUTTON_STATE[NES_KEYBOARD_MAP[i][0]]) {
Keyboard.press(NES_KEYBOARD_MAP[i][1]);
} else {
Keyboard.release(NES_KEYBOARD_MAP[i][1]);
}
}
}
void ProcessInputSNES()
{
int i, hat0;
ReadInput((uint8_t *)&BUTTON_STATE, 8);
hat0 = GetHatState();
for (i = 0; i < 8; i++) {
Joystick.button(
SNES_JOYSTICK_MAP[i][1],
BUTTON_STATE[SNES_JOYSTICK_MAP[i][0]]);
}
Joystick.hat(hat0);
}
void ProcessInputSNESUsingKeyboard()
{
int i;
ReadInput((uint8_t *)&BUTTON_STATE, 16);
for (i = 0; i < 16; i++) {
if (BUTTON_STATE[SNES_KEYBOARD_MAP[i][0]]) {
Keyboard.press(SNES_KEYBOARD_MAP[i][1]);
} else {
Keyboard.release(SNES_KEYBOARD_MAP[i][1]);
}
}
}
void ConfigureSelf()
{
int i, d;
if (DEVICE_TYPE == DEVICE_TYPE_AUTO) {
// Auto detect device type
SendCmdTakeSample(CLOCK, LATCH);
for (i = 0; i < 16; i++) {
SendCmdSendData(CLOCK);
d = digitalRead(DATA);
if (i < 8 && d == 1) {
DEVICE_TYPE = DEVICE_TYPE_NES;
} else if (i > 7 && d == 1) {
DEVICE_TYPE = DEVICE_TYPE_SNES;
}
SendCmdShiftOut(CLOCK);
}
};
if (DEVICE_TYPE == DEVICE_TYPE_NES) {
if (DEVICE_METHOD == DEVICE_METHOD_JOYSTICK) {
// NES, Joystick
PROCESS_INPUT_FN = &ProcessInputNES;
} else {
// NES, Keybaord
PROCESS_INPUT_FN = &ProcessInputNESUsingKeyboard;
}
} else {
if (DEVICE_METHOD == DEVICE_METHOD_JOYSTICK) {
// SNES, Joystick
PROCESS_INPUT_FN = &ProcessInputSNES;
} else {
// SNES, Keybaord
PROCESS_INPUT_FN = &ProcessInputSNESUsingKeyboard;
}
}
}
void setup() {
pinMode(CLOCK, OUTPUT);
pinMode(LATCH, OUTPUT);
pinMode(DATA, INPUT);
ConfigureSelf();
}
void loop() {
if (DEVICE_TYPE == DEVICE_TYPE_AUTO) {
ConfigureSelf();
}
PROCESS_INPUT_FN();
delayMicroseconds(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment