Sega Mega Drive / Genesis 6-Button (X,Y,Z) Controller

In this post we will look at how to interface with a Sega Mega Drive 6-button controller. I will use an Arduino pro Micro (Leonardo) board to imitate the protocol between a Sega Mega Drive console and the controller. The button presses will be converted to keyboard presses. The Arduinos in the Leonardo series can emulate a keyboard out of the box!

TL;DR

Pulse the SELECT wire 3 times to activate readings for 6-button mode.

Sega Mega Drive 6-button Controller – Scheme of possible readings
PULSE SELECT PIN 1 PIN 2 PIN 3 PIN 4 PIN 6 PIN 9
0 (idle) HIGH UP DOWN LEFT RIGHT B C
1 LOW UP DOWN * * A START
1 HIGH UP DOWN LEFT RIGHT B C
2 LOW UP DOWN * * A START
2 HIGH UP DOWN LEFT RIGHT B C
3 LOW ** ** * * A START
3 HIGH Z Y X MODE B C
4 LOW A START
4/0 (idle) HIGH UP DOWN LEFT RIGHT B C

*) If LOW, it Indicates that a 3-button controller is present.

**) If LOW, it indicates that a 6-button controller is present.

Other Pins:

PINS FUNCTION
PIN 5 +5V
PIN 7 SELECT (HIGH/LOW)
PIN 8 GND

Index

Prerequisites

In this post I won’t go into mush details. One could say that this post extends several previous posts where I worked to interface the Sega Master System controller and the Sega Mega Drive/Genesis original controller. If you found parts in this post too hard or unclear, I highly suggest that you read these posts first:

Back to Index.


Pinout

The cable between the controller and the console is a RS-232 DB9.

pinout

Note! Never trust the colour scheme. Your cable/controller might have another colour scheme than presented in this post.

Pin 7 is the Select wire, it is controlled by the console (host). In this project, we will control it from the Arduino (our host). The select signal will toggle from Low to High repeatedly, we can read different buttons depending on if the select wire is set high or low.

If we 3 times toggle select wire fast enough, it will trigger a mode in the IC that makes it possible during the 3rd cycle to read the button presses from X, Y, Z and MODE. A forth cycle is needed to reset the IC.

Info. One cycle would be from high to low back to high, not slower than ~12 microseconds, do this three times and the IC in the controller is triggered to read the X,Y,Z and MODE buttons. There are screenshots in this post from the readings I made, that clearly show how the Sega Mega Drive console does this.

Sega Mega Drive 6-Button Controller Pinout
Pin# Select = LOW (GND) Select = HIGH (+5V) 3rd Pulse Select = LOW (GND) 3rd Pulse Select = HIGH (+5V) Colour
1 UP UP LOW(GND)** Z Brown
2 DOWN DOWN LOW(GND)** Y Red
3 LOW(GND)* LEFT LOW(GND)** X Orange
4 LOW(GND)* RIGHT LOW(GND)** MODE Yellow
5 +5V +5V +5V +5V +5V
6 A B (A) (B) Blue
7 SELECT SELECT SELECT SELECT Grey
8 GND GND GND GND Black
9 START C (START) (C) White

*) When the Select (Pin 7) is low AND Pin3 and Pin4 are low to, that indicates that a 3-button controller is plugged in.

**) When the Select (Pin 7) is low AND Pin1, Pin2, Pin3 and Pin4 are low to, that indicates that a 6-button controller is plugged in.

(*) It is possible to read the state of A, B, C and START button even during the 3 cycle for X, Y ,Z, MODE buttons, but I don’t recommend it. Timing might be hard.

Back to Index.


The Controller

The six button Sega Mega Drive Controller:

Sega controller 6 buttons

Five screws on the back:

back of controller

Inside the controller:

Note! The wires are not connected in the socket in order. E.g. DB Pin1 in the cable is not #1 in the socket.

The PCB:

inside controller

The other side of the PCB:

PCB

Closer look:

close up PCB

Back to Index.


The Protocol – Overview

Here is my setup for this project:

setup testing controller
  1. 6-button controller compatible game. (Comix Zone is the only game I own that uses a 6-button controller.)
  2. Sega Mega Drive with a 6-button controller.
  3. Custom extension cable with exposed wires for the testing clips.
  4. Saleae Logic Analyzer.
  5. Laptop with software installed for the Logic Analyzer.

Here is the first reading, just on overview what is going on:

overview readings
  • Each ~20ms is there activity on the wires.

Let’s zoom in to one of the activities (no buttons pressed):

