diff options
| author | Miquel Sabaté Solà <msabate@suse.com> | 2023-09-22 14:05:19 +0200 |
|---|---|---|
| committer | Miquel Sabaté Solà <mikisabate@gmail.com> | 2025-02-04 20:58:02 +0100 |
| commit | bc87587330e9265b23861d0f763fdb1612e5f632 (patch) | |
| tree | 2576cea91325dc3739c9c494e20f6ceb47faefb4 /examples | |
| download | code.nes-bc87587330e9265b23861d0f763fdb1612e5f632.tar.gz code.nes-bc87587330e9265b23861d0f763fdb1612e5f632.zip | |
Initial commit
Add the basic structure and the first example: managing controller
input.
Signed-off-by: Miquel Sabaté Solà <msabate@suse.com>
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/input.s | 110 | ||||
| -rw-r--r-- | examples/wrapper.s | 90 |
2 files changed, 200 insertions, 0 deletions
diff --git a/examples/input.s b/examples/input.s new file mode 100644 index 0000000..0bc0d04 --- /dev/null +++ b/examples/input.s @@ -0,0 +1,110 @@ +;;; +;; 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. +;;; + +.export Main +.segment "CODE" + +.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 (we don't want to be on the infinite +;; loop from the wrapper.s file which includes this function). 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 + + ;; We don't want to ever return into the `wrapper.s` file: go back into our + ;; loop. + jmp loop + + rts +.endproc diff --git a/examples/wrapper.s b/examples/wrapper.s new file mode 100644 index 0000000..673c697 --- /dev/null +++ b/examples/wrapper.s @@ -0,0 +1,90 @@ +;;; +;; This is an empty wrapper for the NES. It does some basic hardware +;; initialization and finally calls a `Main` subroutine that is to be provided +;; by another file. After that, it loops indefinitely. +;; +;; There are some variations of this file already, I just took it from +;; NESHacker: https://github.com/NesHacker (definitely check his Youtube +;; channel, it's extra-dope!); originally licensed under the MIT License. +;;; + +.import Main + +.segment "HEADER" + .byte $4E, $45, $53, $1A ; iNES header identifier + .byte 2 ; 2x 16KB PRG-ROM Banks + .byte 1 ; 1x 8KB CHR-ROM + .byte $01, $00 ; mapper 0, vertical mirroring + +.segment "VECTORS" + .addr nmi + .addr reset + .addr 0 + +.segment "STARTUP" + +.segment "CHARS" + +.segment "CODE" + +.proc nmi + bit $2002 + lda #0 + sta $2006 + sta $2006 + rti +.endproc + +.proc ResetPalettes + bit $2002 + lda #$3f + sta $2006 + lda #$00 + sta $2006 + lda #$0F + ldx #$20 +@paletteLoadLoop: + sta $2007 + dex + bne @paletteLoadLoop + rts +.endproc + +.proc reset + sei + cld + ldx #%01000000 + stx $4017 + ldx #$ff + txs + ldx #0 + stx $2000 + stx $2001 + stx $4010 + bit $2002 +@vblankWait1: + bit $2002 + bpl @vblankWait1 +@clearMemory: + lda #$00 + sta $0000, x + sta $0100, x + sta $0200, x + sta $0300, x + sta $0400, x + sta $0500, x + sta $0600, x + sta $0700, x + inx + bne @clearMemory +@vblankWait2: + bit $2002 + bpl @vblankWait2 + jsr ResetPalettes +main: + jsr Main + lda #%00001000 + sta $2001 +endlessLoop: + jmp endlessLoop +.endproc |
