Reading Sega Genesis controllers with Arduino

by Jon

segaarduino1

Background

The Sega Genesis was my first and favorite childhood game console, so when I first picked up an Arduino a couple years ago, my first thought was to build something, anything, that used a Genesis controller. Unfortunately I got side-tracked by other projects, and the Arduino Uno I’d purchased got set aside.

Fast-forward to this year’s picade build, when I had to re-flash the main controller board, which at its heart is an Arduino Leonardo. Seeing how easy it was to work with, I finally decided to break out my Uno. After a couple sample sketches, I figured it was time start interfacing with some Genesis controllers.

Research

I started poking around online to see what others had done, and couldn’t find quite what I was looking for. What I did find was plenty of information on how the three-button pads worked. Some used that info to implement full three-button support – others, it seems, were satisfied with having just some of the buttons working (essentially using the controller’s innate backward compatibility with the simpler Sega Master System’s two-button controller design). No one had six-button controllers working.

What I want is full three and six-button support, something that I can plug any Genesis controller into and it’ll “just work”, like an actual Genesis console. My requirements therefore are:

  1. Correctly reads connected three-button controllers.
  2. Correctly reads connected six-button controllers.
  3. Automatically detects which type of controller is connected, with hot-swapping.
  4. Bonus: Support more than one controller at a time.

The real godsend to the first two was finding Charles Rosenberg’s Sega Six Button Controller Hardware Info. There he describes almost everything you need to know about how Genesis controllers work. I highly recommend giving it a full read for the really gory details before continuing, but here’s a “quick” overview:

How Sega Genesis controllers work

All Genesis controllers use a standard nine-pin DB9 serial port. On your regular three-button controller, you really have a total of eight buttons: Up, Down, Left, Right, A, B, C, and Start. With nine pins to work with, Sega could easily have gone with one +5v in and eight outputs back to the console and be done with it. But instead, in the interest of backwards compatibility with the Sega Master System (and potentially other DB9 based controllers like the old Atari joysticks), they implemented a simple multiplexer circuit.

Essentially you have three control pins (+5v power, ground, select) and six output pins. By default, all of the output pins are set high (meaning a button press will bring the pin down to ground). The Genesis (or more specifically, the game running on the Genesis) sets the select pin (DB9 pin seven) low, then reads the state of the six output pins to get the states for the Up, Down, A, and Start buttons. Then the game toggles the select pin to high, and re-reads those same six output pins to get the states of the Up (again), Down (again), Left, Right, B, and C buttons.

DB9 Pin Select is low Select is high
1 Up Up
2 Down Down
3 Ground Left
4 Ground Right
5 Control: +5V
6 A B
7 Control: Select
8 Control: Ground
9 Start C

The algorithm is pretty straight forward to implement on the Arduino, polling the controller exactly the way a Genesis game would. This satisfies my first requirement. Now, things get a little more complicated with the six-button controller:

DB9 Pin Select is low Select is high Every 3rd select pulse
1 Up Up Z
2 Down Down Y
3 Ground Left X
4 Ground Right
5 Control: +5V
6 A B
7 Control: Select
8 Control: Ground
9 Start C

First, let’s call each dropping of the select pin to low then back to high a “select pulse”. Now, on every third select pulse the six-button controller will instead report back the states of the X, Y, and Z buttons (instead of Up, Down, and Left). On its face, it looks like we couldn’t have a game that supports both three and six-button controllers, because how does a game know what kind of controller is connected? On every third pulse how does a six-button enabled game know whether to use the first table or the second? On every third pulse, how does a six-button controller know not to report X, Y, Z for games that only support three-buttons? If the game and controller aren’t on the same page and they use the wrong mappings, they’ll record incorrect button presses.

How do the games and controllers make the right decisions?

One part of the answer (as described in Rosenberg’s notes) is in how often games actually poll the controller. The three-button controller uses dumb direct logic, which means it always uses the first table. It also means that technically you can poll the controller state super-fast (say every 50 microseconds) or super-slow (say every 20 milliseconds) and always get the same result. Now typically, a game is only going to poll the controller once per game frame (sixty times per second, or every 16.6 milliseconds). Which means, at the time of the six-button controller’s release, the vast majority of the games already published (which were three-button enabled only) only sent a single select pulse every ~16 milliseconds.

The six-button controller can use this to its advantage. Instead of dumb direct logic, it uses an IC to watch how often the select pulses come in. The IC knows that (given the game indicates it wants six-button mode), it should return the states of X, Y, and Z on every third select pulse. But it also knows that most games only support three-button mode, so a safe default is to just pretend to be a three-button controller and ignore reporting X, Y, and Z on every third pulse.

How does the controller decide? The frequency of the select pulses. If the IC only sees one select pulse every ~16 milliseconds, or one pulse per game frame, then its best bet is to take the safe route and assume three-button mode. In this way, the six-button controller is backwards-compatible, and most games will never get any incorrect button presses.

If that’s the case, how does a game indicate that it actually wants those X, Y, and Z buttons?

If the game believes that a six-button controller is attached, it will instead pulse the select line three times very quickly in one game frame. The first two times the game reads (and the controller reports) the button presses for three-button mode. Then the game pulses the controller a third time. At that point, the controller’s IC, seeing how quickly those pulses came in, presumes the game wants X, Y, and Z, so it reports X, Y, and Z.

So to sum up: if a game just wants the three-button control states, it pulses once every frame and uses the first table to read the results. If a game wants six-button control states, it pulses three times in one frame, using the second table to read the results. With this in mind, we can now read both controller types, which satisfies the first two of my requirements. We can easily implement an Arduino sketch that implements one or the other algorithm, if we already know which type of controller we’re going to have connected.