readings_zoomin
  • The SELECT wire is idle HIGH (+5V).
  • It drops to low and back to high four times.
  • The drop to low last ~4.6us and it stays high ~7us until it drops again.
  • After four cycles, SELECT stays idle high for ~20ms until it repeats the toggling.

Note. Glitch filter is enabled on some of the channels.

Back to Index.


Readings for Each Button

As usually when I am trying to figure out how a controller works, I make a reading for each button. I only press one button at a time while collecting data.

All buttons are active low.

 The UP-button (Pin1):

UP
  • You can read the state of the UP-button almost all the time, except for the 3rd and first half of the 4th SELECT pulse cycle.
  • I think it is best to read it during the idle high period of the SELECT.

The DOWN-button (Pin2):

DOWN
  • Same logic as for the UP-button.

The LEFT-button (Pin3):

LEFT
  • When SELECT is HIGH and Pin3 is LOW, the LEFT-button is pressed.
  • Can’t read LEFT during 3rd and first half of the 4th SELECT pulse.

The RIGHT-button (Pin4):

RIGHT
  • Same logic as the LEFT-button.

The A-button (Pin6):

A
  • Whenever the SELECT is LOW and Pin6 is LOW, the A-button is being pressed.
  • Even during the 3rd and 4th SELECT pulse, it is possible to read the A-button.

The B-button (Pin6):

B
  • Like the A-button. But now, whenever the SELECT is HIGH and the Pin6 is LOW, the B-button is being pressed.

The START-button (Pin9):

START
  • Same logic as the A-button. When SELECT is LOW, it is possible to read if START is being pressed.

The C-button (Pin9):

C
  • Same logic as the B-button. Whenever SELECT is HIGH and Pin9 is low, the button is pressed.

The controller is backwards compatible with most (maybe all?) of the games to the Sega Mega Drive. So far, all the readings have been done on the buttons that it shares with the 3-button controller. Now we will look at the X, Y, Z and MODE buttons. The MODE button is rarely used as a normal button in games, however there are a few out there that does. The main purpose of MODE is to increase backwards compatibility with some of the game. While being pressed when a game starts, the controller can activate the controller to truly act as a 3-button controller. (How this is done, I don’t know, and I haven’t tried to trigger this behaviour. But I thought it is worth to mention).

Info. X, Y, Z and MODE will have the same logic on respective pin.

The X-button (Pin3):

X
  • During the 3rd SELECT pulse, when SELECT is HIGH, if pin3 is LOW, the X-button is being pressed.

The Y-button (Pin2):

Y
  • Same logic as X but on Pin2.

The Z-button (Pin1):

Z
  • Same logic as X and Y but on the Pin1 wire.

The MODE-button:

MODE
  • Same logic as X, Y and Z but on the Pin4.

Back to Index.


The Protocol – Conclusion

Now when all the readings are done and interpreted, we can collect the data in a table that makes it easier to read:

Info. The table will be concentrated around the pulses that the SELECT does. One pulse is from high to low and back to high again. One pulse cycle has a LOW and a HIGH state on SELECT.

The 4th cycle (HIGH) is back to 0 (idle) HIGH again. And a complete protocol reding/writing cycle between the controller and the host is complete. Wait ~20ms to the next one and start all over again.

The table shows all possible readings:

Sega Mega Drive 6-button Controller – Scheme of possible readings
PULSE SELECT PIN 1 PIN 2 PIN 3 PIN 4 PIN 6 PIN 9
0 (idle) HIGH UP DOWN LEFT RIGHT B C
1 LOW UP DOWN * * A START
1 HIGH UP DOWN LEFT RIGHT B C
2 LOW UP DOWN * * A START
2 HIGH UP DOWN LEFT RIGHT B C
3 LOW ** ** * * A START
3 HIGH Z Y X MODE B C
4 LOW A START
4/0 (idle) HIGH UP DOWN LEFT RIGHT B C

*) When LOW, it Indicates that a 3-button controller is present.

**) When, LOW it indicates that a 6-button controller is present.

Other Pins:

PINS FUNCTION
PIN 5 +5V
PIN 7 SELECT (HIGH/LOW)
PIN 8 GND

Back to Index.


Arduino Code

Okay, with all this knowledge we can finally do some programming!

This is a very simple implementation. I always write in a style that suits beginners. I leave it up to you to further improve the code.

Sega6buttonControllerEasyVersion.ino :


/*Interface with a 6-button Sega Mega Drive Controller, 
 * easy (almost pseudocode) version
 * Converts button presses to keyboard strokes,
 *Raspberryfield.life 2019-02-24*/
