Controller reading code

From Nesdev wiki
Revision as of 21:05, 1 June 2016 by Rainwarrior (Talk | contribs) (OAM DMA technique: propagating emulator caveat reference)

Jump to: navigation, search

Basic Example

This describes an efficient method of reading the standard controller using ca65 syntax.

The result byte buttons should be placed in zero page to save a cycle each time through the loop.

; we reserve one byte for storing the data that is read from controller
buttons .res 1

When reading from JOYPAD* what is read might be different from $01/$00 for various reasons. (See Controller port registers.) In this code the only concern is bit 0 read from JOYPAD*..

JOYPAD1 = $4016
JOYPAD2 = $4017

This is the end result that will be stored in buttons. 1 if the button was pressed, 0 otherwise.

bit:   	 7     6     5     4     3     2     1     0
button:	 A     B  Select Start  Up   Down  Left  Right

This subroutine takes 132 cycles to execute but ignores the Famicom expansion controller. Many controller reading subroutines use the X or Y register to count 8 times through the loop. But this one uses a more clever ring counter technique: $01 is loaded into the result first, and once eight bits are shifted in, the 1 bit will be shifted out, terminating the loop.

; At the same time that we strobe bit 0, we initialize the ring counter
; so we're hitting two birds with one stone here
  lda #$01
  ; While the strobe bit is set, buttons will be continuously reloaded.
  ; This means that reading from JOYPAD1 will only return the state of the
  ; first button: button A.
  sta JOYPAD1
  sta buttons
  lsr a        ; now A is 0
  ; By storing 0 into JOYPAD1, the strobe bit is cleared and the reloading stops.
  ; This allows all 8 buttons (newly reloaded) to be read from JOYPAD1.
  sta JOYPAD1
  lda JOYPAD1
  lsr a	       ; bit0 -> Carry
  rol buttons  ; Carry -> bit0; bit 7 -> Carry
  bcc loop

Adding support for controllers on the Famicom's DA15 expansion port and for player 2's controller is straightforward.

buttons1: .res 1
buttons2: .res 1

  lda #$01
  sta JOYPAD1
  sta buttons2  ; player 2's buttons double as a ring counter
  lsr a         ; now A is 0
  sta JOYPAD1
  lda JOYPAD1
  and #$03      ; ignore bits other than controller
  cmp #$01      ; Set carry if and only if nonzero
  rol buttons1  ; Carry -> bit0; bit 7 -> Carry
  lda JOYPAD2   ; Repeat 
  and #$03
  cmp #$01
  rol buttons2  ; Carry -> bit0; bit 7 -> Carry
  bcc loop

DPCM Safety using Repeated Reads

If your code is intended to be used with APU DMC playback, this code will need to be altered. The NES occasionally glitches the controller port twice in a row if sample playback is enabled, and games using samples need to work around this. For example, Super Mario Bros. 3 reads each controller's data at least two times each frame. First it reads it as normal, then it reads it again. If the two results differ, it does the procedure all over. Because repeated rereads can take a long time, another way is to just use the previous frame's button press data if the results differ:

last_frame_buttons1 = $00
last_frame_buttons2 = $01
first_read_buttons1 = $02
first_read_buttons2 = $03

  lda buttons2
  sta last_frame_buttons2
  lda buttons1
  sta last_frame_buttons1

  ; Read the controllers once and stash the result
  jsr readjoy
  lda buttons2
  sta first_read_buttons2
  lda buttons1
  sta first_read_buttons1

  ; Read the controllers again and compare
  jsr readjoy
  ldx #1
  ; Ignore read values if a bit deletion occurred
  lda buttons1,x
  cmp first_read_buttons1,x
  beq not_glitched
    lda last_frame_buttons,x
    sta buttons1,x

  bpl cleanup_loop


Note that if the player presses or releases a button between the two reads, this will interpret that as a corrupt read. Unfortunately, there's no way for it to tell the difference.

DPCM Safety using OAM DMA

Because DPCM reads may only occur on an odd cycle, it is possible to get glitch-free controller reads by timing all $4016/$4017 reads to fall on even cycles. This is made possible by the behavior of OAM DMA: the first cycle after an OAM DMA is guaranteed to be an even cycle.[1]

This is a relatively new technique, and is not entirely supported by emulators.Cite error: Closing </ref> missing for <ref> tag
  1. Forum post: Rahsennor's OAM-synchronized controller read