|
Tag: Redirect target changed |
(One intermediate revision by one other user not shown) |
Line 1: |
Line 1: |
| This describes an efficient method of reading the [[standard controller]].
| | #REDIRECT [[Controller reading]] |
| It is designed for [[ca65]], using a few [https://cc65.github.io/doc/ca65.html#ss6.6 unnamed labels] for short loops.
| |
| | |
| The result byte ''buttons'' should be placed in zero page to save a cycle each time through the loop.
| |
| <pre>
| |
| ; we reserve one byte for storing the data that is read from controller
| |
| .zeropage
| |
| buttons .res 1
| |
| </pre>
| |
| | |
| 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*.''.
| |
| <pre>
| |
| JOYPAD1 = $4016
| |
| JOYPAD2 = $4017
| |
| </pre>
| |
| | |
| This is the end result that will be stored in ''buttons''. '''1''' if the button was pressed, '''0''' otherwise.
| |
| <pre>
| |
| bit: 7 6 5 4 3 2 1 0
| |
| button: A B Select Start Up Down Left Right
| |
| </pre>
| |
| | |
| 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 [[wikipedia:Ring counter|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.
| |
| <pre>
| |
| ; 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
| |
| sta JOYPAD1
| |
| sta buttons
| |
| lsr a ; now A is 0
| |
| sta JOYPAD1
| |
| :
| |
| lda JOYPAD1
| |
| lsr a ; bit0 -> Carry
| |
| rol buttons ; Carry -> bit0; bit 7 -> Carry
| |
| bcc :-
| |
| rts
| |
| </pre>
| |
| | |
| Adding support for controllers on the Famicom's DA15 expansion port and for player 2's controller is straightforward.
| |
| <pre>
| |
| .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
| |
| :
| |
| 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 :-
| |
| rts
| |
| </pre>
| |
| | |
| 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:
| |
| | |
| <pre>
| |
| last_frame_buttons1 = $00
| |
| last_frame_buttons2 = $01
| |
| first_read_buttons1 = $02
| |
| first_read_buttons2 = $03
| |
| | |
| readjoy_safe:
| |
| 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
| |
| cleanup_loop:
| |
| ; 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
| |
| not_glitched:
| |
| | |
| ; Other filters, as described below, can go here
| |
| | |
| dex
| |
| bpl cleanup_loop
| |
| | |
| rts
| |
| </pre>
| |
| | |
| To reject opposing presses (Up+Down and Left+Right), which are possible on a worn Control Pad:
| |
| <pre>
| |
| lda buttons1,x
| |
| and #$0A ; 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 #$F0
| |
| eor last_frame_buttons1,x
| |
| sta buttons1,x
| |
| not_updown:
| |
| </pre>
| |
| | |
| To instead reject all diagonal presses, simulating a 4-way joystick:
| |
| <pre>
| |
| lda buttons1,x
| |
| and #$0F ; If A & (A - 1) is nonzero, A has more than one bit set
| |
| beq not_diagonal
| |
| sec
| |
| sbc #1
| |
| and buttons1,x ; to Down and Right
| |
| beq not_updown1
| |
| ; Use previous frame's directions
| |
| lda buttons1,x
| |
| eor last_frame_buttons1,x
| |
| and #$F0
| |
| eor last_frame_buttons1,x
| |
| sta buttons1,x
| |
| not_diagonal:
| |
| </pre>
| |
| | |
| To calculate newly pressed and newly released buttons:
| |
| <pre>
| |
| lda buttons1,x
| |
| eor #$FF
| |
| and last_frame_buttons1,x
| |
| sta released_buttons1,x
| |
| lda last_frame_buttons1,x
| |
| eor #$FF
| |
| and buttons1,x
| |
| sta pressed_buttons1,x
| |
| </pre>
| |