; PiCBoard ; ; PC-Keyboard emulator using a PIC16F84 ; Scan a 32 key keyboard ( with alternative mapping = 64 keys ) ; ; COPYRIGHT (c)1999 BY Tony Kübek ; This is kindly donated to the PIC community. It may be used freely, ; and you are forbidden to deprive others from those rights. ; Please read the FSF (Free Software Foundation) GNU statement. ; ;********************************************************************* ; ; E-MAIL tony.kubek@flintab.se ; ; ; DATE 2000-01-23 ; ITERATION 1.0B ; FILE SAVED AS PiCBoard.ASM ; FOR PIC16F84-10/P ; CLOCK 10.00 MHz RESONATOR ( OSC-HS ) ; INSTRUCTION CLOCK 2.50 MHz T= 0.4 us ; SETTINGS WDT-ON, PWT-ON, CP-OFF ; REVISION HISTORY ; 0.1b - First beta, mainly for testing ; 1.0b - First public beta release ; ; ; ; ;*************************************************************************** ; ; PREFACE ;-) ; ; This is NOT an tutorial on pc keyboards in general, there are quite ; a few sites/etc that have that already covered. However i DID find ; some minor ambiguities regarding the actual protocol used but nothing ; that warrants me to rewrite an complete pc keyboard FAQ. ; So for 'general' pc keyboard info ( scancodes/protocol/pin config/etc ) ; here are some useful links: ; ; http://www.senet.com.au/~cpeacock/ ; http://ourworld.compuserve.com/homepages/steve_lawther/keybinfo.htm ; http://204.210.50.240/techref/default.asp?url=io/keyboard.htm ; http://www.arne.si/~mauricio/PIC.HTM ; ; PLEASE do not complain about code implementation, I know there are parts ; in which is it 'a bit' messy and hard to follow, and other parts where ; the code is not optimised. Take it as is. Futhermore I did hesitate ; to include all of the functionality thats currently in, but decided to ; keep most of it, this as the major complaint i had with the other available ; pc-keyboard code was just that - 'is was incomplete'. Also do not ; be discoraged by the size and complexity of it if you are an beginner, ; as a matter of fact this is only my SECOND program ever using a pic. ; I think I managed to give credit were credit was due ( 'borrowed code' ). ; But the originators of these snippets has nothing to do with this project ; and are probably totally unaware of this, so please do not contact them ; if you have problems with 'my' implementation. ; ; BTW ( shameless plug ) UltraEdit rules ! ( if you dont have it, get it ! ) ; http://www.ultraedit.com ( dont 'forget' the 'wordfile' for PIC, color indenting ) ; Without that I guess this file would be 'messy'. ; ; Ok with that out of the way here we go: ; ; ;*************************************************************************** ; DESCRIPTION ( short version ;-) ) ; A set of routines which forms a PC keyboard emulator. ; Routines included are: ; Interrupt controlled clock ( used for kb comm. and 'heart beat ) ; A 4x8 matrix keyboard scanner ( using an 74HCT4051 3 to 8 analogue multiplexer ) ; Communincation with PC keyboard controller, both send end recive. ; PC keyboard routines are 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 ; ; ( ASCII art/info shamelessly 'borrowed' from http://www.arne.si/~mauricio/PIC.HTM ) ; ; vcc vcc ; | | ; \ -+- ; / 2K2 /_\ 1N4148 ; \ | ; pcCLOCK_in -----------------o---o------o--------- kbd CLOCK ; | | | ; 2N2222 |50pF -+- ; pcCLOCK_out ____/\/\/\____|/ === /_\ ; 2K2 |\> | | ; | | | ; /// /// /// ; ; An identical circuit is used for the DATA line. ; Note: The 2N2222 transitors can be replaced with BC337 ( NPN ) which are cheaper ; The keyboard matrix routines are using RB4-RB7 as inputs. ; and RB0-RB2 as output to/from an 3 to 8 multiplexer ; so that it can read up to 4x8 keys ( 32 ). ; ; RA4/TOCK1 is an input from an jumper, if low then keyrepeat is disabled and ; alt-key is enabled instead i.e. instead of repeating a key that has ; been depressed a certain amount of time, a bit is set that can change the ; scancode for a key ( of course, all keys can have an alternate scancode ). ; To exit the alt. keymap press the 'enter alt. keymap' key once again or wait until ; timeout. ( defined in the code ) ; NOTE !! The so called 'enter alt. keymap' key is hardcoded, i.e the key ; i've choosen in this 'example' is Column 1 Row 2, if this is to be changed ; code has to be changed/moved in the debounce and checkkeystate routines. ; ( marked with <-------Alt keymap code-------> ) ; RB3 is currently used for a flashing diode. ( running ) ; Note my real keyboard ( KeyTronic ) uses a clock rate of 40 us, my implementation ; uses about 50 us, easily to change. ; ;*************************************************************************** ; DESCRIPTION ( longer version ;-) ) ; ; Pin Used for ; ------------------------------------------------------------ ; RA0 Pc keyboard data out ( to pc ) ; RA1 Pc keyboard data in ( from pc ) ; RA2 Pc keyboard clock in ; RA3 Pc keyboard clock out ; RA4 Flashing disco led 0.5s duty cycle( could be used for some more meaningful purpose ) ; ; RB0 Least significant bit in column adress to 4051 multiplexer ( own keyboard ) ; RB1 Middle... ; RB2 Most significant bit -- || -- ; RB3 Input. Disable keyrepeat & enable alternative keymapping ( if = '0',ground) ; OR if '1' ( free,+V ) enable keyrepeat & disable alternative keymapping ; RB4 Own keyboard input row 1 ; RB5 --- || --- row 2 ; RB6 --- || --- row 3 ; RB7 --- || --- row 4 ; ; 'Basic program structure': ; ; Init - Initialise ports , ram, int, vars ; Start delay - After init the timer int is enabled and the flashing led will ; start to toggle ( flash ). Before I enter the mainloop ; ( and send any keycodes ) I wait until the led has flashed ; twice. This is of course not really needed but I normally ; like to have some kind of start delay ( I know 1 sec is a bit much :-) ) ; Time Int - The timer interrupt (BIG), runs in the background: ; - 'Normal' rate 0.5 ms, when in rx/tx rate is 27 us ; - Performs an rx/tx check, then jumps to rx, tx or 'heart beat' code. ; - TX code sends a byte to pc, at a rate of 27us per int. ; The int rate is actually double the bit rate, as ; a bit is shifted out in the middle of the clock pulse, ; I've seen different implementations of this and I think ; that the bit is not sampled until clock goes low again BUT ; when logging my keyboard ( Keytronic ) this is the way that it ; does it. When all bits are sent, stopbit/parity is sent. ; And the key is removed from the buffer. ; After stopbit/parity is sent, a delay is inserted ( 0.5 ms ) ; before next rx/tx check. ; - RX code recevies a byte from the pc, PIC is controlling clock !! ; Int rate ( 27 us ) is, again, double bit rate. ; Toggles clock and samples the data pin to read a byte from pc. ; When reception is finished an 'handshake' takes place. ; When a byte has been recevied a routine is called to check ; which command and/or data was received. If it was ; keyboard rate/delay or led status byte, it is stored in local ram ; variables. NOTE: The rate/delay is not actually used for ; key repeat as the code is right now ( I use my 'own' fixed rate/delay ) ; however it is very easy to implement. ; After handshake a delay is inserted ( 0.5 ms ) ; before next rx/tx check. ; - 'Heart beat' ( idle code 0.5 ms tick ) performs: ; - Check clock/data lines to see if pc wants to send something or ; if tx is allowed. ; - If tx is possible it checks the keybuffer for an available key and ; if keys are in buffer then it initiates a tx seq. ; and sets the int rate to 27 us. ; - If the pc wants to send something, an rx seq. is initiated ; ( there is some handshaking involved, during which ; the int rate is set to 60 us ) after that, the int rate is ; set to 27 us and an rx seq is started. ; - Divides some clock counters to achive 10ms,100ms,500ms sections. ; - In 100 ms section it performes a numlock status check and ; keyrepeat check ( both rate and delay is local in 100 ms ticks, ; thats why I dont use the 'real' rate delay ) ; - If numlock status is not the desired one code is called to ; toggle the numlock status. ; - If a key has been pressed long enough for repeat, an bit is set ; so we can repeat the key ( send the scancode again ) in the main loop. ; - In 500 ms section the led is toggled on each loop ; - Some various alternative keymap checks to get out of ; alternative keymap. ( i'll get to that in a bit ) ; ; Main loop - Outputs an adress to the 4051 multiplexer waits 1 ms ; reads the row inputs ( 4 bits/keys ), increments address ; and outputs the new adress, waits 1 ms and reads the input ; ( next 4 bits/keys ). Now using the address counter, calls a ; debounce/send routine that first debounces the input, ; ( four consecutive readings before current state is affected ) ; and when a key is changed a make/break code is sent ( put in buffer ). ; In the next loop the next two columns are read etc. until all ; 4 column pairs are read. ; - If keyrepeat is enabled ( see pin conf. above ) the ; repeat flag is checked and if '1' the last pressed key scancode ; is sent again ( put in buffer ). ; - If keyrepeat is not enabled( alternative keymap is enabled instead ) ; then various checks to exit the alternative keymap are performed instead. ; ; Scancodes for all key are located in a lookup table at the end of this file, ; each key has four program rows, to make room for extended codes and alt. keymap codes. ; ; Explanation of 'alternative' keymap: ; ; Using this program ( or an heavily modified version of it anyway :-) ) ; on a computer running Windows posed some small problems; namely: ; -The keyboard ( mapping ) I used did not have any 'special' key such as ; ,, etc. ; -In windows, things can go wrong, :-) if a dialog pops up or something similar ; there were just no way one could dispose of this with the keymapping i used. ; - 'Only' 28 keys were implemented ( hardware wise ). ; In this particular case the keyrepeat was disabled ( due to the nature of the application ) ; Therefore i came up with the solution to use the keyrepeat related routines and vars. ; To handle a so called 'alternative' keymapping. ; This means that an key is dedicated to be the alt. keymap toggle key, ; when pressing this longer than the programmed repeat delay, instead of ; repeating the key a bit variable is set to use another set of scancodes for ; the keyboard. This 'alternative' keymap is then enabled even though the ; alt. keymap toggle key is released, but it also incorporates an timeout ; that will return to normal keymap if no key is pressed within a certain time ( currently 7 sec ) ; Of course pressing the alt. keymap toggle key again ( while in alt. keymap ) ; will return the keyboard to the normal keymap. ; NOTE !! the key choosen for the alt. keymap toggle is hardcoded, so if the scancode ; for this is changed in the lookup table, changes have to be made in other routines ; as well. ( namly : CHECK_KEY_STATE, and DEBOUNCE_COLUMN routines ) ; While in alt. keymap each key CAN have an alternative scancode ( see lookup table ). ; Also by using the numlock bit, one can toggle numlock status 'on the fly' when entering ; or exiting the alt keymap. ; ; Some notes about the local keyboard interface ( matrix ): ; Although the hardware circuit and software allows virtually unlimited ; simultaneosly pressed keys, the keyboard matrix itself normally poses ; some limitations on this. If an keymatrix without any protective diodes ; are used then one would have loops INSIDE the keymatrix itself when ; multiple keys are pressed in different columns . ; Look at the ( although horrible ;-) ) ASCII art below(internal weak pullup enabled): ; 0 - Key is free ; 1 - Key is pressed ( connection between hor/ver rows ) ; Three keys pressed adressing ( reading ) left column : ; ; To pic1 --------0-------0------ ( row 1 ) ; | | ; To pic2 --------1-------1------ ( row 2 ) ; | | ; To pic3 --------1-------0------ ( row 3 ) ; | | ; Column(4051) 0V - ( Current column is set to 0V when adressing ) ; ; This works as intended, we can read a '0' on pic inputs 2,3 which ; is what we expected. The current ( signal ) follows the route marked with '*': ; ( only the 'signal path' is shown for clarity ) ; ; To pic1 --------0-------0------ ( row 1 ) ; | | ; To pic2 *********-------1------ ( row 2 ) ; * | ; To pic3 *********-------0------ ( row 3 ) ; * | ; Column(4051) 0V - ( Current column is set to 0V when adressing ) ; ; However, when we now read ( address ) the right column instead we ; do not read what is expected ( same three keys still pressed ): ; ; To pic1 --------0-------0------ ( row 1 ) ; | | ; To pic2 *****************------ ( row 2 ) ; *<- * ; To pic3 *********-------*------ ( row 3 ) ; | * ; Column(4051) - 0V ( Current read column is set to 0V when adressing ) ; ; As you can see the 'signal' travels in the keymatrix itself ( where the '<-' is )and ; will cause a 'ghost' signal to be read on the pic. So instead ; of having an '0' on input 2 only we also can read an '0' on input 3. ; This is because the two keys in column 1 are interconnected ( when they are pressed ). ; Keep this in mind if you are planning to support multiple pressed keys. ; ; ;*************************************************************************** ; ; Some suggestions for 'improvements' or alternations ; ; - Using the jumper 'disable-repeat' as a dedicated key for switching ; to alternative keymapping. ; - Enable repeat in alternative keymapping ; - Clean up TX/RX code ( a bit messy ) ; - Using the led output ( or jumper input ) as an extra adress line ; to the multiplexers ( in this case 2 pcs. 74HCT4051 ) we could have ; 4x16 keys instead. Would require some heavy modifications though ; as there are not much ram/program space left. But if alternative ; keymapping is discarded ( most likely if one has 64 keys ) each ; key in the lookup table only needs to be 2 lines instead of 4. ; That would 'only' require some modifications to preserv ram. ; - Using the EERAM for 'macros' or similar ( not used at all now ) ; ;*************************************************************************** ; ; LEDGEND ; ; I tend to use the following when naming vars. etc. : ; ( yes i DO like long names ) ; ; For 'general' purpose pins: ; ; An input pin is named I_xxx_Name where : ; ; I_ - This is an input pin ;-) ; xxx_ - Optional what type of input, jmp=jumper etc. ; Name - Self explanatory ; ; An output pin is named O_xxx_Name where: ; ; O_ - This is an output pin ;-) ; xxx_ - Optional what type of output, led=LED etc. ; Name - Self explanatory ; ; Application(function) specific pins: ; ; An application(function) specific pin is named xxName where: ; ; xx - What/Where, for example pc=To/From pc ; Name - Self explanatory ( what does it control etc ) ; ; An #define/constant/equ (no pin or ram ) uses all capital letters e.x. #define BREAK 0xF0 ; ; A bit variable will always start with '_'. For example '_IsLedStatus' ; ; All other ( mostly ramvars. ) are named in various ways. ; ; ;*************************************************************************** TITLE "PC Keyboard emulator - By Tony Kübek" Processor 16F84 Radix DEC EXPAND ;***** HARDWARE DEFINITIONS ( processor type include file ) INCLUDE ; this might need changing ! ;***** CONFIGURATION BITS __CONFIG _WDT_ON&_HS_OSC&_CP_OFF&_PWRTE_ON ; _WDT_OFF __IDLOCS 010Bh ; version 1.0B ;***** CONSTANT DEFINITIONS CONSTANT BREAK = 0xF0 ; the break key postfix ( when key is released ) CONSTANT EXTENDED = 0xE0 ; the extended key postfix ; As i dont really use the rate/delay I receive from the pc ( easy to change ) ; this is the current rate/delay times i use: CONSTANT DELAY_ENTER_ALTKEYMAP = 0x1E ; x100 ms , approx 3 seconds ( 30 x 100 ms ) ; how long the 'enter altkeymap' key must ; be in pressed state before the altkeymap is enabled CONSTANT DELAY_EXIT_ALTKEYMAP = 0x0F ; x0.5 sec , approx 7.5 sec ; how long before we exit the alt keymap if no key is ; pressed. CONSTANT DELAY_REPEAT = 0x08 ; x100 ms, approx 800 ms ; how long before we START repeating a key CONSTANT DELAY_RATE = 0x02 ; x100 ms, approx 200 ms repeat rate ; how fast we are repeating a key ( after the delay above ) ;***** CONSTANT DEFINITIONS ( pins ) ; For connection with PC keyboard #define pcCLOCK_in PORTA,2 ; Input PC keyboard clock #define pcCLOCK_out PORTA,3 ; Output PC keyboard clock #define pcDATA_in PORTA,1 ; Input PC keyboard data #define pcDATA_out PORTA,0 ; Output PC Keyboard data ; For connection (input) with our own keyboard ; Note I actually dont use these (definitions!) in the program, but they might come in handy ; at one time or another ;-) ( I use the pins though.. ) #define kbROW_1 PORTB,7 #define kbROW_2 PORTB,6 #define kbROW_3 PORTB,5 #define kbROW_4 PORTB,4 ; Indications ( output ) #define O_led_KEYCOMM_ok PORTA,4 ; communication seems ok led ( flashing ) ; Disable/enable key repeat input jumper #define I_jmp_NoRepeat PORTB,3 ; note: internal weak pullup enabled ; For keybuffer ( using the indirect file selector FSR ) #define KeyBufferHead FSR ;***** RAM ASSIGNMENT CBLOCK 0x0C KeyBufferTail ; where the last byte in buffer is.. clkCount ; used for clock timing Offset ; used for table reads Saved_Pclath ; Saved registers during interrrupt Saved_Status ; ----- Saved_w ; ----- CurrKey ; current key ( rx or tx ).. KeyParity ; key parity storage ( inc. for every '1' ) Divisor_10ms ; for the timer Divisor_100ms ; ditto Divisor_500ms ; Divisor_Repeat ; timer for repeated key sends Flags ; various flags RepeatFlags ; flags for repeating a key bitCount ; bitcounter for tx/rx Comm_Flags ; flags used by both rx and tx routines Temp_Var ; temp storage, can be used outside int loop TRX_Flags ; flags used by both rx and tx routines CommandData ; bit map when receving data bytes from pc ; for example led status/ delay / etc KbLedStatus ; to store status led bitmap for keyboard ( pc first sends 'ED' then this byte ) ; bit 0=Scroll lock ( 1=on ) ; bit 1=Num lock ; bit 2=Caps lock ; bits 3-7 = unused KbRateDelay ; to store repeat delay/rate ( pc first sends 'F3' then this byte ) ; bit 0-4 (rate) = '00000' is 30x/sec '11111' is 2x/sec ( i.e. value * 33 ms ) ; bit 5-7 (delay)= '00' is 250 ms, '11' is 1000 ms ( ie. value * 250 ms ) ; bit 7 = unused BufTemp ; temp byte for storing scancode to put in buffer Temp ; temp byte, used locally in buffer routine Temp2 ; LastKey ; stores the last sent key KbBufferMin ; where our keybuffer starts Kb1 ; used in keybuffer Kb2 ; used in keybuffer Kb3 ; used in keybuffer Kb4 ; used in keybuffer Kb5 ; used in keybuffer Kb6 ; used in keybuffer Kb7 ; used in keybuffer Kb8 ; used in keybuffer Kb9 ; used in keybuffer Kb10 ; used in keybuffer KbBufferMax ; end of keybuffer TempOffset ; temporary storage for key offset ( make/break ) LastMakeOffset ; storage of last pressed key ( offset in table ) RepeatTimer ; timer to determine how long a key has been pressed RepeatKey ; the key to repeat repTemp ; temporary storage in repeat key calc. repKeyMap ; bit pattern for the column in which the repeat key is in ; i.e. a copy of kbColumnXX_Old where 'XX' is the column LastKeyTime ; counter when last key was pressed, used to get out of altkeymap ; after a specific 'timeout' kbScan ; scan code for pressed/released key kbTemp ; temp storage for key states kbState ; which keys that has changed in current columns kbBitCnt ; bit counter for key check ( which key/bit ) kbColumnCnt ; column counter ( loops from 8 to 0 ) ; Used as output to multiplexer/decoder and in debounce routines kbColumnVal ; current value of the last 2 columns ( 2x4bits = 8 bits ) input pins ( keys ) ; ; Note the kbColumnXX_New variables is not really needed ; used it while making the program ( debugging ;-) ). ; so if more free ram is needed change code using these to use ; the current 'input' sample instead. ( kbColumnVal ) kbColumn12_New ; New debounced reading for column 1 & 2 kbColumn12_Old ; Latest known valid status of column 1 & 2 kbColumn12Cnt ; Debounce counter for column 1 & 2 kbColumn12State ; State of debounce for column 1 & 2 kbColumn34_New ; New debounced reading for column 3 & 4 kbColumn34_Old ; Latest known valid status of column 3 & 4 kbColumn34Cnt ; Debounce counter for column 3 & 4 kbColumn34State ; State of debounce for column 3 & 4 kbColumn56_New ; New debounced reading for column 5 & 6 kbColumn56_Old ; Latest known valid status of column 5 & 6 kbColumn56Cnt ; Debounce counter for column 5 & 6 kbColumn56State ; State of debounce for column 5 & 6 kbColumn78_New ; New debounced reading for column 7 & 8 kbColumn78_Old ; Latest known valid status of column 7 & 8 kbColumn78Cnt ; Debounce counter for column 7 & 8 kbColumn78State ; State of debounce for column 7 & 8 ENDC ;******* END RAM ; *** flags used by both rx and tx routines #define _isParity Comm_Flags,0 ; bit in rx/tx is parity bit #define _KeyError Comm_Flags,1 ; set to '1' when an error is detected #define _isStartBit Comm_Flags,2 ; set to '1' when bit in rx/tx is startbit #define _isStopBit Comm_Flags,3 ; --- || --- but stopbit #define _RxCanStart Comm_Flags,4 ; for handshaking, when negotiating an rx seq. ; *** dito used for rx and tx #define _KeyReceived TRX_Flags,0 ; rx #define _RX_Mode TRX_Flags,1 ; rx is in progress ( started ) #define _doRXAck TRX_Flags,2 ; do rx handshake #define _RXAckDone TRX_Flags,3 ; rx handshake is done #define _RXEnd TRX_Flags,4 ; rx seq is finished #define _RXDone TRX_Flags,5 ; rx exit bit #define _KeySent TRX_Flags,6 ; tx key has been succesfully sent #define _TX_Mode TRX_Flags,7 ; tx is in progress ( started ) ; *** flags to determine the meaning of an incomming data is ; i.e. when then pc has sent a 'data follow' instruction ; these bits are set according to the command, ; i.e. the next incomming byte is..... #define _IsLedStatus CommandData,0 ; the next incoming byte contains kb led status #define _IsRateDelay CommandData,1 ; the next incoming byte contains kb rate/delay #define _SkipByte CommandData,2 ; other data not saved ; *** bit meaning in KbLedStatus ( the leds on the keyboard ) ; i.e local ram copy of this byte #define _LedScrollLock KbLedStatus,0 ; '1' led is on #define _LedNumLock KbLedStatus,1 ; #define _LedCapsLock KbLedStatus,2 ; #define _IsFirstLedStatus KbLedStatus,7 ; set this to '1' at startup, to know that our local ; ; copy (led status) is not yet syncronised. Used to ; ; 'force' sync. the first time we set the local numlock bit. ; *** flags used for various purposes #define _Timer Flags,0 ; used for waiting ;#define _WrongPar Flags,1 ; ?? ( used during dev. to force wrong parity sends, removed ) #define _LastColumn Flags,2 ; for kb scan code #define _isBreak Flags,3 ; if '1' the currently handled key is break ( released ), else make ( pressed ) ;#define _isFree Flags,4 ; Not used ! ( temporary during dev. ) #define _AltKeymap Flags,5 ; is we use alternative keymap ( other scancodes ) see below ! ; enabled ONLY when keyrepeat is disabled ( I_jmp_NoRepeat (RB3) is low ) #define _ExitAltKeymap Flags,6 ; start checking if we should exit alternative keymap ; if all keys are 'free' ( not pressed ) we exit altkeymap #define _InAltKeymap Flags,7 ; if we are waiting for second press/release to get out of altkeymap #define _doRepeat RepeatFlags,0 ; if the last pressed key should be 'repeated' #define _doSendKey RepeatFlags,1 ; send the key in RepeatKey to pc #define _isExtended RepeatFlags,2 ; temp flag if last key is extended #define _RepeatIsExt RepeatFlags,3 ; the key to repeat is extended key #define _startRepeat RepeatFlags,4 ; start repeat timer #define _DoExitAltKeymap RepeatFlags,5 ; bit set when we are getting out of alternative keymap #define _NumLock RepeatFlags,6 ; 'mirror' of numlockstatus, by setting/clearing this bit ; numlock status will be changed. ; I.e. there is no need to 'manually' send break/make code for numlock ; key, by setting this bit to '1' numlock status will by automaticlly ; ( inside int handler ) set to 'on'( within 100 ms ). Se code for example. #define _WaitNumLock RepeatFlags,7 ; bit set when we have sent make/break numlock scancode ; and waiting for numlock status byte reply. ; ( to inhibit a new numlock scancode send ) ;************************************************************************** ; Macros ;************************************************************************** ;+++++ ; BANK0/1 selects register bank 0/1. ; Leave set to BANK0 normally. BANK0 MACRO BCF STATUS,RP0 ENDM BANK1 MACRO BSF STATUS,RP0 ENDM ;+++++ ; PUSH/PULL save and restore W, PCLATH and STATUS registers - ; used on interrupt entry/exit PUSH MACRO MOVWF Saved_w ; Save W register on current bank SWAPF STATUS,W ; Swap status to be saved into W BANK0 ; Select BANK0 MOVWF Saved_Status ; Save STATUS register on bank 0 MOVFW PCLATH MOVWF Saved_Pclath ; Save PCLATH on bank 0 ENDM PULL MACRO BANK0 ; Select BANK0 MOVFW Saved_Pclath MOVWF PCLATH ; Restore PCLATH SWAPF Saved_Status,W MOVWF STATUS ; Restore STATUS register - restores bank SWAPF Saved_w,F SWAPF Saved_w,W ; Restore W register ENDM ;+++++ ; We define a macro that will switch an output pin on or off depending ; on its previous state. We must be on bank0 !! ; TOGGLE_PIN MACRO WHICH_PORT,WHICH_PIN LOCAL TOGGLE_PIN10, TOGGLE_END BTFSC WHICH_PORT,WHICH_PIN ; is the pin high ? GOTO TOGGLE_PIN10 ; yes, clear it BSF WHICH_PORT,WHICH_PIN ; no, so set it GOTO TOGGLE_END TOGGLE_PIN10: BCF WHICH_PORT,WHICH_PIN ; clear the pin TOGGLE_END: ENDM ;************************************************************* ; Credit for routine ( almost unchanged, expect it's now a macro ;-) ) ; goes to Scott Dattalo http://www.interstice.com/~sdattalo/technical/software/software.html ; ( debouce routine at http://www.interstice.com/~sdattalo/technical/software/pic/debounce.html ) ; ; DEBOUNCE_BYTE - 'debounces' 8 bits, when a bit state ; has been 'active' for 4 consecutive debounce loops ; it's state is put into the 'debounced' sample ( i.e. output ) ; ; The purpose of this routine is to debounce, i.e. digitally low pass filter ; inputs. The algorithm handles upto 8 bits at a time. An input is considered ; filtered if it has not changed states in the last 4 samples. ; ; 2-bit cyclic vertical counters count the 4 samples. As long as there is no ; change, the counters are held in the reset state of 00b. When a change is detected ; between the current sample and the filtered or debounced sample, the counters ; are incremented. The counting sequence is 00,01,10,11,00... When the counters ; roll over from 11b to 00b, the debounced state is updated. If the input changes ; back to the filtered state while the counters are counting, then the counters ; are re-initialized to the reset state and the filtered state is unaffected. ; In other words, a glitch or transient input has been filtered. ; ; Here's the C-psuedo code: ; ;static unsigned clock_A,clock_B,debounced_state ; ;debounce(unsigned new_sample) ;{ ; unsigned delta; ; ; delta = new_sample ^ debounced_state; //Find all of the changes; ; clock_A ^= clock_B; //Increment the counters ; clock_B = ~clock_B; ; ; clock_A &= delta; //Reset the counters if no changes ; clock_B &= delta; //were detected. ; ; //Preserve the state of those bits that are being filtered and simultaneously ; //clear the states of those bits that are already filtered. ; debounced_state &= (clock_A | clock_B); ; //Re-write the bits that are already filtered. ; debounced_state |= (~(clock_A | clock_B) & new_sample); ;} ; ; The 2-bit counters are arranged "vertically". In other words 8 counters ; are formed with 2 bytes such that the corresponding bits in the bytes are ; paired (e.g. MSBit of each byte is paired to form one counter). ; The counting sequence is 0,1,2,3,0,1,... And the state tables and Karnaugh ; maps are: ; ;State Table: Karnaugh Maps: ;pres next B ; SS SS 0 1 ; AB AB +---+---+ +---+---+ ;-------- A 0| | 1 | | 1 | | ; 00 01 +---+---+ +---+---+ ; 01 10 1| 1 | | | 1 | | ; 10 11 +---+---+ +---+---+ ; 11 00 A+ = A ^ B B+ = ~B ; ; Here's the PIC code that implements the counter: ; MOVF SB,W ;W = B ; XORWF SA,F ;A+ = A ^ B ; COMF SB,F ;B+ = ~B ; 14 instructions ; 15 cycles ; Inputs: ; NewSample - The current sample ; Outputs ; DebouncedSample - The current value (filtered version of NewSample) ; ; VARS used ; DBCnt, ; DBState - State variables for the 8 2-bit counters ; DEBOUNCE_BYTE MACRO NewSample,DebouncedSample,DBCnt,DBState ;Increment the vertical counter MOVF DBState,W XORWF DBCnt,F COMF DBState,F ;See if any changes occurred MOVF NewSample,W XORWF DebouncedSample,W ;Reset the counter if no change has occurred ANDWF DBState,F ANDWF DBCnt,F ;Determine the counter's state MOVF DBState,W IORWF DBCnt,W ;Clear all bits that are filtered-or more accurately, save ;the state of those that are being filtered ANDWF DebouncedSample,F XORLW 0xff ;Re-write the bits that haven't changed. ANDWF NewSample,W IORWF DebouncedSample,F ENDM ;************************************************************************** ; Program Start ;************************************************************************** ; Reset Vector ORG H'00' ; For the sole purpose of squeezing every last byte of the programming mem ; I actually use the 3 program positions before the interrupt vector ; before jumping to the main program. Take note though that ; ONLY 3 instructions are allowed before the jump to main loop !! BANK0 CLRF PCLATH CLRF INTCON GOTO INIT ;************************************************************************** ; Interrupt routine ; An humongously big int handler here ;-) ; but the keyboard handling is FULLY in the background, without any intervention ; in the main loop whatsoever. I like the solution anyway. ; ;************************************************************************** ; Interrupt vector ORG H'04' INT PUSH ; Save registers and set to BANK 0 BTFSS INTCON,T0IF ; check if TMR0 interrupt GOTO INTX ; whoops ! 'unknown' int, should be disabled... ; NOTE ! if an 'unknown' int triggers the int routine ; the program will loop here for ever ;-) ( as the calling flag is not cleared ) ;+++ ; Timer (TMR0) timeout either heart beat or tx/rx mode ; In 'heart beat mode' we monitor the clock and data lines ; at ( roughly )= 0.5 ms interval, we also check the send buffer ; if there are any keys to send to pc ( if clock/data levels allows us to ) ; In tx/rx mode we are controlling the clock/data line = 27 us tick ; Note: This software works using both 8Mhz and 10Mhz resonators without modifications ; however the 'timing' will then of course be 'off'. INT_CHECK BCF INTCON,T0IF ; Clear the calling flag ! BTFSC _TX_Mode ; check if we are in tx mode GOTO INT_TX ; yep, goto tx mode code.. BTFSC _RX_Mode ; are we in rx mode ? GOTO INT_RX ; yep goto rx mode code GOTO INT_HEART_BEAT ; nope just goto 'heart beat' mode ;*************** TX code start *********************************** INT_TX MOVLW H'FA' ; preset timer with 252 ( 256 - 6 = 250 ) ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us MOVWF TMR0 BTFSS clkCount,0 ; check if we should toggle clock GOTO INT_DEC_CLOCK ; bit low,decrement and check if we should toggle data instead DECFSZ clkCount,F ; decrement and check if we are at zero.. GOTO INT_CLOCK ; not zero then toggle clock line GOTO INT_EXIT_TX INT_CLOCK BTFSC pcCLOCK_out ; check if we are low GOTO INT_CLOCK_HIGH ; yep set to high BTFSS pcCLOCK_in ; check if pc is pulling the clock line low ; i.e. it wants to abort and send instead.. GOTO INT_TX_CHECK_ABORT ; abort this transfer BSF pcCLOCK_out ; ok to set clock low ( pull down ) GOTO INTX INT_CLOCK_HIGH BCF pcCLOCK_out ; set high ( release line ) ;BCF _ClockHigh ; GOTO INTX INT_TX_CHECK_ABORT GOTO INT_EXIT_TX INT_DEC_CLOCK DECF clkCount,F ; decrement clock counter ( so we toggle next time ) INT_DATA BTFSS bitCount,0 ; check bit counter GOTO INT_DATA_IDLE ; no data toggle DECFSZ bitCount,F ; decrement bit counter GOTO INT_DATA_NEXT ; next bit.. INT_NO_BITS BSF bitCount,0 ; just in case ( stupid code, not sure its needed, just ; to make it impossible to overdecrement )*** BTFSC _isParity ; are we sending parity ? GOTO INT_DATA_END ; exit ; all bits sent ; delete the last key from the buffer CALL INC_KEY_HEAD ; remove the ( last ) key form the buffer as is was sent ok.. ; all bits sent check parity BSF _isParity ; set flag data is parity BTFSS KeyParity, 0 ; is the last parity bit high? ( odd num of bits ) ; then parity should be high ( free ) GOTO INT_DATA_HIGH_PAR ; yes BSF pcDATA_out ; no, parity should be 'low' ( pulled down ) GOTO INTX ; INT_DATA_HIGH_PAR: BCF pcDATA_out ; set parity bit high ( release data line ) GOTO INTX ; and exit.. INT_DATA_END BTFSS _isStopBit ; is the stopbit sent ? GOTO INT_DATA_STOPB ; nope then set stopbit flag BCF pcDATA_out ; parity bit sent, always release data line ( stop bit ) GOTO INTX INT_DATA_STOPB BSF _isStopBit ; set the stopbit flag GOTO INTX ; and exit INT_DATA_IDLE DECF bitCount,F ; decrement bit counter GOTO INTX ; no toggle of data line INT_DATA_NEXT BTFSS CurrKey,0 ; is the last bit of the key_buffer high? GOTO INT_DATA_LOW ; no, pull data low BCF pcDATA_out ; yes, release data line INCF KeyParity,F ; increment parity bit GOTO INT_DATA_ROTATE ; rotate data INT_DATA_LOW ; last bit is low BSF pcDATA_out ; set the bit INT_DATA_ROTATE RRF CurrKey,F ; rotate right by 1 bit GOTO INTX INT_EXIT_TX ; setup the timer so we accomplish an delay after an tx seq BCF _TX_Mode ; clear tx mode flag BCF pcCLOCK_out ; release clock line BCF pcDATA_out ; and data line ; MOVLW H'64' ; start timer with 100 ( 256-100 = 156 ) ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS MOVWF TMR0 GOTO INTX ;************** TX code end ***************** ;************** RX code start *************** INT_RX MOVLW H'FA' ; preset timer with 252 ( 256 - 6 = 250 ) ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us MOVWF TMR0 BTFSS clkCount,0 ; check if we should toggle clock GOTO INT_RX_DEC_CLOCK ; bit low,decrement and check if we should read data instead DECFSZ clkCount,F ; decrement and check if we are at zero.. GOTO INT_RX_CLOCK ; not zero then toggle clock line BCF pcCLOCK_out ; release the clock line we are done.. BCF _RX_Mode ; clear rx mode bit ( go over to heart beat mode ) GOTO INT_EXIT_RX INT_RX_CLOCK BTFSC pcCLOCK_out ; check if we are low GOTO INT_RX_CLOCK_HIGH ; yep set to high ( release line ) BTFSC _isStartBit ; check if this is the first bit ( start ) GOTO INT_RX_START ; clear start bit and continue BTFSC _isParity ; check if this is the parity bit ( or parity has been received ) GOTO INT_RX_PAR ; yep check parity GOTO INT_RX_BIT ; ok just a 'normal' bit read it INT_RX_PAR ; check parity BTFSC _doRXAck ; check the handshake flag GOTO INT_RX_HNDSHK ; start handshake check BTFSS pcDATA_in ; is the input high ? GOTO INT_RX_PAR_HIGH ; yep BTFSC KeyParity,0 ; is the parity '0' ( should be ) GOTO INT_RX_PAR_ERR ; nope parity error GOTO INT_RX_ACK ; parity ok next should be ack ( we take data line low ) INT_RX_PAR_HIGH BTFSS KeyParity,0 ; check that parity bit is '1' GOTO INT_RX_PAR_ERR ; nope parity error GOTO INT_RX_ACK ; parity ok, next is ack ( we take data line low ) INT_RX_PAR_ERR BSF _KeyError ; set error flag INT_RX_ACK BSF pcCLOCK_out ; ok to set clock low ( pull down ) BSF _doRXAck ; enable ack check GOTO INTX INT_RX_HNDSHK BTFSS _RXEnd ; if we are done dont take data low BSF pcCLOCK_out ; ok to set clock low ( pull down ) BTFSC _RXAckDone ; chek if hand shake ( ack ) is done ? BSF _RXEnd ; ok we are now done just make one more clock pulse GOTO INTX ; exit INT_RX_CLOCK_HIGH BCF pcCLOCK_out ; set high ( release line ) BTFSS _RXAckDone ; are we done.. ? GOTO INTX BTFSS _RXDone ; finished ? GOTO INTX BCF _RX_Mode ; and clear rx flag.. GOTO INT_EXIT_RX ; bye bye baby INT_RX_DEC_CLOCK DECF clkCount,F ; decrement clock counter ( so we toggle next time ) BTFSS _doRXAck ; check if we are waiting for handshake GOTO INTX BTFSC pcCLOCK_out ; check if the clock is low ( pulled down ) GOTO INTX ; nope we are pulling down then exit ; we only take over the data line if ; the clock is high ( idle ) ; not sure about this though.. ??? BTFSC _RXEnd ; are we done ? GOTO INT_RX_END ; handshake check if data line is free ( high ) BTFSS pcDATA_in ; is data line free ? GOTO INTX ; nope BSF pcDATA_out ; takeover data line BSF _RXAckDone ; we are done..at next switchover from low-high we exit GOTO INTX ; INT_RX_END BCF pcDATA_out ; release data line BSF _RXDone ; we are now done GOTO INTX INT_RX_START BCF _isStartBit ; clear start bit flag BSF pcCLOCK_out ; ok to set clock low ( pull down ) GOTO INTX INT_RX_BIT BCF CurrKey,7 BTFSS pcDATA_in ; is bit high GOTO INT_RX_NEXT ; nope , it's a '0' BSF CurrKey,7 ; set highest bit to 1 INCF KeyParity,F ; increase parity bit counter INT_RX_NEXT BSF pcCLOCK_out ; ok to set clock low ( pull down ) DECFSZ bitCount,F ; decrement data bit counter GOTO INT_RX_NEXT_OK BSF bitCount,0 ; just in case ( so we cannot overdecrement ) BSF _isParity ; next bit is parity GOTO INTX INT_RX_NEXT_OK CLRC ; clear carry, so it doesnt affect receving byte RRF CurrKey,F ; rotate to make room for next bit GOTO INTX ; and exit INT_EXIT_RX ; handle the recevied key ( if not it is an 'data' byte ) MOVLW H'C4' ; preset timer with 196 ( 256 - 60 = 196 ) ; timetick = 0.4uS x 8 ( prescale ) x 60 + 8 ( int handling code )= 200 us ; MOVWF TMR0 ; this delay seems to be needed ( handshake ? ) ; check if this is an data byte ( rate/delay led status etc ) MOVF CommandData,F ; reload into itself ( affect zero flag ) BTFSS STATUS,Z ; check zero flag GOTO INT_STORE_DATA ; byte contains data ( rate/delay etc ) CALL CHECK_RX_KEY ; no data, handle recevied command GOTO INTX INT_STORE_DATA ; store data byte in 'currkey', ; first reply with 'ack' MOVLW H'FA' ; keyboard ack CALL ADD_KEY ; BTFSS _IsLedStatus ; is it led status byte ? GOTO INT_STORE_RATE ; nope check next INT_STORE_NUM ; byte in 'currkey' is led status byte, store it MOVF CurrKey,W ; get byte MOVWF KbLedStatus ; and store it BTFSC _WaitNumLock ; was this something we were waiting for ? ; i.e. we sent the make scancode for numlock. CALL RELEASE_NUMLOCK ; yep, then send release code for 'soft' numlock GOTO INT_STORE_EXIT ; store it in local ram copy and exit INT_STORE_RATE BTFSS _IsRateDelay ; is it rate/delay byte ? GOTO INT_STORE_EXIT ; nope then send ack end exit ; byte in 'currkey' is rate/delay byte, store it MOVF CurrKey,W ; get byte MOVWF KbRateDelay ; and store it INT_STORE_EXIT CLRF CommandData ; clear data byte flags GOTO INTX ;******************* 'heart' beat code ( 0.5 ms tick ) ********* ; Note: As I 'mess' with the int timer this 'heart beat' is by no means ; an accurate clock. However it can work as a rough estimate for local time. ; INT_HEART_BEAT MOVLW H'64' ; start timer with 100 ( 256-100 = 156 ) ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS MOVWF TMR0 INT_CHECK_CLKDTA: ; CLOCK DATA Action ;-----------+-------- ; L L | wait ? ; L H | wait, buffer keys ; H L | start an rx sequence ; H H | keyboard can tx BTFSS pcDATA_in ; is the data line high ( free ).. GOTO INT_CHECK_RX ; Nope it's pulled down, check if rx is requested BTFSC pcCLOCK_in ; Is the clk line low ( pulled down ) ? GOTO INT_CHECK_BUFF ; Nope, so check if we have any keys to send GOTO INT_IDLE ; clock is low , wait and buffer keys( i.e. no rx/tx ) INT_CHECK_RX ; pc ( probably ) wants to send something.. BTFSS pcCLOCK_in ; wait until clock is released before we go into receving mode.. GOTO INT_RX_IDLE ; nope still low ; clock now high test if we are set to start an rx seq. BTFSS _RxCanStart ; have we set the flag ? GOTO INT_WAIT_RX ; nope then set it BTFSC pcDATA_in ; make sure that data still is low GOTO INT_ABORT_RX ; nope abort rx req, might been a 'glitch' ; initiate the rx seq. CLRF Comm_Flags ; used by both tx/rx routines ( and _RxCanStart bit !! ) CLRF TRX_Flags ; clear tx/rx flags CLRF KeyParity ; clear parity counter BSF _RX_Mode ; set rx mode flag.. BSF _isStartBit ; set that next sampling is start bit ; preset bit and clock counters MOVLW H'2F' ; = 47 dec, will toggle clock output every even number until zero MOVWF clkCount ; preset clock pulse counter MOVLW H'08' ; = 8 dec, number of bits to read ; then parity bit will be set instead MOVWF bitCount ; preset bit counter ; note as we are starting the clock here we allow a longer time before we start ; the actual 20 us tick, this the first time we wait about 200 us before the first clock 'tick' MOVLW H'C4' ; preset timer with 196 ( 256 - 60 = 196 ) ; timetick = 0.4uS x 8 ( prescale ) x 60 + 8us ( int handling code )= 200 us ; MOVWF TMR0 GOTO INTX ; exit, the next int will start an rx seq INT_WAIT_RX: BSF _RxCanStart ; set flag so we start rx next int ( 0.5 ms ) INT_RX_IDLE ; reload clock so we check more often MOVLW H'F0' ; start timer with 240 ( 256-16 = 240 ) ; timetick = 0.4uS x 8 ( prescale ) x 16 + 8/10us ( int handling code )= (about) 60 us MOVWF TMR0 GOTO INTX ; INT_ABORT_RX BCF _RxCanStart ; clear flag ( forces a 'new' rx start delay ) GOTO INT_IDLE ; INT_CHECK_BUFF: ; check if we have any keys to send to pc MOVF KeyBufferTail,W ; get end of buffer to 'w' SUBWF KeyBufferHead,W ; subtract start of buffer if head - tail = zero, no byte BTFSC STATUS, Z ; zero flag = no byte to send GOTO INT_IDLE ; then do the 'idle' stuff INT_SEND_KEY ;key in buffer, get it and initiate an tx seq... CALL GET_KEY_BUFFER ; get the key into CurrKey MOVF CurrKey,W MOVWF LastKey ; store last sent key ; setup our tx/rx vars CLRF Comm_Flags ; used by both tx/rx routines ( and _RxCanStart bit !! ) CLRF TRX_Flags ; clear tx/rx flags CLRF KeyParity ; clear parity counter BSF _TX_Mode ; set tx mode flag.. ; preset bit and clock counters MOVLW H'2B' ; = 43 dec, will toggle clock out put every even number until zero MOVWF clkCount ; preset clock pulse counter MOVLW H'12' ; = 18 dec, will shift data out every even number until zero ; then parity bit will be set instead MOVWF bitCount ; preset bit counter ; data now set, initiate the clock to generate a 20 us tick ( rx/tx heart beat ) BSF pcDATA_out ; start bit, always 'low' ( we pull down ) MOVLW H'FA' ; start timer with 252 ( 256-6 = 250 ) ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8 ( int handling code )= 27 us MOVWF TMR0 GOTO INTX ; exit, the next int will start an tx INT_IDLE: ; 'Idle' code i.e. no rx or tx possible AND/OR nothing to send DECF Divisor_10ms,F ; Count 0.5ms down to give 10 milli second tick BNZ INTX ; Exit if divider not zeroed MOVLW .20 MOVWF Divisor_10ms ; Preset the divide by 20 ;+++ ; 10 ms tick here ; Divide the 10 ms tick to give 100 ms second tick INT_10MS DECF Divisor_100ms,F ; Count 10ms down to give 100 milli second tick BNZ INTX ; Exit if divider not zeroed MOVLW .10 MOVWF Divisor_100ms ; Preset the divide by 10 ;+++ ; 100 ms tick here INT_100MS ; numlock check !! bit variable _Numlock does not actually set the numlock status directly. ; However, by setting this bit to '1' we make a test against the current numlock led status ; ( bit no 1 in variable KbLedStatus ), if that is different from this variable ; we send a 'numlock' press/release ( to toggle numlock status ) ; so in essence, setting this bit to '1' will force numlock to be 'on' etc. BTFSC _IsFirstLedStatus ; if = 1 then we have not yet received numlock status GOTO INT_REPEAT_CHECK ; nope, then this is a consecutive byte, store as 'normal' BTFSS _WaitNumLock ; are we waiting for pc numlock reply ? GOTO INT_NUMLOCK_CHECK ; yep then do repeat check instead DECFSZ Temp_Var,F ; GOTO INT_REPEAT_CHECK CALL RELEASE_NUMLOCK INT_NUMLOCK_CHECK BTFSC _LedNumLock ; is the led on ? GOTO INT_NUMLOCK_ON ; yep, then test our 'local' numlock state ( wanted numlock state ) ; nope numlock is off, is our wanted state also off ? BTFSS _NumLock ; is wanted state off ? GOTO INT_REPEAT_CHECK ; yep continue CALL PRESS_NUMLOCK ; nope then send numlock press/release code GOTO INT_REPEAT_CHECK INT_NUMLOCK_ON BTFSC _NumLock ; is wanted state also 'on' ? GOTO INT_REPEAT_CHECK ; yep CALL PRESS_NUMLOCK ; nope then toggle numlock state INT_REPEAT_CHECK ; check if a key should be 'repeated' ( when pressed longer than 500 ms ) BTFSS _startRepeat ; start repeating a key ? ( delay !!! ) GOTO INT_CHECK_KEY ; nope, then check if key should be repeated DECF RepeatTimer,F ; BNZ INT_500MS ; not zero yet, check timer instead BCF _startRepeat ; stop repeat timer ( delay is accomplished ) BSF _doRepeat ; and enable 'key' is still down check MOVLW .02 ; start repeat send timer MOVWF Divisor_Repeat ; GOTO INT_500MS ; do next timer check INT_CHECK_KEY BTFSS _doRepeat ; key should be repeated ? GOTO INT_500MS ; nope ; ok key should be repeated, check if it still pressed ? CALL CHECK_KEY_STATE ; uses MakeKeyOffset to calculate which key that was ; the last pressed, and then check if it's still pressed ; if still pressed carry = '1', BTFSS STATUS,C ; check carry BCF _doRepeat ; clear repeat bit, stop repeating the key BTFSS _doRepeat ; still pressed ? GOTO INT_500MS ; nope DECF Divisor_Repeat,F ; should we send the key ? BNZ INT_500MS ; nope MOVLW DELAY_RATE ; reload timer with key rate delay ;MOVLW .02 ; restart timer MOVWF Divisor_Repeat ; BSF _doSendKey ; set flag to send key, NOTE the actual sending ( putting into send buffer ) ; is done inside mainloop. INT_500MS DECF Divisor_500ms,F ; Count 100ms down to give 500 milli second tick BNZ INTX ; Exit if divider not zeroed MOVLW .05 MOVWF Divisor_500ms ; Preset the divide by 5 ;+++ ; 500 ms tick here INT_500_NEXT TOGGLE_PIN O_led_KEYCOMM_ok ; toggle the disco light ;-) BTFSS _DoExitAltKeymap ; is the alt keymap toggle key pressed the second time ? ; if so skip timeout test and exit BTFSS _InAltKeymap ; are we in altkeymap ? GOTO INTX ; nope ; we are in altkeymap, decrement the lastkeytime ; and check if we are at zero then we exit ; the altkeymap. DECF LastKeyTime,F ; decrease time BNZ INTX ; exit, timer has not expired ; timer expired, get out of altkey map BSF _ExitAltKeymap ; ; ***************** 'heart' beat code end *************** INTX ;BCF INTCON,T0IF ; Clear the calling flag PULL ; Restore registers RETFIE ; **************** end interrupt routine ************** ;+++++ ; Routines that will 'toggle' keyboard numlock status ; by sending numlock make/break code ; PRESS_NUMLOCK: MOVLW H'77' ; numlock key scancode, make CALL ADD_KEY MOVLW H'06' ; 6 x 100 ms = 600 ms ( release delay ) MOVWF Temp_Var ; BSF _WaitNumLock ; we are waitin for numlock status reply from pc RETURN RELEASE_NUMLOCK: MOVLW BREAK ; break prefix CALL ADD_KEY MOVLW H'77' ; numlock key scancode CALL ADD_KEY BCF _WaitNumLock RETURN ; *********************************************************************** ; ; CHECK_RX_KEY - handles the received commands from pc ; CHECK_RX_KEY ; check the key in 'currkey' ( command from pc ) CHECK_ED MOVF CurrKey,W ; move key buffer into W register SUBLW H'ED' ; subtract value in W with 0xED BTFSS STATUS, Z ; check if the zero bit is set GOTO CHECK_EE ; the result of the subtraction was not zero check next ; ok 'ED'=set status leds ( in next byte ) received BSF _IsLedStatus ; set bit that next incoming byte is kb led staus GOTO CHECK_SEND_ACK ; send ack CHECK_EE MOVF CurrKey,W ; move key buffer into W register SUBLW H'EE' ; subtract value in W with 0xEE BTFSS STATUS, Z ; check if the zero bit is set GOTO CHECK_F0 ; the result of the subtraction was not zero check next ; ok 'EE'= echo command received GOTO CHECK_SEND_EE ; send echo CHECK_F0 MOVF CurrKey,W ; move key buffer into W register SUBLW H'F0' ; subtract value in W with 0xF0 BTFSS STATUS, Z ; check if the zero bit is set GOTO CHECK_F2 ; the result of the subtraction was not zero check next ; ok 'F0'= scan code set ( in next commming byte ) received BSF _SkipByte ; skip next incomming byte ( or dont interpret ) GOTO CHECK_DONE ; do not send ack ! CHECK_F2 MOVF CurrKey,W ; move key buffer into W register SUBLW H'F2' ; subtract value in W with 0xF0 BTFSS STATUS, Z ; check if the zero bit is set GOTO CHECK_F3 ; the result of the subtraction was not zero check next ; ok 'F2'= Read ID command responds with 'AB' '83' GOTO CHECK_SEND_ID ; send id bytes CHECK_F3 MOVF CurrKey,W ; move key buffer into W register SUBLW H'F3' ; subtract value in W with 0xF3 BTFSS STATUS, Z ; check if the zero bit is set GOTO CHECK_FE ; GOTO CHECK_F4 ; the result of the subtraction was not zero check next ; ok 'F3'= set repeat rate ( in next commming byte ) received BSF _IsRateDelay ; next incomming byte is rate/delay info GOTO CHECK_SEND_ACK ; send ack ; **** Note ! removed from test as I 'don't care' ******** ; **** i.e. I dont disable or enable the keyboard at any time. ;CHECK_F4 ; MOVF CurrKey,W ; move key buffer into W register ; SUBLW H'F4' ; subtract value in W with 0xF4 ; BTFSS STATUS, Z ; check if the zero bit is set ; GOTO CHECK_F5 ; the result of the subtraction was not zero check next ; ok 'F4'= keyboard enable received ; GOTO CHECK_SEND_ACK ; send ack ;CHECK_F5 ; MOVF CurrKey,W ; move key buffer into W register ; SUBLW H'F5' ; subtract value in W with 0xF5 ; BTFSS STATUS, Z ; check if the zero bit is set ; GOTO CHECK_FE ; the result of the subtraction was not zero check next ; ok 'F5'= keyboard disable received ; GOTO CHECK_SEND_ACK ; send ack CHECK_FE MOVF CurrKey,W ; move key buffer into W register SUBLW H'FE' ; subtract value in W with 0xFE BTFSS STATUS, Z ; check if the zero bit is set GOTO CHECK_FF ; the result of the subtraction was not zero check next ; ok 'FE'= resend last sent byte MOVF LastKey,W ; get last key CALL ADD_KEY ; and put it on the que GOTO CHECK_DONE CHECK_FF MOVF CurrKey,W ; move key buffer into W register SUBLW H'FF' ; subtract value in W with 0xFF BTFSS STATUS, Z ; check if the zero bit is set GOTO CHECK_ERROR ; the result of the subtraction was not zero, unknown command ; ok 'FF'= reset keyboard received GOTO CHECK_SEND_AA ; send 'AA' power on self test passed CHECK_ERROR ; unknown command ( or command not interpreted ) GOTO CHECK_SEND_ACK CHECK_SEND_ID MOVLW H'FA' ; keyboard ack CALL ADD_KEY ; MOVLW H'AB' ; keyboard id first byte, always 0xAB CALL ADD_KEY ; MOVLW H'83' ; keyboard id second byte, always 0x83 CALL ADD_KEY ; GOTO CHECK_DONE CHECK_SEND_ACK MOVLW H'FA' ; keyboard ack CALL ADD_KEY ; GOTO CHECK_DONE CHECK_SEND_AA MOVLW H'FA' ; keyboard ack CALL ADD_KEY ; MOVLW H'AA' ; keyboard post passed CALL ADD_KEY ; GOTO CHECK_DONE CHECK_SEND_EE MOVLW H'EE' ; keyboard echo CALL ADD_KEY ; CHECK_DONE RETLW 0 ; and we are done ; *********************************************************************** ; Buffer code ( a bit modified ) from Stewe Lawther ; http://ourworld.compuserve.com/homepages/steve_lawther/ucindex.htm ; And of course source of the exellent keyboard viewer. !! ( without which, this ; project would have been close to impossible ) ( and of course my nifty ; memory oscilloscope ) ; ; ADD_KEY_BUFFER - (outside int)add the key in CurrKey to our keybuffer que in the first ; free position. If there is no more room the oldest byte is ; 'dumped'. ; ADD_KEY - Same but to be used inside int routine.( just skips int disable code ) ADD_KEY_BUFFER ADD_STOP_INT ; first stop all interrupts !!!!!!! BCF INTCON,GIE ; disable global interrupts.. BTFSC INTCON,GIE ; check that is really was disabled GOTO ADD_STOP_INT ; nope try again ADD_KEY ; inside interuppt we call this instead ( as we dont need to disable int :-) ) MOVWF BufTemp ; store key temporary MOVF KeyBufferHead,W ; move buffer head out of FSR temporarily MOVWF Temp ; store in temp MOVF KeyBufferTail,W ; set FSR to buffer tail MOVWF FSR ; set indirect file pointer MOVF BufTemp,W ; set W to new scancode to send MOVWF INDF ; and put it in the buffer MOVF Temp,W ; get the head pointer back MOVWF KeyBufferHead ; INCF KeyBufferTail,W ; get the end of the buffer SUBLW KbBufferMax ; check if at buffer max INCF KeyBufferTail,W ; (reload value to w - doesn't affect C) BTFSS STATUS, C ; if so (negative result) MOVLW KbBufferMin ; set to buffer min ( wrap around ) MOVWF KeyBufferTail SUBWF KeyBufferHead,W ; see if we have any room ( head and tail have meet ) BTFSC STATUS, Z ; if so (Z set) CALL INC_KEY_HEAD ; dump oldest byte ; finally turn on interrupts again MOVLW b'10100000' ; enable global & TMR0 interrupts MOVWF INTCON RETURN ; *********************************************************************** ; ; GET_KEY_BUFFER - Gets a char from the buffer, and puts it into KeyBuffer ; NOTE: Does not increase buffer pointers ( dump this key ). ; A call to INC_KEY_HEAD will do this if the key is sent ok GET_KEY_BUFFER MOVF INDF, W ;put the byte to send into key buffer MOVWF CurrKey RETURN ; and go back, NOTE ! the key is not ; removed from the buffer until a call ; to INC_KEY_HEAD is done. ; *********************************************************************** ; ; INC_KEY_HEAD - dump oldest byte in keybuffer, Do not call if byte ; has not been fetched before ( GET_KEY_BUFFER ) ; INC_KEY_HEAD INCF KeyBufferHead,W ; set to next byte in buffer SUBLW KbBufferMax ; check if at buffer max INCF KeyBufferHead,W ; (reload value to w - doesn't affect C) BTFSS STATUS, C ; if so (negative result) MOVLW KbBufferMin ; set to buffer min ( wrap around ) MOVWF KeyBufferHead ; and store ( in FSR ) RETURN ; go back ; *********************************************************************** ; ; CHECK_KEY_STATE - Check if the last pressed key is still pressed ; Returns with carry = '1' if still pressed ; else carry = '0' ( or error ) ; CHECK_KEY_STATE: ; uses LastMakeOffset to calculate which key to test MOVF LastMakeOffset,W ; get offset ANDLW H'18' ; mask out column bits ; lastmake offset has the following bits: ; '000yyxxx' where 'yy' is column no ; and 'xxx' is key num, BTFSC STATUS,Z ; zero = column 1 & 2 GOTO CHECK_COL_12 ; it is in column 1 MOVWF repTemp ; save it temporary SUBLW H'08' ; subtract value in W with 0x08 ( columns 3 & 4 ) BTFSC STATUS, Z ; check if the zero bit is set GOTO CHECK_COL_34 ; it is in column 3 & 4 MOVF repTemp,W ; get the column bits back SUBLW H'10' ; subtract value in W with 0x10 ( columns 5 & 6 ) BTFSC STATUS, Z ; check if the zero bit is set GOTO CHECK_COL_56 ; it is in column 5 & 6 CHECK_COL_78 MOVF kbColumn78_Old,W ; get bit map ( key status ) for keys in column 7 & 8 MOVWF repKeyMap ; and store it GOTO CHECK_KEY ; and continue to check bit CHECK_COL_56 MOVF kbColumn56_Old,W ; get bit map ( key status ) for keys in column 5 & 6 MOVWF repKeyMap ; and store it GOTO CHECK_KEY ; and continue to check bit CHECK_COL_34 MOVF kbColumn34_Old,W ; get bit map ( key status ) for keys in column 3 & 4 MOVWF repKeyMap ; and store it GOTO CHECK_KEY ; and continue to check bit CHECK_COL_12 MOVF kbColumn12_Old,W ; get bit map ( key status ) for keys in column 1 & 2 MOVWF repKeyMap ; and store it ;<-------Alt keymap code-------> ;Checks ONLY column 1&2 bitmap ( keymap ) ( as it is now ) ; this code has to be moved/changed if another column is choosen as the alt. keymap toggle key ; alternative keymap handling if key r3 c1 is pressed ( bit 2 in column 1&2 ) ; then enable alternative keymap ( only if keyrepeat is disabled ) ; check if this was the last key pressed ; check bit representing the alt. keymap key ( i've choosen key 2 ) MOVF LastMakeOffset,W ; get key offset again ANDLW H'07' ; mask out column bits SUBLW H'02' ; check if its bit num 2 ( the enter 'alt keymap' key ) BTFSS STATUS, Z ; check if the zero bit is set GOTO CHECK_KEY ; nope than another key was the last ; skip altkeymap enable ; the altkeymap key was the last pressed ! ; is key repeat disabled ? BTFSS I_jmp_NoRepeat ; check if repeat code is enabled ? GOTO CHECK_KEY ; yep, then skip altkeymap enable test ; enable altkeymap if key is still pressed BTFSC repKeyMap,2 ; test bit 2 ( should be key 'F7' ) GOTO CHECK_ENABLE_ALT ; ok enable altkeymap ( if we are not already in altkeymap ) GOTO CHECK_KEY ; nope another key in column 1&2 continue check CHECK_ENABLE_ALT BTFSC _AltKeymap ; are we already in altkeymap ? GOTO CHECK_KEY ; yep then just continue ; We are just entering/enabling the alt. keymap BSF _AltKeymap ; enable alternative keymap ; Example of using an 'advanced' alt keymap handling ; not enabled, to avoid intial confusion. ; I.E This snippet would only be called once when we ; are just entering(enabling) the alternative keymapping ! ; This example code will 'soft' release the current altkeymap key ( it is in pressed state ! ) ; ( i.e send release code for the enter alt. keymap key 'F7' ) ; send the make scancode for left key instead. ; and force numlock to be off. ; Do not use if you dont understand the implifications ! ; Also note that the scancodes are hardcoded here ! ; ( i.e do not use the lookup table definition of the key/s ) ; ***** start snippet ;MOVLW BREAK ; send break prefix ;CALL ADD_KEY ;MOVLW H'83' ; and scancode for the enter alt keymap ;CALL ADD_KEY ;MOVLW H'11' ; send make code for the left key ;CALL ADD_KEY ; example of forcing the numlock status to a particular state ; the numlockstatus will change ( be checked ) inside the int routine ; See also at the end of KB_DEBOUNCE_12 where the numlock status ; will be restored when we release the key ;BCF _NumLock ; 'force' numlock to be off ; This bit MUST also be checked as we do not know if we have recevied ; first numlock status byte yet ( pc does not send numlock/led status ; after intial poweron, if not one of the numlock/capslock/scrolllock are pressed ) ; i.e. if you connect this keyboard to a 'running' pc, the numlock status ; will be unknown.However if connected before poweron, it will be updated ; as numlock status is sent during pre-boot seq. ;BTFSC _IsFirstLedStatus ; have we recevied numlock status yet ? ;CALL PRESS_NUMLOCK ; ***** end snippet CHECK_KEY ; 'normal' key down check ; column for pressed key is now in repKeyMap MOVF LastMakeOffset,W ; get offset again ANDLW H'07' ; mask out key number ( lowest 3 bits ) BTFSC STATUS,Z ; bit num zero ? GOTO CHECK_KEY_DONE ; yep lowest bit, check and return MOVWF repTemp ; and store it CHECK_KEY_LOOP RRF repKeyMap,F ; rotate one step to right DECFSZ repTemp,F ; decrement bit counter GOTO CHECK_KEY_LOOP ; loop again CHECK_KEY_DONE ; ok the key to test should now be the lowest bit in repKeyMap CLRC ; clear carry BTFSC repKeyMap,0 ; check bit 0 BSF STATUS,C ; ok key is pressed set carry RETURN ; and we are done.. ; *********************************************************************** ; ; DELAY_1ms - Delay routine ! used when scanning our own keyboard ; Delay is between output of adress to 4051 and reading of inputs ; Increase to have a slower 'scan' rate or decrease to have a higher scan rate. ; DELAY_1ms MOVLW H'F0' ; wait 255 cycles MOVWF kbTemp ; this var is 'safe' to be used in side mainloop MOVLW H'03' MOVWF kbState DELAY_LOOP DECFSZ kbTemp,F ; decrement GOTO $-1 ; MOVLW H'F0' MOVWF kbTemp DECFSZ kbState,F GOTO DELAY_LOOP RETURN ;--------------------------------------------------------------------------- ; ; Initialisation ; ;--------------------------------------------------------------------------- INIT: ;+++ ; Set up the ports ; PORT A BANK1 MOVLW b'00000110' ; Set port data directions RA1,RA2 inputs RA0,RA3,RA4 outputs MOVWF TRISA ; PC keyboard connections ; PORT B ; Used for our own 3x8 matrix keyboard BANK1 MOVLW b'11111000' ; Set port data directions RB4-RB7 inputs rest outputs MOVWF TRISB ; Clear all registers on bank 0 ( memory ) BANK0 MOVLW H'0C' MOVWF FSR INITMEM CLRF 0 ; Clear a register pointed to be FSR INCF FSR,F CLRWDT ; clear watchdog MOVLW H'50' ; Test if at top of memory SUBWF FSR,W BNZ INITMEM ; Loop until all cleared ;+++ ; Initiate the keybuffer pointers INIT_BUFF: MOVLW KbBufferMin ; get adress of first buffer byte MOVWF KeyBufferHead ; store in FSR MOVWF KeyBufferTail ; and set last byte to the same ( no bytes in buffer ) ;+++ ; Preset the timer dividers MOVLW .20 MOVWF Divisor_10ms MOVLW .10 MOVWF Divisor_100ms MOVLW .05 MOVWF Divisor_500ms ;+++ ; Set up Timer 0. ; Set up TMR0 to generate a 0.5ms tick ; Pre scale of /8, post scale of /1 BANK1 MOVLW b'00000010' ; Initialisation of TMR0 prescale 8 '010' ; weak pullup enabled by latch values. MOVWF OPTION_REG ; load option reg with prescale of 8 BANK0 MOVLW H'64' ; start timer with 100 ( 256-100 = 156 ) ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS MOVWF TMR0 ;--------------------------------------------------------------------------- ; ; the main 'program' loop ( starts really at MAIN_LOOP ) ; ;--------------------------------------------------------------------------- MAIN: BSF pcDATA_in BSF pcCLOCK_in BCF pcDATA_out BCF pcCLOCK_out CLRF PORTB MOVLW H'08' ; preset the column counter MOVWF kbColumnCnt ; BSF _NumLock ; default state is numlock = on BSF _IsFirstLedStatus ; we have not yet recevied led status byte. MOVLW b'10100000' ; enable global & TMR0 interrupts MOVWF INTCON CLRWDT ; clear watchdog BTFSS O_led_KEYCOMM_ok GOTO $-2 ; make an 0.5 second delay here ; i.e. the led will come on when 0.5 seconds has passed ; set inside the timer int. CLRWDT ; clear watchdog BTFSC O_led_KEYCOMM_ok GOTO $-2 ; make an additional 0.5 second delay here ; i.e. the led will be dark when 0.5 seconds has passed ; set inside the timer int. MOVLW H'AA' ; post passed :-), always 0xAA CALL ADD_KEY_BUFFER ; now go into infinite loop, the pc kb interface runs in the background ( as an int ) ; where we continuously monitor the pcCLOCK/DATA_in lines MAIN_LOOP: ; check whatever :-) CLRWDT ; clear watchdog MAIN_CHECK_COL_1: ; scan our own keyboard, first four bits ; address and read column, read as complement so key pressed = '1' ; since we pull down when key is pressed ( weak pullup enabled ) ; ( i.e. pressed key has level 0V ) to make it more 'logical' to work with CLRF kbColumnVal ; get column counter / adress out MOVF kbColumnCnt,W MOVWF PORTB ; set the columns adress to the 74HCT4051 ; i.e. make column low IFNDEF DEBUG CALL DELAY_1ms ; wait 1 ms let pins stabilize ENDIF COMF PORTB,W ; read back the pin values ( complement i.e. key pressed = '1' ) ANDLW b'11110000' ; mask out unused pins MOVWF kbColumnVal ; store the pin values SWAPF kbColumnVal,F ; swap nibbles ( low<->high ) to make room for next column INCF kbColumnCnt,F ; inc column adress MAIN_CHECK_COL_2: ; read next four bits ; put out adress and read next column, read as complement so key pressed = '1' ; this as we pull down when key is pressed ( weak pullup enabled ) ; get column counter / adress out MOVF kbColumnCnt,W MOVWF PORTB ; set the columns adress to the 74HCT4051 ; i.e. make column low IFNDEF DEBUG CALL DELAY_1ms ; wait 1 ms ENDIF COMF PORTB,W ; read back the pin values ( complement i.e. key pressed = '1' ) ANDLW b'11110000' ; mask out unused pins ADDWF kbColumnVal,F ; and store pin values INCF kbColumnCnt,F ; reset column counter check ; i.e. we are 'only' using adress 0 - 7 MOVF kbColumnCnt,W SUBLW H'08' ; subtract value in W with 0x08 BTFSS STATUS, Z ; check if the zero bit is set GOTO MAIN_CHECK_DEBOUNCE ; nope continue CLRF kbColumnCnt ; reset counter/adress MAIN_CHECK_DEBOUNCE: CALL KB_DEBOUNCE ; do debouncing on the current values and send make/break ; for any key that has changed ; NOTE uses the current column adress to determine which ; columns to debounce! MAIN_REPEAT: BTFSS I_jmp_NoRepeat ; check if repeat code is enabled ? GOTO MAIN_CHECK_REPEAT ; yep check key repeating ; keyrepeat disabled then do check on exit of altkeymap instead BTFSS _ExitAltKeymap ; we want to exit altkeymap ? GOTO MAIN_LOOP ; nope ; check that ALL keys are released ; before exiting the alt keymap MOVF kbColumn78_Old,F ; reload column 78 to itself ( affect zero flag ) BTFSS STATUS,Z ; check if zero ? GOTO MAIN_LOOP ; key/s still down in column 78 MOVF kbColumn56_Old,F ; reload column 56 to itself ( affect zero flag ) BTFSS STATUS,Z ; check if zero ? GOTO MAIN_LOOP ; key/s still down in column 56 MOVF kbColumn34_Old,F ; reload column 34 to itself ( affect zero flag ) BTFSS STATUS,Z ; check if zero ? GOTO MAIN_LOOP ; key/s still down in column 34 MOVF kbColumn12_Old,F ; reload column 12 to itself ( affect zero flag ) BTFSS STATUS,Z ; check if zero ? GOTO MAIN_LOOP ; key/s still down in column 12 ; all keys released !! BCF _AltKeymap ; exit altkeymap BCF _ExitAltKeymap ; exit release check BCF _InAltKeymap ; clear flag for second keypress check BCF _DoExitAltKeymap ; GOTO MAIN_LOOP MAIN_CHECK_REPEAT BTFSS _doSendKey ; if we should send a repeated key GOTO MAIN_LOOP ; nope continue ; send the key in RepeatedKey but first check if its an extended key BTFSS _RepeatIsExt ; is it extended ? GOTO MAIN_SEND_REPEAT ; nope just send scan code ; last key pressed was extended send extended prefix MOVLW EXTENDED ; get extended code CALL ADD_KEY_BUFFER ; and put it into the buffer MAIN_SEND_REPEAT: MOVF RepeatKey,W ; get key code for the last pressed key CALL ADD_KEY_BUFFER ; and put it into the buffer BCF _doSendKey ; and clear the flag, it will be set again ; inside int handler if key still is pressed GOTO MAIN_LOOP ; and return ; *********************************************************************** ; ; KB_SEND_KEY - uses the Offset stored in var 'Offset' to fetch a key from our ; key lookup table. ; then checks the bit var _isBreak to see if make or break codes should be sent ; It then puts the code/s into the key buffer ( for sending later, in int routine ) KB_SEND_KEY: MOVF Offset,W ; get current offset MOVWF TempOffset ; save it ( to be used in key repeat code, is its 'make' ) ; temp offset has the following bits: ; '000yyxxx' where 'yy' is column offset ; and 'xxx' is key num, CLRC ; clear carry so it dont affect byte rotation RLF Offset,F ; first rotate RLF Offset,F ; second rotate ; offset no have the following bits: ; '0yyxxx01' where 'yy' is column offset ; and 'xxx' is key num, ; as each key in table has 4 bytes of 'space' INCF Offset,F ; add one, for the 'movwf pcl' at the start of the table BCF Offset,7 ; clear to bit, just in case so we dont ; 'overflow' the table, should not be needed ! BCF _isExtended ; clear extended flag MOVLW LOW LOOKUP_KEY ; get low bit of table adress ADDWF Offset,F ; 8 bit add MOVLW HIGH LOOKUP_KEY ; get high 5 bits BTFSC STATUS,C ; is page boundary crossed ? ADDLW 1 ; yep, then inc high adress MOVWF PCLATH ; load high adress in latch MOVF Offset,W ; load computed offset in w CLRC ; clear carry ( default= key is not extended ) ; if key is extended then carry is set in jumptable lookup_key CALL LOOKUP_KEY ; get key scan code/s for this key ; key scan code/s are saved in ; W - scancode, should go into kbScan ; carry set - extend code ; carry clear - not extended code MOVWF kbScan ; store scancode ; if carry is set then key is extended so first send extended code ; before any make or break code BTFSS STATUS,C ; check carry flag GOTO KB_CHK_BREAK ; nope then check make/break status BSF _isExtended ; set extended flag MOVLW EXTENDED ; CALL ADD_KEY_BUFFER ; get extended code and put in in the buffer KB_CHK_BREAK: ; check if it's make or break BTFSS _isBreak ; check if its pressed or released ? GOTO KB_DO_MAKE_ONLY ; send make code BCF _isBreak ; clear bit for next key ; break code, key is released MOVLW BREAK ; get break code CALL ADD_KEY_BUFFER ; and put into buffer GOTO KB_DO_MAKE ; and send key code also ; key is pressed ! KB_DO_MAKE_ONLY: BCF _doSendKey ; stop repeat sending BCF _doRepeat ; and bit for repeat key send BSF _startRepeat ; and set flag for start key repeat check BCF _RepeatIsExt ; clear repeat key extended flag ( just in case ) BTFSC _isExtended ; is it extended ? BSF _RepeatIsExt ; set the flag ; save this key in 'last' pressed, to be used in key repeat code MOVF TempOffset,W ; get saved offset MOVWF LastMakeOffset ; and store it ; if keyrepat = enabled, alternative mapping = disabled BTFSS I_jmp_NoRepeat ; check if repeat code is enabled ? GOTO KB_REP_NOR ; yep set normal delay ( 800 ms ) ; else keyrepat = disabled, alternative mapping = enabled MOVLW DELAY_ENTER_ALTKEYMAP ;reload delay before entering the altkeymap ( 3 sec ) ; i.e how long the enter altkeymap key must be pressed before ; we enable altkey keymap codes. GOTO KB_REP_SET ; and set it KB_REP_NOR: MOVLW DELAY_REPEAT ; reload 'normal' repeat delay ( 800 ms ) KB_REP_SET: MOVWF RepeatTimer ; and (re)start the timer for key repeat MOVF kbScan,W ; get key scan code MOVWF RepeatKey ; and save it KB_DO_MAKE: ; key pressed/released ( i.e. the scancode is sent both on make and break ) MOVF kbScan,W ; get scan code into w CALL ADD_KEY_BUFFER ; and add to send buffer ; reset the 'get out of alt. keymap timer for each keypress ; note don't care if we are 'in' alt. keymap. Reset this timer anyway ; as the code for checking if we are currently in alt. key map ; would be as long as it takes to reset the timer. MOVLW DELAY_EXIT_ALTKEYMAP ; reload the delay for exiting the altkeymap when no ; key is pressed ( 7.5 sec ) MOVWF LastKeyTime ; (re)set lastkey timer ( used to get out of altkeymap ) RETURN ; *********************************************************************** ; ; KB_DEBOUNCE - debounces two column readings from our keyboard ; If a bit 'state' has been 'stable' for 4 consecutive debounces ; the 'new' byte is updated with the new state ; 'normal' loop time ( no tx/rx/key press ) is about 2-3 ms ; so from 'key' down until 'new' is updated it takes about 8-10 ms ; ( as we are scanning columns two by two, the whole keyboard needs ; 4 loops to be fully updated, then 4 debounce samples for each 'pair' ) KB_DEBOUNCE: ; debounce current column(s) MOVF kbColumnCnt,F ; reload value into itself ( affect zero flag ) BTFSC STATUS,Z ; is it zero ? GOTO KB_DEBOUNCE_78 ; debounce columns 7 & 8 MOVF kbColumnCnt,W ; move column counter into W register SUBLW H'04' ; subtract value in W with 0x04 ( columns 5 & 6 ) BTFSC STATUS, Z ; check if the zero bit is set GOTO KB_DEBOUNCE_34 ; debounce columns 3 & 4 MOVF kbColumnCnt,W ; move column counter into W register SUBLW H'06' ; subtract value in W with 0x02 ( columns 3 & 4 ) BTFSC STATUS, Z ; check if the zero bit is set GOTO KB_DEBOUNCE_56 ; ok column 1 & 2 debounce ; all above tests 'failed' ; columns to debouce are 1 & 2 KB_DEBOUNCE_12: ; debounce columns 1 & 2 DEBOUNCE_BYTE kbColumnVal,kbColumn12_New,kbColumn12Cnt,kbColumn12State MOVF kbColumn12_New,W ; get debounced sample XORWF kbColumn12_Old,W ; get changed bits BTFSC STATUS,Z ; check if zero = no change RETURN ; no change. return ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8 ; Note ! Not the actual state of the key, only 'change has occured' = '1' MOVWF kbState ; save change bit/s MOVLW H'07' ; preset bit counter MOVWF kbBitCnt ; loop though all eight bits. BCF _LastColumn ; clear end seq bit ( set when are done with last bit ) MOVF kbColumn12_New,W ; get new sample MOVWF kbTemp ; and store it KB_LOOP_12 CLRF Offset ; clear offset counter ( for table read ) CLRC ; clear carry RLF kbState,F ; rotate left, and store back BTFSS STATUS,C ; check carry '1' = bit was high = change has occured GOTO KB_LOOP_12_SKIP ; nope, no change check next bit ( or exit ) ; bit changed MOVF kbBitCnt,W ; get bit counter ( for offset calc. ) MOVWF Offset ; store bit num ( for offset ) CLRC ; clear carry RLF kbTemp,F ; rotate left ( next bit ) BTFSS STATUS,C ; check carry '1' = key is down ( i.e. make ) BSF _isBreak ; c = '0' = send break code, i.e. key is released CALL KB_SEND_KEY ; send key code/s make/break uses ; Offset, and _isBreak vars GOTO KB_LOOP_12_NEXT KB_LOOP_12_SKIP RLF kbTemp,F ; rotate so we read next key KB_LOOP_12_NEXT BTFSC _LastColumn ; are we done ? GOTO KB_12_DONE ; yep, save new key bit map and exit DECFSZ kbBitCnt,F ; decrement bit counter GOTO KB_LOOP_12 ; bits left BSF _LastColumn ; set bit so we break out after next run GOTO KB_LOOP_12 KB_12_DONE: ; and update our 'last known' status for the columns MOVF kbColumn12_New,W ; get new status MOVWF kbColumn12_Old ; and store it.. ;<-------Alt keymap code-------> ; ***** alternative keymap handling ; The alternative keymap is enabled by pressing key r4 c1 ( i.e. bit 3 in column 12 ) ; Here, we enable a check to turn off alternative keymap if ; that key and all others are released ( bit is cleared ). ; ( else no (alternative)break codes would be sent for those keys that are still pressed ) ; NOTE: _Altkeymap is set inside int routine when checking ; keyrepeat so there is a 'variable' delay before the altkeymap is active ; BTFSS _AltKeymap ; is altkeymap enabled ? RETURN ; nope return BTFSC _InAltKeymap ; are we in altkeymap ? GOTO KB_12_IN ; yep alt keymap key has been released once ; nope still waiting for first release BTFSS kbColumn12_Old,2 ; is key released ? ( first time ) GOTO KB_12_ALT ; yep, reset timers and set bit variables KB_12_IN BTFSC _DoExitAltKeymap ; are we waiting for release ? GOTO KB_12_OUT ; yes ; the key has been released once test for second press BTFSC kbColumn12_Old,2 ; is it still pressed ? GOTO KB_12_ALT2 ; yep BTFSS _DoExitAltKeymap ; are we now waiting for the last ( second ) release ? RETURN ; nope KB_12_OUT BTFSS kbColumn12_Old,2 ; check if key still pressed ? BSF _ExitAltKeymap ; nope, then enable exit check that ; will exit alt keymap as soon as all key are released KB_12_ALT2 BSF _DoExitAltKeymap ; check for second release RETURN KB_12_ALT ; first release of the enter alt keymap key ; reset 'get out' timer and set bit variables to enable check ; for second press/release MOVLW H'0F' ; x0.5 sec = 7.5 sec MOVWF LastKeyTime ; (re)set lastkey timer ( used to get out of altkeymap automaticly) BSF _InAltKeymap ; yep the first time, then set flag that we are now ; waiting for a second press/release to exit alt key map ; all keys are released before exiting altkeymap ;***** Example snippet(one line) to be paired with code in CHECK_KEY_STATE where I ; forced numlock status to be off while enetering the alt keymap ; but have not yet released the alt keymap toggle key. ; this code will be called at the first release of this key. Used ; to restore numlock status. ; As said before, do not use if implifications are not known ! ;BSF _NumLock ; and also force numlock to be 'on' ; as it is set to 'off' when we enter altkeymap ; we must set it 'back' RETURN KB_DEBOUNCE_34: ; debounce columns 3 & 4 DEBOUNCE_BYTE kbColumnVal,kbColumn34_New,kbColumn34Cnt,kbColumn34State MOVF kbColumn34_New,W ; get debounced sample XORWF kbColumn34_Old,W ; get changed bits BTFSC STATUS,Z ; check if zero = no change RETURN ; no change. return ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8 ; Note ! Not the actual state of the key, only 'change has occured' = '1' MOVWF kbState ; save change bit/s MOVLW H'07' ; preset bit counter MOVWF kbBitCnt ; loop though all eight bits. BCF _LastColumn ; clear end seq bit ( set when are done with last bit ) MOVF kbColumn34_New,W ; get new sample MOVWF kbTemp ; and store it KB_LOOP_34 CLRF Offset ; clear offset counter ( for table read ) CLRC ; clear carry RLF kbState,F ; rotate left, and store back BTFSS STATUS,C ; check carry '1' = bit was high = change has occured GOTO KB_LOOP_34_SKIP ; nope, no change check next bit ( or exit ) ; bit changed MOVF kbBitCnt,W ; get bit counter ( for offset calc. ) MOVWF Offset ; store bit num ( for offset ) BSF Offset,3 ; set bit 3 for table read ( column 3 & 4 ) ;BCF _isBreak ; clear break flag CLRC ; clear carry RLF kbTemp,F ; rotate left ( next bit ) BTFSS STATUS,C ; check carry '1' = key is down ( i.e. make ) BSF _isBreak ; c = '0' = send break code, i.e. key is released CALL KB_SEND_KEY ; send key code/s make/break uses ; Offset, and _isBreak vars GOTO KB_LOOP_34_NEXT KB_LOOP_34_SKIP RLF kbTemp,F ; rotate so we read next key KB_LOOP_34_NEXT BTFSC _LastColumn ; are we done ? GOTO KB_34_DONE ; yep, save new key bit map and exit DECFSZ kbBitCnt,F ; decrement bit counter GOTO KB_LOOP_34 ; bits left BSF _LastColumn ; set bit so we break out after next run GOTO KB_LOOP_34 KB_34_DONE: ; and update our 'last known' status for the columns MOVF kbColumn34_New,W ; get new status MOVWF kbColumn34_Old ; and store it.. RETURN KB_DEBOUNCE_56: ; debounce columns 5 & 6 DEBOUNCE_BYTE kbColumnVal,kbColumn56_New,kbColumn56Cnt,kbColumn56State MOVF kbColumn56_New,W ; get debounced sample XORWF kbColumn56_Old,W ; get changed bits BTFSC STATUS,Z ; check if zero = no change RETURN ; no change. return ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8 ; Note ! Not the actual state of the key, only that 'a change has occured' = '1' MOVWF kbState ; save change bit/s MOVLW H'07' ; preset bit counter MOVWF kbBitCnt ; loop though all eight bits. BCF _LastColumn ; clear end seq bit ( set when are done with last bit ) MOVF kbColumn56_New,W ; get new sample MOVWF kbTemp ; and store it KB_LOOP_56 CLRF Offset ; clear offset counter ( for table read ) CLRC ; clear carry RLF kbState,F ; rotate left, and store back BTFSS STATUS,C ; check carry '1' = bit was high = change has occured GOTO KB_LOOP_56_SKIP ; nope, no change check next bit ( or exit ) ; bit changed MOVF kbBitCnt,W ; get bit counter ( for offset calc. ) MOVWF Offset ; store bit num ( for offset ) BSF Offset,4 ; set bit 4 for table read ( column 5 & 6 ) CLRC ; clear carry RLF kbTemp,F ; rotate left ( next bit ) BTFSS STATUS,C ; check carry '1' = key is down ( i.e. make ) BSF _isBreak ; c = '0' = send break code, i.e. key is released CALL KB_SEND_KEY ; send key code/s make/break uses ; Offset, and _isBreak vars GOTO KB_LOOP_56_NEXT KB_LOOP_56_SKIP RLF kbTemp,F ; rotate so we read next key KB_LOOP_56_NEXT BTFSC _LastColumn ; are we done ? GOTO KB_56_DONE ; yep, save new key bit map and exit DECFSZ kbBitCnt,F ; decrement bit counter GOTO KB_LOOP_56 ; bits left BSF _LastColumn ; set bit so we break out after next run GOTO KB_LOOP_56 KB_56_DONE: ; and update our 'last known' status for the columns MOVF kbColumn56_New,W ; get new status MOVWF kbColumn56_Old ; and store it.. RETURN KB_DEBOUNCE_78: ; debounce columns 7 & 8 DEBOUNCE_BYTE kbColumnVal,kbColumn78_New,kbColumn78Cnt,kbColumn78State MOVF kbColumn78_New,W ; get debounced sample XORWF kbColumn78_Old,W ; get changed bits BTFSC STATUS,Z ; check if zero = no change RETURN ; no change. return ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8 ; Note ! Not the actual state of the key, only 'change has occured' = '1' MOVWF kbState ; save change bit/s MOVLW H'07' ; preset bit counter MOVWF kbBitCnt ; loop though all eight bits. ( 7-0 ) BCF _LastColumn ; clear end seq bit ( set when are done with last bit ) MOVF kbColumn78_New,W ; get new sample MOVWF kbTemp ; and store it KB_LOOP_78 CLRF Offset ; clear offset counter ( for table read ) CLRC ; clear carry RLF kbState,F ; rotate left, and store back BTFSS STATUS,C ; check carry '1' = bit was high = change has occured GOTO KB_LOOP_78_SKIP ; nope, no change check next bit ( or exit ) ; bit changed MOVF kbBitCnt,W ; get bit counter ( for offset calc. ) MOVWF Offset ; store bit num ( for offset ) BSF Offset,4 ; set bit 3,4 for table read ( column 7 & 8 ) BSF Offset,3 ; CLRC ; clear carry RLF kbTemp,F ; rotate left ( next bit ) BTFSS STATUS,C ; check carry '1' = key is down ( i.e. make ) BSF _isBreak ; c = '0' = send break code, i.e. key is released CALL KB_SEND_KEY ; send key code/s make/break uses ; Offset, and _isBreak vars GOTO KB_LOOP_78_NEXT KB_LOOP_78_SKIP RLF kbTemp,F ; rotate so we read next key KB_LOOP_78_NEXT BTFSC _LastColumn ; are we done ? GOTO KB_78_DONE ; yep, save new key bit map and exit DECFSZ kbBitCnt,F ; decrement bit counter GOTO KB_LOOP_78 ; bits left BSF _LastColumn ; set bit so we break out after next run GOTO KB_LOOP_78 KB_78_DONE: ; and update our 'last known' status for the columns MOVF kbColumn78_New,W ; get new status MOVWF kbColumn78_Old ; and store it.. RETURN ; *********************************************************************** ; ; LOOKUP_KEY - lookup table for key scancodes. ; Returns a scancode in w ; Sets carry if key is extended ; NOTE: If key R3 C1 has been pressed longer than keyrepeat delay ; AND keyrepeat is disabled ( jumper on pin RB3 ) THEN the ; bit _AltKeymap is set and we can return an alternative scancode in W ; LOOKUP_KEY ; lookup table for the keys ( 32 keys x 4 lines + 1 = 129 lines ) ; keys are labelled Rx - Cy where x = row number and y = column number ; handles a 4 row x 8 column keyboard = 32 keys MOVWF PCL ; add to program counter ; R1 - C1 i.e. key 1 NOP NOP NOP RETLW H'05' ; scan code 'F1' ; R2 - C1 i.e. key 2 NOP NOP NOP RETLW H'0C' ; scan code 'F4' ; R3 - C1 i.e. key 3 ; The famous alternative keymap toggle key !!! ;-) ; It is adviced that this key does not use an alt scancode ; makes things cleaner and simplified. ; IF USED though, remember that a 'soft' release code must be sent ; for this key when entering the altkeymap !( + 'soft' make code for the alternative key ) ; This as the key is pressed when entering altkeymap ; which makes the bit _Altkeymap be set, and hence when released ; the release code for this 'normal' key will never be sent ; instead the release code for the alternative key will be sent. ; To 'fix' this put the release code at the end of CHECK_KEY_STATE ( where the ; alt keymap bit is set ).i.e. break prefix+scancode for normal key. NOP NOP NOP RETLW H'83' ; scan code for F7 ( in 'normal' mode ) ; R4 - C1 i.e. key 4 BTFSC _AltKeymap ; check for alternative keymap RETLW H'76' ; send scancode for 'ESC' instead BSF STATUS,C ; set carry ( i.e. extended code ) RETLW H'6B' ; scan code 'arrow left' '<-' ; R1 - C2 i.e. key 5 NOP NOP NOP RETLW H'06' ; scan code 'F2' hex06 ; R2 - C2 i.e. key 6 NOP NOP NOP RETLW H'03' ; scan code 'F5' ; R3 - C2 i.e. key 7 BTFSC _AltKeymap ; check for alternative keymap RETLW H'0D' ; send scancode for 'horizontaltab' HT instead BSF STATUS,C ; set carry ( i.e. extended code ) RETLW H'75' ; scan code 'arrow up' '^' ; R4 - C2 i.e. key 8 BTFSC _AltKeymap ; check for alternative keymap RETLW H'14' ; send scancode for 'left ctrl' instead BSF STATUS,C ; set carry ( i.e. extended code ) RETLW H'72' ; scan code 'arrow down' ; R1 - C3 i.e. key 9 NOP NOP NOP RETLW H'04' ; scan code 'F3' ; R2 - C3 i.e. key 10 NOP NOP NOP RETLW H'0B' ; scan code 'F6' ; R3 - C3 i.e. key 11 NOP NOP NOP RETLW H'0A' ; scan code 'F8' ; R4 - C3 i.e. key 12 BTFSC _AltKeymap ; check for alternative keymap RETLW H'11' ; send scancode for 'left alt' instead BSF STATUS,C ; set carry ( i.e. extended code ) RETLW H'74' ; scan code 'arrow right' '->' ; R1 - C4 i.e. key 13 BTFSC _AltKeymap ; check for alternative keymap RETLW H'6C' ; send scancode for numeric '7' instead NOP RETLW H'3D' ; scan code '7' ; R2 - C4 i.e. key 14 BTFSC _AltKeymap ; check for alternative keymap RETLW H'6B' ; send scancode for numeric '4' instead NOP RETLW H'25' ; scan code '4' ; R3 - C4 i.e. key 15 BTFSC _AltKeymap ; check for alternative keymap RETLW H'69' ; send scancode for numeric '1' instead NOP RETLW H'16' ; scan code '1' ; R4 - C4 i.e. key 16 BTFSC _AltKeymap ; check for alternative keymap RETLW H'7B' ; send scancode for numeric '-' instead NOP RETLW H'4A' ; scan code '-' minus ( swe kbd ) ; R1 - C5 i.e. key 17 BTFSC _AltKeymap ; check for alternative keymap RETLW H'75' ; send scancode for numeric '8' instead NOP RETLW H'3E' ; scan code '8' ; R2 - C5 i.e. key 18 BTFSC _AltKeymap ; check for alternative keymap RETLW H'73' ; send scancode for numeric '5' instead NOP RETLW H'2E' ; scan code '5' ; R3 - C5 i.e. key 19 BTFSC _AltKeymap ; check for alternative keymap RETLW H'72' ; send scancode for numeric '2' instead NOP RETLW H'1E' ; scan code '2' ; R4 - C5 i.e. key 20 BTFSS _AltKeymap ; check for alternative keymap RETLW H'45' ; scan code '0' ( from keypad ) normal key BSF STATUS,C ; set carry ( i.e. extended code ) RETLW H'1F' ; alt keycode ( windows start menu activate ) ; R1 - C6 i.e. key 21 BTFSC _AltKeymap ; check for alternative keymap RETLW H'7D' ; send scancode for numeric '9' instead NOP RETLW H'46' ; scan code '9' ; R2 - C6 i.e. key 22 BTFSC _AltKeymap ; check for alternative keymap RETLW H'74' ; send scancode for numeric '6' instead NOP RETLW H'36' ; scan code '6' ; R3 - C6 i.e. key 23 BTFSC _AltKeymap ; check for alternative keymap RETLW H'7A' ; send scancode for numeric '3' instead NOP RETLW H'26' ; scan code '3' ; R4 - C6 i.e. key 24 BTFSS _AltKeymap ; check for alternative keymap RETLW H'49' ; scan code '.' ( swe kbd ) normal key ; use alternative keymap BSF STATUS,C ; set carry ( i.e. extended code ) RETLW H'4A' ; send scancode for numeric '/' instead ; R1 - C7 i.e. key 25 BTFSC _AltKeymap ; check for alternative keymap RETLW H'79' ; send scancode for numeric '+' instead NOP RETLW H'4E' ; scan code '+' ; R2 - C7 i.e. key 26 BTFSS _AltKeymap ; check for alternative keymap RETLW H'66' ; scan code 'back space' BS, normal key ; use alternative keymap BSF STATUS,C ; set carry ( i.e. extended code ) RETLW H'71' ; send scancode for 'delete' instead ; R3 - C7 i.e. key 27 BTFSS _AltKeymap ; check for alternative keymap RETLW H'5A' ; scan code 'enter', normal key ; use alternative keymap BSF STATUS,C ; set carry ( i.e. extended code ) RETLW H'5A' ; send scancode for numeric enter instead ( note ! extended ) ; R4 - C7 i.e. key 28 BTFSS _AltKeymap ; check for alternative keymap RETLW H'5A' ; scan code 'enter', normal key ; use alternative keymap BSF STATUS,C ; set carry ( i.e. extended code ) RETLW H'5A' ; send scancode for numeric enter instead ( note ! extended ) ; R1 - C8 i.e. key 29 NOP NOP NOP RETLW H'2C' ; scan code 't' ; R2 - C8 i.e. key 30 NOP NOP NOP RETLW H'24' ; scan code 'e' ; R3 - C8 i.e. key 31 NOP NOP NOP RETLW H'1B' ; scan code 's' ; R4 - C8 i.e. key 32 NOP NOP NOP RETLW H'2C' ; scan code 't' END