Skip to content

Character Printing

Print ASCII characters to the screen using the ROM character set. Direct display file access for fast text rendering.

Taught in Game 1, Unit 4 textcharactersdisplayrom

Overview

Print text directly to the display file by copying character data from the ROM character set at $3C00. Faster than using ROM routines and gives you full control over positioning. Essential for scores, messages, and UI text.

Code

; =============================================================================
; CHARACTER PRINTING - ZX SPECTRUM
; Print characters using ROM character set
; Taught: Game 1 (Ink War), Unit 4
; CPU: ~200 cycles per character | Memory: ~50 bytes
; =============================================================================

DISPLAY_FILE equ    $4000       ; Screen memory
CHAR_SET    equ     $3C00       ; ROM character set (chars 0-127)

; Print single character
; Input: A = ASCII character (32-127)
;        B = row (0-23), C = column (0-31)
print_char:
    push    bc
    push    de
    push    hl
    push    af

    ; Calculate character data address: CHAR_SET + char * 8
    ld      l, a
    ld      h, 0
    add     hl, hl              ; * 2
    add     hl, hl              ; * 4
    add     hl, hl              ; * 8
    ld      de, CHAR_SET
    add     hl, de              ; HL = character data source

    push    hl                  ; Save source address

    ; Calculate display file address
    ; The Spectrum's display is organised in thirds with interleaved lines
    ld      a, b                ; A = row (0-23)
    and     %00011000           ; Which third (0, 8, or 16)?
    add     a, $40              ; Add display file base ($4000 high byte)
    ld      d, a                ; D = high byte

    ld      a, b                ; A = row
    and     %00000111           ; Line within character row (0-7)
    rrca
    rrca
    rrca                        ; Move to bits 5-7
    add     a, c                ; Add column
    ld      e, a                ; E = low byte, DE = screen address

    pop     hl                  ; HL = character data

    ; Copy 8 bytes (8 pixel rows)
    ld      b, 8
.loop:
    ld      a, (hl)             ; Get character row
    ld      (de), a             ; Write to screen
    inc     hl                  ; Next character row
    inc     d                   ; Next screen line (add 256)
    djnz    .loop

    pop     af
    pop     hl
    pop     de
    pop     bc
    ret

Print a string:

; Print null-terminated string
; Input: HL = string address, B = row, C = column
print_string:
    push    bc
    push    hl

.loop:
    ld      a, (hl)
    or      a                   ; Check for null terminator
    jr      z, .done

    call    print_char
    inc     hl
    inc     c                   ; Next column
    jr      .loop

.done:
    pop     hl
    pop     bc
    ret

; Example usage:
;   ld      hl, message
;   ld      b, 10               ; Row 10
;   ld      c, 5                ; Column 5
;   call    print_string
;
; message:
;   defm    "HELLO WORLD"
;   defb    0

Trade-offs

AspectCost
CPU~200 cycles per character
Memory~50 bytes for routines
LimitationROM charset only (use custom font for alternatives)

When to use: Scores, labels, messages, any text display.

When to avoid: When you need custom graphics characters (use a custom charset instead).

Display File Address Calculation

The Spectrum's display memory is non-linear. For character row R and column C:

High byte: $40 + (R AND $18)
Low byte:  ((R AND 7) * 32) + C
         = ((R AND 7) RRCA RRCA RRCA) + C

This interleaved layout was designed for TV signal generation, not programmer convenience!

The thirds layout

The display memory at $4000-$57FF is split into three 2 KB regions corresponding to the top, middle, and bottom thirds of the screen (64 pixel lines / 8 character rows each):

ThirdAddress rangeCharacter rows
Top$4000-$47FF0-7
Middle$4800-$4FFF8-15
Bottom$5000-$57FF16-23

Within a third, adding 256 to the address moves down one pixel (the next scan line of the same character row). After 8 pixel rows, you've drawn one full character row; the next character row's address is + $0020 (or + 32) into the third's region.

This means horizontal scrolling within a third is fast (write to 8 contiguous addresses with +$0100 strides), but scrolling across third boundaries needs an explicit fix-up. Many games scroll only within one third (e.g. status bar in the bottom 8 rows, gameplay in middle, sky in top) to dodge this.

ROM Character Set

The character set at $3C00 contains:

  • Characters 0-31: Control characters (don't use)
  • Characters 32-127: Printable ASCII
  • Each character is 8 bytes (8x8 pixels)

Patterns: Attribute Writing, Numeric Display

Vault: ULA — display memory layout | ZX Spectrum