AT keyboard interface

AT keyboard interface

atkey
Hardware
The connector on the top left of the diagram is from my own 6502 boards (see sbc project) and is as it is for two reasons. It’s easy to wire on a stripboard layout and I have a lot of 26 way ribbon, headers and plugs. All the signals are directly from the 6502 except /SEL0 and /SEL1 which are used to select the block $F1xx with /SEL0=1 and /SEL1=0.

The 5 pin DIN socket is shown looking at the holes of the socket. On the finished board I also fitted a PS2 style socket, either one works but the DIN type is easier to mount on stripboard.

The two capacitors are low ESR electrolytics and are placed near the GAL and near the keyboard socket. If you don’t have this type to hand you can use standard electrolytics with some low value ceramic capacitor, say 0.1uF, in parallel.

The GAL16V8A is used purely to generate the read and write strobes. Each is a negative going pulse coincident with phase 2. The interface uses just one byte in the address range. For anyone interested the equations for this chip are in atkey_01.pld and can be compiled with WinCUPL. The fuse file, atkey_01.jed and the compiler listing atkey_01.txt are also included.

The 74LS74 is used to latch the two lowest bits of the data bus during write access to the interface. The reset line is connected so that both of these outputs are cleared at start-up (this disables the keyboard until required).

The outputs from the latches are used to drive the enable pins on two of the four buffers from the 74LS125, these are then used to drive the data and clock lines of the keyboard. The buffers have their inputs tied low so behave like open collector outputs when used like this.

The other two buffers are used to drive the data bus during read access. Note that the keyboard supplies the pull-up for both the data and clock lines so with no keyboard connected you may read zero when you expect a one.

The finished board
atkeyr atkeyl
Software
The software has a few basic routines to raw handle the keyboard …

ResetAT
This routine sets the pointers to the decode table, clears the lock LEDs and the key status bits for the decode routine. Finally it resets the keyboard.

KeyLEDs
This routine sets the keyboard lock LEDs from the lock LED status byte. Only bits 0, 1 and 2 are used.

ScanAT
This routine scans the keyboard to see if a scancode is waiting. If not it will return with A and RxChar set to zero after about 300uS, otherwise it will return the scancode in A and RxChar.
As well as matrix scancodes possible bytes returned by this routine are …

$00 No key waiting
$AA Power On Self Test Passed (BAT Completed)
$EE See Echo Command (Host Commands)
$FA Acknowledge
$FE Resend
$FF Error or Buffer Overflow

SendAT
This routine is used to send command and control bytes to the keyboard, the codes are ..

$ED Set the LEDs according to the next byte I send
bit 0 = scroll lock
bit 1 = num lock
bit 2 = caps lock
bits 3-7 must be 0, 1 = LED on
$EE echo, keyboard will respond with $EE
$F0 set scan code set, upon sending the keyboard will respond with ACK ($FA) and then wait for a second byte. sending $01 to $03 determines the code set used, sending $00 will return the code set currently in use.
$F3 set typematic repeat rate, upon sending the keyboard will respond with ACK ($FA) and then wait for a second byte. this byte sets the rate.
$F4 keyboard enable, clears the keyboard buffer and starts scanning.
$F5 keyboard disable, clears the keyboard buffer and stops scanning.
$F6 restore default values upon sending the keyboard will respond with ACK ($FA).
$FE retransmit the last character please, upon sending the keyboard will respond by
resending the last character.
$FF reset, you stupid keyboard

There is one routine to make the keyboard look something like a standard ASCII character device. This is …

ScanKey
This routine checks for a key waiting and if there is will decode it to ASCII and return it in RxChar, else, on return, RxChar will be null. Note some valid key actions will result in a null return such as control, shift and lock keys and any undecoded key(cursor keys for example).

Notes on the decoding table …

Most AT keyboard decoding software uses two tables, one for the unshifted characters and one for the shifted characters, some use further tables for control and alt character decoding as well. In this software The normal and shifted characters are held in the same table but shifted half the table length. This only causes a problem with the numeric pad slash character, /, but this is corrected with a small bit of extra code.
Also some special characters, ¬ and £ (uk keymap), are outside the normal ASCII range.
These are also handled by some extra code. Control characters are handled entirely by code as it is far cheaper, space wise, to do this.

The function keys each generate a unique code but this is not output by the routine, neither is the Win menu key code. It is left to the user how to use these keys. Cursor control keys are not coded as this interface was for a system without a screen type display, but this could easily be changed by adding the desired codes to the decode table.

Lastly the table overlaps the RTS of the last subroutine. This is not a mistake, the first byte in the table is never used so this will save a byte without penalty. For the same reason the table stops two bytes short of the end, again with no real loss.
If space on the target system was really tight then some parts of the code could even be moved into the table, saving about fourteen bytes in total at the cost of a small slow down and increased stack use.

Use of the routines …
To save code, time and space nearly all of the subroutines rely on the values held in two or more registers upon calling and some routines rely on register values and flag states returned. If you are going to change the code read the comments carefully before changing any parts to ensure you don’t violate any of these requirements. The code is fully commented.

View the assembler source

Board close-ups

atkey_bot atkey_top

A final thought
With the addition of two 4k7 resistors this interface would be ideal for use as an i2c bus master, if you look at the finished board photos you can see these resistors and a pair of jumpers to select them. It turns out that another small change needs to be made.
The result can be seen in my I2C project page.

Download here atkey_01.pld

Name     atkey_01 ;
PartNo   00 ;
Date     19/03/01 ;
Revision 01 ;
Designer Lee ;
Company   ;
Assembly None ;
Location  ;
Device   g16v8as ;

/* This GAL is the select logic for the AT keyboard interface board	*/

/* Logic minimisations			None				*/
/* Optimisations			None				*/
/* Download				JEDEC/POF/PRG			*/
/* Doc File Options			fuse plot, equations		*/
/* Output				None				*/

/* *************** INPUT PINS *********************/

PIN	1 = A4		;				/* address bus	*/ 
PIN	2 = A3		;				/* address bus	*/ 
PIN	3 = A2		;				/* address bus	*/ 
PIN	4 = A0		;				/* address bus	*/ 
PIN	5 = A1		;				/* address bus	*/ 
PIN	6 = p02		;				/* CPU phase 2	*/ 
PIN	7 = RW		;				/* read write	*/ 
PIN	8 = !SEL1	;				/* block select	*/ 
PIN	9 = !SEL0	;				/* block select	*/ 
PIN	11 = A6		;				/* address bus	*/ 
PIN	12 = A5		;				/* address bus	*/ 
PIN	13 = A7		;				/* address bus	*/ 

/* *************** OUTPUT PINS *********************/

PIN  19 = !KBR		;			/* key port read strobe		*/ 
PIN  18 = !KBW		;			/* key port write strobe	*/ 
/*PIN  17 = 		;			/* 				*/ 
/*PIN  16 = 		;			/* 				*/ 
/*PIN  15 = 		;			/* 				*/ 
/*PIN  14 = 		;			/* 				*/ 

/* intermediate terms */

ADDR		= !A7 & !A6 & A5 & !A4 & !A3 & !A2 & !A1 & !A0 & !SEL0 & SEL1 ;
							/* F120h (0010 0000)

/* Output terms */

KBR	= ADDR &  RW & p02 ;			/* key port read strobe		*/
KBW	= ADDR & !RW & p02 ;			/* key port write strobe	*/


Download here atkey_01.jed

CUPL(WM)        4.7b  Serial# XX-xxxxxxxx
Device          g16v8as  Library DLIB-h-36-2				
Created         Wed Mar 14 21:00:56 2001
Name            ATkey_01 
Partno          00 
Revision        01 
Date            01/08/00 
Designer        Lee 
Company         P1R8 
Assembly        None 
Location        
*QP20 
*QF2194 
*G0 
*F0 
*L00000 10101011101110110111011010010110
*L00256 10101011101110110111101010010110
*L02048 00000000001100000011000000100000
*L02112 00000000001111111111111111111111
*L02144 11111111111111111111111111111111
*L02176 111111111111111110
*C0E13
*7A09


Download here atkey_01.txt

*******************************************************************************
                                   ATkey_01
*******************************************************************************

CUPL(WM)        4.7b Serial# XX-xxxxxxxx
Device          g16v8as  Library DLIB-h-36-2
Created         Wed Mar 14 21:00:56 2001
Name            ATkey_01 
Partno          00 
Revision        01 
Date            01/08/00 
Designer        Lee 
Company
Assembly        None 
Location        

===============================================================================
                            Expanded Product Terms
===============================================================================

ADDR =>
    !A0 & !A1 & !A2 & !A3 & !A4 & A5 & !A6 & !A7 & !SEL0 & SEL1

KBR =>
    !A0 & !A1 & !A2 & !A3 & !A4 & A5 & !A6 & !A7 & RW & !SEL0 & SEL1 & p02

KBW =>
    !A0 & !A1 & !A2 & !A3 & !A4 & A5 & !A6 & !A7 & !RW & !SEL0 & SEL1 & p02



===============================================================================
                                 Symbol Table
===============================================================================

