Buttons & Console Switches#

The joystick directions come from SWCHA, but the fire buttons and the console switches live elsewhere — and they introduce the one idea that separates “input that works” from “input that feels right”: telling a held button from a just-pressed one.

Fire buttons (INPT4 / INPT5)#

A joystick’s fire button is read not from the RIOT but from two TIA registers: INPT4 for player 0, INPT5 for player 1. The state is in bit 7, and like everything else it’s active-low — pressed reads 0. Because the flag is in bit 7, BIT is the natural test:

    bit INPT4        ; P0 fire in bit 7 → N flag
    bpl FirePressed  ; N clear (bit 7 = 0)  button is down

By default these read the button’s instantaneous state. The TIA can also latch them: setting bit 6 of VBLANK makes a press stick until you clear the latch (write VBLANK with bit 6 clear). That guarantees you won’t miss a tap that happens and releases between two reads — handy when you only poll once a frame.

Console switches (SWCHB)#

The six switches on the console front are read from the RIOT port SWCHB:

BitSwitchTypePressed / position
0Resetmomentary0 = pressed
1Selectmomentary0 = pressed
3Color / B&Wtoggle1 = color, 0 = B&W
6Left difficulty (P0)toggle0 = B (novice), 1 = A (pro)
7Right difficulty (P1)toggle0 = B, 1 = A

Two kinds live here. Reset and Select are momentary push-buttons — you press them and they spring back — used to start/restart the game and to cycle through its variations. The difficulty and Color/B&W switches are slide toggles: you read their position, not a press. Games lean on them creatively — the difficulty switches often pick a game mode or handicap, and Color/B&W is sometimes repurposed as a pause.

Held vs. just-pressed: edge detection#

Polling tells you what’s down this instant. But “Select cycles to the next game variation” must fire once per press, not sixty times a second while the switch is held. The fix is edge detection: remember last frame’s reading, and act only when a bit changed from released to pressed.

    lda SWCHB
    eor Prev         ; bits that CHANGED since last frame
    and SWCHB        ; ...wait — keep only bits now 0 (newly pressed)
    ; (in practice: compare Prev vs current, act on the press transition)
    ...
    lda SWCHB
    sta Prev         ; remember for next frame

The principle matters more than any one snippet: a held state is a question you ask the port; a press event is something you detect by comparing two frames. Get this wrong and Select races through every variation in a fraction of a second.

In Practice#

  • Per-frame polling debounces for free. Mechanical switches bounce for a few milliseconds; reading once per 60 Hz frame samples well after the bounce has settled, so you rarely need explicit debounce logic — the frame is the debounce.
  • Keep a “previous” byte for anything one-shot. Fire-to-shoot, Reset-to-restart, Select-to-change-mode — each needs the edge, so each needs last frame’s value stored in RAM.
  • Difficulty switches are free game-design knobs. They cost no input slots and no buttons; many classic games use them for two difficulty levels, a one- vs. two-player toggle, or a hidden mode.