The Joypad in Your Hand
Read the NES controller the honest way — strobe it, shift the buttons out one at a time, isolate the one you care about, and branch on it. Hold A and watch the screen react, every pass of the loop.
A program that decides things (Unit 8) gets interesting when the thing it decides on is you. So: how does the NES know a button is pressed?
There's no INKEY$ waiting around. You read the controller yourself, from a hardware address — but the NES does it in a way the C64's joystick never did. The eight buttons don't each get their own bit sitting ready to read. They come out one at a time, through a single door at $4016, and you have to ask for them in order. It's a tiny conveyor belt:
- Strobe. Write a
1then a0to$4016. That snapshot-latches the current state of all eight buttons and rewinds the belt to the start. - Read, eight times. Each read of
$4016hands you one button in bit 0, in a fixed order: A, B, Select, Start, Up, Down, Left, Right. The first read is A; read again for B; and so on.
To test the button you've got, you mask it: and #%00000001 keeps bit 0 and zeroes the rest. Then you branch on whether the result is zero. And here's the flip from the C64: on the NES a pressed button reads as 1, not 0. (The C64's joystick read backwards — pushed was 0. The NES reads the natural way.)
What you'll see by the end
A green screen — while A is held. Let go and it springs back to red. The program reads the pad every pass of the loop and repaints the backdrop to match, so the colour is a live readout of whether you're pressing. (The screenshot catches it mid-press; at rest it's red.)
Reading the pad, round and round
; ============================================================================
; Meet the Machine (NES) - Unit 10: The Joypad in Your Hand
;
; You read the controller yourself: strobe it to latch the buttons, then read
; them out one at a time from $4016. Here we read button A and paint the screen
; green while it is held, red while it is not - re-reading every pass.
; ============================================================================
.segment "HEADER"
.byte "NES", $1a
.byte 2
.byte 1
.byte $00, $00
.segment "CODE"
reset:
sei
cld
ldx #$40
stx $4017
ldx #$ff
txs
inx
stx $2000
stx $2001
stx $4010
warm1:
bit $2002
bpl warm1
warm2:
bit $2002
bpl warm2
read:
; --- strobe the controller: a 1 then a 0 latches the current buttons ---
lda #$01
sta $4016
lda #$00
sta $4016
; --- the first read after a strobe is button A, in bit 0 ---
lda $4016
and #$01 ; keep only bit 0
beq not_pressed ; 0 = A is up (on the NES, pressed reads as 1)
lda #$2a ; A held: green
jmp show
not_pressed:
lda #$16 ; A up: red
show:
; --- paint the backdrop with the colour now in A ---
sta $00
bit $2002
lda #$3f
sta $2006
lda #$00
sta $2006
lda $00
sta $2007
bit $2002 ; re-aim at $3F00 so the backdrop displays it
lda #$3f
sta $2006
lda #$00
sta $2006
jmp read ; read the pad again, forever
nmi:
rti
irq:
rti
.segment "VECTORS"
.word nmi
.word reset
.word irq
.segment "CHARS"
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00
.byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
.byte $00,$00,$00,$00,$00,$00,$00,$00
.res 8192 - 32, $00
It's the test-then-branch from Unit 8, now fed by the controller instead of a fixed value. The loop you've had since Unit 1 finally earns its keep: each time round, it strobes, reads A afresh, and paints accordingly. That "read, decide, draw, repeat" rhythm is the heartbeat of every game's input — and from Unit 9 you know where the real version of that loop will live: the NMI.
Why strobe every time? The strobe is what takes the snapshot. Without it you'd read stale buttons, or read them out of order. Strobe, then read A: that pairing is the habit to keep.
One button now; the rest later
This reads exactly one button — A, the first off the belt. Reading several at once (A, B, and a direction, all in a pass) means reading $4016 eight times and stashing each bit, usually shifting them into one byte with lsr. That's a job of its own, and it belongs to the first game, where moving a character needs the D-pad. For now, the machine can hear one press, and that's the whole idea.
Assemble and run
ca65 read-pad.asm -o read-pad.o && ld65 -C nes.cfg read-pad.o -o read-pad.nes
Load it, and hold A: the screen turns green, and releases back to red when you let go. The machine is answering you in real time.
Try this: a different button
B is the second button off the belt. Read $4016 a second time before you mask, and you're testing B instead of A:
lda $4016 ; first read - button A (discard it)
lda $4016 ; second read - button B
and #$01
beq not_pressed
Now the screen answers to B. Read it a third and fourth time and you reach Select, then Start — the belt's fixed order.
Try this: invert it
Swap the two colours — lda #$16 (red) on the held path and lda #$2a (green) at rest — so the screen is green until you push, then goes red while held. Same reading, opposite reaction. Predict it, then confirm.
If it doesn't work
- The screen never changes. Check the strobe is there —
1then0to$4016— before the read. Without it the belt never latches. - It's backwards — green at rest, red when pushed. You've treated
beqas "pressed". On the NES pressed is 1, so a non-zero mask result means held:beqis the not-pressed road. - It reacts once then freezes. A
jmp readis missing, so the pad is never re-read. The loop must run back to the strobe every pass.
What you've learnt
You read the NES controller by strobing $4016 (a 1 then a 0), then reading it once per button in the fixed order A, B, Select, Start, Up, Down, Left, Right — each arriving in bit 0. Mask the bit, branch on it, and remember: on the NES a pressed button reads as 1.
What's next
You've stepped back to a one-cell screen for these CPU lessons. Time to fill it again — but cleverly. Next — A Finger on the Boxes — we stop baking a fixed address into every instruction and meet indexing: a register used as a finger that slides along a table, reading or writing as it goes.