; AT Keyboard (keypad) emulation v1.0 * preliminary * ; v1.0 96-07-23 (C) ARNE Computers All rights reserved ; written by Mauricio Culibrk ; ; contact: mauricio@arne.si www.arne.si/~mauricio ; this routine is using 4 pins (2 inputs & 2 outputs) to control ; keyboard's 2 bidirectional OC lines (CLK & DATA). The following ; 'drawing' conceptually shows how to connect the related pins/lines ; ; vcc vcc ; | | ; \ -+- ; / 2K2 /_\ 1N4148 ; \ | ; hoCLKi ------------------o---o------o--------- kbd CLOCK ; | | | ; 2N2222 |50pF -+- ; hoCLKo ______/\/\/\____|/ === /_\ ; 2K2 |\> | | ; | | | ; /// /// /// ; ; an identical circuit is used for the DATA line. This diagram was ; 'copied' from an classic AT keyboard. I did not use the clamping ; diodes because they are already included as standard pin protection ; in PC microcontrollers. This can be simplified more if the ; related pin is switched between input & output low (simulate OC) ; ; if you need more information about PC AT/XT keyboard specification, ; take a look at www.paranoia.com/~filipg ; ; NOTE: if you implement/use this routine, please let me know if you ; encunter any incompatibility or any other problems. I'll be happy to ; expand/share my experience about this matter. Thanx. ; Keyboard Emulation Module ; Generates Clock #define kbCLKi PORTB, 2 ; clock line #define kbCLKo PORTB, 3 #define kbDATAi PORTB, 4 ; data line #define kbDATAo PORTB, 5 #define ODDPARITY #define kbParity kbFlags ; alias for parity byte #define kbParityB kbFlags, 7 ; parity bit #define kbParityM B'10000000' ; mask for parity bit #define kbActive kbFlags, 6 ; KeyboardEngine Active #define kbError kbFlags, 4 ; 1=error during transfer (parity) #define kbAbort kbFlags, 3 ; 1=transfer aborted (issue resend) #define kbRXMode kbFlags, 2 ; 1=RX mode #define kbRXCheck kbFlags, 1 ; 1=checking if PC wants to TX #define kbMustRX kbFlags, 0 ; 1=forced RX mode #define kbSeq2ON kbFlags2, 1 ; proceed with Seq 2 #define kbRXBufF kbFlags2, 2 ; RX buffer full #define kbOvrClk kbFlags2, 3 ; pc req more clk's than needed New kbFlags ; kb emu Flags New kbFlags2 New kbCount ; kb emu bit count New kbShBufL ; kb emu 16 bit shift buffer New kbShBufH New kbRXBuff ; RX buff #define kbNoBits (1+11) ; number of message bits #define kbTXreqTime 3 ; @ 19200/2bps, cca 0.3ms #include "p16c84.inc" #include "custom.h" #include "iopins.inc" ; initialize the Keyboard Emulation Engine KeyboardInit: PinHI kbCLKo PinHI kbDATAo SetRPage 1 bcf kbDATAo bcf kbCLKo bsf kbDATAi bsf kbCLKi SetRPage 0 clrf kbFlags clrf kbFlags2 ret ; Keyboard Emulation Engine, Sequence 1 KBEngineS1: btfss kbActive ; check if Engine active ret ; no, exit KBEs1Run: btfss kbRXMode ; execute RX/TX code depending jmp KEs1TXMode ; on status bits jmp KEs1RXMode ; Keyboard Emulation Engine, Sequence 2 KBEngineS2: btfss kbSeq2ON ; is seq 2 requested? jmp KErxchk ; no, check TX req PinLO kbCLKo ; Seq 2; clock the CLK line bcf kbSeq2ON ; Seq 2 end ret ; check if PC wants to transmit data KErxchk: btfsc kbActive ; is engine ret ; yes, exit btfsc kbRXCheck ; if inactv, chk if TX Req started jmp KEreqcnt ; TXreq strtd, wait to meet timings btfss kbCLKi ; CLK must be Hi (free) ... ret ; if CLK is LO, PC disables all btfss kbDATAi ; and DATA Lo... KErxchks: bsf kbRXCheck ; PC starting TX request clrf kbCount ; reset counters ret ; PC TX initiation ; ; CLK DTA Action ;---------+-------- ; L L | wait ; L H | abort (or start counting either?) ; H L | start counting for TX request ; H H | abort KEreqcnt: btfsc kbDATAi ; check if DATA lo jmp KEreqabort ; no, abort process btfss kbCLKi ; check if CLK lo ret ; yes, wait to go hi incf kbCount ; CLK hi & DATA lo, count movlw kbTXreqTime ; check if time spec reached xorfw kbCount skpz ; yes, initiate RX (from PC) ret ; no, continue counting bcf kbRXCheck ; stop PC TX init sequence flag clrf kbFlags ; clear all flags (set even parity) clrf kbFlags2 #ifdef ODDPARITY bsf kbParityB ; set ODD parity if defined #endif clrf kbCount ; clear bit counter bsf kbRXMode ; set RX mode bsf kbActive ; activate engine ret KEreqabort: ; abort TX init sequence btfss kbMustRX ; if set, remain in RX mode bcf kbRXCheck ; stop counting ret ; ***** RX/TX code ; send data to PC, part 1 KEs1TXMode: PinHI kbCLKo ; bit sent, set CLK for new cycle incf kbCount ; move to next bit movfw kbCount xorlw kbNoBits ; was this the last bit? skpnz ; ZF -> last bit? jmp KEDone ; yes, end transfer lnop lnop ; problems with raise/fall lnop ; times... lnop lnop lnop lnop lnop lnop lnop btfss kbCLKi ; check if CLK went HI jmp KEtxabort ; no, PC aborting transfer movfw kbCount ; is now time for P bit? xorlw 1+9 skpz ; not yet! jmp KEs1txnrm ; normal TX btfss kbParityB ; set P bit in tx data bcf kbShBufL, 0 btfsc kbParityB bsf kbShBufL, 0 jmp KEs1txpar ; tx parity bit KEs1txnrm: movlw kbParityM btfsc kbShBufL, 0 ; check current bit xorwf kbParity ; recalculate parity rrf kbShBufH ; 16 bit shift ShBuff KEs1txpar: rrf kbShBufL ; and move bit to CF skpnc PinHI kbDATAo ; assume bit is 1 skpc ; if CF=1, dit already set PinLO kbDATAo ; otherwise, set pin to 0 bsf kbSeq2ON ; activate Seq2 ret ; remaining code in Seq 2 ; PC is aborting transfer, check if P bit sent KEtxabort: decfw kbCount ; check if this is the first bit skpnz ; it's not the first jmp KEtxbusy ; PC is busy, wait to TX movfw kbCount ; check if 10th bit (P) was sent xorlw 1+10 skpz ; if sent, no errors ; PC is aborting the transfer KEPCAbort: bsf kbAbort ; set error flag KEDone: bcf kbSeq2ON bcf kbActive ; stop engine PinHI kbCLKo ; release data lines PinHI kbDATAo ret ; PC has pulled low CLK line -> PC is Busy, deffer TX KEtxbusy: clrf kbCount ; reset counter & wait PinHI kbDATAo ret ; receive data from PC, part 1 KEs1RXMode: ; part 2 of RX PinHI kbCLKo ; bit sent, set CLK for new cycle incf kbCount ; move to next bit movfw kbCount xorlw kbNoBits ; was this the last bit? skpnz ; ZF -> last bit? jmp KErxack ; yes, check for ack lnop lnop ; problems with raise/fall lnop ; times .... lnop lnop lnop lnop lnop lnop lnop btfss kbCLKi ; check if CLK went HI jmp KEPCAbort ; no, PC aborting transfer clrc ; presume data=0 btfsc kbDATAi ; test data bit setc ; correct to 1 rrf kbShBufH ; shift CF in ShBufH:ShBufL rrf kbShBufL ; and move bit to CF movfw kbShBufH ; calculate new parity andlw H'80' ; isolate received bit xorwf kbParity ; update parity, MUST be Bit 7! bsf kbSeq2ON ret ; after transfer, PC must release Data line KErxack: btfsc kbDATAi ; check if Data returned HI jmp KErxpar ; yes, check parity bit decf kbCount ; no, PC wants more CLKs bsf kbError ; too much CLKs..., set error ret ; continue with 'next' bit KErxpar: rrfw kbParity ; get calced parity at w's bit 6 xorfw kbShBufH ; xor with rcvd (stop is at bit 7) andlw H'40' ; isolate parity only skpz bsf kbError ; set parity error ;; call PCGetData ;; ;; movwf kbRXBuff ;; bsf kbRXBufF ;; jmp KEDone ; done ; ********** ; routines for Sending & Receiveing data to/from PC keyboard bus ; ***** Start Receiving Data from PC, activated automatically by Int Seq 2 PCReceive: clrf kbFlags ; clear all flags (set even parity) clrf kbFlags2 #ifdef ODDPARITY bsf kbParityB ; set ODD parity if defined #endif clrf kbCount ; clear bit counter bsf kbRXMode ; set RX mode bsf kbMustRX ; set Forced RX mode bsf kbRXCheck ; activate engine ret ; ***** retrives data received from PC (W=received data) PCGetData: rlf kbShBufL ; discard stop bit rlf kbShBufH rlf kbShBufL ; and parity bit rlfw kbShBufH ; return data in W bcf kbRXBufF ;; ret ; ***** Start Sending Data to PC, activated manually (W=Data to send) ; returns CF = 1 if engine busy PCSend: setc ; presume engine busy btfsc kbActive ; is engine active? ret ; yes, return error btfsc kbRXCheck ; is engine waiting to RX? ret ; yes, return error PCSrun: movwf kbShBufL ; save data in shift register clrf kbFlags ; clear all flags clrf kbFlags2 #ifdef ODDPARITY bsf kbParityB ; set odd parity #endif clrc ; prepare shift register rlf kbShBufL ; insert StartBit (0) and rlf kbShBufH ; move DataBits to bits 1..8 bcf kbShBufH, 1 ; presume parity bit = 0 bsf kbShBufH, 2 ; set the StopBit (10) clrf kbCount ; not needed, decfsz leaves it 0 bsf kbActive clrc ; return ok ret