Archivos para marzo, 2018

“Los viejos hábitos tardan en morir” dice Mick Jagger en la canción de nombre homónimo compuesta para la película “Alfie”, y si eres lector asiduo de este blog sabrás cuán cierto es ya que como makers siempre estamos trasteando con diferentes artilugios electrónicos de diferentes épocas apoyándonos en tecnologías actuales como microcontroladores o mini computadoras, desde tubos nixie y tubos geiger hasta proyectos de robótica avanzada, pasando por seguridad electrónica y seguridad en sistemas.

En esta ocasión tenemos sobre la mesa un proyecto de consolas retro para devolverles la vida a nuestros antiguos (pero infalibles) mandos de nuestras excelsas consolas como Sega Mega Drive o Super Nintendo, en este punto se me “pianta” un lagrimón como diría Carlos Gardel.

Recordando aquellos tiempos cuando todo era mejor (difícil es mirar al pasado con los ojos del presente), la Mega Drive, conocida en diversos territorios de América como Genesis, fue la tercera consola de Sega cuya primera versión fue lanzada en Japón en 1988, sucedida por el lanzamiento en Norteamérica bajo el renombramiento de Genesis en 1989.

Por otra parte, la Super Nintendo Entertainment System, abreviada habitualmente como Super Nintendo, Super NES o SNES, también llamada Super Famicom es la tercera videoconsola de sobremesa de Nintendo, se lanzó en Japón el miércoles 21 de noviembre de 1990. Fue la más exitosa y vendida de la era de los 16 bits. Gracias al chip Super FX, la SNES tuvo los primeros videojuegos totalmente tridimensionales en la consola, siendo Star Fox el primer videojuego para consola de videojuegos con gráficos completamente tridimensionales.

La rivalidad entre Nintendo y Sega dio lugar a lo que se ha descrito como una de las guerras de consolas más notables en la historia de los videojuegos. Mientras que en Japón Sega se mantuvo distante en el mercado de videojuegos, en Norteamérica se mantendría dominante con su consola Sega Genesis, llamada Sega Mega Drive en Europa, desarrollando importantes campañas publicitarias agresivas en contra de Nintendo a partir del año 1989, una de las más recordadas por el público americano fue la campaña publicitaria llamada “Genesis does what Nintendon’t” (en español: “Genesis hace lo que Nintendo no puede“) que demostraba las capacidades técnicas superiores de la consola de Sega en 16 bits y un extenso cátalogo de videojuegos deportivos frente a las capacidades en 8 bits de la consola NES con sus videojuegos licenciados de títulos arcade. Sin embargo, uno de los factores claves del éxito de la Genesis en Estados Unidos fue la incorporación del videojuego Sonic the Hedgehog en el paquete de la consola en 1991, el título logró tener críticas favorables y ayudó a que varios jugadores compraran la consola Genesis en lugar de la SNES. Para 1992, Nintendo tendría que enfrentarse ahora con un rival ya establecido en los mercados americanos y europeos durante algunos años, solo en Norteamérica lograría consagrarse hasta el año 1994.

Con estos titanes y pioneros de las consolas de videojuegos, probablemente en algún lugar de casa tenemos guardada y cubierta de polvo alguna de estas consolas o ambas, ¿por qué no? en su caja con los respectivos mandos. En caso de no ser funcional la consola o simplemente deseamos conectar los mandos a nuestra PC para reutilizarlos en nuestros emuladores favoritos deberemos seguir los pasos de esta humilde publicación.

Previamente a realizar desarme, soldado y programación investigué acerca de los mandos mencionados anteriormente, sus pines, conectores y señales utilizadas por los mismos. Sega Genesis utilizó un enfoque ligeramente diferente en la configuración de sus pines con respecto al mando de Atari. La conexión del mando de Sega parece ser la configuración estándar de un mando de 2 botones donde los pines 6 y 9 se usan para los botones que conectan a tierra el pin cuando se presionan. Lo único extraño es que el mando también requiere 5V en el pin 5. Sega ha agregado un modelo de 6 botones además de su configuración original de 3 botones. Los mandos de 6 botones usan esencialmente la misma interfaz, pero agregan más botones mediante un circuito integrado adicional (CMOS 74HC157) que multiplexa las nuevas señales con las señales de joystick existentes, usando el bit de control en el pin 7 del conector. Este es un multiplexor CMOS cuádruple de 2 líneas a 1 línea de alta velocidad. La consola puede, con ayuda del pin “Select”, elegir entre dos funciones en cada entrada. Si el pin 7 se deja flotando, la línea asume por defecto un valor lógico alto.

Lista de Componentes
1 Mando de Consola Sega Genesis o SNES.
1 Sparkfun Arduino Pro Micro 5V (o compatible).
35mm Tubo Termocontraíble (diám. 20mm)
Librería Joystick de Matthew Heironimus

