Skip to content
Techniques & Technology

ZX Spectrum hardware ports

Talking to the ULA, the AY chip, and the joystick — one port at a time

The Spectrum's I/O port map: port $FE for keyboard, border, beeper, and tape on every model; port $1F for the Kempston joystick; ports $7FFD and $1FFD for 128K and +2A/+3 memory paging; ports $FFFD and $BFFD for the AY-3-8912 sound chip on 128K+ models. The technical reference Shadowkeep Unit 4 and Unit 7 read from.

sinclair-zx-spectrum hardwareportsi/oulakeyboardreference

Overview

The Z80 has a separate I/O address space — 256 ports addressed by the low 8 bits of the address bus on the simplest instructions, or by the full 16 bits when using IN A,(C) / OUT (C),A. On the Spectrum, almost every I/O operation is one of a small set of well-defined port addresses, decoded by the ULA or by add-on hardware. This entry catalogues the ports that matter for game development on the platform.

The most important port, by a long margin, is $FE — the ULA's single multi-purpose port. It carries the keyboard, the border colour, the beeper, and the tape interface, all on one address. Almost every Spectrum game touches $FE on every frame.


Port $FE - The Multi-Purpose Port

Port $FE is the most important port on the Spectrum. It controls border colour, reads the keyboard, controls the speaker, and handles tape I/O.

Port $FE Output (Writing)

Usage: OUT ($FE),A

Bit Layout:

Bit76543210
PurposeSpeakerTapeBorderBorderBorder
ValuexxxMICEARBorderBorderBorder

Bit Functions:

  • Bits 0-2: Border colour (0-7)
  • Bit 3: Tape output (EAR socket)
  • Bit 4: Speaker control (0 = off, 1 = on)
  • Bits 5-7: Not used (but should be preserved)

Example - Change Border:

LD A,2           ; Red border
OUT ($FE),A

Example - Make Sound:

; Toggle speaker bit for simple beep
LD A,$10         ; Speaker on, black border
OUT ($FE),A
; Delay
LD A,$00         ; Speaker off
OUT ($FE),A

Example - Preserve Upper Bits:

; Read current value, modify border only
IN A,($FE)       ; Read current port state
AND $F8          ; Clear border bits (0-2)
OR 5             ; Set to cyan (5)
OUT ($FE),A      ; Write back

Port $FE Input (Reading)

Usage: IN A,($FE)

Bit Layout:

Bit76543210
PurposeEARKeyKeyKeyKeyKey
Activereads 1tape inputreads 1row bit 4row bit 3row bit 2row bit 1row bit 0

Bit Functions:

  • Bits 0-4: Keyboard row data (active low - 0 = pressed)
  • Bit 5: Not used
  • Bit 6: Tape input (EAR socket)
  • Bit 7: Not used

Important: Keyboard reading requires setting the upper byte of BC to select keyboard half-row.


Keyboard Reading

The Spectrum keyboard is arranged in a matrix of 8 half-rows × 5 keys each.

Keyboard Matrix

To read keyboard:

  1. Set BC register: High byte selects half-row, low byte = $FE
  2. Read with IN A,(C)
  3. Test bits 0-4 (active low: 0 = pressed, 1 = not pressed)

Keyboard Half-Row Map

BC HighHalf-rowBit 4Bit 3Bit 2Bit 1Bit 0
$FERow 0VCXZSHIFT
$FDRow 1GFDSA
$FBRow 2TREWQ
$F7Row 354321
$EFRow 467890
$DFRow 5YUIOP
$BFRow 6HJKLENTER
$7FRow 7BNMSYMSPACE

Reading Single Key

Example - Read Q key:

        LD BC,$FBFE       ; Half-row $FB (Q-T row)
        IN A,(C)          ; Read keyboard
        BIT 0,A           ; Test bit 0 (Q key)
        JR Z,QPressed     ; Jump if Q pressed (bit = 0)

Example - Read Arrow Keys (using QAOP):

; Read Q (up) key
        LD BC,$FBFE       ; Q-T row
        IN A,(C)
        BIT 0,A
        JR Z,MoveUp

; Read A (down) key
        LD BC,$FDFE       ; A-G row
        IN A,(C)
        BIT 0,A
        JR Z,MoveDown

; Read O (right) key
        LD BC,$DFFE       ; P-Y row
        IN A,(C)
        BIT 1,A
        JR Z,MoveRight

; Read P (left) key
        LD BC,$DFFE       ; Same row
        IN A,(C)
        BIT 0,A
        JR Z,MoveLeft

Reading Multiple Keys Simultaneously

The Spectrum supports multiple simultaneous key presses (within matrix limitations).

Example - Detect multiple keys:

; Read Q-T row
        LD BC,$FBFE
        IN A,(C)
        LD D,A            ; Save for later

        BIT 0,D           ; Q pressed?
        CALL Z,HandleQ

        BIT 1,D           ; W pressed?
        CALL Z,HandleW

Scan All Keys

Example - Detect any key press:

ScanKeyboard:
        LD BC,$FEFE       ; Start with row 0
        LD D,8            ; 8 rows to scan
ScanLoop:
        IN A,(C)          ; Read row
        AND $1F           ; Mask to key bits
        CP $1F            ; All bits set = no keys
        JR NZ,KeyFound    ; Jump if any key pressed

        RLC B             ; Next row (rotate high byte)
        DEC D
        JR NZ,ScanLoop

        ; No keys found
        RET

KeyFound:
        ; Key detected - A contains key bits
        RET

Ghost Keys and Limitations

Matrix ghosting: Pressing certain 3-key combinations can register phantom 4th key.

