Controllers

The main input device for the Mega Drive and the one you can expect everybody to have.

Note that there are two controllers: the original big 3-button controller (with D-pad, A, B, C and Start) and the later smaller 6-button controller (which adds X, Y, Z and Mode). Both types of controllers are explained here.

Setting up

Before using the controllers first we have to set up the ports.

Each controller port has two addresses: the "control port" (used to define how signals are sent) and "data port" (the one we use to actually send data back and forth), as explained in the I/O ports page.

Port addresses for each player
PortPlayer 1Player 2
Data port$A10003$A10005
Control port$A10009$A1000B

The I/O ports page include these constants for 68000 assembly that we're going to use:

IoCtrl1: equ $A10009   ; 1P control port
IoCtrl2: equ $A1000B   ; 2P control port
IoData1: equ $A10003   ; 1P data port
IoData2: equ $A10005   ; 2P data port

To set up the controllers, you have to write $40 to both control and data port. You may want to do this early in the program (before e.g. setting up the video hardware and such) to give time for the controllers to get ready.

Something important: we need to prevent the Z80 from accessing the 68000 bus while we're touching these ports (due to a hardware bug). The easiest way is to halt the Z80 while we're doing it. Remember that if the Z80 isn't running, it's still safe to do a Z80 bus request since the Z80 is still being reset anyway.

Anyway, here's how it'd look like:

    FastPauseZ80
    move.b  #$40, (IoCtrl1)   ; 1P control port
    move.b  #$40, (IoData1)   ; 1P data port
    move.b  #$40, (IoCtrl2)   ; 2P control port
    move.b  #$40, (IoData2)   ; 2P data port
    ResumeZ80

That's all.

Reading the buttons

Now we can start using the controllers. The optimal way to do things is to read the buttons once every frame, store the result somewhere in RAM and let the rest of the game look there. This also avoids issues with 6-button controllers (see warning below).

For this you need the data port (IoData1 or IoData2). Reading the buttons is a two step process: first write $40 to the data port, wait a bit (a few nop will do), then read back a byte from the port. Then write $00 and do the same.

  1. Write $40, wait a bit, read back
  2. Write $00, wait a bit, read back

Bits 5-0 from each read includes the current state of the buttons, where each bit belongs to a button (and yes, up/down show up twice). A bit = 1 means the button is not pressed, a bit = 0 means it's pressed. Refer to the table below to check which bit is each button.

3-button controller steps
Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
1st step (write $40) C B Right Left Down Up
2nd step (write $00) Start A 0 0 Down Up

You may want to rearrange the bits in a more convenient way. A common scheme is called "SACBRLDU" (from the button initials), where bits 5-0 from the 1st step are left in their place and bits 5-4 from the 2nd step are moved to bits 7-6.

SACBRLDU scheme
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
Start A C B Right Left Down Up

Example assembly code to give an idea:

    ; a0 = IoData1 for player 1
    ;    = IoData2 for player 2
    
    FastPauseZ80        ; Pause Z80 for a bit
    
    move.b  #$40, (a0)  ; Do 1st step
    nop                 ; D-pad, B and C
    nop
    nop
    nop
    move.b  (a0), d0
    
    move.b  #$00, (a0)  ; Do 2nd step
    nop                 ; A and Start
    nop
    nop
    nop
    move.b  (a0), d1
    
    ResumeZ80           ; Z80 can run now
    
    and.b   #$3F, d0    ; Rearrange bits
    and.b   #$30, d1    ; into SACBRLDU
    lsl.b   #2, d1
    or.b    d1, d0
    
    ; Now d0 contains all the buttons
    ; May want to use a NOT if you prefer
    ; 1 = pressed and 0 = released

Do not read the controllers more than once per frame! Doing so will break 6-button controllers which rely on timing to provide their backwards compatibility. Instead store the result somewhere in RAM and let the game look there.

Check when a button just went down

The above is enough to know if a button is being held down (e.g. hold down the D-pad to run). But what about actions that should only take effect once when you just press the button, instead of every frame?