En primer lugar tomaremos el mando por el extremo del conector y cortaremos aproximadamente 10cm de cable como observamos en la siguiente imagen:

(click para ampliar)

Ahora debemos identificar y establecer una relación entre los pines del conector y los colores de los cables para así conectarlos correctamente a nuestro Arduino Pro Micro. Como vemos en la imagen he utilizado un pequeño alfiler (que también podría ser un pequeño trozo de cable o un clip) para establecer la correspondencia. Desde luego el multímetro debe encontrarse seteado para medición de continuidad, prueba de diodos o un valor óhmico lo más bajo posible.

(click para ampliar)

Esta prueba la llevaremos a cabo con la ayuda de la siguiente imagen y tabla.

(Vista frontal del conector hembra D-SUB9 bajo prueba)

Pin D-SUB9
Descripción
Pin Arduino Pro Micro
Color (*)
1 Arriba / Z 2 Amarillo
2 Abajo / Y 3 Naranja
3 Izquierda / X 4 Rojo
4 Derecha / Mode 5 Marrón
5 5V VCC Negro
6 A / B 6 Verde
7 SELECT 8 Azul
8 GND GND Gris
9 START / C 7 Blanco

(*) Los colores pueden variar.

Habiendo establecido exitosamente la relación entre los pines del conector D-SUB9 y los colores de los cables, procederemos a programar nuestro Arduino Pro Micro dejando como último paso el soldado de los cables del mando a los pines de nuestro Arduino.

#include <Joystick.h>

// Create Joystick
Joystick_ Joystick;

const int PLAYERS = 2;
 
// Controller Button Flags
const int ON = 1;
const int UP = 2;
const int DOWN = 4;
const int LEFT = 8;
const int RIGHT = 16;
const int START = 32;
const int A = 64;
const int B = 128;
const int C = 256;
const int X = 512;
const int Y = 1024;
const int Z = 2048;
const int MODE = 4096;
 
// Controller DB9 Pin 7 Mappings. Controller 0,1
const int SELECT[] = { 8, 9 };
 
typedef struct
{
  int player;
  int pin;
  int lowFlag;
  int highFlag;
  int pulse3Flag;
} input;
 
// Controller DB9 Pin to Button Flag Mappings
// First column is the controller index, second column
// is the Arduino pin that the controller's DB9 pin is
// attached to
input inputMap[] = {
  { 0,  2,  UP,    UP,     Z    }, // P0 DB9 Pin 1
  { 0,  3,  DOWN,  DOWN,   Y    }, // P0 DB9 Pin 2
  { 0,  4,  ON,    LEFT,   X    }, // P0 DB9 Pin 3
  { 0,  5,  ON,    RIGHT,  MODE }, // P0 DB9 Pin 4
  { 0,  6,  A,     B,      0    }, // P0 DB9 Pin 6
  { 0,  7,  START, C,      0    }, // P0 DB9 Pin 9
  { 1,  A0, UP,    UP,     Z    }, // P1 DB9 Pin 1
  { 1,  A1, DOWN,  DOWN,   Y    }, // P1 DB9 Pin 2
  { 1,  A2, ON,    LEFT,   X    }, // P1 DB9 Pin 3
  { 1,  A3, ON,    RIGHT,  MODE }, // P1 DB9 Pin 4
  { 1,  A4, A,     B,      0    }, // P1 DB9 Pin 6
  { 1,  A5, START, C,      0    }  // P1 DB9 Pin 9
};
 
typedef struct
{
  int player;
  int flag;
  char key;
} output;
 
// Controller Button Flag to Keyboard Mappings
// First column is the controller index, second column
// is the button flag, third is joystick button
output outputMap[] = {
  { 0, UP,    0 },
  { 0, DOWN,  1 },
  { 0, LEFT,  2 },
  { 0, RIGHT, 3 },
  { 0, START, 4 },
  { 0, A,     5 },
  { 0, B,     6 },
  { 0, C,     7 },
  { 0, X,     8 },
  { 0, Y,     9 },
  { 0, Z,     10 },
  { 0, MODE,  11 },
  { 1, UP,    12 },
  { 1, DOWN,  13 },
  { 1, LEFT,  14 },
  { 1, RIGHT, 15 },
  { 1, START, 16 },
  { 1, A,     17 },
  { 1, B,     18 },
  { 1, C,     19 },
  { 1, X,     20 },
  { 1, Y,     21 },
  { 1, Z,     22 },
  { 1, MODE,  23 }
};

 
// Controller State
int currentState[] = { 0, 0 };
int lastState[] = { -1, -1 };
 
// Default to three-button mode until six-button connects
boolean sixButtonMode[] = { false, false };
 
