The Basics of Beeper Sound
Many simple Z80 systems (like the ZX Spectrum) lack a dedicated sound chip and rely on a simple speaker connected to an I/O port. This speaker is known as the beeper.
How Tones are Made: A tone is generated by rapidly toggling a single bit in the output port (switching the speaker ON and OFF) at a specific frequency.
Frequency & Pitch: The speed of the toggling loop determines the tone’s frequency (pitch).
- A faster loop = a higher pitch.
- A slower loop = a lower pitch.
The I/O Port and Toggling
We use the OUT
instruction to write to the beeper port. We only care about the single bit that controls the speaker.
Generic Beeper Port Example:
Assume the beeper is controlled by Bit 4 of I/O port FEH
.
BEEPER_PORT EQU 0FEH
SPEAKER_ON EQU 10H ; 00010000b (Bit 4 set)
SPEAKER_OFF EQU 00H ; 00000000b (Bit 4 clear)
TONE_LOOP:
; 1. Turn Speaker ON
LD A, SPEAKER_ON
OUT (BEEPER_PORT), A
; 2. Delay for half a cycle (sets the frequency)
CALL DELAY_ROUTINE
; 3. Turn Speaker OFF
LD A, SPEAKER_OFF
OUT (BEEPER_PORT), A
; 4. Delay for the other half cycle
CALL DELAY_ROUTINE
; 5. Loop this entire block repeatedly
JP TONE_LOOP
Creating the DELAY_ROUTINE
The DELAY_ROUTINE
is critical. Its total T-state count determines the pitch of the tone. You must calculate the T-states of the instructions in the routine to achieve a precise musical frequency (e.g., 440 Hz for A4).
Delay Routine Structure:
DELAY_ROUTINE:
LD BC, 500 ; Load a counter value (must be calculated for T-states)
DELAY_LOOP:
DEC BC ; Decrements the 16-bit BC pair (6 cycles)
LD A, B ; Check if BC has reached zero
OR C ; (A OR C) sets Z if both B and C are zero
JP NZ, DELAY_LOOP ; Loop if NOT Zero (BC is not zero)
RET
Creating Melody: To play a melody, you simply change the value loaded into `BC′ between notes and use an outer loop to control the note’s duration.