For this, you need to keep track of the buttons as they were in the last frame. Then (maybe in the same routine that reads the controller!) you can check if a button was just pressed by comparing it against the current frame:

just_pressed = pressed_now and (not pressed_before)

You can also check if a button just got released by doing it the other way:

just_released = (not pressed_now) and pressed_before

Another remark: humans are not perfect and hence you can't expect them to press two buttons at the same time (this includes diagonals too). If your game absolutely needs this, then make sure to include some sort of grace period so it still works as long as the two buttons are pressed within a few frames.

Checking if there's a controller

If there's nothing in the port it should look as if no buttons are pressed, so normally you don't have to bother with this.

If you want to check if there's a controller however (e.g. to disable a "2 players" option if there's only one controller), look at bits 3-2 from the second step: they should be 00. If they aren't, then it means there isn't a controller plugged in.

6-button controller

Reading the 6-button controller is done in a similar way to the 3-button one. The main difference is that instead of writing to the port twice, we write to it seven times (alternating between writing $40 and $00):

6-button controller steps
Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
1st step (write $40) C B Right Left Down Up
2nd step (write $00) Start A 0 0 Down Up
3rd step (write $40) C B Right Left Down Up
4th step (write $00) Start A 0 0 Down Up
5th step (write $40) C B Right Left Down Up
6th step (write $00) Start A 0 0 0 0
7th step (write $40) C B Mode X Y Z

1st and 2nd steps are as usual. 3rd, 4th and 5th steps are for compatibility only and you can ignore their values (i.e. do the writes without the reads).

6th step is the important one: it tells you if a 3-button controller is in use. If bits 3-0 are not 0000, then you can assume it's a 3-button controller and stop here. If they are, however, proceed to the 7th step to read the extra buttons.

In other words:

  1. Do 1st and 2nd steps (D-pad, A/B/C, Start)
  2. Do 3rd, 4th and 5th steps (ignore their input)
  3. Do 6th step and check bits 3-0
    • If it is 0000, do 7th step (X/Y/Z, Mode)
    • If it isn't 0000, it's a 3-button controller

Do not read the controllers more often than once per frame! Their backwards compatibility relies on timing and reading them too often will make them return the wrong values. Instead store the result somewhere in RAM and let the game look there.

Do not do anything that requires holding down the Mode button when turning on the console, since that's how 6-button controllers go into 3-button compatibility mode. Similarly, don't do anything that requires pressing Mode+X+C (which does the same for wireless controllers). Pressing the Mode button after power on is fine.

Troubleshooting

Remember to always check if the controller works with other games! It's perfectly possible that a controller is broken (e.g. if some pin is broken, some buttons may not work or act as if another button was pressed too), as well as that player port. Only start troubleshooting if you know for sure that the controller works.

B and C also trigger A and Start

Make sure that the controller has been setup correctly, and that you're toggling the data port correctly as well. If you're sure it's correct (especially if it does work in an emulator) then it's possible the controller is broken (certain pin breaking will cause this). Check it in other games to make sure.

6-button controllers act weird

Did you heed the warning about reading them only once per frame? Make sure to read the controllers when the frame starts, store the buttons somewhere in RAM and let the rest of the game rely on that.

Also 6-button controllers are a lot more picky about the delay between the write to the data port and reading back from it, so make sure to get this part right as well.

The X and Z buttons are swapped

Check the 6-button table again. X is in bit 2 and Z in bit 0. It's easy to miss this detail.

Button layout

3-button controller

Under the left thumb sits the D-pad. Under the right thumb sit the letter buttons, from left to right: A, B, C (with the latter being the most accessible). Above those buttons is the Start button.

6-button controller

Under the left thumb sits the D-pad. Under the right thumb sit the letter buttons in a 3 by 2 arrangement. From left to right: A, B, C (bottom row), X, Y, Z (top row). The C button is the most accessible. The Start button is in the middle of the controller. On the top right of the controller (on the border) is the Mode button.

Remarks: