; 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
; <alt>,<ctrl>,<tab> 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 <C:\PROGRAM\MPLAB\P16F84.INC> ; 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 <alt> 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 <alt> 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
Comments: