Oli's old stuff

Tinkering with retro and electronics

Mar 13, 2023 - 8 minute read - retro electronics hardware pio minicube64 rp2040 raspberry pi pico

Reading a Mega Drive Controller from Raspberry Pi Pico

I have been experimenting with getting the Minicube64 running on the Raspberry Pi Pico. I’ll talk more about that another time, but today I’ll cover one part which was seemingly simple but caused me a few headaches along the way - reading a Sega Mega Drive pad. The Minicube64 has support for a 3 button pad with directional controls, so the Mega Drive pad is ideal here.

The Sega Mega Drive originally shipped with a 3 button controller, it had the standard UP, DOWN, LEFT, RIGHT switches as well as buttons A, B, C and START. It has a standard DB-9 connector so seemed quite simple to interface with - after all I did explore how the Kempston Joystick interface was put together.

The Protocol

Unlike the Kempston, the Mega Drive has too many buttons to fit into the DB9 connector. As a result, there is a simple protocol to access it.

Rather than reverse engineer it myself, I used the excellent description from Jon Thysell.

Select (Pin 7) Pin 1 Pin 2 Pin 3 Pin 4 Pin 6 Pin 9

Basically, you have to perform two reads from the controller to get the state. The first read has the SELECT line HIGH to read the UP, DOWN, LEFT, RIGHT, B and C buttons. The second read is done with the SELECT line LOW to pick up the A and START button.

Side note: I find it a little strange that the HIGH strobe maps in B and C rather than A and B

The six button pad has a different protocol (but is still compatible with the original), but I won’t cover that here.

The ideal target would be end up with a bit pattern for each strobe:

7 6 5 4 3 2 1 0

The two * are always low and can be used to detect the presence of a controller when querying he controller with SELECT pulled LOW. This could be pretty handy as a check.

Wiring up to a Pico

The first thing to be aware of is that the Mega Drive controller is a 5V device and the Pico is a 3.3V device. This means that you can’t just connect the output pins from the controller to the Pico, you need to level shift them.

For this I chose to use my standard approach using a voltage divider; use a 2k2 resistor on the signal line and a 3k3 resistor to pull to ground.

Initial voltage divider for signals

I pulled the SELECT line to +5V for this test as I didn’t want to worry about having to strobe the line for now.

When reading the signals from the Pico I found that the UP and DOWN were always being pulled to ground and therefore being registered as “pressed”.

I hunted around for a schematic of the pad and found that GameSX came to the rescue.

Sega Mega Drive Schematic by GameSX

This image is Copyright GameSX and is distributed under the CC-NC-SA-4.0 terms

The buttons UP and DOWN are the only two connected directly via pull-ups to 5V. I opened up my Mega Drive pad and confirmed that the internal pull-ups are all 10K and that the schematic there is sound.

To figure out what was happening, you have to dig into how all these resistors affects the total circuit.

The resistors involved in the voltage divider circuit are acting as a stronger pull than the 10K pullup, which means that a high logic level is still LOW.

I changed this to 3K3 and 6K8 and the Pico could read it.

However looking at the simulation of this, it’s barely under 1.7V which is probably at the edge of tolerance for the Pico’s GPIO (which states logic HIGH is at 2V, but could be around 1.8V). Better values for these resistors would be 6K8 and 13K6 or thereabouts; this would give a logic high of over 2V which is well in the range for the logic HIGH.

This is an interesting lesson; you have to consider the whole circuit when interfacing with external devices.

Finally; why does this affect only the UP and DOWN buttons and not the others? The other signals are driven from the 74xx157 that deal with the SELECT line switching; I suspect that they’re driven enough so that the voltage divider resistors don’t pull them to ground.

SELECT line shenanigans

Remember I said I pulled the SELECT line HIGH for the tests? The next stage of fun came to actually using it for real.

After connecting the SELECT output to the Pico, I got what looked like a floating behaviour on the line when the logic level was HIGH. Opening up the controller it confirmed my suspicion, the 74xx157 inside is a HC variant, a CMOS 5V part.

The logic levels for CMOS 5V are 3.6V HIGH - well above my 3.3V pico levels. I needed to boost it to 5V.

Side note: Normally when shifting from 3.3V to 5V TTL/HCT, the logic levels are fine as TTL has a lower HIGH threshold, but because this is a HC part, the levels are very different.

Boosting 3.3V to 5V is not something I’ve done before, so I had to explore the options.

Microchip published a document of 3V tricks and tips that describe some of the approaches.

These are basically:

  • Diode offsets
  • Dual NPN transistors with pullups to each level
  • MOSFET based solution

3.3V to 5V via Diode Offset

I simulated a few of these on the Falstad sim:

For some reason I couldn’t get any of these approaches working reliably. I even tried using an NPN transistor based inverter which should boost the signal to 5V (albeit inverting it), but didn’t get a good signal from that.

I ended up using a MOSFET-based bi-directional shifter board that are widely available and that did the trick.

I’m going to keep trying on the simpler approaches, as they should work, so I assume I did something wrong.

Pico code

The last thing to talk about is the code to actually read the controller. During testing I was using the gpio libraries in the Pico SDK, however there’s something to consider - the hold times of the SELECT line before the outputs from the pad become valid. The datasheet for the 74HC157 indicates that a propagation delay of 20ns is reasonable, which means that on the Pico we have to artificially delay between setting the SELECT line and reading the pins.

I decided to move the code into the PIO and have it continuously read and push the state of the controller back to the system. The hold time was dealt with by using “sticky” outputs and holding the pio with a few NOP cycles.

    .program controller
        mov pins,!null  [31]    ; SELECT line HIGH
        nop             [31]    ; hold
        in pins,8       [31]    ; read pins (UDLRBC)
        mov pins,null   [31]    ; SELECT line LOW
        nop             [31]    ; hold
        in pins,8       [31]    ; read pins (A/Start)
        push noblock

Doing this via PIO is convenient as you get a 16-bit value in the RX buffer with the state of both strobes. I can just sample this buffer and pull the latest value.

    enum md_controller_buttons {
        MD_CONTROLLER_UP = (1 << 0),
        MD_CONTROLLER_DOWN = (1 << 1),
        MD_CONTROLLER_LEFT = (1 << 2),
        MD_CONTROLLER_RIGHT = (1 << 3),
        MD_CONTROLLER_B = (1 << 4),
        MD_CONTROLLER_C = (1 << 5),
        MD_CONTROLLER_A = (1 << 6),
        MD_CONTROLLER_START = (1 << 7),

    unsigned int pins = MD_PIO->rxf[MD_SM];    
    uint8_t a = (~(pins >> 8)) & 0b00111111;
    uint8_t b = (~pins) & 0b00110000;
    uint8_t result = a 
        | ((b & MD_CONTROLLER_BIT_B_A) ? MD_CONTROLLER_A : 0)

I could probably improve this by waiting for IRQ signals and then firing an interrupt back to the main cpu, but for my purposes just reading and pushing the state to the RX buffer is good enough.

Final Wiring

With all this done I ended up with this schematic:

Pico Mega Drive pad wiring.

You can ignore the SELECT line being on an odd pin, it’s because I’m using those for an SD Card so I omitted them here.

I could also have kept the 2K2/3K3 resistors for all but MD_1 ad MD_2 (as these two don’t use the 74HC157) but I figured it’s best to be consistent (I may end up regretting this though!).

Wrap up

What seemed like a pretty simple system turned out to have a couple of surprises along the way. We got there in the end though.

’til next time.


I realised after I wrote this post that the 10K pullups in the Mega Drive pad could actually be used to create a voltage divider to drop it to ~3V.

By using a higher resistor value at the bottom of the divider (15K or so) you can drop the 5V to around 3V for use in the Pico.

This should be usable for the UP & DOWN lines, but obviously it’s not suitable on the other lines. It saves needing an extra couple of resistors and simplifies the circuit a little.