Jon Thysell

Xbox engineer. Fiction writer. Returned Peace Corps Volunteer. Ukulele nut. Nerd.

Building a custom retro console running RetroPie

Ever since I first heard about Raspberry Pi and the RetroPie project, I’ve wanted to build a retro console. Something with all my favorite old game systems emulated in a compact classy box, with nice controllers and fully customizable.

Back in the day I used to have a soft-modded Xbox original to play retro games, but it was loud, bulky, and the controllers weren’t great. When I got my first Raspberry Pi, I tried to make my custom console, but I ran into several problems:

  1. Power issues: A non-clean shutdown meant corrupting the SD card, which isn’t user-friendly, and no power switch
  2. Weak ports: Connecting straight to the light-weight Pi meant it was often left hovering in the air, which would strain the HDMI and USB connections
  3. Available cases: Raspberry Pi cases all seem to fall into the category of tight as possible, giving no internal room to address 1 and 2 above

My first successful attempt was of course building the Picade, but it has two major limitations:

  1. One player only
  2. Kind of heavy to play on a couch

Since then I’ve been planning a new machine. The goal has been a small, two-player console that connects to modern TVs. Then after I discovered this lovely Polycase ZN-40 electronics enclosure, I new it was time to get to work. Yesterday was Pi Day, and I’d just cloned my Picade SD card, so I decided to finally try putting a console together.

It went together way faster than I anticipated! Now I can’t wait to make a couple more: one for home, one for work, one for the game room… the opportunities are endless.

Okay, enough typing, time to play!

/jon

P.S. Here’s a quick video and some development pics:

 

retroconsole01 retroconsole02 retroconsole03 retroconsole04 retroconsole05 retroconsole06 retroconsole07 retroconsole08 retroconsole09 retroconsole10 retroconsole11

2014 in review

The WordPress.com stats helper monkeys prepared a 2014 annual report for this blog.

Here’s an excerpt:

The concert hall at the Sydney Opera House holds 2,700 people. This blog was viewed about 40,000 times in 2014. If it were a concert at Sydney Opera House, it would take about 15 sold-out performances for that many people to see it.

Click here to see the complete report.

Aloha ‘Oe on baritone ukulele (slack key style)

Took some time this weekend to record this. It went a lot smoother than I anticipated, though it still needs some work.

Recording some slack key ukulele videos

For a while now I’ve wanted to record some videos of myself playing ukulele, both to keep tabs on my progress and to introduce some more slack key uke videos online. Now it’s true I’ve recorded some videos in the past, but I was never impressed with the video quality of my regular webcams. So this past week I finally found a HD camcorder I liked (the awesomely cheap Zoom Q2HD) and on Saturday I sat down to record.

These were all recorded on my Black Bear baritone ukulele, which I talk about in the first video. Enjoy!

/jon

Sega Genesis controllers and Arduino revisited

Version 1.0

A couple of months ago I detailed the process by which I used an Arduino Uno to read the button presses of a pair of Sega Genesis controllers. It was my first bit of micro-controller programming, and it has exposed me to a whole new world of possibilities for hardware hacking. So it is with some certain future projects in mind that I’ve revisited and updated that initial code.

Version 1.1

Other than some minor code cleanup, the only major feature-add to the core of the sketch is to support reading the “Mode” button on six-button controllers (thanks to a comment from soe for that). The other change, now that I’ve acquired an Arduino Leonardo, is to create a fork of the sketch which reports the button presses as keyboard key presses via the ATmega32u4 chip’s ability appear as a USB keyboard.

Anyway, here’s the updated code (for reporting over the Serial connection):

/*
 * Sega Controller Reader
 * Author: Jon Thysell <thysell@gmail.com>
 * Version: 1.1
 * Date: 9/29/2014
 *
 * Reads buttons presses from Sega Genesis 3/6 button controllers
 * and reports their state via the Serial connection. Handles hot
 * swapping of controllers and auto-switches between 3 and 6 button
 * polling patterns.
 *
 */

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
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, remaing columns are the button flags
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
};

