' Test and control a PC keyboard with a PIC ' for UK layout standard PC keyboard but easily modified for other layout ' F1 turns all keyboard LEDs on, F2 turns them off ' Escape key clears display ' uses parallel display on PORTD, keyboard clock on B.0 and keyboard data on B.1 Device = 16F877 Xtal 20 ' ----------------------------------- LCD used for tests Declare LCD_TYPE 0 Declare LCD_INTERFACE 4 Declare LCD_LINES 2 Declare LCD_DTPIN PortD.4 Declare LCD_RSPIN PortD.3 Declare LCD_ENPIN PortD.2 ' ----------------------------------- ' -------------------------------------------- Constants for control DIM Control AS $ED ' tell keyboard to expect data DIM Echo AS $EE ' request echo from keyboard DIM Code_Sel AS $F0 ' select code set DIM Get_ID AS $F2 ' Keyboard returns $AB, $83 DIM ACK AS $FA ' returned if keyboard receives good data DIM Err AS $FE ' returned if data not good DIM RST AS $FF ' send reset to keyboard DIM Boot_OK AS $AA ' returned after power up DIM Overrun_2 AS $00 ' buffer overrun for set 2 and 3 DIM Overrun_1 AS $FF ' buffer overrun for set 1 DIM Key_Up AS $F0 ' key up break code '--------------------------------------------- Alias INTERRUPT_REG bits SYMBOL RBIF = INTCON.0 ' PortB change flag SYMBOL INTF = INTCON.1 ' B.0 interrupt flag SYMBOL T0IF = INTCON.2 ' Timer 0 overflow flag SYMBOL RBIE = INTCON.3 ' Port B interrupt enable SYMBOL INTE = INTCON.4 ' Port B.0 interrupt enable SYMBOL T0IE = INTCON.5 ' Timer 0 interrupt enable SYMBOL PEIE = INTCON.6 ' Peripheral interrupt enable SYMBOL GIE = INTCON.7 ' All interrupts ON or OFF ' ------------------------------------------ Alias OPTION_REG bits SYMBOL PS0 = OPTION_REG.0 ' Prescaler select bit 0 SYMBOL PS1 = OPTION_REG.1 ' Prescaler select bit 1 SYMBOL PS2 = OPTION_REG.2 ' Prescaler select bit 2 SYMBOL PSA = OPTION_REG.3 ' Prescaler source, 1 = WDT, 0 = xtal/4 SYMBOL T0SE = OPTION_REG.4 ' Timer 0 edge select SYMBOL T0CS = OPTION_REG.5 ' Timer 0 clock source SYMBOL INTEDG = OPTION_REG.6 ' 1 = rising edge of Port B.0 SYMBOL RBPU = OPTION_REG.7 ' 0 = Port B pullup resistors = ON ' ------------------------------------------ SYMBOL Clock PortB.0 SYMBOL Dat PortB.1 ' ------------------------------------------ Variables DIM ASCII AS BYTE ' converted key data DIM TX_Count AS BYTE ' TX bit count DIM TX_Data AS BYTE ' Data byte to send to keyboard DIM Parity AS BYTE ' parity bit, is transferred to TX_Byte DIM RX_Count AS BYTE ' RX bit count DIM RX_Byte AS BYTE ' Received byte (complete) DIM RX_Temp AS BYTE ' Received bits DIM Leds AS BYTE ' keyboard Leds DIM Temp AS BYTE DIM Flags AS BYTE DIM Enter AS BYTE ' count enter press DIM RX_OK AS Flags.0 ' 1 = received packet complete DIM Make AS Flags.1 ' used by keys with make and brake code DIM Char_Cnt AS Flags.2 ' used to porcess key up DIM TRUE AS 1 DIM FALSE AS 0 ' ------------------------------------------- ON_INTERRUPT GOTO Key_Int GOTO Start ' ------------------------------------------- Interrupt routines Key_Int: ' IF TX_Count = 0 THEN GOTO RX_Int ' goto receive interrupt 'TX0: IF TX_Count = 9 THEN TX_Data = Parity ' send Parity 'TX1: IF TX_Count = 10 THEN ' TRISB = %00000011 ' B.0 to input ' GOTO TX4 ' ENDIF ' PortB.1 = 1 ' send a high bit ' @COMF PARITY,F ' compliment parity bit ' GOTO TX4 'TX3: PortB.1 = 0 ' send a low bit 'TX4: INC TX_Count ' next bit ' IF TX_Count = 12 THEN TX_Count = 0 ' done with TX 'TX5: GOTO Int_End ' ----------------------------------------------- ASM MOVF TX_Count,W ; if TX_Count = 0 send is not needed BTFSC STATUS,Z GOTO RX_Int ; goto receive interrupt ' ----------------------------------------------- send data to keyboard TX0 MOVF TX_Count,W ; send interrupt SUBLW 9 ; is this bit 9 BTFSS STATUS,Z GOTO TX1 ; no - skip MOVF Parity,W ; yes this is parity bit MOVWF TX_Data ; move parity into TX_Data TX1 MOVF TX_Count,W ; is this bit 10 SUBLW 10 BTFSS STATUS,Z GOTO TX2 ; no - skip MOVLW 255 ; set all inputs BSF STATUS,RP0 ; select page 1 MOVWF TRISB ; set B.1 as input BCF STATUS,RP0 ; select page 0 TX2 CLRC RRF TX_Data,F ; rotate next bit into carry BTFSS STATUS,C GOTO TX3 BSF PORTB,1 ; send a high bit COMF Parity,F ; compliment parity bit GOTO TX4 TX3 BCF PORTB,1 ; send a low bit TX4 INCF TX_Count ; next bit MOVF TX_Count,W SUBLW 12 ; is this bit 11 + 1 BTFSC STATUS,Z CLRF TX_Count ; yes so end of send, clear bit counter TX5 GOTO Int_End ENDASM ' -------------------------------------------- receive interrupt RX_Int: ' INC RX_Count ' next receive bit ' IF RX_Count = 11 THEN ' last bit ' RX_Byte = RX_Temp ' move temp to RX_Byte ' RX_OK = 1 ' signal byte received OK ' RX_Count = 0 ' reset bit counter ' GOTO Int_End ' exit ' ENDIF 'RX1: IF RX_Count = 10 THEN GOTO Int_End 'RX2: IF PortB.1 = 1 THEN ' @BSF STATUS,C ' move a high bit into temp ' @RRF RX_Temp,F ' GOTO Int_End ' ENDIF 'RX3: @BCF STATUS,C ' move a low bit into temp ' @RRF RX_Temp,F 'Int_End:INTF = 0 ' clear B.0 interrupt flag ' TMR0 = 0 ' reset timer ' CONTEXT RESTORE ' return ' ------------------------------------------------ ASM RX0 INCF RX_Count ; next bit MOVF RX_Count,W SUBLW 11 ; is this bit 11 BTFSS STATUS,Z GOTO RX1 ; no MOVF RX_Temp,W ; yes so copy RX_Temp to MOVWF RX_Byte ; RX_Byte BSF RX_OK ; and set flag for main loop CLRF RX_Count ; clear used variables CLRF RX_Temp GOTO Int_End ; and exit RX1 MOVF RX_Count,W SUBLW 10 ; is this bit 10 BTFSC STATUS,Z ; ignore parity bit GOTO Int_End ; yes so exit INT RX2 BTFSS PORTB,1 ; no so test B.1 data line GOTO RX3 BSF STATUS,C ; data bit is high RRF RX_Temp,F ; rotate into RX_Temp GOTO Int_End ; and skip next part RX3 BCF STATUS,C ; data bit is low RRF RX_Temp,F ; and rotate into RX_Temp Int_End BCF INTF ; clear B.0 interrupt flag CLRF TMR0 ; reset timer ENDASM CONTEXT RESTORE ; return ' ---------------------------------------------- TX_Start: ' send start bit WHILE GIE = 1 : GIE = 0 : WEND ' turn off interrupts DELAYUS 50 ' might need this at 20MHz PORTB = 0 TRISB = 0 DELAYUS 100 ' hold data and clock low to signal start of send TX_Count = 1 ' that was start bit or bit 1 Parity = 1 TRISB = %00000001 ' release clock line INTF = 0 GIE = 1 RETURN ' -------------------------------------------- start of main prog Start: TRISB = 3 ' B.0 and B.1 input PORTB = 0 ' keyboard WHILE GIE = 1 : GIE = 0 : WEND ALL_DIGITAL = TRUE CLEAR PORTD = 0 ' test LEDs TRISA = 0 ' all output TRISD = 0 ' all output TMR0 = 0 RBPU = 0 ' pullups ON INTEDG = 0 T0CS = 0 PS0 = 1 ' prescaler bits PS1 = 0 PS2 = 1 PSA = 0 ' --------------------------------------------- DELAYMS 100 PRINT CLS,"Press escape to" ' test display PRINT AT 2,1,"clear display " ' --------------------------------------------- T0IE = 0 INTF = 0 INTE = 1 GIE = 1 ' --------------------------------------------- for test only ' ----------- Send commands to select set 3, not available on all keyboards ' TX_Data = $F0 ' code set select command ' GOSUB TX_Start ' start sending to keyboard ' WHILE TX_Count > 0 : WEND ' wait for end of send ' TX_Data = 3 ' set 3 ' GOSUB TX_Start ' start sending to keyboard ' WHILE TX_Count > 0 : WEND ' wait for end of send ' --------------------------------------------- reset keyboard.' This was used during bootloader programming. The keyboard LED status cannot be read ' so we have to reset to a known state each time the PIC is reset TX_Data = $ED ' LED control command GOSUB TX_Start ' send start bit to control keyboard LEDS = 0 ' clear LED variable ready for sending to keyboard ' --------------------------------------------- Main loop M1: IF RX_OK = FALSE THEN GOTO M5 ' receive incomplete or nothing to do RX_OK = FALSE ' clear flag ' PRINT HEX RX_Byte," " ' show received byte on display IF RX_Byte = $12 THEN GOTO M4 ' left shift is special case ' all of the GOTO M5 in the following code shorten the loop by not processing ' tests that are not needed ' --------------------------------------------- following code used with SET 2 ' to eliminate key release codes IF RX_Byte = Key_Up THEN ' ignore key up Make = 1 GOTO M5 ENDIF IF Make = 1 THEN ' ignore character after key up Make = 0 GOTO M5 ENDIF ' ---------------------- if a byte was sent to keyboard expect $FA to be returned IF RX_Byte = $FA THEN ' packet received by KYBD OK TX_Data = LEDs ' so send LED data GOSUB TX_Start GOTO M5 ENDIF ' ----------------------------------------- now check for function keys M2: IF RX_Byte = $05 THEN ' was received byte 'F1' TX_Data = $ED ' keyboard control command GOSUB TX_Start ' start sending to keyboard LEDs = 7 ' turn all three leds on GOTO M5 ENDIF M3: IF RX_Byte = $06 THEN ' was received byte 'F2' TX_Data = $ED GOSUB TX_Start LEDs = 0 GOTO M5 ENDIF IF RX_Byte = $76 THEN ' 'Esc' key set 2 (default) ' IF RX_Byte = $08 THEN ' 'Esc' key set 3 PRINT CLS ' clear display if 'ESC' received GOTO M5 ENDIF IF RX_Byte = $58 THEN ' 'caps lock' set 2 ' IF RX_Byte = $14 THEN ' 'Caps Lock' set 3 LEDs = LEDs ^ %00000100 TX_Data = $ED GOSUB TX_Start GOTO M5 ENDIF ' ----------------------------------- enter key down = $E0,5A enter key up = $E0,F0,5A IF RX_Byte = $5A THEN ' test for enter key Enter = Enter + 1 ' count number of presses PRINT CLS,"Enter key ",DEC Enter GOTO M5 ENDIF M4: ' -------------------------------------------- shift keys IF RX_Byte = $12 THEN ' $12 = 'left shift' set 2 and set 3 = dec 18 ' $89 = 'right shift' set 2 and set 3 LEDs = LEDs | %00000001 ' indicate on 'scroll lock' LED IF Make = TRUE THEN LEDs =LEDS ^ %00000001 MAKE = FALSE ENDIF TX_Data = $ED GOSUB TX_Start GOTO M5 ENDIF ' ------------------------------- all other tests done so call ASCII conversion GOSUB M7 M5: ' ------------------------------------------- end of main IF T0IF = 0 THEN GOTO M1 IF TX_Count = 0 THEN GOTO M1 RX_Count = 0 RX_Byte = 0 T0IF = 0 GOTO M1 M6: ' -------------------------------------------- M7: ' -------------------------------------------- decode keyboard to ASCII WHILE GIE = 1 : GIE = 0 : WEND ' turn off interrupt while processing PORTB.0 = 0 ' key data TRISB = $00000010 ' low clock to stop KYBD output TMR0 = 0 ' Print HEX RX_byte," " IF RX_Byte = 85 THEN ' UK keyboard + and = Temp = 58 ' add to end of list GOTO M8 ' skip next bit ENDIF Temp = RX_Byte - 21 IF Temp > 57 THEN ASCII = 0 GOTO M9 ENDIF M8: ' ------------------------------------------- IF LEDs = 0 THEN ' caps = off, shift = off ASCII = LREAD SET1 + Temp GOTO M9 ENDIF IF LEDs = 1 THEN ' caps = off, shift = on ASCII = LREAD SET2 + Temp GOTO M9 ENDIF IF LEDs = 4 THEN ' Caps = on, shift = off ASCII = LREAD SET3 + Temp GOTO M9 ENDIF ASCII = LREAD SET4 + Temp ' LEDS = 5, caps = on, shift = on M9: ' ------------------------------------------- IF ASCII > 0 THEN PRINT ASCII ' PRINT AT 2,1,DEC LEDs," " TRISB = $00000011 ' return clock pin to input TMR0 = 0 INTF = 0 GIE = 1 RETURN STOP END SET1: LDATA "q1",0,0,0,"zsaw2",0,0,"cxde43",0,0," vftr5",0,0,"nbhgy6",0,0,",mju78",0,0,",kio09",0,0,".?l:p-=",0 SET2: LDATA "Q!",0,0,0,"ZSAW",34,0,0,"CXDE$#",0,0," VFTR%",0,0,"NBHGY^",0,0,",MJU&*",0,0,",KIO)(",0,0,"./L;P_+",0 SET3: LDATA "Q1",0,0,0,"ZSAW2",0,0,"CXDE43",0,0," VFTR5",0,0,"NBHGY6",0,0,",MJU78",0,0,",KIO09",0,0,"./L;P-=",0 SET4: LDATA "q!",0,0,0,"zsaw",34,0,0,"cxde$#",0,0," vftr%",0,0,"nbhgy^",0,0,",mju&*",0,0,",kio)(",0,0,".?l:p_+",0 ; SET1 = normal ; SET2 = shift. DEC 34 is ASCII for " key ; SET3 = caps lock on ; SET4 = Caps lock on, shift key down, is same as SET1 except number keys