1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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
|