#include <Keyboard.h>
const int builtInLedPinUno = 17; //Never used but good to know about.
const int stopPin = 15;
const int upzPin = 2; //DB1 'u' & 'z'
const int downyPin = 3; //DB2 'd' & 'y'
const int leftxPin = 4; //DB3 'l' & 'x'
const int rightmodePin = 5; //DB4 'r' & 'm'
const int abPin = 6; //DB6 'b'when select high and 'a' when select low.
const int selectPin = 7; //DB7
const int startcPin = 8; //DB9 'c'when select high and 's' when select low.
//DB5 +5V vcc
//DB8 GND

bool stopButton = false;

void setup()
{
  pinMode(builtInLedPinUno, OUTPUT);
  pinMode(selectPin, OUTPUT);
  pinMode(stopPin, INPUT_PULLUP);//Note! Internal pull-up activated.
  pinMode(upzPin, INPUT_PULLUP);
  pinMode(downyPin, INPUT_PULLUP);
  pinMode(leftxPin, INPUT_PULLUP);
  pinMode(rightmodePin, INPUT_PULLUP);
  pinMode(abPin, INPUT_PULLUP);
  pinMode(startcPin, INPUT_PULLUP);

  digitalWrite(selectPin, HIGH);
} 
void loop()
{
  StopButton();//Check if failover button is pressed. Only for development.
  /*Start by pulse the SELCT 3 times.
  * It will trigger the X, Y, Z, MODE readings*/
  PulseSelect();
  PulseSelect();
  PulseSelect();
  /*Controller should now be in X,Y,Z,MODE state.*/
  //Z-Button reading
  if(digitalRead(upzPin)==LOW){
    Keyboard.press('z');
    }else{
      Keyboard.release('z');
  }//End Z
  //Y-Button reading
  if(digitalRead(downyPin)==LOW){
    Keyboard.press('y');
    }else{
      Keyboard.release('y');
  }//End Y
  //X-Button reading
  if(digitalRead(leftxPin)==LOW){
    Keyboard.press('x');
    }else{
      Keyboard.release('x');
  }//End X
  //MODE-Button reading
  if(digitalRead(rightmodePin)==LOW){
    Keyboard.press('m');
    }else{
      Keyboard.release('m');
  }//End MODE

  /*Start the 4th (last) Select pulse.*/
  digitalWrite(selectPin, LOW);
  //Read A and START buttons.
  //A-Button reading
  if(digitalRead(abPin)==LOW){
    Keyboard.press('a');
    }else{
      Keyboard.release('a');
  }//End A
  //START-Button reading
  if(digitalRead(startcPin)==LOW){
    Keyboard.press('s');
    }else{
      Keyboard.release('s');
  }//End START
  
  /*End 4th pulse.*/
  digitalWrite(selectPin, HIGH);
  
  /*After the 4th pulse, we should be back in idle HIGH state, normal state*/
  /*Now,Read all possible buttons on idle HIGH.*/
  //UP-Button reading
  if(digitalRead(upzPin)==LOW){
    Keyboard.press('u');
    }else{
      Keyboard.release('u');
  }//End UP
  //DOWN-Button reading
  if(digitalRead(downyPin)==LOW){
    Keyboard.press('d');
    }else{
      Keyboard.release('d');
  }//End DOWN
  //LEFT-Button reading
  if(digitalRead(leftxPin)==LOW){
    Keyboard.press('l');
    }else{
      Keyboard.release('l');
  }//End LEFT
  //RIGHT-Button reading
  if(digitalRead(rightmodePin)==LOW){
    Keyboard.press('r');
    }else{
      Keyboard.release('r');
  }//End RIGHT
  //B-Button reading
  if(digitalRead(abPin)==LOW){
    Keyboard.press('b');
    }else{
      Keyboard.release('b');
  }//End B
  //C-Button reading
  if(digitalRead(startcPin)==LOW){
    Keyboard.press('c');
    }else{
      Keyboard.release('c');
  }//End C

  delay(20);//delay 20ms between cycle of readings.
} //END Main Loop

/*Controll physical failover button
*The microcontroller emulates and takes over the keyboard.
*If you write bad code, it might be hard to actually change it,
*because it might prevent you from using the keyboard. This failover button stops
*unwanted behaviour from the keyboard library.
*/
void StopButton(){
  if(digitalRead(stopPin)==LOW){//(Active low)
    stopButton = true;
    }
  while(stopButton){
    Keyboard.releaseAll();
    Serial.println("---STOP Button pressed, program interrupted---");
    delay(1000);
  } 
}//END StopButton()

void PulseSelect(){
  digitalWrite(selectPin, LOW);
  digitalWrite(selectPin, HIGH);
}//End PulseSelect()


Back to Index.


References

Back to Index.


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.