void setup()
{
  // Setup input pins
  for (int i = 0; i < sizeof(inputMap) / sizeof(input); i++)
  {
    pinMode(inputMap[i].pin, INPUT);
    digitalWrite(inputMap[i].pin, HIGH);
  }
  
  // Setup select pins
  for (int i = 0; i < PLAYERS; i++)
  {
    pinMode(SELECT[i], OUTPUT);
    digitalWrite(SELECT[i], HIGH);
  }

  // Initialize Joystick Library
  Joystick.begin(false);  
}
 
void loop()
{
  readButtons();
  sendStates();
}
 
void readButtons()
{
  for (int i = 0; i < PLAYERS; i++)
  {
    resetState(i);
    if (sixButtonMode[i])
    {
      read6buttons(i);
    }
    else
    {
      read3buttons(i);
    }
  }
}
 
void resetState(int player)
{
  currentState[player] = 0;
}
 
void read3buttons(int player)
{
  // Set SELECT LOW and read lowFlag
  digitalWrite(SELECT[player], LOW);
     
  delayMicroseconds(20);
     
  for (int i = 0; i < sizeof(inputMap) / sizeof(input); i++)
  {
    if (inputMap[i].player == player && digitalRead(inputMap[i].pin) == LOW)
    {
      currentState[player] |= inputMap[i].lowFlag;
    }
  }
 
  // Set SELECT HIGH and read highFlag
  digitalWrite(SELECT[player], HIGH);
     
  delayMicroseconds(20);
     
  for (int i = 0; i < sizeof(inputMap) / sizeof(input); i++)
  {
    if (inputMap[i].player == player && digitalRead(inputMap[i].pin) == LOW)
    {
      currentState[player] |= inputMap[i].highFlag;
    }
  }
    
  // When a six-button first connects, it'll spam UP and DOWN,
  // which signals the game to switch to 6-button polling
  if (currentState[player] == (ON | UP | DOWN))
  {
    sixButtonMode[player] = true;
  }
  // When a controller disconnects, revert to three-button polling
  else if ((currentState[player] & ON) == 0)
  {
    sixButtonMode[player] = false;
  }
  
  delayMicroseconds(20);
}
 
void read6buttons(int player)
{
  // Poll for three-button states twice
  read3buttons(player);
  read3buttons(player);
  
  // After two three-button polls, pulse the SELECT line
  // so the six-button reports the higher button states
  digitalWrite(SELECT[player], LOW);
  delayMicroseconds(20);
  digitalWrite(SELECT[player], HIGH);
  
  for(int i = 0; i < sizeof(inputMap) / sizeof(input); i++)
  {
    if (inputMap[i].player == player && digitalRead(inputMap[i].pin) == LOW)
    {
      currentState[player] |= inputMap[i].pulse3Flag;
    }
  }
  
  delayMicroseconds(1360); // Increased from 1000 for Sega Mega Drive original Arcade 6 Button Pad Controller MK-1653-50
}
 
void sendStates()
{
  for (int i = 0; i < sizeof(outputMap) / sizeof(output); i++)
  {
    int last = (lastState[outputMap[i].player] & outputMap[i].flag);
    int current = (currentState[outputMap[i].player] & outputMap[i].flag);
     
    if (last != current)
    {
      if (current == outputMap[i].flag)
      {
        Joystick.pressButton(outputMap[i].key);
      }
      else
      {
        Joystick.releaseButton(outputMap[i].key);
      }
    }
  }
  
  for (int i = 0; i < PLAYERS; i++)
  {
    lastState[i] = currentState[i];
  }
  Joystick.sendState();
}

Con la programación exitosa de nuestro Arduino Pro Micro, ahora solo debemos soldar los cables del mando a los pines del mismo y colocar el tubo termocontraíble. Al final de la publicación se encuentra el vídeo donde podemos verificar el correcto funcionamiento de nuestro mando modificado con conexión USB.

Para realizar la misma modificación en un mando de SNES el procedimiento es similar al descripto anteriormente.

(click para ampliar)

En primer lugar tomaremos el mando por el extremo del conector y cortaremos aproximadamente 10cm de cable como observamos en la siguiente imagen:

(click para ampliar)

Ahora debemos identificar y establecer una relación entre los pines del conector y los colores de los cables para así conectarlos correctamente a nuestro Arduino Pro Micro. Como vemos en la imagen he utilizado un pequeño alfiler (que también podría ser un pequeño trozo de cable o un clip) para establecer la correspondencia. Desde luego el multímetro debe encontrarse seteado para medición de continuidad, prueba de diodos o un valor óhmico lo más bajo posible.

(click para ampliar)

Esta prueba la llevaremos a cabo con la ayuda de la siguiente imagen y tabla.

