Difference between revisions of "Howto:Read Control Pads"

From MegaDrive Wiki
Jump to: navigation, search
(Initialising Control Pads: Cleaned up code to make it look neater.)
m (21 revisions imported)
 
(6 intermediate revisions by 2 users not shown)
Line 1: Line 1:
All Mega Drives have at least two connectors for joypads, which are the only source of input to the game without special hardware on the cartridge or extension port. Reading the control pads is accomplished through a simple routine. One might expect to just read a register and get all button states, but due to the limited amount of IO lines, data has to be multiplexed. This is especially true of 6 button controllers, and any other peripherals that utilise the Mega Drive's controller port connectors.
+
All Mega Drives have at least two connectors for joypads, which are the only source of input to the game without special hardware on the cartridge or extension port. Reading the control pads is accomplished through a simple routine. One might expect to just read a register and get all button states, but due to the limited amount of I/O lines, data has to be multiplexed. This is especially true of 6 button controllers, and any other peripherals that utilise the Mega Drive's controller port connectors.
  
 
Controller interfacing and the version register is handled by the I/O controller, a discrete chip labeled [[315-5309]] on early Mega Drives, or integrated with the Z80 control chip in later revisions, and integrated with all other system control chips in Model 2 and later Mega Drives.
 
Controller interfacing and the version register is handled by the I/O controller, a discrete chip labeled [[315-5309]] on early Mega Drives, or integrated with the Z80 control chip in later revisions, and integrated with all other system control chips in Model 2 and later Mega Drives.
  
 
== Initialising Control Pads ==
 
== Initialising Control Pads ==
To read this data, we first must initialise the control ports to configure them as inputs, save for the /TH pin. This pin controls the multiplexer in control pads. (The same applies to six button controllers - although those require a slightly different /TH toggling sequence to read data.) The following code can be used to set up the controllers:
+
To read this data, we first must initialise the control ports to configure them as inputs, save for the <code><span style="text-decoration:overline;">TH</span></code> pin. This pin controls the multiplexer in control pads. (The same applies to six button controllers - although those require a slightly different <code><span style="text-decoration:overline;">TH</span></code> toggling sequence to read data.) The following code can be used to set up the controllers:
 
+
<source lang="asm" line="GESHI_FANCY_LINE_NUMBERS">
<pre>
 
;-------------------------------------
 
 
; Routine to initialise control ports
 
; Routine to initialise control ports
;-------------------------------------
 
 
SetUpJoypads:
 
SetUpJoypads:
move   #$2700,sr ; Disable Interrupts.
+
move #$2700, sr ; Disable all ints
move.w #$100,($A11100) ; Stop the Z80.
+
 
 +
move.w #$100, $A11100 ; Stop Z80
 +
 
@waitZ80:
 
@waitZ80:
btst   #0,($A11101) ; Has the Z80 stopped?
+
btst #0, $A11101 ; Has the Z80 stopped?
bne.s   @waitZ80 ; If not, wait.
+
bne.s @waitZ80 ; If not, wait.
moveq   #$40,d0 ; PD6 is an output.
+
move.b d0,($A10009) ; Configure Port A.
+
moveq #$40, d0 ; PD6 is an output
move.b d0,($A1000B) ; Configure Port B.
+
move.b d0, $A10009 ; Configure port A
move.b d0,($A1000D) ; Configure Port C.
+
move.b d0, $A1000B ; Configure port B
move.w #0,($A11100) ; Restart the Z80.
+
move.b d0, $A1000D ; Configure port C
move   #$2000,sr ; Enable Interrupts.
+
 
rts
+
move.w #0, $A11100 ; Restart the Z80
</pre>
+
move #$2000, sr ; Re-enable ints
 +
rts
 +
</source>
  
To begin with, the code will disable interrupts, so it can not get interrupted while the Z80 is stopped. This is a precaution so that in case of VBlank or HBlank routines also stop and afterwards resume the Z80 interrupt the code are called. This would leave the Z80 running during joypad reads. Controller ports could be read with the Z80 running, but this is not recommended practice, due to a bug in the [[315-5309|I/O controller,]] which may cause the Z80's wait state timing to change from 250ns to 110ns, which is too fast for it to read data correctly. Thusly, it will misread all data.
+
To begin with, the code will disable interrupts, so it can not get interrupted while the Z80 is stopped. This is a precaution so that in case of VBlank or HBlank routines also stop and afterwards resume the Z80 interrupt the code are called. This would leave the Z80 running during joypad reads. Controller ports could be read with the Z80 running, but this is not recommended practice, due to a bug in the [[315-5309|I/O controller,]] which may cause the Z80's wait state timing to change from 250ns to 110ns. When the Z80 waitstate is reduced to 110ns, is too low for the Z80 to read data correctly from the bus. Thusly, it will misread all data.
  
