diff options
| author | Miquel Sabaté Solà <msabate@suse.com> | 2023-10-24 17:40:19 +0200 |
|---|---|---|
| committer | Miquel Sabaté Solà <mikisabate@gmail.com> | 2025-02-04 20:58:36 +0100 |
| commit | 149020464517ff3d021eef5ccd50d3939ad463e6 (patch) | |
| tree | 056ec1919fe0c0d515e7a0a414916307b768ef84 /basics/input.s | |
| parent | be2414f5b11b182998fca7e87712ff0502c8ee93 (diff) | |
| download | code.nes-149020464517ff3d021eef5ccd50d3939ad463e6.tar.gz code.nes-149020464517ff3d021eef5ccd50d3939ad463e6.zip | |
Lay out a proper structure for the project
Let's divide things by topic instead of a broad "examples" directory.
This will allow for simple files to co-exist together with full-blown
projects.
Signed-off-by: Miquel Sabaté Solà <msabate@suse.com>
Diffstat (limited to 'basics/input.s')
| -rw-r--r-- | basics/input.s | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/basics/input.s b/basics/input.s new file mode 100644 index 0000000..dbb9ee6 --- /dev/null +++ b/basics/input.s @@ -0,0 +1,170 @@ +;;; +;; This example shows how to read from one controller and set it into the $20 +;; memory address. The `Main` subroutine will call the `ReadController` +;; subroutine and then increment the value on $42 if the right arrow was +;; pressed. When running this ROM, watch for the following RAM addresses: +;; +;; - $20: the bitmap of the current status of the controller (notice that since +;; we are constantly polling it and filling it, the value will move constantly). +;; - $21: the previous status of the right arrow. +;; - $42: the counter which is incremented on each press of the right arrow button. +;;; + +;;; +;; You can safely ignore all of this up until the `ReadController` subroutine. +;; This is boilerplate that is explained on the `sprite.s` example. +;;; + +.segment "HEADER" + .byte 'N', 'E', 'S', $1A + .byte $02 + .byte $01 + .byte $00 + .byte $00 + +.segment "VECTORS" + .addr nmi + .addr reset + .addr irq + +.segment "STARTUP" + +.segment "CODE" + +nmi: +irq: + rti + +reset: + sei + cld + ldx #$40 + stx $4017 + + ldx #$ff + txs + + inx + stx $2000 + stx $2001 + stx $4010 + +@vblankwait1: + bit $2002 + bpl @vblankwait1 + + ldx #0 + lda #0 +@ram_reset_loop: + sta $000, x + sta $100, x + sta $200, x + sta $300, x + sta $400, x + sta $500, x + sta $600, x + sta $700, x + inx + bne @ram_reset_loop + +@vblankwait2: + bit $2002 + bpl @vblankwait2 + + jmp main + +.proc ReadController + ;; The status of the eight buttons fits into a single byte. We start the whole + ;; dance by setting the first bit of the position we are storing this info + ;; ($20). This bit will act as a guard in the following code. + lda #1 + sta $20 + + ;; The 4021 chip is the one responsible to bring the input from the controller + ;; into the NES. The console reserves two addresses on the memory for the + ;; controllers: $4016 and $4017 (see + ;; https://www.nesdev.org/wiki/Input_devices). If you write into one of them + ;; first with a #1 and then with a #0, we activate the latch for the + ;; controller, and it will start to send a bit representing the state for each + ;; button upon each read. + ;; + ;; Thus, since we conveniently now have #1 into the 'a' register, we send this + ;; value to the 4021 chip, and we follow it by sending #0. This way we tell + ;; the controller to start to deliver the data. + sta $4016 + lda #0 + sta $4016 + + ;; The status of the buttons will be provided one by one following a specific + ;; order. The algorithm goes as follows: + ;; + ;; 1. Load the bit you get from the 4021 chip into `a`. After performing + ;; this read the controller will send the next one so it's ready for the + ;; next iteration. + ;; 2. Shift the value right so to set the carry flag as its comes (note: + ;; overflowing from the right also sets the carry flag on!). + ;; 3. Rotate one bit left from $20: C <- [$20] <- C. This way, we always get + ;; the result we put on the carry register at the right-most part of the + ;; byte on $20, and we clear the carry flag (the previous left-most bit + ;; moves into the carry register, which is 0 until we reach the one we + ;; planted as a guard). + ;; 4. We jump back into `read_loop` if the carry flag is clear. This is the + ;; case for most of the time until the #1 that we set at the very + ;; beginning as a guard flows into the carry flag as expected from the + ;; `rol` instruction. At this point, we have already read the full byte. +read_loop: + lda $4016 + lsr a + rol $20 + bcc read_loop + + rts +.endproc + +;; The main function will run indefinitely and it will continuously poll from +;; the controller and increment the value on $42 each time the user performs a +;; new press on the right arrow (that is, we want to count new presses on this +;; button, and we don't want to increment this value while the right arrow is +;; being pressed). +.proc main + ;; Initialize the value on $21 (previous state) and on $42 (counter). + lda #0 + sta $42 + sta $21 + +loop: + jsr ReadController + + ;; Was the right arrow being pressed? If that's the case, then jump into the + ;; `pressed` label to compare it with the previous state. + lda #1 + and $20 + bne pressed + + ;; The right arrow was not being pressed. Thus, we need to update the previous + ;; state to #0 before we read the controller again. + lda #0 + sta $21 + jmp loop + +pressed: + ;; Now the right arrow is being pressed, and we have the guarantee that `a = + ;; 1` (because of the `and $20` instruction returning a non-zero result). Now + ;; do the same with the previous state. If it's a non-zero result, then it + ;; means that the previous state was already of pressed. Hence, at this point + ;; we can return into the main loop. If this was not the case, then it's a new + ;; press. + and $21 + bne loop + + ;; It's a new press, set $21 to #1 accordinly and increment the counter on $42. + inc $21 + inc $42 + + ;; There and back again. + jmp loop + + rts +.endproc + +.segment "CHARS" |