// 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);
  }
 
  Serial.begin(9600);
}

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(1000);
}

void sendStates()
{
  // Only report controller states if at least one has changed
  boolean hasChanged = false;
 
  for (int i = 0; i < PLAYERS; i++)
  {
    if (currentState[i] != lastState[i])
    {
      hasChanged = true;
    }
  }
 
  if (hasChanged)
  {
    for (int i = 0; i < PLAYERS; i++)
    {
      Serial.print((currentState[i] & ON) == ON ? "+" : "-");
      Serial.print((currentState[i] & UP) == UP ? "U" : "0");
      Serial.print((currentState[i] & DOWN) == DOWN ? "D" : "0");
      Serial.print((currentState[i] & LEFT) == LEFT ? "L" : "0");
      Serial.print((currentState[i] & RIGHT) == RIGHT ? "R" : "0");
      Serial.print((currentState[i] & START) == START ? "S" : "0");
      Serial.print((currentState[i] & A) == A ? "A" : "0");
      Serial.print((currentState[i] & B) == B ? "B" : "0");
      Serial.print((currentState[i] & C) == C ? "C" : "0");
      Serial.print((currentState[i] & X) == X ? "X" : "0");
      Serial.print((currentState[i] & Y) == Y ? "Y" : "0");
      Serial.print((currentState[i] & Z) == Z ? "Z" : "0");
      Serial.print((currentState[i] & MODE) == MODE ? "M" : "0");
        
      Serial.print((i == 0) ? "," : "\n");
      lastState[i] = currentState[i];
    }
  }
}

As with the previous version, all you have to do is upload the sketch and connect the controller’s DB9 pins to the Arduino following the mapping in inputMap in the code.

Now, if you have an Arduino Leonardo (or compatible board), and want the controller button presses to map to keyboard keys instead, you can upload the following sketch:

/*
 * Sega Controller Reader (Keyboard)
 * Author: Jon Thysell <thysell@gmail.com>
 * Version: 1.1
 * Date: 9/29/2014
 *
 * Reads buttons presses from Sega Genesis 3/6 button controllers
 * and reports their state via keyboard button presses. Handles hot
 * swapping of controllers and auto-switches between 3 and 6 button
 * polling patterns.
 *
 */

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
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 keyboard key
output outputMap[] = {
  { 0, UP,    KEY_UP_ARROW },
  { 0, DOWN,  KEY_DOWN_ARROW },
  { 0, LEFT,  KEY_LEFT_ARROW },
  { 0, RIGHT, KEY_RIGHT_ARROW },
  { 0, START, KEY_RETURN },
  { 0, A,     'z' },
  { 0, B,     'x' },
  { 0, C,     'c' },
  { 0, X,     'a' },
  { 0, Y,     's' },
  { 0, Z,     'd' },
  { 0, MODE,  'q' },
  { 1, UP,    'i' },
  { 1, DOWN,  'k' },
  { 1, LEFT,  'j' },
  { 1, RIGHT, 'l' },
  { 1, START, 't' },
  { 1, A,     'v' },
  { 1, B,     'b' },
  { 1, C,     'n' },
  { 1, X,     'f' },
  { 1, Y,     'g' },
  { 1, Z,     'h' },
  { 1, MODE,  'r' }
};

// 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);
  }
 
  Keyboard.begin();
}

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(1000);
}

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)
      {
        Keyboard.press(outputMap[i].key);
      }
      else
      {
        Keyboard.release(outputMap[i].key);
      }
    }
  }
 
  for (int i = 0; i < PLAYERS; i++)
  {
    lastState[i] = currentState[i];
  }
}

If you want to change the button to key mapping, simply update outputMap in the code. You can use any key on the keyboard, including special keys and modifiers.

I hope this is a worthy updated to the sketch – it was a great delight to test it by playing emulated games on my laptop with real Genesis controllers. It worked wonderfully, and I can’t wait to get this integrated into a bigger project.

So happy hacking and stay tuned for more!

/jon

Follow

Get every new post delivered to your Inbox.

Join 527 other followers