Next, the code writes the value $40 to a register for optimisation purposes, reducing size by two bytes as well as time required to fetch the opcode. That value is then written to the control registers for ports A through C (front ports and EXT port.) The control port's bit format is as follows:
+
Next, the code writes the value <code>$40</code> to a register for optimisation purposes, reducing size by two bytes as well as time required to fetch the opcode. That value is then written to the control registers for ports A through C (front ports and EXT port.) The control port's bit format is as follows:
  
 
{| class="wikitable"
 
{| class="wikitable"
Line 52: Line 53:
 
|}
 
|}
  
A set bit indicates that the pin of the port is configured as an output. In the example above, we configure /TH as an output, and all other pins as an input. If INT were set and /TH was an input, it could be used to cause a level 2 interrupt on the [[Motorola 68000]] which is used by light guns. The set up routine should be ran once, as close to the beginning of your program as possible to ensure that the pads are initialised.
+
A set bit indicates that the pin of the port is configured as an output. In the example above, the <code><span style="text-decoration:overline;">TH</span></code> pin is configured as an output, and all other pins as an input. If INT were set and <code><span style="text-decoration:overline;">TH</span></code> was an input, it could be used to cause a level 2 interrupt on the [[Motorola 68000]] which is used by light guns. The set up routine should be ran once, as close to the beginning of the program as possible to ensure that the pads are initialised when they are accessed later.
  
 
== Reading Data ==
 
== Reading Data ==
Line 58: Line 59:
  
 
<source lang="asm" line="GESHI_FANCY_LINE_NUMBERS">
 
<source lang="asm" line="GESHI_FANCY_LINE_NUMBERS">
ControlStateArea: EQU $FFFFFF82 ; Area in RAM that holds the button states
+
ControlStateArea: EQU $FFFFFF82 ; Area in RAM that holds the button states
  
 
; Routine to read the currently pressed buttons from all three IO ports (control 1&2, EXT)
 
; Routine to read the currently pressed buttons from all three IO ports (control 1&2, EXT)
Line 64: Line 65:
  
 
JoypadRead:
 
JoypadRead:
move.w #$100, $A11100 ; Stop Z80 and wait
+
move.w #$100, $A11100 ; Stop Z80 and wait
 
 
 
@waitZ80:
 
@waitZ80:
btst #0, $A11101 ; Has the Z80 stopped?
+
btst #0, $A11101 ; Has the Z80 stopped?
bne.s @waitZ80 ; If not, wait.
+
bne.s @waitZ80 ; If not, wait.
 
 
lea ControlStateArea, a0 ; Area where joypad states are written
+
lea ControlStateArea, a0 ; Area where joypad states are written
lea $A10003, a1 ; First joypad port
+
lea $A10003, a1 ; First joypad port
 
 
moveq #2, d7 ; Read all 3 control ports
+
moveq #2, d7 ; Read all 3 control ports
 
 
 
@readJoypads:
 
@readJoypads:
move.b #0, (a1) ; Assert /TH
+
move.b #0, (a1) ; Assert /TH
 
rept 4
 
rept 4
nop ; Wait until data is ready.
+
nop ; Wait until data is ready.
 
endr
 
endr
  
move.b (a1), d0 ; Read back controller states. (00SA00DU)
+
move.b (a1), d0 ; Read back controller states. (00SA00DU)
lsl.b #2, d0 ; Shift start and A into the high 2 bits
+
lsl.b #2, d0 ; Shift start and A into the high 2 bits
and.b #$C0, d0 ; Get only S+A buttons
+
and.b #$C0, d0 ; Get only S+A buttons
 
 
move.b #$40, (a1) ; De-assert /TH
+
move.b #$40, (a1) ; De-assert /TH
 
rept 4
 
rept 4
nop ; Wait until data is ready.
+
nop ; Wait until data is ready.
 
endr
 
