--- title: "KeyScan" id: 56301 type: "computer_media" slug: "keyscan" url: "http://localhost/computer_media/keyscan/" markdown_url: "http://localhost/computer_media/keyscan.md" published_at: "2024-08-04T10:49:54+00:00" modified_at: "2026-03-30T21:43:24+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/08/SCR-20240804-jgur.png" excerpt: "A tiny 25-byte machine code routine slots itself just above RAMTOP and returns real-time two-key keyboard state — callable from BASIC with a single USR expression." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" genre: - name: "Programming" slug: "programming" taxonomy: "genre" url: "http://localhost/type/programming/" media_contents: - id: 56277 title: "Timex Sinclair Public Domain Library Tape 2001" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-2001/" - id: 60550 title: "Fort Worth TS2068 Club Library Tape" type: "computer_media" url: "http://localhost/computer_media/fort-worth-ts2068-club-library-tape/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/KeyScan%20%28198x%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/08/SCR-20240804-jgur.png" media_type_tags: "Programming" --- # KeyScan This program installs and demonstrates a 25-byte Z80 machine code routine that scans two specific keyboard rows and returns a value indicating which of two keys are pressed. The routine is loaded just above the current RAMTOP using PEEK of system variables 23730–23731 (the RAMTOP pointer) and CLEAR to lower RAMTOP by 26 bytes, reserving safe space for the code. It is invoked as a USR function, returning 0 for no key, 1 for left, 2 for right, or 3 for both keys simultaneously. The machine code uses IN instructions with port FEh against keyboard half-rows, masking the result with AND 31 to isolate the five relevant bits of each row. The BASIC demo loop continuously polls the routine and displays the decoded result on screen. *** ## Program Analysis ### Program Structure The program is divided into four logical phases, clearly marked by REM blocks: 1. Lower RAMTOP by 26 bytes to reserve space for machine code (line 30). 2. POKE 25 bytes of Z80 machine code into the reserved area (lines 50–60). 3. Print the USR address so the user knows where the routine lives (line 80). 4. Continuously call the routine, decode the return value, and display it (lines 100–170). ### RAMTOP Manipulation System variables at addresses 23730–23731 hold the current RAMTOP as a little-endian 16-bit value. Line 30 reads this, subtracts 26, and issues `CLEAR x` to lower RAMTOP — this prevents the BASIC memory manager from overwriting the code. Line 50 then adds 1 back, because the actual writable area starts one byte above the new RAMTOP. This is a standard and reliable technique for embedding machine code in a BASIC program. ### Machine Code Disassembly The 25 bytes loaded by the DATA statements (lines 190–210) disassemble as follows: | Offset | Bytes | Mnemonic | Notes | | --- | --- | --- | --- | | 0 | 01 00 00 | LD BC,0000h | Initialise result register to 0 | | 3 | 3E F0 | LD A,F0h | Address keyboard half-row (row containing left key) | | 5 | DB FE | IN A,(FEh) | Read keyboard port | | 7 | 2F | CPL | Invert (pressed = 1) | | 8 | E6 1F | AND 1Fh | Mask to 5 key bits | | 10 | 28 02 | JR Z,+2 | Skip if no key in this row | | 12 | CB C1 | SET 0,C | Set bit 0 of C → left pressed | | 14 | 3E 0F | LD A,0Fh | Address second keyboard half-row (right key) | | 16 | DB FE | IN A,(FEh) | Read keyboard port | | 18 | 2F | CPL | Invert | | 19 | E6 1F | AND 1Fh | Mask to 5 key bits | | 21 | C8 | RET Z | Return if no key in second row | | 22 | CB C9 | SET 1,C | Set bit 1 of C → right pressed | | 24 | C9 | RET | Return BC to USR | The routine returns the value of the BC register pair to BASIC via the USR mechanism. Bit 0 of C encodes one key group and bit 1 the other, giving the four states 0–3 decoded in lines 120–150. ### Key BASIC Idioms - `PEEK 23730+256*PEEK 23731` — standard little-endian 16-bit read of a system variable pair. - `RESTORE` at line 30 ensures the DATA pointer is reset before the POKE loop, preventing issues if the program is re-run. - `FOR y=x TO x+24: READ z: POKE y,z: NEXT y` — compact single-line loop for loading machine code from DATA. - `PRINT AT 12,14;: LET a=USR x` — the `PRINT AT` positions the cursor before the USR call so the decoded string is always printed at a fixed location, avoiding screen drift. ### Notable Techniques Using `AND 1Fh` (31 decimal) after `CPL` is the canonical Z80 keyboard-scanning idiom: the five low bits of the IN result each represent one key on that half-row, and inverting before masking converts “low = pressed” hardware logic into “non-zero = pressed” software logic. The asymmetry between the two half-rows is notable: the first row uses `JR Z` to skip the SET instruction, whereas the second row uses `RET Z` to exit entirely. This means if the left key is not pressed but the right key is, bit 0 is never set — only bit 1 is set, returning 2. The result table is therefore: 0 = none, 1 = left only, 2 = right only, 3 = both. ### Bugs and Anomalies The first three bytes of the machine code (`LD BC,0000h`) initialise BC to zero, but BC is only ever modified by setting bits of C; the B register is not used. This is harmless but slightly wasteful — `LD C,0` (two bytes) would suffice. Additionally, the address bytes `F0h` and `0Fh` are the high bytes of the keyboard port address, selecting specific half-rows; these correspond to particular physical key groups rather than universally labelled “left” and “right” — the actual keys depend on which half-rows those values address on the keyboard matrix. ## Source Code ``` 10 REM Keyboard scanning 11 REM function 12 REM 20 REM lower RAMTOP 21 REM 30 RESTORE : LET x=(PEEK 23730+256*PEEK 23731)-26: CLEAR x 40 REM 41 REM POKE Machine code 42 REM into memory 43 REM 50 LET x=(PEEK 23730+256*PEEK 23731)+1 60 FOR y=x TO x+24: READ z: POKE y,z: NEXT y 70 REM 71 REM Print USR address 72 REM 80 PRINT AT 1,2;"The routine is called by the";TAB 7;"function USR ";x 90 REM 91 REM Example 92 REM 100 PRINT AT 8,10;"Press any keys" 110 PRINT AT 12,14;: LET a=USR x 120 IF a=0 THEN PRINT "NONE " 130 IF a=1 THEN PRINT "LEFT " 140 IF a=2 THEN PRINT "RIGHT" 150 IF a=3 THEN PRINT "BOTH " 160 PRINT AT 16,10;"USR ";x;" = ";a 170 GO TO 110 180 REM 181 REM Machine code data 182 REM 190 DATA 1,0,0,62,240,219,254,47 200 DATA 230,31,40,2,203,193,62,15 210 DATA 219,254,47,230,31,200,203,201,201 ```