But what about my third requirement? What if we want one sketch that implements both modes? How do we make our board detect what kind of controller is connected?

This one took a little bit of experimenting to figure out, since Rosenberg’s notes don’t address the issue. Turns out the six-button controller’s IC has another trick up its sleeve with watching how fast those select pulses come in. As we just saw, since most games only expect three-button controls, the controller can default to three-button mode, and seeing slow pulses, will stay in three-button mode.

But now, if the game wants to check for a six-button controller, it can send rapid select pulses when a controller is connected, and if it’s a six-button controller, the IC will report that both the Up and Down buttons are being pressed at the same time!

Under normal circumstances this is impossible, as the controller’s d-pad rocks in the direction you press it. So with this neat trick, the controller lets the game know that a six-button controller is connected, giving the game to option to start polling the controller in six-button mode.

The way we implement this is simple: by default we poll in three-button mode very quickly. For three-button controllers, this works perfectly. After every pulse, we can check for both Up and Down being pressed at the same time. If we see that, we know a six-button controller is attached, so we switch to six-button mode, pulsing more slowly so that we don’t reset the IC.

This solves the first part of my third requirement: detecting when a six-button controller is connected. But what about the other way? The way it stands, once we connect a six-button controller, and our board switches to six-button polling, it’s stuck that way until we reset the board. If we hot-swap from a six-button to a three-button controller, we’ll get those annoying paired inputs (specifically, pressing Up will return Up and Z, Down will return Down and Y, Left will return Left and X).

What we need is a way of knowing when a controller is disconnected, so that we can switch back to the default three-button polling. Turns out we have everything we need in the tables above, something that works for both three and six-button controllers.

At the very beginning I said that by default the console puts all six of the DB9 output pins high, so that a button press causes the pin to drop low. So if no controllers are connected those output pins should stay high. Only a button press from a connected controller will drop a pin low, right? We could just press a button to let the board know we have a controller connected, but wait, there’s a better way!

As we can see in the tables above, when the select pin is low, DB9 pins three and four both go low, regardless of any button presses. So in effect, the controller presses imaginary buttons on pins three and four when select is low automatically. So, all we have to do is watch those pins – if they go low when select is low, then we know a controller is connected. If they’re high when select is low, it means the controller is no longer connected.

In implementation terms, when select is low, we can simply check those two pins like we would for any other button, and map the results of those imaginary “On” buttons. Watching those “buttons” we know when a controller is connected or not, and therefore we can easily switch back to three-button mode when a controller is disconnected. With that we now have everything we need to satisfy my main three requirements for the board. As for the 4th and final bonus requirement, recognizing that we only needed seven pins to read one controller, we’ve got plenty of left-over pins on the Uno to cover connecting one more.

The Sketch

Ok, so now for the sketch. Our basic algorithm is the following:

  • Default to three-button polling as fast as possible, using the first table and select pulsing algorithm.
  • If you ever see both Up and Down pressed at the same time, switch to six-button polling, using the second table and select pulsing algorithm.
  • If you ever see the “On” button state go away, switch back to three-button controller polling.
/*
 * Sega Controller Reader
 * Author: Jon Thysell <thysell@gmail.com>
 * Version: 1.0
 * Date: 7/26/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.
 *
 */

// 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;

// 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,  0}, // 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,  0}, // 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 < 2; i++)
  {
    pinMode(SELECT[i], OUTPUT);
    digitalWrite(SELECT[i], HIGH);
  }
  
  Serial.begin(9600);
}

void loop()
{
  readButtons();
  sendStates();
}

void readButtons()
{
  for (int i = 0; i < 2; 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 < 2; i++)
  {
    if (currentState[i] != lastState[i])
    {
      hasChanged = true;
    }
  }
  
  if (hasChanged)
  { 
    for (int i = 0; i < 2; 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((i == 0) ? "," : "\n");
      lastState[i] = currentState[i];
    }
  }
}

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. Then start up the Serial Monitor so you can see the output from the controller on your PC.

Here’s a closeup of how I’ve wired my Uno to a male DB9 breakout board (you can wire straight to the controller, or a male DB9 port, but I found the board the easiest solution):

segaarduino2

The sketch is also set up for a second controller connected to the analog pins on the other side of the Arduino, though I only had one male DB9 breakout board. Also note that this is my first real Arduino sketch, so I’m sure there are some best practices I’m breaking in my design.

I can already see room for future improvements. Right now I report the controller states over the serial connection as strings – easy to debug, but slow and wasteful. Of course, in your own sketches, you can just read the currentState integers directly.

Hope this helps anyone else out there trying to interface with Genesis controllers. Happy hacking!

/jon

PS. Obviously in the process of plugging and unplugging controllers, you may see some errant random button presses recorded. In the world of video games, this is to be expected, and only lasts for a second. If however, you’re planning on doing anything “bad” with a button press (say enabling a thermonuclear detonator), you might want to avoid hot-swapping controllers.

PPS. Final note, concerning the six-button controller’s “mode” button. Some games (notably Ms. Pac Man), don’t follow the rule of only polling the controller once per game frame, and select pulses more often. This causes erratic behavior as incorrect buttons presses are recorded. Ostensibly, when you plug in a six-button controller with the mode button held down, that signals the IC in the controller to always be in three-button mode and never report X, Y, or Z. Unfortunately, I couldn’t get it to work with my sketch and gave up because I saw no reason to limit my six-button controller to three-buttons anyway.

Update 29-SEP-2014: I’ve uploaded a new version of the sketch in Sega Genesis controllers and Arduino revisited, which includes support for reading the “Mode” button and reporting button presses as keyboard key presses (on compatible Arduino boards).