endr
 
 
move.b (a1), d1 ; Read back the controller states. (11CBRLDU)
+
move.b (a1), d1 ; Read back the controller states. (11CBRLDU)
and.b #$3F, d1 ; Get only CBRLDU alone
+
and.b #$3F, d1 ; Get only CBRLDU alone
or.b d1, d0 ; OR together the control states
+
or.b d1, d0 ; OR together the control states
not.b d0 ; Invert the bits
+
not.b d0 ; Invert the bits
 
 
move.b (a0), d1 ; Get the current button press bits from RAM
+
move.b (a0), d1 ; Get the current button press bits from RAM
eor.b d0, d1 ; OR the pressed buttons with the last frame's pressed buttons
+
eor.b d0, d1 ; OR the pressed buttons with the last frame's pressed buttons
 
 
move.b d0, (a0)+ ; Write the pressed bits
+
move.b d0, (a0)+ ; Write the pressed bits
and.b d0, d1 ; AND the bits together.
+
and.b d0, d1 ; AND the bits together.
move.b d1, (a0)+ ; Write the held bits
+
move.b d1, (a0)+ ; Write the held bits
 
 
addq.w #2, a1 ; Use next control port
+
addq.w #2, a1 ; Use next control port
dbf d7, @readJoypads ; Loop until all joypads are read
+
dbf d7, @readJoypads ; Loop until all joypads are read
 
 
move.w #$0, $A11100 ; Re-start the Z80
+
move.w #$0, $A11100 ; Re-start the Z80
 
rts
 
rts
 
</source>
 
</source>
  
The bit layout for each RAM address will be Start, A, C, B, Right, Left, Down, Up, starting from the MSB. For example, the following code can be used to check the 'raw' button state (the condition will evaluate to true every frame the button is pressed):
+
The bit layout for each RAM address is be Start, A, C, B, Right, Left, Down, Up, starting from the MSB. For example, the following code can be used to check the 'raw' button state (the condition will evaluate to true every frame the button is pressed):
 
<source lang="asm" line="GESHI_FANCY_LINE_NUMBERS">
 
<source lang="asm" line="GESHI_FANCY_LINE_NUMBERS">
btst #7, ControlStateArea ; Is start pressed?
+
btst #7, ControlStateArea ; Is start pressed?
bne.s @startPressed ; If so, branch.
+
bne.s @startPressed ; If so, branch.
 
</source>
 
</source>
  
However, if you were to want to check if start was pressed using the held button state (will only be ran the first frame the button is held down) the following code could be used:
+
However, if one were to want to check if start was pressed using the held button state (will only be ran the first frame the button is held down) the following code could be used:
 
<source lang="asm" line="GESHI_FANCY_LINE_NUMBERS">
 
<source lang="asm" line="GESHI_FANCY_LINE_NUMBERS">
move.b ControlStateArea+1, d0 ; Get button state to d0 (if we AND the address, it will get destroyed)
+
move.b ControlStateArea+1, d0 ; Get button state to d0 (if we AND the address directly, it will get destroyed)
and.b #$80, d0 ; Is start pressed?
+
and.b #$80, d0 ; Is start pressed?
bne.s @startPressed ; If so, branch.
+
bne.s @startPressed ; If so, branch.
 
</source>
 
</source>
Note the address is directly the next one over from the raw state. Each port's state is 2 bytes, so the second controller port's data would be at $FFFFFF84.
+
Note the address is directly the next one over from the raw state. Each port's state is 2 bytes, so the second controller port's data would be at <code>$FFFFFF84</code>.
  
 
[[Category:Howto]]
 
[[Category:Howto]]

Latest revision as of 11:29, 16 March 2015

All Mega Drives have at least two connectors for joypads, which are the only source of input to the game without special hardware on the cartridge or extension port. Reading the control pads is accomplished through a simple routine. One might expect to just read a register and get all button states, but due to the limited amount of I/O lines, data has to be multiplexed. This is especially true of 6 button controllers, and any other peripherals that utilise the Mega Drive's controller port connectors.

Controller interfacing and the version register is handled by the I/O controller, a discrete chip labeled 315-5309 on early Mega Drives, or integrated with the Z80 control chip in later revisions, and integrated with all other system control chips in Model 2 and later Mega Drives.

Initialising Control Pads

To read this data, we first must initialise the control ports to configure them as inputs, save for the TH pin. This pin controls the multiplexer in control pads. (The same applies to six button controllers - although those require a slightly different TH toggling sequence to read data.) The following code can be used to set up the controllers: <source lang="asm" line="GESHI_FANCY_LINE_NUMBERS">

Routine to initialise control ports

SetUpJoypads: move #$2700, sr ; Disable all ints

move.w #$100, $A11100 ; Stop Z80

@waitZ80: btst #0, $A11101 ; Has the Z80 stopped? bne.s @waitZ80 ; If not, wait.

