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 4textcharactersdisplayrom

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

Aspect Cost
CPU ~200 cycles per character
Memory ~50 bytes for routines
Limitation ROM 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):

Third Address range Character rows
Top $4000-$47FF 0-7
Middle $4800-$4FFF 8-15
Bottom $5000-$57FF 16-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