AT keyboard interface

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

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.
Board close-ups
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 */
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
*******************************************************************************
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
|______________|
; 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