moveq #$40, d0 ; PD6 is an output move.b d0, $A10009 ; Configure port A move.b d0, $A1000B ; Configure port B move.b d0, $A1000D ; Configure port C

move.w #0, $A11100 ; Restart the Z80 move #$2000, sr ; Re-enable ints rts </source>

To begin with, the code will disable interrupts, so it can not get interrupted while the Z80 is stopped. This is a precaution so that in case of VBlank or HBlank routines also stop and afterwards resume the Z80 interrupt the code are called. This would leave the Z80 running during joypad reads. Controller ports could be read with the Z80 running, but this is not recommended practice, due to a bug in the I/O controller, which may cause the Z80's wait state timing to change from 250ns to 110ns. When the Z80 waitstate is reduced to 110ns, is too low for the Z80 to read data correctly from the bus. Thusly, it will misread all data.

Next, the code writes the value $40 to a register for optimisation purposes, reducing size by two bytes as well as time required to fetch the opcode. That value is then written to the control registers for ports A through C (front ports and EXT port.) The control port's bit format is as follows:

Control Port Format
128 64 32 16 8 4 2 1
INT PC6 PC5 PC4 PC3 PC2 PC1 PC0

A set bit indicates that the pin of the port is configured as an output. In the example above, the TH pin is configured as an output, and all other pins as an input. If INT were set and TH was an input, it could be used to cause a level 2 interrupt on the Motorola 68000 which is used by light guns. The set up routine should be ran once, as close to the beginning of the program as possible to ensure that the pads are initialised when they are accessed later.

Reading Data

With control pads set up, the pads themselves can finally be read. The routine below will read all three ports, and writes not only the 'raw' button state to a RAM address, but also the 'pressed' state of buttons, which will only be set for one frame.

<source lang="asm" line="GESHI_FANCY_LINE_NUMBERS"> ControlStateArea: EQU $FFFFFF82 ; Area in RAM that holds the button states

Routine to read the currently pressed buttons from all three IO ports (control 1&2, EXT)
Outputted format is in S ACB RL DU

JoypadRead: move.w #$100, $A11100 ; Stop Z80 and wait

@waitZ80: btst #0, $A11101 ; Has the Z80 stopped? bne.s @waitZ80 ; If not, wait.

lea ControlStateArea, a0 ; Area where joypad states are written lea $A10003, a1 ; First joypad port

moveq #2, d7 ; Read all 3 control ports

@readJoypads: move.b #0, (a1) ; Assert /TH rept 4 nop ; Wait until data is ready. endr

move.b (a1), d0 ; Read back controller states. (00SA00DU) lsl.b #2, d0 ; Shift start and A into the high 2 bits and.b #$C0, d0 ; Get only S+A buttons

move.b #$40, (a1) ; De-assert /TH rept 4 nop ; Wait until data is ready. endr

move.b (a1), d1 ; Read back the controller states. (11CBRLDU) and.b #$3F, d1 ; Get only CBRLDU alone or.b d1, d0 ; OR together the control states not.b d0 ; Invert the bits

move.b (a0), d1 ; Get the current button press bits from RAM eor.b d0, d1 ; OR the pressed buttons with the last frame's pressed buttons

move.b d0, (a0)+ ; Write the pressed bits and.b d0, d1 ; AND the bits together. move.b d1, (a0)+ ; Write the held bits

addq.w #2, a1 ; Use next control port dbf d7, @readJoypads ; Loop until all joypads are read

move.w #$0, $A11100 ; Re-start the Z80 rts </source>

The bit layout for each RAM address is be Start, A, C, B, Right, Left, Down, Up, starting from the MSB. For example, the following code can be used to check the 'raw' button state (the condition will evaluate to true every frame the button is pressed): <source lang="asm" line="GESHI_FANCY_LINE_NUMBERS"> btst #7, ControlStateArea ; Is start pressed? bne.s @startPressed ; If so, branch. </source>

However, if one were to want to check if start was pressed using the held button state (will only be ran the first frame the button is held down) the following code could be used: <source lang="asm" line="GESHI_FANCY_LINE_NUMBERS"> move.b ControlStateArea+1, d0 ; Get button state to d0 (if we AND the address directly, it will get destroyed) and.b #$80, d0 ; Is start pressed? bne.s @startPressed ; If so, branch. </source> Note the address is directly the next one over from the raw state. Each port's state is 2 bytes, so the second controller port's data would be at $FFFFFF84.