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