Controller reading code: Difference between revisions
Rainwarrior (talk | contribs) (→Other Useful Operations: reversing the order of the 3 examples (i think they were in reverse order of usefulness)) |
Rainwarrior (talk | contribs) (→Directional Safety: trying to improve the commentary on this, why it might be used) |
||
Line 243: | Line 243: | ||
=== Directional Safety === | === Directional Safety === | ||
Opposing directions (Up+Down and Left+Right) are possible on non-standard controllers, or even on a worn out standard one. Both directions can be tested for at the same time in an efficient bitwise operation: | |||
<pre> | <pre> | ||
lda buttons, x | lda buttons, x | ||
Line 249: | Line 249: | ||
lsr a | lsr a | ||
and buttons, x ; to Down and Right | and buttons, x ; to Down and Right | ||
beq | beq not_opposing | ||
; Use previous frame's directions | ; Use previous frame's directions | ||
lda buttons, x | lda buttons, x | ||
Line 256: | Line 256: | ||
eor last_frame_buttons, x | eor last_frame_buttons, x | ||
sta buttons, x | sta buttons, x | ||
not_opposing: | |||
</pre> | </pre> | ||
Diagonal directions can also be detected at the same time as opposing ones, by testing if more than 1 directional bit is set. This could be used as part of a 4-way joystick style control scheme: | |||
<pre> | <pre> | ||
lda buttons, x | lda buttons, x | ||
Line 276: | Line 276: | ||
not_diagonal: | not_diagonal: | ||
</pre> | </pre> | ||
As an example, the code above rejects the detected cases by using the direction bits from the previous frame, but depending on the situation another response might be more appropriate (e.g. cancel just the opposing bits, or clear all directions). | |||
== External Examples == | == External Examples == |
Revision as of 19:21, 12 January 2024
This page contains example code for reading the NES controller.
See also: Controller reading
Controller Reading Code
Basic Example
This is a tutorial example of the bare minimum needed to read the controller. It will explain the basic principles in detail, but once understood, you may wish to continue to the Standard Read example that follows, as a more complete and ready-to-use code example.
This code 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
Continue to the next example for a more complete read routine that handles both controllers and the standard Famicom expansion controllers.
Standard Read for 2 Controllers and Famicom
Adding support for controllers on the Famicom's DA15 expansion port and for player 2's controller is straightforward. Something similar to the following routine is used in most Famicom games. Even though the expansion port is unused on the NES, the unconnected bit will read as 0, so this solution works safely with both Famicom and NES hardware.
.zeropage buttons: .res 2 ; space for 2 reads .code readjoyx2: ldx #$00 jsr readjoyx ; X=0: read controller 1 inx ; fall through to readjoyx below, X=1: read controller 2 readjoyx: ; X register = 0 for controller 1, 1 for controller 2 lda #$01 sta JOYPAD1 sta buttons, x lsr a sta JOYPAD1 loop: lda JOYPAD1, x and #%00000011 ; ignore bits other than controller cmp #$01 ; Set carry if and only if nonzero rol buttons, x ; Carry -> bit 0; but 7 -> Carry bcc loop rts
If playing DPCM samples, there is an additional reread step to prevent errors (see below).
Note that the and to ignore bits is not optional, as the upper bits of JOYPAD1 read are not guaranteed to be 1 or 0[1].
Alternative 2 Controllers Read
Alternatively, we could combine both controller reads into 1 loop with a single strobe, though this routine is not safe to use with DPCM samples playing (see below).
.zeropage buttons: .res 2 .code readjoy2: lda #$01 sta JOYPAD1 sta buttons+1 ; player 2's buttons double as a ring counter lsr a sta JOYPAD1 loop: lda JOYPAD1 and #%00000011 cmp #$01 rol buttons+0 lda JOYPAD2 and #%00000011 cmp #$01 rol buttons+1 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. The most common technique, as seen in Super Mario Bros. 3[2] and other games, will read each controller twice, but in the event of a mismatch it will keep re-reading the controller until two results in a row are the same.
readjoy2_safe: ldx #$00 jsr readjoyx_safe ; X=0: safe read controller 1 inx ; fall through to readjoyx_safe, X=1: safe read controller 2 readjoyx_safe: jsr readjoyx reread: lda buttons, x pha jsr readjoyx pla cmp buttons, x bne reread rts
Note that the time between the start of one read and the end of the next read must be less than the length of the fastest DMC fetch period (432 cycles). For this reason, it is normal to read controllers one at a time with this method, rather than attempting both at once. (Note: readjoy2
above takes too long to be suitable.) Gimmick! has such a bug resulting from trying to read both at once.
Most often a controller will be read 2 times, and 3 or 4 in the case of a DPCM corruption, or the player pressing a button during the read. With the assistance of tools, a malicious controller input could change the buttons on every read, holding it in this reread loop indefinitely[3][4], but this is generally not an important edge case to account for.
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.[5] This is a relatively new technique and is not supported by some emulators.[6] In the following example code, the controller1 and controller2 labels must be in zeropage for the timing to work.
This solution might be ideal, using fewer cycles than doing repeated reads, and taking a consistent amount of time. It can also be adapted for DPCM-conflict free reading of controllers that require a longer report like four player adapters, or the SNES mouse (see notes below).
However, unlike the other solutions above, it can't be executed at any time. Instead it must be integrated into your NMI routine to coincide with your sprite OAM DMA once per frame.
lda #OAM sta $4014 ; ------ OAM DMA ------ ldx #1 ; get put <- strobe code must take an odd number of cycles total stx buttons+0 ; get put get <- buttons must be in the zeropage stx $4016 ; put get put get dex ; put get stx $4016 ; put get put get 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 buttons+1, 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 buttons+0 ; put get put get put bcc 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.[7] If an interrupt can occur during the routine, it must be aligned so the fetch can only fall on the second of the three automatic stack 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.
Other Useful Operations
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:
Calculating Presses and Releases
To calculate newly pressed and newly released buttons by comparing against the last frame's buttons:
; newly pressed buttons: not held last frame, and held now lda last_frame_buttons, x eor #%11111111 and buttons, x sta pressed_buttons, x ; newly released buttons: not held now, and held last frame lda buttons, x eor #%11111111 and last_frame_buttons, x sta released_buttons, x
Directional Safety
Opposing directions (Up+Down and Left+Right) are possible on non-standard controllers, or even on a worn out standard one. Both directions can be tested for at the same time in an efficient bitwise operation:
lda buttons, x and #%00001010 ; Compare Up and Left... lsr a and buttons, x ; to Down and Right beq not_opposing ; Use previous frame's directions lda buttons, x eor last_frame_buttons, x and #%11110000 eor last_frame_buttons, x sta buttons, x not_opposing:
Diagonal directions can also be detected at the same time as opposing ones, by testing if more than 1 directional bit is set. This could be used as part of a 4-way joystick style control scheme:
lda buttons, x and #%00001111 ; If A & (A - 1) is nonzero, A has more than one bit set beq not_diagonal sec sbc #1 and buttons, x beq not_diagonal ; Use previous frame's directions lda buttons, x eor last_frame_buttons, x and #%11110000 eor last_frame_buttons, x sta buttons, x not_diagonal:
As an example, the code above rejects the detected cases by using the direction bits from the previous frame, but depending on the situation another response might be more appropriate (e.g. cancel just the opposing bits, or clear all directions).
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
- pads.s: pinobatch's NROM-template controller read routine
References
- ↑ Controller reading: Unconnected data lines and open bus
- ↑ Super Mario Bros. 3 controller reread method
- ↑ Ars Technica: How to beat Super Mario Bros. 3 in less than a second
- ↑ https://tasvideos.org/6466S Tool-Assisted Speedrun of Super Mario Bros. 3 which first demonstrated abusing controller reread routines.
- ↑ 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.