codeslinger.co.uk

Gameboy - Joypad.

Joypad:

The gameboy has an inbuilt joypad with 8 buttons. There are 4 directional buttons (up, down, left and right) and standard buttons (start,select, A and B). The joypad register can be found at address 0xFF00 and it is broken down like so:

Taken from pandocs
Bit 7 - Not used
Bit 6 - Not used
Bit 5 - P15 Select Button Keys (0=Select)
Bit 4 - P14 Select Direction Keys (0=Select)
Bit 3 - P13 Input Down or Start (0=Pressed) (Read Only)
Bit 2 - P12 Input Up or Select (0=Pressed) (Read Only)
Bit 1 - P11 Input Left or Button B (0=Pressed) (Read Only)
Bit 0 - P10 Input Right or Button A (0=Pressed) (Read Only)

Bits 0-3 are set by the emulator to show the state of the joypad. As you can see the directional buttons and the standard buttons share this range of bits so how would the game know if bit 3 was set whether it was the directional down button or the stanadrd start button? The way this works is that the game sets bit 4 and 5 depending on whether it wants to check on the directional buttons or the standard buttons.

The way I believe this works in the original gameboy hardware is the game writes to memory 0xFF00 with bit 4 or 5 set (never both). It then reads from memory 0xFF00 and instead of reading back what it just wrote what is returned is the state of the joypad based on whether bit 4 or bit 5 was set. For example if the game wanted to check which directional buttons was pressed it would set bit 4 to 1 and then it would do a read memory on 0xFF00. If the up key is pressed then when reading 0xFF00 bit 2 would be set to 0 to signal that the directional button up is pressed (0 means pressed, 1 unpressed). However if up was not pressed but the select button was then bit 2 would be left at 1 meaning nothing is pressed, even though the select button is pressed which maps on to bit 2. The reason why bit 2 would be set to 1 signalling it is not pressed even when it is is because bit 4 was set to 1 meaning the game is only interested in the state of the directional buttons.

The way I emulate this is I have a BYTE variabled called m_JoypadState where each bit represents the state of the joypad (8 buttons and 8 bits so it works fine). Whenever a button is pressed I set the correct bit in m_JoypadState to 0 and if it is not pressed it is set to 1. If a bit in m_JoypadState goes from 1 to 0 then it means this button has just been pressed so a joypad interupt is requested.

Whenever the game reads from 0xFF00 i trap it with ReadMemory and call a function which looks at memory address 0xFF00 to see if the game is interest in directional buttons or standard buttons and then I return a byte data which combines m_JoypadState with 0xFF00 so the game gets the correct state of the joypad. While I remember lets add the following to ReadMemory

else if (0xFF00 == address)
   return GetJoypadState() ;

Before I show you the implementation of GetJoypadState I'll show you how to set m_JoypadState which GetJoypadState will need.

void Emulator::KeyPressed(int key)
{
   bool previouslyUnset = false ;

   // if setting from 1 to 0 we may have to request an interupt
   if (TestBit(m_JoypadState, key)==false)
     previouslyUnset = true ;

   // remember if a keypressed its bit is 0 not 1
   m_JoypadState = BitReset(m_JoypadState, key) ;

   // button pressed
   bool button = true ;

   // is this a standard button or a directional button?
   if (key > 3)
     button = true ;
   else // directional button pressed
     button = false ;

   BYTE keyReq = m_Rom[0xFF00] ;
   bool requestInterupt = false ;

   // only request interupt if the button just pressed is
   // the style of button the game is interested in
   if (button && !TestBit(keyReq,5))
     requestInterupt = true ;

   // same as above but for directional button
   else if (!button && !TestBit(keyReq,4))
     requestInterupt = true ;

   // request interupt
   if (requestInterupt && !previouslyUnset)
     RequestInterupt(4) ;
}

The function thats called when a key is released is much simpler

void Emulator::KeyReleased(int key)
{
   m_JoypadState = BitSet(m_JoypadState,key) ;
}

The way I call the above functions is with SDL_KeyEvents. Whenever a key is pressed I call KeyPressed function passing the following in as arguments

SDLK_a : key = 4
SDLK_s : key = 5
SDLK_RETURN : key = 7
SDLK_SPACE : key = 6
SDLK_RIGHT : key = 0
SDLK_LEFT : key = 1
SDLK_UP : key = 2
SDLK_DOWN : key = 3

Now we have everything needed to write the GetJoypadState function

BYTE Emulator::GetJoypadState() const
{
   BYTE res = m_Rom[0xFF00] ;
   // flip all the bits
   res ^= 0xFF ;

   // are we interested in the standard buttons?
   if (!TestBit(res, 4))
   {
     BYTE topJoypad = m_JoypadState >> 4 ;
     topJoypad |= 0xF0 ; // turn the top 4 bits on
     res &= topJoypad ; // show what buttons are pressed
   }
   else if (!TestBit(res,5))//directional buttons
   {
     BYTE bottomJoypad = m_JoypadState & 0xF ;
     bottomJoypad |= 0xF0 ;
     res &= bottomJoypad ;
   }
   return res ;
}

If you are confused why m_JoypadState is shifted left 4 times when checking standard buttons is because the standard buttons are stored in the top nibble of m_JoypadState but then need to be in the lower nibble for when it gets logically or'd.