KEYSCN2

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:

  1. Pass 1 strobes the PIA0 port B output and polls the PIA0 port A for each strobe, saving the port to RAM for later processing
  2. Pass 2 works through the 8 stored port B values (the 7 row values from each of the 8 strobes) and decodes the key to a screen address

While that works like a champ and was great for validating I could read any combination of keys I liked, it also has problems:

  1. It takes nearly a KB of RAM for the code
  2. It's a huge wall of cut and paste code with a little data change in each block
  3. It only works for Color Computer keyboard layout, not Dragon
  4. It doesn't really handle the case of joystick buttons versus keyboard inputs

I really want a nice keyboard driver so I am revisiting and reimplementing a keyboard scanner from the ground up.

TL;DR

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 …

STILL HERE?

I am keeping the two-pass design, but replacing the unrolled loop for the PIA polling with a small ROR loop.

ROR OR ROL?

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.

DECODING

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.

SAVINGS

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.

DISCUSSION

Please feel free to discuss this code with me on the Tandy Radio Shack Color Computer Discord in the PROGRAMMING / #assembly channel

KEYSCN2.ASM

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
;*********************************************************************

SEE ALSO

RTS

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website.More information about cookies