This program implements a 64-column text display mode for the TS2068, using machine code to reconfigure the SCLD and custom character rendering. It embeds a Z80 machine code routine as a hex string in line 65, POKEing it into RAM at address 63206, then executes it via RANDOMIZE USR to set up the hardware for the wider display. The program manages a scrolling text buffer in the display file area (16384–24575), cycling through screen thirds in 8192-byte steps, and supports two character sets (normal and inverse, toggled with key 6) by switching between ROM font data at 15616 and custom UDG data at 63272. Key 5 sets inverse video (i=255), key 4 sets normal (i=0), and key 12 triggers a backspace-like operation; all other printable ASCII characters (32–122) are rendered into the display using the GO SUB at line 20, which copies eight bytes of font data using a stride of 256 bytes to write one column of the 64-column grid.
Program Structure
The program is divided into three broad phases:
- Initialisation (lines 10–95): Checks whether the machine code is already installed (line 40, testing address 23746 for 128), clears RAM above 63205, reads UDG slot assignments, decodes and POKEs the hex machine code string, runs it, then patches eight bytes of UDG data.
- Main editor loop (lines 100–245): Sets up the
ON ERRhandler, sendsOUT 255,14to select the 64-column mode, then processes keystrokes and manages the display file pointerdf. - Error / save epilogue (lines 9989–9998): On error, restores normal display via
OUT 255,128, re-enters the machine code, resets the error trap, and optionally saves the program.
Machine Code Loader
Lines 65–85 implement a classic hex-string loader. The string in u$ encodes Z80 opcodes as ASCII hex pairs. The loop at line 70 steps through the string two characters at a time, computing each byte as 16*VAL u$(n) + VAL u$(n+1) and POKEing it to successive addresses from 63206. RANDOMIZE USR 63218 then calls into the middle of the routine, suggesting the first few bytes at 63206–63217 are a separate sub-routine (called later in the error handler at line 9991) and the entry point at 63218 is the ULA reconfiguration code.
The hex string decodes to approximately 48 bytes of Z80 code. Notable opcodes visible in the stream include DI (F3), EI (FB), OUT (n),A (D3), IN A,(n) (DB), and RET (C9), consistent with ULA port manipulation (port 0xF4 is the TS2068 ULA control port).
Display File Management
The TS2068 Spectrum-compatible display occupies 6144 bytes from 16384 to 22527, but this program extends its notion of the display across three 8192-byte “pages” between 16384 and 24575. The variable df tracks the current character cell’s display-file byte address. Wrapping logic appears in two places:
- Line 200 (forward scroll):
df = df + 8192*(df<22528) - 8191*(df>24575)— advances one page, wrapping back to 16384 when past 24575. - Line 310 (backward scroll):
df = df - 8192*(df>24575) + 8191*(df<22528)— the inverse operation.
Column position is tracked in c (1–64) and row in b (1–24). When c reaches 0 the program decrements b and resets c to 64, implementing a simple 64×24 grid.
Character Rendering Subroutine
The subroutine at lines 20–35 renders one character into the display. Line 20 computes s = 15616 + 8*(k-32), the address of the character’s font data in the ROM (the Spectrum font starts at 15616, i.e. 0x3C00). Lines 25–35 then loop over eight pixel rows, each separated by 256 bytes in the display file (one scan line in 64-column mode uses 256-byte row strides rather than the normal 32). The expression ABS(i - PEEK s) implements colour inversion: when i=0 the byte is used as-is; when i=255 all bits are flipped via 255 - PEEK s.
Character Set Switching
Two sources of font data are used:
| Variable i | Font source address | Description |
|---|---|---|
| 0 | 15616 | ROM character set (normal) |
| 255 | 63272 | Custom UDG area (patched at lines 90–95) |
Line 120 selects the UDG set when PEEK 23658 = 8 (a flag byte toggled by key 6 at line 145). The patch loop at lines 90–95 sets bytes at 63256–63263 to 248 and computes rows at +8 and +16 from existing data, creating a derived alternate character shape.
Key Handling
| Key code (k) | Action |
|---|---|
| 4 | Normal video (i=0) |
| 5 | Inverse video (i=255) |
| 6 | Toggle alternate character set (flips byte at 23658 between 0 and 8) |
| 12 | Backspace / cursor left (GO TO 250, which calls line 255 to reverse-render and decrements position) |
| <32 or >122 | Ignored |
| 32–122 | Render character via GO SUB 15 then advance cursor |
Notable Techniques and Idioms
VAL u$(n)on a single hex digit character correctly returns 0–9, but will return 0 for hex digits A–F. The hex string in line 65 uses only lowercase letters; howeverVAL "a"returns 0 in Spectrum BASIC, so bytes containing nibbles a–f would be mis-decoded. Inspection shows the string uses only digits 0–9 and letters — this is a potential bug for any byte nibble above 9.- The
ON ERR GO TO 9990/ON ERR RESETidiom provides a clean shutdown path that restores normal display mode before stopping. - Line 40’s guard (
PEEK 23746 = 128) allows the program to be re-run without re-installing the machine code, saving time and avoiding corruption of already-patched memory. GO SUB 15at line 190 is a shorthand jump to line 20 via the nearest line; the actual subroutine starts at line 20 but BASIC finds the next line at or after 15, which is 20.- The boolean arithmetic for display wrapping (
8192*(df<22528)etc.) exploits the fact that Spectrum BASIC evaluates boolean expressions as 1 (true) or 0 (false).
Content
Source Code
10 GO TO 40
20 LET s=15616+8*(k-32)
25 FOR d=df TO df+1792 STEP 256
30 POKE d,ABS (i-PEEK s): LET s=s+1
35 NEXT d: RETURN
40 IF PEEK 23746=128 THEN GO TO 100
50 CLEAR 63205: LET u=63206
55 DATA 10,11,12,13,14,15
60 READ a,b,c,d,e,f
65 LET u$="*2100603600237cfe7820f8c9f33e01d3f4dbffcbffd3ff3e80f5fbcd8e0ef3dbffcbbfd3ffafd3f4f1fe80200332c25cfbc9"
70 FOR n=2 TO LEN u$-1 STEP 2
75 POKE u,16*VAL u$(n)+VAL u$(n+1)
80 LET u=u+1: NEXT n
85 RANDOMIZE USR 63218
90 FOR a=63256 TO 63263
95 POKE a,248: POKE (a+8),255-PEEK (a+16): POKE (a+16),255: NEXT a
100 ON ERR GO TO 9990
105 OUT 255,14
110 LET df=16384: LET b=24: LET c=65: LET i=0
120 LET s=63256+8*(PEEK 23658=8): GO SUB 25
130 LET k=CODE INKEY$
135 IF k=0 THEN GO TO 130
145 IF k=6 THEN POKE 23658,8*(PEEK 23658=0)
155 IF k=5 THEN LET i=255
160 IF k=4 THEN LET i=0
170 IF k=12 THEN GO TO 250
180 IF k<32 OR k>122 THEN GO TO 115
190 GO SUB 15: IF b=1 AND c=2 THEN GO TO 115
200 LET df=df+8192*(df<22528)-8191*(df>24575)
205 LET c=c-1
210 IF c>1 THEN GO TO 115
220 LET b=b-1: LET c=64
225 GO TO 115
230 LET l=df-256*INT (df/256)
235 IF l<>0 THEN GO TO 115
240 LET df=df+1792
245 GO TO 115
255 LET s=15616*(i=0)+63272*(i=255)
260 GO SUB 25
270 IF b=24 AND c=65 THEN GO TO 115
275 IF c<65 THEN GO TO 285
280 LET c=1: LET b=b+1
285 IF c>1 THEN GO TO 305
300 LET df=df-1792
305 LET c=c+1
310 LET df=df-8192*(df>24575)+8191*(df<22528)
315 GO TO 115
9989 STOP
9990 OUT 255,128
9991 RANDOMIZE USR 63206
9992 ON ERR RESET
9998 SAVE "64 col" LINE 10
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