Pin Variable                                    Pterms   Max     Min    
Pol   Name              Ext     Pin     Type     Used   Pterms  Level   
--- --------            ---     ---     ----    ------  ------  -----   

    A0                          4        V        -       -       -     
    A1                          5        V        -       -       -     
    A2                          3        V        -       -       -     
    A3                          2        V        -       -       -     
    A4                          1        V        -       -       -     
    A5                          12       V        -       -       -     
    A6                          11       V        -       -       -     
    A7                          13       V        -       -       -     
    ADDR                        0        I        1       -       -     
 !  KBR                         19       V        1       8       1     
 !  KBW                         18       V        1       8       1     
    RW                          7        V        -       -       -     
 !  SEL0                        9        V        -       -       -     
 !  SEL1                        8        V        -       -       -     
    p02                         6        V        -       -       -     


LEGEND    D : default variable         F : field      G : group
          I : intermediate variable    N : node       M : extended node
          U : undefined                V : variable   X : extended variable
          T : function



===============================================================================
                                   Fuse Plot
===============================================================================

Syn   02192 - Ac0   02193 x 

Pin #19  02048  Pol x  02120  Ac1 x 
 00000 -x-x-x---x---x--x---x--x-xx-x--x 
 00032 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00064 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00096 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00128 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00160 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00192 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00224 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
Pin #18  02049  Pol x  02121  Ac1 x 
 00256 -x-x-x---x---x--x----x-x-xx-x--x 
 00288 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00320 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00352 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00384 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00416 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00448 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00480 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
Pin #17  02050  Pol x  02122  Ac1 - 
 00512 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00544 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00576 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00608 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00640 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00672 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00704 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00736 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
Pin #16  02051  Pol x  02123  Ac1 - 
 00768 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00800 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00832 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00864 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00896 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00928 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00960 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 00992 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
Pin #15  02052  Pol x  02124  Ac1 - 
 01024 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01056 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01088 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01120 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01152 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01184 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01216 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01248 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
Pin #14  02053  Pol x  02125  Ac1 - 
 01280 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01312 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01344 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01376 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01408 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01440 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01472 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01504 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
Pin #13  02054  Pol x  02126  Ac1 - 
 01536 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01568 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01600 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01632 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01664 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01696 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01728 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01760 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
Pin #12  02055  Pol x  02127  Ac1 - 
 01792 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01824 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01856 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01888 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01920 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01952 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 01984 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 02016 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 


LEGEND    X : fuse not blown 
          - : fuse blown 


===============================================================================
                                 Chip Diagram
===============================================================================

                               ______________
                              |   ATkey_01   |
                       A4 x---|1           20|---x Vcc                      
                       A3 x---|2           19|---x !KBR                     
                       A2 x---|3           18|---x !KBW                     
                       A0 x---|4           17|---x                          
                       A1 x---|5           16|---x                          
                      p02 x---|6           15|---x                          
                       RW x---|7           14|---x                          
                    !SEL1 x---|8           13|---x A7                       
                    !SEL0 x---|9           12|---x A5                       
                      GND x---|10          11|---x A6                       
                              |______________|


Download here atkey.asm

