In February 2020 I wrote keyscn, a quick and dirty chunk of 6809 assembly to scan the CoCo keyboard and display them, similar to a keyboard diagnostic.
What I built was a two-pass driver:
While that works like a champ and was great for validating I could read any combination of keys I liked, it also has problems:
I really want a nice keyboard driver so I am revisiting and reimplementing a keyboard scanner from the ground up.
I'll save you a lot of headache trying to follow my mental meanderings:
If you're looking for a keyboard scanning routine with bells and whistles that isn't POLCAT, take a stroll over the Sixxie's Dragon subsite and check out his keyboard.s:
http://www.6809.org.uk/dragon/
Sixxie knows a thing or two about the Dragon and CoCo family so its worth a look …
I am keeping the two-pass design, but replacing the unrolled loop for the PIA polling with a small ROR loop.
The pass 1 polling code is KEYSCN is down to 24 bytes of code from 99 bytes of code used in the previous version.
Along the way I discovered why POLCAT used ROL in stead of ROR in its loop shift…
The pass 1 loop samples the PIA 8 times, reading a whole column of key data at a time.
With ROR the key matrix has to be decoded right to left, top to bottom (KEYBUF + 7 to KEYBUF + 0) to get a 0-55 key value:
ROW 7 | ROW 6 | ROW 5 | ROW 4 | ROW 3 | ROW 2 | ROW 1 | ROW 0 | ADDRESS |
---|---|---|---|---|---|---|---|---|
SHIFT | / | , | SPACE | W | O | G | KEYBUF + 0 | |
F2 | ? | 6 | RIGHT ARROW | V | N | F | KEYBUF + 1 | |
F1 | - | 5 | LEFT ARROW | U | M | E | KEYBUF + 2 | |
CTRL | , | 4 | DOWN ARROW | T | L | D | KEYBUF + 3 | |
ALT | ; | 3 | UP ARROW | S | K | C | KEYBUF + 4 | |
BRK | : | 2 | Z | R | J | B | KEYBUF + 5 | |
CLR | 9 | 1 | Y | Q | I | A | KEYBUF + 6 | |
ENTER | 8 | 0 | X | P | H | @ | KEYBUF + 7 |
With ROL, the key matrix can be decoded bottom to top (KEYBUF +0 to KEYBUF + 7) instead.
ROW 7 | ROW 6 | ROW 5 | ROW 4 | ROW 3 | ROW 2 | ROW 1 | ROW 0 | ADDRESS |
---|---|---|---|---|---|---|---|---|
ENTER | 8 | 0 | X | P | H | @ | KEYBUF + 0 | |
CLR | 9 | 1 | Y | Q | I | A | KEYBUF + 1 | |
BRK | : | 2 | Z | R | J | B | KEYBUF + 2 | |
ALT | ; | 3 | UP ARROW | S | K | C | KEYBUF + 3 | |
CTRL | , | 4 | DOWN ARROW | T | L | D | KEYBUF + 4 | |
F1 | - | 5 | LEFT ARROW | U | M | E | KEYBUF + 5 | |
F2 | ? | 6 | RIGHT ARROW | V | N | F | KEYBUF + 6 | |
SHIFT | / | , | SPACE | W | O | G | KEYBUF + 7 |
And pass 2 needed to be completely rebuilt and rethought to allow for CoCo or Dragon decoding without using nearly a KB of copy-paste code with minor changes.
The example code below polls the keys and store the raw polled data directly to video screen.
Row one shows 9 orange blocks, one for joybuf and each of the 8 rows of the keybuf scan matrix.
The orange block is the semigraphics character for a value or 255 which means no keys were pressed in that column strobe.
Pressing a key will bring a bit low which will shown as one quarter of block disappearing, or the block changing colors.
Pressing joystick buttons show more interesting patterns as it activates the entire row at once.
Row two shows the decoded key.
The decoding is essentially an inner and outer loop.
The outer loop steps through each of the 8 bytes samples from strobing each column of the keyboard matrix.
The inner loop steps bit-by-bit though the sampled byte.
The B register is being incremented by 8 for each bit to act as a “cursor” stepping through the sampled PIA data.
So, B starts at 0, then goes to 8, 16, 24, 32, 40, 48, and when it hits 56 those 7 bits read from the PIA have been handled.
Then we subtract 55 which leaves B at 1 for the start of the next column of sampled data, rather than 0.
The reason for the funky stepping is to convert the sampled 56 bit pattern into a linear offset into a key decoding table which I can switch between CoCo and Dragon versions as needed.
The pshs a/puls a is a cheap hack to get a display where I just call an address table and plonk the character decoded from the B-register decoded and the current keyboard table onto the screen.
I've marked the start and end off the cheap “show a key on screen in a keyboard-like pattern” sort of like the Tandy Diagnostic cartridge display.
If you just want to return the key, then branch to DECD030 goes here instead.
I went with this design to also allow for additional key tables and functions such as different tables for unshifted, shifted, control, and alt key combinations to all be handled differently if I wanted.
This design also allows for direct sampling of any combination of keys active at once supported by the matrix itself.
This version is now 492 bytes of code and data, including the Dragon key decoding, rather than 979 for just CoCo key decoding - a ~50% space savings while more than doubling the features.
Compared to POLCAT this thing is still blazing fast just like keyscn.
The biggest delay is a completely optional DELAY loop called in the screen display just to give us time to see the key on the screen before blanking it again.
In usage, I would probably call KEYSCN at the end of each display field.
That would sample the joystick buttons and keys up/down at a 50 or 60Hz frequency.
Then, in the main game loop, check the JOYBUF and KEYBUF for any desired button presses, all the way up to using DECODE to pull everything active into a larger typing buffer or some such.
Please feel free to discuss this code with me on the Tandy Radio Shack Color Computer Discord in the PROGRAMMING / #assembly channel
For Color Computer 1, 2, 3, and Dragon 32 or 64 with 4K or more.
;********************************************************************* ;* Title: KEYSCN2 ;********************************************************************* ;* Author: R. Allen Murphey ;* ;* License: ;* Copyright (c) 2022 R. "Allen" Murphey aka "Exile In Paradise" ;* Released to you under Creative Commons Attribution-Share Alike (CC-BY-SA) ;* Additional/other licenses possible by request. ;* ;* Description: Color Computer and Dragon keyboard driver ;* ;* Documentation: ;* https://exileinparadise.com/tandy_color_computer:keyscn2 ;* https://exileinparadise.com/tandy_color_computer:keyscn ;* ;* Include Files: none ;* ;* Assembler: lwasm from LWtools 4.19+ ;* ;* Revision History: ;* Rev # Date Who Comments ;* ----- ----------- ------ --------------------------------------- ;* 00 2022 RAM Created initial file ;********************************************************************* VIDRAM: equ $0400 ; start of video memory JOYBUF: equ $0400 ; just store stuff on the screen for now KEYBUF: equ $0401 ; just store more on the screen for now PIA0AD: equ $FF00 ; PIA0 port A data = keyboard row inputs PIA0BD: equ $FF02 ; PIA0 port B data = keyboard column strobe out RESET: equ $FFFE ; Address of MPU Reset Vector VECCOCO: equ $A027 ; CoCo 1/2 reset vector value VECDRGN: equ $B3B4 ; Dragon32/64 reset vector value VECCOCO3: equ $8C1B ; CoCo3 reset vector value KEY_UP: equ 27 KEY_DOWN: equ 28 KEY_LEFT: equ 29 KEY_RIGHT: equ 30 KEY_ENTER: equ 48 KEY_CLEAR: equ 49 KEY_BREAK: equ 50 KEY_ALT: equ 51 KEY_CTRL: equ 52 KEY_F1: equ 53 KEY_F2: equ 54 KEY_SHIFT: equ 55 org $0E00 ; some place to live INIT: bsr CLS ; clear screen ldx RESET ; Get RESET vector cmpx #VECDRGN ; if Dragon beq CFGDRGN ; swap keymaps ; fallthru for CoCo1/2/3 CFGCOCO: ldx #COCOKEYS ; get address of CoCo key table stx KEYTABLE ; set the pointer ldx #COCOPOS ; get address of CoCo screen addrs stx POSTABLE ; set that point bra MAIN ; configured, lets go! CFGDRGN: ldx #DRGNKEYS ; get address of CoCo key table stx KEYTABLE ; set the pointer ldx #DRGNPOS ; get address of CoCo screen addrs stx POSTABLE ; set that point ; fallthru to MAIN MAIN: bsr KEYSCN ; poll keys bsr DECODE ; can we find the key? bra MAIN ; endless loop CLS: pshs X,D,CC ; save our registers ldd #$8080 ; fade to black ldx #VIDRAM ; point to start of video memory CLS010: std ,X++ ; clear two characters cmpx #VIDRAM+512 ; reached end of video memory? bne CLS010 ; go back puls X,D,CC,PC ; restore registers and bail DELAY: pshs X,CC ; save our registers ldx #$045E ; some delay DELAY010: leax -1,X ; countdown X bne DELAY010 ; done counting? puls X,CC,PC ; restore registers and bail KEYSCN: pshs X,A,CC ; save our registers ldx #JOYBUF ; point to place to save polled data lda #$FF ; all bits high sta PIA0BD ; disable all keyboard column strobes andcc #%11111110 ; CLC clear carry KEY010: lda PIA0AD ; read the keyboard rows, first joy, then keys sta ,X+ ; store to pointer and advance to next save ptr rol PIA0BD ; move the zero bit right to strobe next column bcs KEY010 ; keep reading until we hit our original CLC puls X,A,CC,PC ; restore registers and bail DECODE: pshs A,B,X,Y,CC ; save our registers ldx #KEYBUF ; pointer to where we saved PIA data ldy KEYTABLE ; load Y with ptr to keys read in clrb ; start with b empty as bit cursor DECD000: lda ,X+ ; get read key bits into A to process DECD010: rora ; move low bit into carry to test bcs DECD020 ; bit high, key not processed, skip lookup ; This next bit is our cheesy keyboard display ; Replace this with whatever you want to do ; with a key in B pshs A,X,CC ; stash A,X,CC for a couple of opcodes... ldx POSTABLE ; get pointer to screen position table tfr B,A ; copy B key index into A ... asla ; to multiply by two for use as ... leax A,X ; the offset into the 2-byte address table lda B,Y ; get the key pressed [0-55] sta [,X] ; put on screen at address from *POS table jsr DELAY ; hang around for a while so we can see it lda #$80 ; load up a black block sta [,X] ; put on screen over the key shown puls A,X,CC ; clean up registers and fall through ; end of sample display code DECD020: addb #8 ; move bit cursor to next column cmpb #56 ; have we read past bit 6 of keybuf? blt DECD010 ; no go get next key bit from current buffer subb #55 ; yes, go back to row 0 of next column cmpb #8 ; is bit cursor past end of column data? blt DECD000 ; no go get next key buffer and decode it DECD030: puls A,B,X,Y,CC,PC ; restore registers and bail COCOKEYS: fcb '@','A','B','C','D','E','F','G' fcb 'H','I','J','K','L','M','N','O' fcb 'P','Q','R','S','T','U','V','W' fcb 'X','Y','Z',KEY_UP,KEY_DOWN,KEY_LEFT,KEY_RIGHT,' ' fcb '0','1','2','3','4','5','6','7' fcb '8','9',':',';',',','-','.','/' fcb KEY_ENTER,KEY_CLEAR,KEY_BREAK,KEY_ALT,KEY_CTRL,KEY_F1,KEY_F2,KEY_SHIFT COCOPOS: fdb $0478,$04A5,$04EE,$04EA,$04A9,$0468,$04AB,$04AD fdb $04AF,$0472,$04B1,$04B3,$04B5,$04F2,$04F0,$0474 fdb $0476,$0464,$046A,$04A7,$046C,$0470,$04EC,$0466 fdb $04E8,$046E,$04E6,$047C,$04FC,$04BB,$04BD,$052F fdb $0435,$0423,$0425,$0427,$0429,$042B,$042D,$042F fdb $0431,$0433,$0437,$04B7,$04F4,$0439,$04F6,$04F8 fdb $04B9,$047A,$043D,$0462,$04A3,$053B,$053D,$04E4 DRGNKEYS: fcb '0','1','2','3','4','5','6','7' fcb '8','9',':',';',',','-','.','/' fcb '@','A','B','C','D','E','F','G' fcb 'H','I','J','K','L','M','N','O' fcb 'P','Q','R','S','T','U','V','W' fcb 'X','Y','Z',KEY_UP,KEY_DOWN,KEY_LEFT,KEY_RIGHT,' ' fcb KEY_ENTER,KEY_CLEAR,KEY_BREAK,KEY_ALT,KEY_CTRL,KEY_F1,KEY_F2,KEY_SHIFT DRGNPOS: fdb $0435,$0423,$0425,$0427,$0429,$042B,$042D,$042F fdb $0431,$0433,$0437,$04B7,$04F4,$0439,$04F6,$04F8 fdb $0478,$04A5,$04EE,$04EA,$04A9,$0468,$04AB,$04AD fdb $04AF,$0472,$04B1,$04B3,$04B5,$04F2,$04F0,$0474 fdb $0476,$0464,$046A,$04A7,$046C,$0470,$04EC,$0466 fdb $04E8,$046E,$04E6,$047C,$04FC,$04BB,$04BD,$052F fdb $04B9,$047A,$043D,$0462,$04A3,$053B,$053D,$04E4 KEYTABLE rmb 2 ; pointer to CoCo or Dragon key decode table POSTABLE rmb 2 ; pointer to CoCo or Dragon screen addresses end INIT ;********************************************************************* ;* End of keyscn2.asm ;*********************************************************************
Color Computer Technical Reference Manual
SAMS Computerfacts: Color Computer
Color Computer 2 Service Manual
SAMS Computerfacts Color Computer 2
Return to Tandy Color Computer