We have a display to output information to the user, now we need a keyboard so that the user can type information into our system. I salvaged a keyboard PCB from an unused USB keyboard and trimmed away the part containing the original controller circuit. We only need the keys and the keyboard matrix circuitry. Now we need to decipher the keyboard matrix by tracing the connections or checking them with a continuity tester. In my case this keyboard needs 14 row output and 8 column input lines. The Fn and Windows are connected to an additional input line. We can use these keys for a "warm reboot" so that pressing them both will trigger an NMI interrupt.
The circuit is straightforward. We only need to connect all keyboard lines to IO pins of our VIAs.
Now this looks more like a real computer! Add a battery pack, put this all in an eclosure and you get a typical mobile computer system of the 1980s like the Epson HX-20 .
The keys_update function scans the whole keyboard, performs a simple debounce (read, wait, read again and compare) and stores the code of a pressed key in KEYS_CODE.
keys_update: phaxy
jsr scan
cmp #$ff
bne @debounce
sta KEYS_CODE
plaxy
rts
@debounce: sta KEYS_CODE
ldx #10
jsr delay_ms
jsr scan
cmp KEYS_CODE
beq @key_pressed
lda #$ff
sta KEYS_CODE
plaxy
rts
@key_pressed: plaxy
rts
The scan function enables one row after the other and reads its column value. For the first row that has a key pressed, the bit position of the column is calculated and #row * 8 is added. The resulting value is the position of the key in the matrix, i.e. the key code. If no key is pressed, $FF is returned.
scan: ldx #13
@next_row: jsr read_row
bne @row_pressed
dex
bpl @next_row
lda #$ff
rts
@row_pressed: ldy #7
@next_column: asl
bcs @column_found
dey
bpl @next_column
@column_found: tya
@add_row_offset: dex
bmi @got_scan_code
clc
adc #8
bne @add_row_offset
@got_scan_code: rts
The read_row function activates a single row (sets it low) and reads and returns the corresponding column value.
read_row: ; Set row X low
lda row_out_reg_lo,x
sta TMP0
lda row_out_reg_hi,x
sta TMP0 + 1
lda row_bit_mask,x
ldy #0
and (TMP0),y
sta (TMP0),y
; Read column values
lda VIA2_IRA
eor #$ff
tay
; Set all rows high
lda #$ff
sta VIA2_ORB
lda VIA1_ORB
ora #$3f
sta VIA1_ORB
tya
rts
; Table of output register addresses for each row
row_out_reg_lo: .byte VIA2_ORB, >VIA2_ORB, >VIA2_ORB, >VIA2_ORB, >VIA2_ORB, >VIA2_ORB, >VIA2_ORB, >VIA2_ORB
.byte >VIA1_ORB, >VIA1_ORB, >VIA1_ORB, >VIA1_ORB, >VIA1_ORB, >VIA1_ORB
; Table of output register bit masks for each row (with a 0 bit for the selected row)
row_bit_mask: .byte <~VIA_PB0, <~VIA_PB1, <~VIA_PB2, <~VIA_PB3, <~VIA_PB4, <~VIA_PB5, <~VIA_PB6, <~VIA_PB7
.byte <~VIA_PB0, <~VIA_PB1, <~VIA_PB2, <~VIA_PB3, <~VIA_PB4, <~VIA_PB5
I added the second VIA and the keyboard connector.