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:
- Lower RAMTOP by 26 bytes to reserve space for machine code (line 30).
- POKE 25 bytes of Z80 machine code into the reserved area (lines 50–60).
- Print the USR address so the user knows where the routine lives (line 80).
- 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.RESTOREat 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— thePRINT ATpositions 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.
Content
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