Example problem:

  • Press: Q + A + O
  • Ghost: P may appear pressed

Solution: Use diagonally opposite keys in your control schemes (e.g., QAOP works well).


Speaker/Beeper ($FE Bit 4)

The Spectrum has a simple 1-bit speaker controlled by port $FE bit 4.

Simple Beep

Beep:
        LD B,200          ; Duration counter
BeepLoop:
        LD A,$10          ; Speaker on
        OUT ($FE),A

        LD E,50           ; Frequency delay
Delay1: DEC E
        JR NZ,Delay1

        LD A,$00          ; Speaker off
        OUT ($FE),A

        LD E,50
Delay2: DEC E
        JR NZ,Delay2

        DJNZ BeepLoop
        RET

Variable Pitch Beep

; HL = pitch (higher = higher pitch)
; B = duration
ToneBeep:
ToneLoop:
        LD A,$10
        OUT ($FE),A
        PUSH HL
ToneDelay1:
        DEC HL
        LD A,H
        OR L
        JR NZ,ToneDelay1
        POP HL

        LD A,$00
        OUT ($FE),A
        PUSH HL
ToneDelay2:
        DEC HL
        LD A,H
        OR L
        JR NZ,ToneDelay2
        POP HL

        DJNZ ToneLoop
        RET

Preserving Border Colour with Speaker

Problem: Writing to port $FE affects border and speaker.

Solution: Read-modify-write doesn't workIN A,($FE) returns the keyboard rows + EAR, not the last-written border colour. The Spectrum has no readback path for OUT-set ULA state. You must keep a software variable holding the current border value:

; Globally maintained border colour (bits 0-2, MIC bit 3 if you use tape)
current_border:
        DEFB    1               ; Blue border, MIC low

play_click:
        LD HL,current_border
        LD A,(HL)
        OR $10                  ; Speaker on, border preserved
        OUT ($FE),A
        ; ... delay ...
        LD A,(HL)               ; Speaker off, border preserved
        OUT ($FE),A
        RET

To change the border colour, write to current_border; the next speaker toggle picks it up.


ULA Port ($FE) - Complete Specification

Output Bit Summary

BitFunctionEffect
0Border LSBBorder colour bit 0
1BorderBorder colour bit 1
2Border MSBBorder colour bit 2
3MICTape output (cassette save)
4Speaker1-bit speaker output
5-7UnusedNo effect (recommended: write 0)

Border colours (bits 0-2):

%000 = 0 = Black      %100 = 4 = Green
%001 = 1 = Blue       %101 = 5 = Cyan
%010 = 2 = Red        %110 = 6 = Yellow
%011 = 3 = Magenta    %111 = 7 = White

Input Bit Summary

BitFunctionEffect
0-4Keyboard5 keys per half-row (active low)
5UnusedAlways reads 1
6EARTape input signal
7UnusedAlways reads 1

Other Spectrum Ports

Port $7FFD - 128K Memory Paging (128K models only)

Not available on 48K Spectrum. Used for bank switching on 128K, +2, +2A, +3 models.

Bit layout (write):

BitFunction
0-2RAM bank at $C000 (selects one of 8 banks)
3Screen RAM bank (0 = normal screen at $C000-$DFFF in bank 5; 1 = shadow screen in bank 7)
4ROM select (0 = 128K editor ROM, 1 = 48K BASIC ROM)
5Paging disable — once set, can only be cleared by reset. Preserves the current configuration; some games use this to lock memory after setup.
6-7Reserved

Bank 5 is always at $4000 (regular screen). Bank 2 is always at $8000. Bank 0-7 is selected by bits 0-2 at $C000.

Port $1FFD - +2A/+3 Special Paging

The +2A/+3 (Amstrad redesign) adds a second paging port. Only relevant for those models — see the +3 technical reference for bit layout.

Ports $FFFD / $BFFD - AY-3-8912 Sound Chip (128K+ models only)

3-channel programmable sound generator on 128K/+2/+3 models (not on 48K). Two ports work together:

  • $FFFD — register select. Write the AY register number (0-15) here first.
  • $BFFD — register data. Write or read the value of the previously selected register.
; Write value $0F to AY register 7 (mixer)
        LD BC,$FFFD
        LD A,7              ; Select register 7
        OUT (C),A
        LD BC,$BFFD
        LD A,$0F            ; New value
        OUT (C),A

See the AY-3-8910 entry for the register set and channel layout.

+3 Disk Drive Ports (+3 only)

The Spectrum +3 has a built-in 3-inch floppy drive controlled by an μPD765 disk controller, accessed through:

  • $2FFD — Disk controller status (read) / command (write)
  • $3FFD — Disk controller data (read/write)
  • $1FFD bits 3-4 — Drive motor control + ROM/RAM switch combinations

Most +3 software uses the +3DOS BIOS rather than poking these ports directly. See the +3 technical manual for the full interface.

Kempston Joystick - Port $1F

Not a standard Spectrum feature - third-party add-on.

Read: IN A,($1F)

BitFunction
0Right
1Left
2Down
3Up
4Fire

Why hardware ports matter for Code Like It's 198x

Shadowkeep Unit 4 reads $FBFE (Q-row), $FDFE (A-row), and $DFFE (P-row) to implement QAOP input — three of the eight half-row reads listed above. Unit 7 will write to $FE bit 4 in a tight loop to generate beeper music. The 128K AY ports become relevant in Phase 2+ when the Spectrum 128 is brought online. The single most-used line of platform-specific code in the entire Project is OUT ($FE), A — every other Spectrum hardware concern flows downstream of this one port.

See also