In this tutorial, you'll learn to read the joypad registers and react to player input ;). We're going to need to store the player's input somewhere, so we'll declare some variables in the system's RAM ($0000-$1FFF
). If you make your own variables later in this part of RAM, keep in mind that the stack is set to $1FFF
, so try not to get too close to that area (of course). We'll define variables for both Controllers (aka joypads) 1 & 2.
Note: Up to 4 controllers can receive input through a multitap. You can expand my code to do so yourself :P. There is a doc explaining this in the snesbase pack.
.ENUM $0000
Joy1Raw DW ; Holder of RAW joypad data from register (from last frame)
Joy2Raw DW
Joy1Press DW ; Contains only pressed buttons (not held down)
Joy2Press DW
Joy1Held DW ; Contains only buttons that are Held
Joy2Held DW
.ENDE
Alright, that's the variables. JoyRaw
will act as a log of the raw data received from reading the joypad registers. JoyPress
will hold the buttons that have only been pressed for 1 frame. JoyHeld
will hold data for the buttons that have been pressed continuously for > 1 frame. One example of the effects you can do with button holds is running, such as in Super Mario World. Once Y is pressed, Mario will accelerate as long as Y is held until he reaches max speed. Once Y is released, he slows down again. ta-da.
.ENUM
is an easy way to map names to addresses. Joy1
is a word in size (DW), so WLA just mapped Joy1
to $0000
, and Joy2
to $0002
(leaving $0000
and $0001
for Joy1
, a word). The other variables are mapped in the same way. If you wanted to do it manually, you could .EQU
(.DEFINE
) xxxx $0000
, but I personally like this method because it automatically skips 1 or 2 addresses if it's a byte or word..
Now, let's get on to reading the controllers, shall we? Onto some registers.
Register $4200: Counter Enable (1b/W)
n-vh---j n: NMI enable v: vertical counter enable
h: horizontal enable register j: joypad enable
We encountered this register in the VBlank tutorial. This time we will write to bit 0 of this register as well. This causes the SNES to read the joypad(s).
Register $4212: Status Register (1b/RW)
vh-----j v: 0 = Not in VBlank state.
1 = In VBlank state.
h: 0 = Not in HBlank state.
1 = In HBlank state.
j: 0 = Joypad not ready.
1 = Joypad ready.
We just check bit 0 here to see if the joypad is ready to be read from. If the bit is set, the joypads are NOT ready to be read from.
Register $4218: Joypad #1 status register (Low Byte) (1b/R)
axlriiii a: A button
x: X button
l: L
r: R
i: Identification code
The button bits are set when the buttons are pressed. Also, the first 4 bits (i) identify the type of controller connected. 0000 is the ID for a standard snes controller. If these bits output anything else, there is something else plugged in or corrupt data. I'm not going to check these in the routine because it's kind of pointless for homebrew. You either plug in the normal controller or the program won't work.. I mean DUH. More important for commercial games to check that...
Register $4219: Joypad #1 status register (High Byte) (1b/R)
bystudlr
b: B button u: Up
y: Y button d: Down
s: Select l: Left
t: Start r: Right
Quick Note: $4218
can be read with a 16 bit A/X/Y
and both $4218
and $4219
will be read at the same time. I think you can read or write to an address like that. But remember that some registers require writing twice, one byte at a time.
Register $421A-$421F - Same as $4218-$4219 but for joypads 2-4.
Registers $4016-$4017: Old-style joypad registers (2*1b/RW)
$4016 - Player 1 Joypad
$4017 - Player 2 Joypad
You can use this register to read the buttons like the NES did. Bit 0 of $4200
needs to be clear for this to work. If it is not, then the registers can be used to return whether the joypad is connected (0=Not Connected). First writing a 0 to $4016
must be done for this to work..
So here's what's going to happen. When we enable NMI we'll also enable reading of the joypads. That will be done through $4200
. We'll also write a 0 to $4016
to have the ability of reading it to check if the pads are connected. After that it's just a matter of jumping to the input routine during VBlank.
; Input Cheat Sheet
; $4218
; $80 = A
; $40 = X
; $20 = L
; $10 = R
;
; $4219:
; $80 = B
; $40 = Y
; $20 = Select
; $10 = Start
; $08 = Up
; $04 = Down
; $02 = Left
; $01 = Right
; Let's define it to make it easier..
; $4218
.EQU Button_A $80
.EQU Button_X $40
.EQU Button_L $20
.EQU Button_R $10
; $4219
.EQU Button_B $80
.EQU Button_Y $40
.EQU Button_Select $20
.EQU Button_Start $10
.EQU Button_Up $08
.EQU Button_Down $04
.EQU Button_Left $02
.EQU Button_Right $01
; ....
; ..
; During initialization..
stz $4016 ; Write a byte of nothing to $4016. You know why I did that?
;.. almost ready to enter main loop..
lda #$81
sta $4200 ; Enable NMI and auto-joypad read
Gameloop:
WAI
;...
;.
;....
; react to input here..
jmp Gameloop
VBlank:
;...
;..
;..
;.
jsr Joypad
;.
;..
rti
; Time for the real code
Joypad:
lda $4212 ; auto-read joypad status
and #$01 ;
bne Joypad ; read is done when 0
rep #$30 ; A/X/Y - 16 bit
; Player 1
ldx Joy1Raw ; load log of last frame's RAW read of $4218
; the log will be 0 the first time read of course..
lda $4218 ; Read current frame's RAW joypad data
sta Joy1Raw ; save it for next frame.. (last frame log is still in X)
txa ; transfer last frame input from X -> A (it's still in X)
eor Joy1Raw ; Xor last frame input with current frame input
; shows the changes in input
; buttons just pressed or just released become set.
; Held or unactive buttons are 0
and Joy1Raw ; AND changes to current frame's input.
; this ends up leaving you with the only the buttons that
; are pressed.. It's MAGIC!
sta Joy1Press ; Store just pressed buttons
txa ; Transfer last frame input from X -> A again
and Joy1Raw ; Find buttons that are still pressed (held)
sta Joy1Held ; by storing only buttons that are pressed both frames
; Player 2 ; Repeat :)
ldx Joy2Raw
lda $421A ; Read Joypad2 Regs
sta Joy2Raw
txa
eor Joy2Raw ; Find just triggered buttons
and Joy2Raw
sta Joy2Press
txa
and Joy2Raw ; Find buttons that are still pressed (held)
sta Joy2Held
; Joypads standard (ie not a mouse or superscope..) and connected?
sep #$20
ldx #$0000 ; we'll clear recorded input if pad is invalid
lda $4016 ; Pad 1 - now we read this (after we stored a 0 to it earlier)
bne _check2 ; $4016 returns 0 if not connected, 1 if connected
stx Joy1Raw ; otherwise clear all recorded input.. it's not valid..
stx Joy1Press
stx Joy1Held
_check2:
lda $4017 ; Pad 2
bne _done ; 0=not connected,
stx Joy2Raw
stx Joy2Press
stx Joy2Held
_done:
RTS
On to Programming with FastROM!
Tutorial by bazz