Skip to content

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.

  • Nintendo Entertainment System
  • Assembly

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:

  1. Strobe. Write a 1 then a 0 to $4016. That snapshot-latches the current state of all eight buttons and rewinds the belt to the start.
  2. Read, eight times. Each read of $4016 hands 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

The NES screen filled with solid green while button A is held.
Green while A is held, springing back to red on release — a live readout of the controller, caught mid-press.

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

read-pad.asm
; ============================================================================
; 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, $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. 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

Code
make

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.

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:

Code
    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 — 1 then 0 to $4016 — before the read. Without it the belt never latches.
  • It’s backwards — green at rest, red when pushed. You’ve treated beq as “pressed”. On the NES pressed is 1, so a non-zero mask result means held: beq is the not-pressed road.
  • It reacts once then freezes. A jmp read is 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)

Download read-pad.nes