The LED program writes output. This one reads input. A button connected to PB7 controls the LED on PB0: press the button, the LED lights; release it, the LED goes off.
Reading an input pin
When a VIA Port B pin is configured as an input (DDRB bit = 0), the chip activates an internal pull-up resistor on that pin. The pin is held near +5V by default. If you connect a wire from that pin to ground through a pushbutton, pressing the button pulls the pin to 0V. Releasing it lets the pull-up restore it to a high level.
This is called active-low logic. The button is "active" — doing its thing — when the pin is low, not high. Reading $00 on the pin means the button is pressed; reading $01 means it is not. The program has to test for the low condition, not the high one.
button.s
PORTB = $4000
DDRB = $4002
.org $8000
reset:
lda #$01
sta DDRB ; PB0 = output, PB7 = input (internal pull-up)
loop:
lda PORTB
and #$80 ; isolate PB7
bne off ; PB7 high = button not pressed
lda #$01
sta PORTB ; button pressed (PB7 low) → LED on
jmp loop
off:
lda #$00
sta PORTB ; LED off
jmp loop
.org $fffc
.word reset
.word resetlda #$01 loads $01 (%00000001) into A. sta DDRB writes it to
the Data Direction Register at $4002. Bit 0 = 1 means PB0 is an
output. Bit 7 = 0 means PB7 is an input. Every other bit is also 0,
leaving those pins as inputs, but only PB0 and PB7 are connected to
anything in this circuit.
lda PORTB reads the full eight-bit state of Port B into A. This
includes all eight pins at once. PB0's current logic level is in bit
0, PB7's current logic level is in bit 7.
and #$80 ANDs A with $80 (binary %10000000). Every bit except bit
7 is forced to zero. The result in A is either $80 (if PB7 was high)
or $00 (if PB7 was low). This isolates the single bit that tells us
about the button, discarding everything else.
bne off tests the Z flag. If the result of the AND is non-zero —
meaning $80, meaning PB7 is high, meaning the button is not pressed —
Z is clear and the branch is taken to off. If the result is zero —
PB7 is low, button is pressed — Z is set and the branch is not taken,
so execution falls through to the next instruction.
lda #$01 / sta PORTB: the button is pressed. Load $01 and write
it to PORTB ($4000). PB0 goes high, lighting the LED. PB1–PB7 go
low. Note that writing to PORTB on an input-configured pin has no
effect on the external signal — PB7 still reads whatever the button
is doing regardless of what is written here.
jmp loop goes back to the top to read the button again.
At off: lda #$00 / sta PORTB writes all zeros to PORTB. PB0
goes low, extinguishing the LED. jmp loop returns to the top.
Wiring the button
Connect one leg of a pushbutton to the PB7 pin on the VIA. Connect the other leg to the ground rail. No external resistor is needed — the VIA's internal pull-up handles the idle-high state.
Assemble, burn, verify
vasm6502_oldstyle -Fbin -dotdir button.s -o button.bin
minipro -p AT28C256 -w button.binPower the board. The LED should be off. Press and hold the button — the LED lights. Release — the LED goes off. The loop runs fast enough that there is no perceptible lag between pressing the button and seeing the LED respond.
The read-modify-write cycle
The pattern used here — read the full port, AND with a mask, branch on the result — is the standard approach for testing any individual input pin on the 6502. You cannot read a single bit directly; the CPU always reads a full byte. The mask isolates the bit you care about, and the flags tell the branch instruction what to do next.
This same pattern scales up: to test two buttons on PB6 and PB7, you would read PORTB once, then AND with $40 to test PB6, then AND with $80 to test PB7, branching accordingly after each test. Reading the port once and masking repeatedly is more efficient than reading it again for every bit — and it means all the pin states are sampled at the same moment, which matters when inputs can change between reads.