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. Those are different hardware conventions, not a more natural or less natural way to represent a press.
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
; Rendering stays disabled ($2001 = 0); NMI stays disabled ($2000 = 0).
; This unrestricted palette loop is not a safe transfer loop for a rendered game.
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, $00It’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. We now have a “read, decide, draw, repeat” loop. Here NMI and rendering are both disabled: $2000 and $2001 remain zero.
This loop is not synchronised to display frames and may sample the controller
many times in one frame. It is a small controller experiment, not a complete
game-loop design.
In a rendered game, we can sample input and update rules in the main loop, prepare a display change, then transfer it in a bounded VBlank handler. Some games make other scheduling choices, but input, simulation and display work need not all live in NMI. Unit 9’s VBlank boundary still matters: this sample’s continuous palette writes rely on rendering being disabled.
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 using shifts and rotates to collect them into one byte. 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
makeLoad 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.
Predict a missed press
If we press and release A entirely between two reads, will this program know? No: it samples the state; it does not receive a history of every press. A longer job between samples can make a brief press easier to miss. Reading quickly here does not establish a zero-latency response: sampling, the palette write, display scanout and the emulator’s host input each have timing costs.
The two colours are a convenient laboratory readout. In a game, an important state should also have a marker, position or text cue, so colour is not the only way to recognise it.
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_pressedNow 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.
Sources
NESdev public hardware research: Standard controller and PPU registers. The sample keeps PPUMASK rendering bits clear; its access pattern is not a rendered game’s VBlank transfer routine.
Run it here
This unit's program, built for you.
- Program
- read-pad.nes
- Machine
- Nintendo NES (NTSC)