Controller reading code: Difference between revisions
(Replaces even/odd cycle terminology with get/put. Adds more relevant caveats. Links to new DMA page.) |
(Clarify that controller1 and controller2 are assumed to be in the zeropage for the OAM DPCM synced controller read example code.) |
||
Line 116: | Line 116: | ||
=== DPCM Safety using OAM DMA === | === DPCM Safety using OAM DMA === | ||
Because halts for DPCM fetches normally only occur on an put cycles, it is possible to get glitch-free controller reads by timing all '''$4016''' and '''$4017''' reads to fall on get cycles. This is made possible by the behavior of OAM DMA: the first cycle after an OAM DMA is normally guaranteed to be a get cycle.<ref>[http://forums.nesdev.org/viewtopic.php?p=171971 Forum post:] Rahsennor's OAM-synchronized controller read</ref> This is a relatively new technique and is not supported by some emulators.<ref>[http://forums.nesdev.org/viewtopic.php?f=2&t=14319&start=15#p172099 Forum post:] as of May 2016, Nintendulator and Nestopia do not accurately emulate OAM-synchronized controller reading.</ref> | Because halts for DPCM fetches normally only occur on an put cycles, it is possible to get glitch-free controller reads by timing all '''$4016''' and '''$4017''' reads to fall on get cycles. This is made possible by the behavior of OAM DMA: the first cycle after an OAM DMA is normally guaranteed to be a get cycle.<ref>[http://forums.nesdev.org/viewtopic.php?p=171971 Forum post:] Rahsennor's OAM-synchronized controller read</ref> This is a relatively new technique and is not supported by some emulators.<ref>[http://forums.nesdev.org/viewtopic.php?f=2&t=14319&start=15#p172099 Forum post:] as of May 2016, Nintendulator and Nestopia do not accurately emulate OAM-synchronized controller reading.</ref> In the following example code, the controller1 and controller2 labels must be in zeropage for the timing to work. | ||
<pre> | <pre> | ||
Line 122: | Line 122: | ||
sta $4014 ; ------ OAM DMA ------ | sta $4014 ; ------ OAM DMA ------ | ||
ldx #1 ; get put <- strobe code must take an odd number of cycles total | ldx #1 ; get put <- strobe code must take an odd number of cycles total | ||
stx controller1 ; get put get | stx controller1 ; get put get <- controller1 and controller2 must be in the zeropage | ||
stx $4016 ; put get put get | stx $4016 ; put get put get | ||
dex ; put get | dex ; put get |
Revision as of 17:31, 25 February 2023
This page contains example code for reading the NES controller.
See also: Controller reading
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 .zeropage buttons .res 1
When reading from JOYPAD* what is read might be different from $01/$00 for various reasons. (See Controller reading.) 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 readjoy: 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 loop: lda JOYPAD1 lsr a ; bit 0 -> Carry rol buttons ; Carry -> bit 0; bit 7 -> Carry bcc loop rts
Adding support for controllers on the Famicom's DA15 expansion port and for player 2's controller is straightforward.
.zeropage buttons1: .res 1 buttons2: .res 1 .code readjoy: lda #$01 sta JOYPAD1 sta buttons2 ; player 2's buttons double as a ring counter lsr a ; now A is 0 sta JOYPAD1 loop: lda JOYPAD1 and #%00000011 ; ignore bits other than controller cmp #$01 ; Set carry if and only if nonzero rol buttons1 ; Carry -> bit 0; bit 7 -> Carry lda JOYPAD2 ; Repeat and #%00000011 cmp #$01 rol buttons2 ; Carry -> bit 0; bit 7 -> Carry bcc loop rts
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.
One controller example:[1]
readjoy_safe: jsr readjoy reread: lda buttons pha jsr readjoy pla cmp buttons bne reread rts
DPCM Safety using OAM DMA
Because halts for DPCM fetches normally only occur on an put cycles, it is possible to get glitch-free controller reads by timing all $4016 and $4017 reads to fall on get cycles. This is made possible by the behavior of OAM DMA: the first cycle after an OAM DMA is normally guaranteed to be a get cycle.[2] This is a relatively new technique and is not supported by some emulators.[3] In the following example code, the controller1 and controller2 labels must be in zeropage for the timing to work.
lda #OAM sta $4014 ; ------ OAM DMA ------ ldx #1 ; get put <- strobe code must take an odd number of cycles total stx controller1 ; get put get <- controller1 and controller2 must be in the zeropage stx $4016 ; put get put get dex ; put get stx $4016 ; put get put get read_loop: lda $4017 ; put get put GET <- loop code must take an even number of cycles total and #3 ; put get cmp #1 ; put get rol controller2, x ; put get put get put get (X = 0; waste 1 cycle and 0 bytes for alignment) lda $4016 ; put get put GET and #3 ; put get cmp #1 ; put get rol controller1 ; put get put get put bcc read_loop ; get put [get] <- this branch must not be allowed to cross a page
Note that this example routine only reads two 8-bit controllers and does not take enough time to span more than one DPCM fetch. Routines longer than this must contend with two additional constraints:
- When DMC DMA is delayed by an odd number of cycles, it takes 3 cycles instead of 4, changing the cycle parity. If extending this function to read more bits, care must be taken so that all CPU write cycles are aligned. Instructions with a single write cycle must align the write to avoid conflict with the DPCM fetch, and double-write instructions like ROL need to align both writes so that the DPCM fetch falls on the first write.[4] If an interrupt can occur during the routine, it must be aligned so the fetch can only fall on the second of the three writes.
- When DMC DMA occurs near the end of OAM DMA, it only steals 1 or 3 cycles, inverting the cycle parity. Every DMC period after that, a misaligned DPCM fetch will occur. Care must be taken to ensure this does not land on a joypad read.
See DMA for detailed information on DMA timing.
Directional Safety
To reject opposing presses (Up+Down and Left+Right), which are possible on a worn Control Pad:
lda buttons1,x and #%00001010 ; Compare Up and Left... lsr a and buttons1,x ; to Down and Right beq not_updown ; Use previous frame's directions lda buttons1,x eor last_frame_buttons1,x and #%11110000 eor last_frame_buttons1,x sta buttons1,x not_updown:
To instead reject all diagonal presses, simulating a 4-way joystick:
lda buttons1,x and #%00001111 ; If A & (A - 1) is nonzero, A has more than one bit set beq not_diagonal sec sbc #1 and buttons1,x beq not_diagonal ; Use previous frame's directions lda buttons1,x eor last_frame_buttons1,x and #%11110000 eor last_frame_buttons1,x sta buttons1,x not_diagonal:
Calculating Presses and Releases
To calculate newly pressed and newly released buttons:
lda buttons1,x eor #%11111111 and last_frame_buttons1,x sta released_buttons1,x lda last_frame_buttons1,x eor #%11111111 and buttons1,x sta pressed_buttons1,x
Button Flags
It is helpful to define the buttons as a series of bit flags:
BUTTON_A = 1 << 7 BUTTON_B = 1 << 6 BUTTON_SELECT = 1 << 5 BUTTON_START = 1 << 4 BUTTON_UP = 1 << 3 BUTTON_DOWN = 1 << 2 BUTTON_LEFT = 1 << 1 BUTTON_RIGHT = 1 << 0
And then buttons can be checked as follows:
lda buttons and #BUTTON_A | BUTTON_B beq notPressingAorB ; Handle presses. notPressingAorB:
External Examples
- Forum post: Blargg's DMC-fortified controller read routine
- Forum post: Rahsennor's OAM-synchronized controller read
- Forum post: Drag's bitwise DMC-safe controller reading
References
- ↑ Super Mario Bros. 3 controller reread method
- ↑ Forum post: Rahsennor's OAM-synchronized controller read
- ↑ Forum post: as of May 2016, Nintendulator and Nestopia do not accurately emulate OAM-synchronized controller reading.
- ↑ Forum post: demonstration of how ROL instruction affects alignment for OAM DMA synchronized controller reading.