aboutsummaryrefslogtreecommitdiff
path: root/examples/input.s
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <msabate@suse.com>2023-09-22 14:05:19 +0200
committerMiquel Sabaté Solà <mikisabate@gmail.com>2025-02-04 20:58:02 +0100
commitbc87587330e9265b23861d0f763fdb1612e5f632 (patch)
tree2576cea91325dc3739c9c494e20f6ceb47faefb4 /examples/input.s
downloadcode.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/input.s')
-rw-r--r--examples/input.s110
1 files changed, 110 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