(Vista frontal del conector SNES 7 pines bajo prueba)

Pin SNES 7
Descripción
Pin Arduino Pro Micro
Color (*)
1 5V VCC Blanco
2 Data Clock 6 Azul
3 Data Latch 7 Amarillo
4 Serial Data 5 Rojo
5 NC
6 NC
7 GND GND Marrón

(*) Los colores pueden variar.

Habiendo establecido exitosamente la relación entre los pines del conector SNES 7 y los colores de los cables, procederemos a programar nuestro Arduino Pro Micro dejando como último paso el soldado de los cables del mando a los pines de nuestro Arduino.

#include <Joystick.h>

// Create Joystick
Joystick_ Joystick;

// Controller Buttons
#define SNES_B       1      //000000000001
#define SNES_Y       2      //000000000010
#define SNES_SELECT  4      //000000000100
#define SNES_START   8      //000000001000
#define SNES_UP      16     //000000010000
#define SNES_DOWN    32     //000000100000
#define SNES_LEFT    64     //000001000000
#define SNES_RIGHT   128    //000010000000
#define SNES_A       256    //000100000000
#define SNES_X       512    //001000000000
#define SNES_L       1024   //010000000000
#define SNES_R       2048   //100000000000

const int PIN_CLOCK = 6;
const int PIN_LATCH = 7;
const int PIN_DATA = 5; //12;

void setup(){
  Joystick.begin(false);
  
  pinMode(PIN_CLOCK, OUTPUT);
  digitalWrite(PIN_CLOCK, HIGH);
  pinMode(PIN_LATCH, OUTPUT);
  digitalWrite(PIN_LATCH, LOW);
  pinMode(PIN_DATA, INPUT_PULLUP);
}

void loop(){
  uint16_t state = 0;
    // 12us latch
    digitalWrite(PIN_LATCH, HIGH);
    delayMicroseconds(12);
    digitalWrite(PIN_LATCH, LOW);
    delayMicroseconds(6);
    // Retrieve 4021s sixteen bits of data
    for(int i = 0; i < 16; i++){
        digitalWrite(PIN_CLOCK, LOW);
        delayMicroseconds(6);
        state |= digitalRead(PIN_DATA) << i;
        digitalWrite(PIN_CLOCK, HIGH);
        delayMicroseconds(6);
    }

  // buttons
  SNES_B & ~state ? Joystick.pressButton(0) : Joystick.releaseButton(0);
  SNES_Y & ~state ? Joystick.pressButton(1) : Joystick.releaseButton(1);
  SNES_SELECT & ~state ? Joystick.pressButton(2) : Joystick.releaseButton(2);
  SNES_START & ~state ? Joystick.pressButton(3) : Joystick.releaseButton(3);
  SNES_A & ~state ? Joystick.pressButton(4) : Joystick.releaseButton(4);
  SNES_X & ~state ? Joystick.pressButton(5) : Joystick.releaseButton(5);
  SNES_L & ~state ? Joystick.pressButton(6) : Joystick.releaseButton(6);
  SNES_R & ~state ? Joystick.pressButton(7) : Joystick.releaseButton(7);

  // 360° Hat Switch 0
  Joystick.setHatSwitch(0, -1); // release
  if (SNES_UP & ~state) Joystick.setHatSwitch(0, 0);
  if (SNES_RIGHT & ~state) Joystick.setHatSwitch(0, 90);
  if (SNES_DOWN & ~state) Joystick.setHatSwitch(0, 180);
  if (SNES_LEFT & ~state) Joystick.setHatSwitch(0, 270);
  if (SNES_UP & ~state && SNES_RIGHT & ~state) Joystick.setHatSwitch(0, 45);
  if (SNES_DOWN & ~state && SNES_RIGHT & ~state) Joystick.setHatSwitch(0, 135);
  if (SNES_DOWN & ~state && SNES_LEFT & ~state) Joystick.setHatSwitch(0, 225);
  if (SNES_UP & ~state && SNES_LEFT & ~state) Joystick.setHatSwitch(0, 315);
  
  Joystick.sendState();
  delay(25);
}

Con la programación exitosa de nuestro Arduino Pro Micro, ahora procederemos a fijar con adhesivo de contacto o silicona caliente el cable de nuestro mando a la parte trasera de nuestro Arduino como observamos en la siguiente imagen:

(click para ampliar)

Ahora soldaremos los cables del mando a los pines de nuestro Arduino Pro Micro.

(click para ampliar)

(click para ampliar)

Como último paso solo nos resta colocar el tubo termocontraíble.

(click para ampliar)

(click para ampliar)

A continuación se encuentra el vídeo donde podemos verificar el correcto funcionamiento de nuestro mando modificado con conexión USB.

Anuncios