From bc87587330e9265b23861d0f763fdb1612e5f632 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Fri, 22 Sep 2023 14:05:19 +0200 Subject: Initial commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the basic structure and the first example: managing controller input. Signed-off-by: Miquel Sabaté Solà --- examples/input.s | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 examples/input.s (limited to 'examples/input.s') 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 -- cgit v1.2.3