The ZX80’s keyboard is of the simple membrane type which is matrix scanned to read a key. The principle behind a membrane keyboard is relatively simple and is illustrated below in Figure 1. The base layer is a printed circuit board which has a matrix of circular contacts, like those shown in Figure 2, laid out in a grid. Each contact has two traces running from it.
The top layer of the system is the flexible keyboard template. Located above each contact on the base layer is a small, circular contact. When a key is pressed, the contact on the bottom side of the template presses down on its respective keyboard contact, creating a conductive path, and thus closing the switch.
[Figure: Figure 1. Underside of keyboard template showing adhesive, conductive material, keyboard template and PC board]
The process by which a key closure is located is called matrix scanning, and it works as follows. As you will note by looking at the schematic diagram of the keyboard in Figure 3, the rows of the keyboard are connected to the anodes of a group of diodes. The cathodes of the diodes are connected to the higher eight address lines. The columns of the keyboard are connected to the inputs of IC10, a 74LS365 tri-state bus driver. The diodes are used to inhibit sinking of the address lines by the pull-up resistors (R13-R17). The resistors are used just on good design principle and do not make any major functional difference in the machine; in fact, the system works without them.
To scan for a key, sequence through each address line, setting it low and all other high. Read the column data from IC10. When an address line is low, its respective diode will allow a logic 0 to pass through; when an address line is high, its respective diode will create an output similar to that of a tri-stated output.
When a key closure is made, either a low signal or a tri-state signal is sent to the input of IC10. IC10, being a standard 74LSxx gate, has internal pull-up resistors on its inputs. A tri-state type signal presented as input to IC10 will, therefore, allow the pull-up resistor to pull-up the input line and turn the input transistor on, thus causing a logic 1 to be the effective input. When a logic 0 input is received, the input line becomes grounded, and the internal input pull-up is disabled, thus causing a logic 0 to be the effective input.
[Figure: Figure 2. Printed circuit keyboard contacts]
[Figure: Figure 3. Schematic of the ZX80 keyboard matrix, diodes D3-D10 on address lines A8-A15, IC10 and KBD decoding gates]
IC10 is enabled when the signal KBD (see Figure 3) is active (i.e., low). As you will note, the signal is derived from two OR gates. Logically, the signal is KBD = A0 + RD + IORQ. Essentially, all of the three inputs must be low to enable IC10. This means that an I/O read (a Z80 IN instruction) is being done from any even address (i.e., any address with A0 = 0).
During an I/O request (IORQ=0), the contents of the A register are placed on the higher eight bits of the address bus. During a keyboard read, the higher eight bits of the address are referred to as the keyboard mask. Executing an IN A, FEh instruction will output the keyboard mask and then read the value of IC10 into the A register. (NB FEh is not the only possible port address; any even value will work.)
A simple routine to test for the BREAK key is shown below:
LD A,7Fh
IN A, (FEh)
RRA
JR NC, BRKPRS
The first instruction loads the keyboard mask into A. This particular mask has all but the ms bit of A (bit 7) set (i.e., 0111 1111 binary). The IN instruction puts out the mask and reads a column from the keyboard. With a mask of 7Fh, the column read is BREAK, EDIT, P, RUBOUT, NOT, NEW, LIST, SHIFT.
When the IN terminates, if no keys were hit, all of the keyboard bits (i.e., d0-d4 of A) will be set. If a key is pressed, then its corresponding bit in A will be a logic 0, provided it was in the selected column. After the IN instruction, the data for the BREAK key will, therefore, reside in bit 0 of A.
The RRA instruction rotates the contents of register A one bit to the right. Bit 7 comes from the data in the carry flag. The carry flag is set to the data in bit 0 position of A (i.e., the data for the BREAK key). Now the carry flag will contain a 0 if BREAK was pressed; otherwise it will hold a 1. The next instruction, if the carry flag is clear, will jump to BRKPRS.
The keyboard and display subroutine scans the keyboard to see if a key was pressed; if not, it passes a frame to the display and loops back to the keyboard scan section. If a key is pressed, then the routine will return to its caller. This routine is shown in Listing 1.
To use the routine, execute a CALL 13Ch instruction. It will return a value in the BC register pair, which corresponds to the keyboard mask and column input for the key pressed. Bits 5, 6, and 7 will be set to ones by the OR 0E0h instruction at 5$:. Bit 0 of B will be zero if SHIFT was pressed; otherwise it will be a 1. C will hold the keyboard mask. For example, if the Z key is pressed, B will hold F7h (i.e., 1111 0111) and C will hold FEh (i.e., 1111 1110).
Listing 2 shows a method for obtaining a ZX80 character in A. The subroutine FILLDF assures that there are enough NewLines in the display file.
I hope that this article has provided some insight into the workings of the ZX80 keyboard.
Listing 1.
RESULT: EQU 4022h
FRAMES: EQU 401Eh
CH_ADD: EQU 4026h
LOOP: Call Show: Space between last line of chars
and fram sync
DISP: : Enter here from BASIC to get a
key and display the current
: display file
:
: Address 319 decimal
: 13F hex
LD B.8
1$ DJNZ $ : Blow away 99 T-States
LD HL. (FRAMES) : Get old frame counter
INC HL : Increment it
LD (FRAMES).HL : Put it back
LD HL.-1
LD B.0FEh
LD C.B
IN A. (C) : Start frame sync
OR 1
5$ OR 0E0h
LD D.A : Zero bit for each key pressed
CPL : Flip bits
CP 1
SBC A.A : 00 if any key pressed. else FFh
OR B
AND L
LD L.A
LD A.H
AND D
LD H.A
RLC B : Rotate mask left
IN A. (C)
JR C.5$ : IF 0 in mask hasn't reach carry
RRA
RL H
RLA
RLA
RLA
SBC A.A
AND 24: 0 if US. 24 if UK
ADD A.32
LD (RESULT+1).A : 32 if US. 56 if UK
: no L has a 0 for each row in
which a key. other than SHIFT.
: was pressed; H similarly for
columns in d1-d5. d6d7 are
: ones. d0=0. if SHIFT pressed.
else d0=1
: 717 T-States since start of frame
sync. 545 before end
LD BC. (CH_ADD) : Pick up last times key hits. or a
value with
: d15d14=01 if first time around
LD (CH_ADD). HL
LD A.B
ADD A.2 : Now either carry is clear and BC
indicates a key was pressed
: or carry is set and BC=FFFFh
or FEFFh.
: N.B. Neither 0000h nor FF00h is a
: possible value for HL. since d6
d7 are set and. if all of d1 to d5
of H
: L=-1
SBC HL.BC : HL:=0 if HL=BC and C=FFH
EX DE HL
LD HL. RESULT
LD A. (HL)
OR D
OR E
RET Z If (X_PTR)=BC. a key is
depressed and
; count = 0. exit with A.D.E=0
LD A.B
CP 254
SBC A.A.
AND B
RRA
LD (HL). A
DEC B
2$: DJNZ 2$
OUT (0FFh). A ; frame sync ends at next M1
LD A.-20
LD B.25
LD HL. (D_FILE) ; Get HL= first byte of display
file
SET 7.H ; Insure Interrupt
CALL SHOW ; Display space above picture and
24
LD A.-13 ; lines of text
INC B
DEC HL
DEC (IY + RESULT
+1-Y) ; One less line below picture than
above
JR LOOP
SHOW: LD C. (IY+RESULT
+1-Y) ; #picture lines in first line of text
(31)
LD R.A
LD A.-35 ; Value for R in subsequent lines
EI
JP (HL) ; Will return to caller at end of
picture
Listing 2.
KB TAB: EQU 06Ch
FILLDF: EQU 05C2h
DISP: EQU 013Fh
KWLOW: EQU 0E6h
GETKEY: Call FILLDP : Fill display file w/reqd N/Ls
Call DISP
SRA B
SBC A.A
OR 38
LD L.5
SUB L
$1: ADD A.L
SCF
RR C
JR C.$1
INC C
JR NZ.GETKEY : If more than one bit set
LD HL.KB__TAB-1
LD E.A
ADD HL.DE
LD A. (HL)
JR.Z.$2
ADD A.0C0h : Here if in KW state: i.e.. convert
from
CP KWLOW : letter to keyword
JR N C.$2
LD A. (HL)
$2: RET : Here with char in A