; IBM AT keyboard interface for 6502 L.Davison, 2001
; port address is set by your hardware configuration
; page zero bytes used = 8
; code bytes (including tables) less than 690
; works with 102 key standard and 105 key Win95/98 keyboards
; The main, externally useable routines are ...
; ResetAT		resets the keyboard, flags & LEDs
; KeyLEDs		sets the keyboard LEDs
; GetAT			scans the keyboard and waits for a key
; ScanAT		scans the keyboard, returns after timeout if no key
; Send_AT		sends a byte to the keyboard
; GetKey		scans & decodes the keyboard, waits for a key but may still
;			return null for some keys.
; ScanKey		scans & decodes the keyboard, returns after a timeout if no key
; addresses used ...
KPort		=	$F120	; keyboard port, o.c. outputs, tristate inputs
; bit 0 is data
; bit 1 is clock
; bits 2 to 7 are unused
KTable		=	$F1	; (and $F2) keyboard table pointer
TxChar		=	$F3	; transmit character buffer
RxChar		=	$F4	; receive character buffer
RxParity	=	$F5	; receive character parity buffer (Bit 0 only)
LEDBits		=	$F6	; LED status bits
; bit 0 = scroll lock,	1 = LED on
; bit 1 = num lock,	1 = LED on
; bit 2 = caps lock,	1 = LED on
; bits 3 to 7 are unused but may get trashed
KeyBits		=	$F7	; key status bits
; bit 0 = Lshift,	1 = key down
; bit 1 = Lcontrol,	1 = key down
; bit 2 = Lwin,		1 = key down
; bit 3 = Lalt,		1 = key down
; bit 4 = Rshift,	1 = key down
; bit 5 = Rcontrol,	1 = key down
; bit 6 = Rwin,		1 = key down
; bit 7 = Ralt,		1 = key down
RxTwo		=	$F8	; key two code flag and temporary counter
*=	$2000	; set origin (put this where you like. ROM or RAM)
; keyboard decoding routine. the entry point is ScanKey for a non
; halting keyboard poll. if there is a key waiting it will be
; returned in RxChar, else, on return, RxChar will be null.
; key pressed was break [pause] key
WasBRK
LDA	#$07		; seven more scancodes to ignore
; ($14,$77,$E1,$F0,$14,$F0,$77)
STA	RxTwo		; copy code count
Ploop
JSR	GetAT		; get scancode
DEC	RxTwo		; decrement count
BNE	Ploop		; loop if not all pause bytes done
LDA	#$03		; make break = [CTRL-C]
STA	RxChar		; save key
RTS
; process lifted key
KeyUp
JSR	GetAT		; get lifted key scancode
ORA	RxTwo		; OR in table half
TAY			; set index from scancode
LDA	(KTable),Y	; get keycode
TAX			; save key for now
AND	#$F0		; mask top nibble
;	CMP	#$C0		; function key ?
;	BEQ	FuncKeyUP	; was function key, use this branch for you own
;				; function key up handler (X holds char code)
CMP	#$90		; bit hold ?
BNE	ScanKey		; not bit hold, go get next
; ignores other key releases
LDA	#$00		; clear A
; was bit hold key (for up OR down key)
BitHold
STA	RxTwo		; save key up/down flag ($90 for down, $00 for up)
TXA			; get key back
CMP	#$98		; compare with win_menu key
BCS	ScanKey		; ignore $98 - $9F
; if here key is either, [L_SHIFT], [L_CTRL],
; [L_WIN], [L_ALT], [R_SHIFT], [RCTRL], [R_WIN]
; or [R_ALT]
; rotates a 1 into a byte of 0s to
; toggle the pressed/lifted key's bit
AND	#$07		; mask lower 3 bits
TAX			; key # to X
LDA	#$00		; clear A
SEC			; set -1 bit
R_Loop
ROL	A		; rotate zero bit through A
DEX			; decrement bit count
BPL	R_Loop		; loop if not that key
LDX	RxTwo		; get key up flag
BEQ	ClearBit	; branch if was it key up
ORA	KeyBits		; set the bit
BNE	SavBits		; branch always (always at least one bit set)
ClearBit
EOR	#$FF		; make only one bit = 0
AND	KeyBits		; clear the bit
SavBits
STA	KeyBits		; and save it back
BCC	ScanKey		; go get next scancode (branch always)
; handle num, caps and scroll lock keys
SetUnset
TXA			; get key back
EOR	LEDBits		; toggle the LED bits
STA	LEDBits		; save new LED bits
JSR	KeyLEDs		; set the keyboard LEDs
; get a key from the keyboard, exits with null after timeout
; This is the main entry point for this routine
ScanKey
JSR	ScanAT		; scan keyboard
BNE	ProcKey		; if key go do next
RTS
; get a key from the keyboard, waits for a valid key
; This is the secondary entry point for this routine
GetKey
JSR	GetAT		; get key scancode
ProcKey
CMP	#$E1		; is it pause (break)
BEQ	WasBRK		; branch if so
CMP	#$AA		; is it selftest pass
BEQ	ScanKey		; branch if was
CMP	#$FF		; is it error or buffer overflow
BNE	NotOF		; branch if not
JSR	ClearOF		; clear overflow
BNE	ScanKey		; get scancode again (branch always)
; when here we have handled startup, errors and [BREAK]
NotOF
CMP	#$E0		; is it $E0 (two byte sequence)
BNE	NotE0		; branch if not
JSR	GetAT		; get second scancode
LDX	#$80		; set for double key code
NotE0
STX	RxTwo		; set/clear two code flag
CMP	#$F0		; is it $F0 (key up)
BEQ	KeyUp		; branch if up
; key has been pressed
ORA	RxTwo		; OR in table half
TAY			; set index from scancode
LDA	(KTable),Y	; get keycode
TAX			; save key for now
AND	#$F0		; mask top nibble
CMP	#$80		; bit set/unset ?
BEQ	SetUnset	; was bit set/unset
CMP	#$90		; bit hold ?
BEQ	BitHold		; was so go set hold bit
CMP	#$C0		; function key ?
;	BEQ	FuncKey		; was function key, use this branch for you own
; function key down handler (X holds char code)
BEQ	ScanKey		; was function key, ignore for now
; key is not function, bit change or lock key
LDA	LEDBits		; get the LED bits
AND	#$04		; check CAPS lock
BEQ	NotCapsL			; skip CAPS if not set
TXA			; get key back
CMP	#("a"+$80)	; compare with "a" (shiftable)
BCC	NotCapsL	; branch if less
CMP	#("z"+$80+1)	; compare with "z"+1 (shiftable)
BCS	NotCapsL	; branch if >=
; caps lock on and was alpha, so shift it
TYA			; copy index
EOR	#$80		; do CAPS LOCK
TAY			; back to index
NotCapsL
TYA			; copy scancode
CMP	#$CA		; compare with keypad "/"
BEQ	Shift		; correct keypad "/"
CMP	#$69		; compare scancode with lowest numeric pad code
BCC	NotNumpad	; branch if < CMP #$7E ; compare scancode with highest numeric pad code+1 BCS NotNumpad ; branch if >= (not numeric)
; gets here if numeric pad code
LDA	LEDBits		; get the LED bits
AND	#$02		; check NUM lock
BNE	NoShift		; skip NUM if set (works backwards! like it should)
TXA			; get key back
BMI	Shift		; branch if was numeric (shiftable)
; key wasn't numeric so now check shift keys status'
NotNumpad
LDA	KeyBits		; get held bit flags
AND	#$11		; mask shift bits
BEQ	NoShift		; there was no shift applied
; if here then either shift was held (or both)
TXA			; get key back
BPL	NoShift		; branch if not shiftable
Shift
TYA			; copy index
EOR	#$80		; do the shift (or NUM LOCK)
TAY			; copy to index
NoShift
LDA	(KTable),Y	; get keycode
CPY	#$80		; check scancode
BCS	FixPound	; skip if from second half of table (¬ and £)
AND	#$7F		; clear shiftable bit (lower half of table only)
FixPound
TAX			; save key for now
LDA	KeyBits		; get held bit flags
AND	#$22		; mask control bits
BEQ	NoCtrl		; was no control key applied
TXA			; get key back (again)
CMP	#$40		; compare with "@"
BCC	NoCtrl		; branch if <, not in range CMP #$80 ; compare with [DEL]+1 BCS NoCtrl ; branch if >=, not in range
; [CTRL] key held and in range, mask it
AND	#$1F		; convert to control key
TAX			; copy key to X
NoCtrl
STX	RxChar		; save key
RTS
; scan the keyboard to see if a key is waiting. returns scancode in A
; and RxChar, or $00 if no key waiting. this is the main entry point.
; if the keyboard response seems flakey when holding a key for auto
; repeat then increase the timeout, it helps. This was the shortest
; I could reliably get away with with all the test keyboards.
; possible bytes (apart from scancodes) are ...
; $AA		Power On Self Test Passed (BAT Completed)
; $EE		See Echo Command (Host Commands)
; $FA		Acknowledge
; $FE		Resend
; $FF		Error or Buffer Overflow
; when exiting this routine the clock is pulled low to hold off transmission.
; the X register is assumed to be $00 on exit and that the Z flag reflects
; the byte in A.
ScanAT
;	LDX	#$0D		; set timeout count (150uS, 1MHz 6502)
LDX	#$1A		; set timeout count (150uS, 2MHz 6502)
LDA	#$03		; clock high, data high
STA	KPort		; out to clock port (allow to send)
LDA	#$02		; set for clock line bit test
WaitW0
BIT	KPort		; test the clock line
BEQ	WaitW1		; go do rest if the clock falls
DEX			; else decrement timeout value
BNE	WaitW0		; go wait some more if time is not up yet
LDA	#$01		; else stop keyboard
STA	KPort		; out to port (prevent sending)
TXA			; copy $00 to A
STA	RxChar		; save in buffer (no character)
RTS			;
; Rx error, try again
RxWasNOK
LDA	#$FE		; resend command
JSR	SendAT		; send A to keyboard
; scan the keyboard and wait for a key. returns scancode in A and RxChar.
; this is the secondary entry point.
GetAT
LDA	#$03		; clock high, data high
STA	KPort		; out to clock port (allow to send)
LDA	#$02		; set for clock line bit test
WaitW1
JSR	GetBit		; get bit from keyboard (start bit)
LDX	#$08		; eight data bits to get
LDY	#$01		; 1's count to one (odd parity)
RecByte
JSR	GetBit		; get bit from keyboard (data bit)
BCC	NoRx1		; brabch if bit was zero
INY			; else increment 1's count
NoRx1
ROR	RxChar		; bit into receive byte
DEX			; decrement bit count
BNE	RecByte		; loop if more data
JSR	GetBit		; get bit from keyboard (parity bit)
ROL	RxParity	; bit into parity byte b0
JSR	GetBit		; get bit from keyboard (stop bit)
LDA	#$01		; drive clock low (hold off more data)
STA	KPort		; out to port
TYA			; get computed parity
EOR	RxParity	; compare with received parity bit
ROR	A		; only interested in bit 0
BCS	RxWasNOK	; go try again if not ok
; (rx parity <> computed parity)
LDA	RxChar		; else copy scancode
BEQ	ClearOF		; branch if was error or buffer overflow
RTS
; gets a bit from the keyboard, bit to send is in Cb
; take care to enter this routine with A = $02
GetBit
BIT	KPort		; test the clock line
BNE	GetBit		; wait for the clock to fall
LDA	KPort		; get data
LSR	A		; data bit to Cb
LDA	#$02		; set for clock line bit test
WaitR1
BIT	KPort		; test the clock line
BEQ	WaitR1		; wait for the clock to rise
RTS			;
; sets the pointers to the decode table, resets the keyboard and sets the
; lock LEDs. also clears the status bits for the decoder.
ResetAT
LDA	#<ATtable ; point to ATtable (low addr) STA KTable ; save pointer low byte LDA #>ATtable	; point to ATtable (high addr)
STA	KTable+1	; save pointer high byte
LDA	#$00		; clear bits
STA	KeyBits		; clear hold bits
STA	LEDBits		; clear LED bits
LDA	#$FF		; reset the keyboard
JSR	SendAT		; send A to keyboard
LDA	#$F6		; restore default settings
JSR	SendAT		; send A to keyboard
JSR	KeyLEDs		; set keyboard LEDs
ClearOF
LDA	#$F4		; clear the buffer
BNE	SendAT		; send A to keyboard & return (branch always)
; set the keyboard LEDs from LEDBits
KeyLEDs
LDA	#$ED		; next byte is LED bits
JSR	SendAT		; send A to keyboard
LDA	LEDBits		; get LED bits
AND	#$07		; make sure bits 3 to 7 = 0
; SendAT sends the special codes to the keyboard, codes are ..
; $ED		set the LEDs according to the next byte I send
;		bit 0 = scroll lock
;		bit 1 = num lock
;		bit 2 = caps lock
;		bits 3-7 must be 0, 1 = LED on
; $EE		echo, keyboard will respond with $EE
; $F0		set scan code set, upon sending the keyboard will
;		respond with ACK ($FA) and then wait for a second
;		byte. sending $01 to $03 determines the code set
;		used, sending $00 will return the code set currently
;		in use.
; $F3		set typematic repeat rate, upon sending the keyboard
;		will respond with ACK ($FA) and then wait for a second
;		byte. this byte sets the rate.
; $F4		keyboard enable, clears the keyboard buffer and starts
;		scanning.
; $F5		keyboard disable, clears the keyboard buffer and stops
;		scanning.
; $F6		restore default values upon sending the keyboard will
;		respond with ACK ($FA)
; $FE		retransmit the last character please upon sending the
;		keyboard will respond by resending the last character
; $FF		reset, you stupid keyboard
SendAT
STA	TxChar		; save A in transmit buffer
Send_AT
LDX	#$08		; eight bits to send
LDY	#$01		; 1's count to one (odd parity)
LDA	#$02		; clock high, data low
STA	KPort		; out to clock port (tell keyboard to receive)
SendByte
ROR	TxChar		; bit into carry
BCC	NotOne		; skip increment if zero
INY			; else increment 1's count
NotOne
JSR	SendBit		; send bit to keyboard
DEX			; decrement bit count
BNE	SendByte	; loop if not all done
ROR	TxChar		; last bit back into TxChar
TYA			; copy parity count
LSR	A		; parity bit into carry
LDA	#$02		; set for clock line bit test
JSR	SendBit		; send bit to keyboard
SEC			; set stop bit
JSR	SendBit		; send bit to keyboard
JSR	SendBit		; send bit to keyboard (skips extra clock after bit 10)
;	LDX	#$04		; 20uS delay (1Mhz 6502)
LDX	#$08		; 20uS delay (2Mhz 6502)
Wait20
DEX			; decrement count
BNE	Wait20		; loop for a while
LDA	#$01		; drive clock low (handshake)
STA	KPort		; out to port
;	LDX	#$09		; 44uS delay (1Mhz 6502)
LDX	#$12		; 44uS delay (2Mhz 6502)
Wait44
DEX			; decrement count
BNE	Wait44		; loop for a while
JSR	GetAT		; get the response
CMP	#$FE		; compare with not ok response
BEQ	Send_AT		; if wrong go do it again
RTS			;
; send bit to keyboard, Cb is the bit to send
; take care to enter this routine with A = $02
SendBit
BIT	KPort		; test the clock line
BNE	SendBit		; wait for the clock to fall
LDA	#$01		; unshifted bit for clock
ROL	A		; shift Cb into A
STA	KPort		; out to clock port
ROR	A		; shift bit back to Cb
LDA	#$02		; set for clock line bit test
WaitT1
BIT	KPort		; test the clock line
BEQ	WaitT1		; wait for the clock to rise
ATtable				; the first byte of the table is unused so
RTS			; this RTS can be the first byte
; AT keyboard decoding table 
; [Fn] keys are coded $C1 to $CC but not acted on
; Lock keys are ..
;	[SCROLL LOCK]		$81
;	[NUM LOCK]		$82
;	[CAPS LOCK]		$84
; ... and they change flags internal to the decode routine,
; they also toggle the keyboard LEDs
; other non character keys are ..
;	[L_SHIFT]		$90
;	[L_CTRL]		$91
;	[L_WIN]			$92 (toggles bit but otherwise ignored)
;	[L_ALT]			$93 (toggles bit but otherwise ignored)
;	[R_SHIFT]		$94
;	[R_CTRL]		$95
;	[R_WIN]			$96 (toggles bit but otherwise ignored)
;	[R_ALT]			$97 (toggles bit but otherwise ignored)
;	[WIN_MENU]		$98 (ignored)
; AT keyboard decoding table first part.
; this mostly holds unshifted key values
; the second character in the comments field is usually the shifted
; character for that key
;ATtable 
;	.byte	$00		; first byte replaced by RTS above
.byte	$C9		; [F9]
.byte	$00  		;
.byte	$C5		; [F5]
.byte	$C3		; [F3]
.byte	$C1		; [F1]
.byte	$C2		; [F2]
.byte	$CC		; [F12]
.byte	$00		;
.byte	$CA		; [F10]
.byte	$C8		; [F8]
.byte	$C6		; [F6]
.byte	$C4		; [F4]
.byte	$09		; [TAB]
.byte	("`"+$80)	; `	¬
.byte	$00		;
.byte	$00		;
.byte	$93		; [L_ALT]
.byte	$90		; [L_SHIFT]
.byte	$00		;
.byte	$91		; [L_CTRL]
.byte	("q"+$80)	; q	Q
.byte	("1"+$80)	; 1	!
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	("z"+$80)	; z	Z
.byte	("s"+$80)	; s	S
.byte	("a"+$80)	; a	A
.byte	("w"+$80)	; w	W
.byte	("2"+$80)	; 2	"
.byte	$00		;
.byte	$00		;
.byte	("c"+$80)	; c	C
.byte	("x"+$80)	; x	X
.byte	("d"+$80)	; d	D
.byte	("e"+$80)	; e	E
.byte	("4"+$80)	; 4	$
.byte	("3"+$80)	; 3	£
.byte	$00		;
.byte	$00		;
.byte	" "		; [SPACE]
.byte	("v"+$80)	; v	V
.byte	("f"+$80)	; f	F
.byte	("t"+$80)	; t	T
.byte	("r"+$80)	; r	R
.byte	("5"+$80)	; 5	%
.byte	$00		;
.byte	$00		;
.byte	("n"+$80)	; n	N
.byte	("b"+$80)	; b	B
.byte	("h"+$80)	; h	H
.byte	("g"+$80)	; g	G
.byte	("y"+$80)	; y	Y
.byte	("6"+$80)	; 6	^
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	("m"+$80)	; m	M
.byte	("j"+$80)	; j	J
.byte	("u"+$80)	; u	U
.byte	("7"+$80)	; 7	&
.byte	("8"+$80)	; 8	*
.byte	$00		;
.byte	$00		;
.byte	(","+$80)	; ,	< .byte ("k"+$80) ; k K .byte ("i"+$80) ; i I .byte ("o"+$80) ; o O .byte ("0"+$80) ; 0 ) .byte ("9"+$80) ; 9 ( .byte $00 ; .byte $00 ; .byte ("."+$80) ; . >
.byte	($2F+$80)	; /	?
.byte	("l"+$80)	; l	L
.byte	(";"+$80)	; ;	:
.byte	("p"+$80)	; p	P
.byte	("-"+$80)	; -	_
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	($27+$80)	; '	@
.byte	$00		;
.byte	("["+$80)	; [	{
.byte	("="+$80)	; =	+
.byte	$00		;
.byte	$00		;
.byte	$84		; [CAPS LOCK]
.byte	$94		; [R_SHIFT]
.byte	$0D		; [RETURN]
.byte	("]"+$80)	; ]	}
.byte	$00		;
.byte	("#"+$80)	; #	~
.byte	$00		;
.byte	$00		;
; keys with bit 7 set, but not the /, are only affected by num lock
; these are the num lock on values
.byte	$00		;
.byte	("\"+$80)	; \	|
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$08		; [BACKSPACE]
.byte	$00		;
.byte	$00		;
.byte	("1"+$80)	; 1 keypad
.byte	$00		;
.byte	("4"+$80)	; 4 keypad
.byte	("7"+$80)	; 7 keypad
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	("0"+$80)	; 0 keypad
.byte	("."+$80)	; . keypad
.byte	("2"+$80)	; 2 keypad
.byte	("5"+$80)	; 5 keypad
.byte	("6"+$80)	; 6 keypad
.byte	("8"+$80)	; 8 keypad
.byte	$1B		; [ESC]
.byte	$82		; [NUM LOCK]
.byte	$CB		; [F11]
.byte	"+"		; + keypad
.byte	("3"+$80)	; 3 keypad
.byte	"-"		; - keypad
.byte	"*"		; * keypad
.byte	("9"+$80)	; 9 keypad
.byte	$81		; [SCROLL LOCK]
.byte	$00		;
; AT keyboard decoding table second part.
; this mostly holds shifted key values of the first half
.byte	$00		;
.byte	$00		;
.byte	$00		;    
.byte	$C7		; [F7]
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	"¬"		; [SHIFT] `
.byte	$00		;
.byte	$00		;
.byte	$97		; [R_ALT]
.byte	$00		;
.byte	$00		;
.byte	$95		; [R_CTRL]
.byte	"Q"		; [SHIFT] q
.byte	"!"		; [SHIFT] 1
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	"Z"		; [SHIFT] z
.byte	"S"		; [SHIFT] s
.byte	"A"		; [SHIFT] a
.byte	"W"		; [SHIFT] w
.byte	$22		; [SHIFT] 2
.byte	$92		; [L_WIN]
.byte	$00		;
.byte	"C"		; [SHIFT] c
.byte	"X"		; [SHIFT] x
.byte	"D"		; [SHIFT] d
.byte	"E"		; [SHIFT] e
.byte	"$"		; [SHIFT] 4
.byte	"£"		; [SHIFT] 3
.byte	$96		; [R_WIN]
.byte	$00		;
.byte	$00		;
.byte	"V"		; [SHIFT] v
.byte	"F"		; [SHIFT] f
.byte	"T"		; [SHIFT] t
.byte	"R"		; [SHIFT] r
.byte	"%"		; [SHIFT] 5
.byte	$98		; [WIN_MENU]
.byte	$00		;
.byte	"N"		; [SHIFT] n
.byte	"B"		; [SHIFT] b
.byte	"H"		; [SHIFT] h
.byte	"G"		; [SHIFT] g
.byte	"Y"		; [SHIFT] y
.byte	"^"		; [SHIFT] 6
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	"M"		; [SHIFT] m
.byte	"J"		; [SHIFT] j
.byte	"U"		; [SHIFT] u
.byte	"&"		; [SHIFT] 7
.byte	"*"		; [SHIFT] 8
.byte	$00		;
.byte	$00		;
.byte	"<" ; [SHIFT] , .byte "K" ; [SHIFT] k .byte "I" ; [SHIFT] i .byte "O" ; [SHIFT] o .byte ")" ; [SHIFT] 0 .byte "(" ; [SHIFT] 9 .byte $00 ; .byte $00 ; .byte ">"		; [SHIFT] .
.byte	$3F		; / keypad
.byte	"L"		; [SHIFT] l
.byte	":"		; [SHIFT] ;
.byte	"P"		; [SHIFT] p
.byte	"_"		; [SHIFT] -
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	"@"		; [SHIFT] '
.byte	$00		;
.byte	"{"		; [SHIFT] [
.byte	"+"		; [SHIFT] =
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$0D		; [ENTER] keypad
.byte	"}"		; [SHIFT] ]
.byte	$00		;
.byte	"~"		; [SHIFT] #
.byte	$00		;
.byte	$00		;
; keys marked "& keypad" are only affected by num lock
; these are the num lock off values
.byte	$00		;
.byte	"|"		; [SHIFT] \
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		; [END]		& keypad
.byte	$00		;
.byte	$00		; [CURSOR_LT]	& keypad
.byte	$00		; [HOME]	& keypad
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		; [INSERT]
.byte	$7F		; [DELETE]	& keypad
.byte	$00		; [CURSOR_DN]	& keypad
.byte	$00		;
.byte	$00		; [CURSOR_RT]	& keypad
.byte	$00		; [CURSOR_UP]	& keypad
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		;
.byte	$00		; [PAGE_DN]	& keypad
.byte	$00		;
.byte	$00		;
.byte	$00		; [PAGE_UP]	& keypad
;	.byte	$00		; these two bytes are never accessed so don't
;	.byte	$00		; need to be defined in the table
END