thecodingidiot.com

Writing 6502 AssemblyThe Tone

The Tone

Audio from a 6502 without a sound chip is purely mechanical. A speaker makes a sound when a cone moves. The cone moves when a magnet is energised. The magnet is energised when current flows. Toggle the current fast enough and the clicks merge into a tone. That is all this program does — toggle PB1 at a fixed frequency and let the speaker turn the electrical oscillation into sound.

Toggling a pin for audio

A passive piezo buzzer converts rapid voltage changes into sound. At 440 Hz, the pin must complete 440 full cycles per second: 440 high halves and 440 low halves, or one toggle every 1,136 clock cycles at a 1 MHz clock.

The program produces the toggle by writing $02 to PORTB (PB1 high), waiting, writing $00 to PORTB (PB1 low), waiting again, and repeating. The wait is a software delay loop: load a count into X, decrement until zero. The count is chosen so the total wait matches the target half-period.

tone.s

PORTB = $4000
DDRB  = $4002
 
DELAY = 227       ; half-cycle delay for ~440 Hz at 1 MHz
 
  .org $8000
 
reset:
  lda #$02
  sta DDRB        ; PB1 = output (speaker)
 
loop:
  lda #$02
  sta PORTB       ; PB1 high
  ldx #DELAY
hi:
  dex
  bne hi
 
  lda #$00
  sta PORTB       ; PB1 low
  ldx #DELAY
lo:
  dex
  bne lo
 
  jmp loop
 
  .org $fffc
  .word reset
  .word reset

lda #$02 loads $02 (%00000010) into A. sta DDRB writes it to the DDRB register at $4002. Bit 1 = 1 means PB1 is an output. All other pins remain inputs. Only PB1 is connected to the speaker.

In the main loop: lda #$02 / sta PORTB drives PB1 high. The value $02 sets bit 1 and clears all others, so PB1 goes to +5V.

ldx #DELAY loads the constant 227 into X.

The hi loop: dex decrements X and sets the Z flag when X reaches zero. bne hi branches back to dex while Z is clear — while X is not yet zero. This runs 227 times before falling through.

lda #$00 / sta PORTB drives PB1 low. All Port B pins go to 0V.

ldx #DELAY reloads X with 227 for the second delay.

The lo loop is identical to hi — 227 iterations of dex / bne lo.

jmp loop starts another full cycle.

The cycle count

The delay loops produce the target frequency because each instruction consumes a fixed number of clock cycles on the 6502:

  • dex: 2 cycles
  • bne taken: 3 cycles
  • bne not taken (final iteration): 2 cycles

Each iteration except the last costs 5 cycles. The final iteration costs 4 cycles (2 for dex, 2 for bne not taken). For 227 iterations:

  • 226 iterations × 5 cycles = 1130 cycles
  • 1 final iteration × 4 cycles = 4 cycles
  • Total: 1134 cycles per half-period

The instructions before each loop (lda + sta + ldx) add 2 + 4 + 2 = 8 cycles per half. Each half is 1134 + 8 = 1142 cycles. The full cycle is 2 × 1142 + 3 (jmp) = 2287 cycles. At 1 MHz, one clock cycle is 1 µs:

2287 µs per cycle = 2.287 ms → 1000 / 2.287 ≈ 437 Hz

That is A4, concert pitch. The result is not perfect — the jmp and the loop overhead add a handful of extra cycles — but it is close enough to be recognisable and audible.

If your clock is not exactly 1 MHz, the pitch shifts proportionally. A 1.023 MHz clock (the Atari 2600 NTSC clock, for reference) would raise the pitch slightly. Adjust DELAY to compensate.

Wiring the speaker

A passive piezo buzzer works directly: connect one leg to PB1 and the other to the ground rail. Passive piezos respond to the frequency of the input signal rather than producing a fixed tone, so they reproduce the 440 Hz toggle faithfully.

A small magnetic speaker needs a transistor driver to deliver enough current. Connect the base of an NPN transistor (2N2222 or similar) to PB1 through a 1kΩ resistor. Connect the collector to one terminal of the speaker; connect the other terminal to +5V. Connect the emitter to ground. When PB1 goes high, the transistor switches on and current flows through the speaker.

Assemble, burn, verify

vasm6502_oldstyle -Fbin -dotdir tone.s -o tone.bin
minipro -p AT28C256 -w tone.bin

Power the board. The buzzer should produce a continuous tone immediately. If the pitch sounds low or high, the clock crystal may not be exactly 1 MHz — adjust DELAY up (lower pitch) or down (higher pitch) by small steps to bring it closer to A4 if precision matters. For the game loop in the next page, exact pitch is not important; audible is enough.