{Ed: This code has been translated to SX but not tested}
; PiCBoard
;
; PC-Keyboard emulator using an SX28
; 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 2002-10-20
; ITERATION 1.0C
; FILE SAVED AS PiCBoard_SX.ASM
; FOR SX28
; 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
; 1.0c - Converted and Optimized for the SX by James Newton
;
;
;
;***************************************************************************
;
; 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://www.sxlist.com/techref/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 RET
; 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"
device SX28
Radix DEC
EXPAND
;***** HARDWARE DEFINITIONS ( processor type include file )
;***** CONFIGURATION BITS
CODE "010C" ; 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
CLRB STATUS.RP0
ENDM
BANK1 MACRO
SETB STATUS.RP0
ENDM
;+++++
; PUSH/PULL save and restore W, PCLATH and STATUS registers -
; used on interrupt entry/exit
PUSH MACRO
MOV Saved_w, W ; Save W register on current bank
MOV W, <>STATUS ; Swap status to be saved into W
BANK0 ; Select BANK0
MOV Saved_Status, W ; Save STATUS register on bank 0
;*** WARNING: PCLATH register bits are in STATUS PAx bits. Or use PAGE/IREAD if possible
; MOVFW PCLATH
MOV W, PCLATH
;*** WARNING: PCLATH register bits are in STATUS PAx bits. Or use PAGE/IREAD if possible
; MOVWF Saved_Pclath ; Save PCLATH on bank 0
MOV Saved_Pclath, W ; Save PCLATH on bank 0
ENDM
PULL MACRO
BANK0 ; Select BANK0
;*** WARNING: PCLATH register bits are in STATUS PAx bits. Or use PAGE/IREAD if possible
; MOVFW Saved_Pclath
MOV W, Saved_Pclath
;*** WARNING: PCLATH register bits are in STATUS PAx bits. Or use PAGE/IREAD if possible
; MOVWF PCLATH ; Restore PCLATH
MOV PCLATH, W ; Restore PCLATH
MOV W, <>Saved_Status
MOV STATUS, W ; Restore STATUS register - restores bank
SWAP Saved_w
MOV W, <>Saved_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
SNB WHICH_PORT.WHICH_PIN ; is the pin high ?
JMP TOGGLE_PIN10 ; yes, clear it
SETB WHICH_PORT.WHICH_PIN ; no, so set it
JMP TOGGLE_END
TOGGLE_PIN10:
CLRB 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:
; MOV W, SB ;W = B
; XOR SA, W ;A+ = A ^ B
; NOT SB ;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
MOV W, DBState
XOR DBCnt, W
NOT DBState
;See if any changes occurred
MOV W, NewSample
XOR W, DebouncedSample
;Reset the counter if no change has occurred
AND DBState, W
AND DBCnt, W ;Determine the counter's state
MOV W, DBState
OR W, DBCnt
;Clear all bits that are filtered-or more accurately, save
;the state of those that are being filtered
AND DebouncedSample, W
XOR W, #$ff ;Re-write the bits that haven't changed.
AND W, NewSample
OR DebouncedSample, W
ENDM
;**************************************************************************
; Program Start
;**************************************************************************
; Reset Vector
ORG $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
;*** WARNING: PCLATH register bits are in STATUS PAx bits. Or use PAGE/IREAD if possible
; CLRF PCLATH
CLR PCLATH
CLR INTCON
JMP 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 $04
INT
PUSH ; Save registers and set to BANK 0
SB INTCON.T0IF ; check if TMR0 interrupt
JMP 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
CLRB INTCON.T0IF ; Clear the calling flag !
SNB _TX_Mode ; check if we are in tx mode
JMP INT_TX ; yep, goto tx mode code..
SNB _RX_Mode ; are we in rx mode ?
JMP INT_RX ; yep goto rx mode code
JMP INT_HEART_BEAT ; nope just goto 'heart beat' mode
;*************** TX code start ***********************************
INT_TX
MOV W, #$FA ; preset timer with 252 ( 256 - 6 = 250 )
; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
MOV RTCC, W
SB clkCount.0 ; check if we should toggle clock
JMP INT_DEC_CLOCK ; bit low,decrement and check if we should toggle data instead
DECSZ clkCount ; decrement and check if we are at zero..
JMP INT_CLOCK ; not zero then toggle clock line
JMP INT_EXIT_TX
INT_CLOCK
SNB pcCLOCK_out ; check if we are low
JMP INT_CLOCK_HIGH ; yep set to high
SB pcCLOCK_in ; check if pc is pulling the clock line low
; i.e. it wants to abort and send instead..
JMP INT_TX_CHECK_ABORT ; abort this transfer
SETB pcCLOCK_out ; ok to set clock low ( pull down )
JMP INTX
INT_CLOCK_HIGH
CLRB pcCLOCK_out ; set high ( release line )
; CLRB _ClockHigh ;
JMP INTX
INT_TX_CHECK_ABORT
JMP INT_EXIT_TX
INT_DEC_CLOCK
DEC clkCount ; decrement clock counter ( so we toggle next time )
INT_DATA
SB bitCount.0 ; check bit counter
JMP INT_DATA_IDLE ; no data toggle
DECSZ bitCount ; decrement bit counter
JMP INT_DATA_NEXT ; next bit..
INT_NO_BITS
SETB bitCount.0 ; just in case ( stupid code, not sure its needed, just
; to make it impossible to overdecrement )***
SNB _isParity ; are we sending parity ?
JMP 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
SETB _isParity ; set flag data is parity
SB KeyParity.0 ; is the last parity bit high? ( odd num of bits )
; then parity should be high ( free )
JMP INT_DATA_HIGH_PAR ; yes
SETB pcDATA_out ; no, parity should be 'low' ( pulled down )
JMP INTX ;
INT_DATA_HIGH_PAR:
CLRB pcDATA_out ; set parity bit high ( release data line )
JMP INTX ; and exit..
INT_DATA_END
SB _isStopBit ; is the stopbit sent ?
JMP INT_DATA_STOPB ; nope then set stopbit flag
CLRB pcDATA_out ; parity bit sent, always release data line ( stop bit )
JMP INTX
INT_DATA_STOPB
SETB _isStopBit ; set the stopbit flag
JMP INTX ; and exit
INT_DATA_IDLE
DEC bitCount ; decrement bit counter
JMP INTX ; no toggle of data line
INT_DATA_NEXT
SB CurrKey.0 ; is the last bit of the key_buffer high?
JMP INT_DATA_LOW ; no, pull data low
CLRB pcDATA_out ; yes, release data line
INC KeyParity ; increment parity bit
JMP INT_DATA_ROTATE ; rotate data
INT_DATA_LOW ; last bit is low
SETB pcDATA_out ; set the bit
INT_DATA_ROTATE
RR CurrKey ; rotate right by 1 bit
JMP INTX
INT_EXIT_TX
; setup the timer so we accomplish an delay after an tx seq
CLRB _TX_Mode ; clear tx mode flag
CLRB pcCLOCK_out ; release clock line
CLRB pcDATA_out ; and data line
;
MOV W, #$64 ; start timer with 100 ( 256-100 = 156 )
; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
MOV RTCC, W
JMP INTX
;************** TX code end *****************
;************** RX code start ***************
INT_RX
MOV W, #$FA ; preset timer with 252 ( 256 - 6 = 250 )
; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
MOV RTCC, W
SB clkCount.0 ; check if we should toggle clock
JMP INT_RX_DEC_CLOCK ; bit low,decrement and check if we should read data instead
DECSZ clkCount ; decrement and check if we are at zero..
JMP INT_RX_CLOCK ; not zero then toggle clock line
CLRB pcCLOCK_out ; release the clock line we are done..
CLRB _RX_Mode ; clear rx mode bit ( go over to heart beat mode )
JMP INT_EXIT_RX
INT_RX_CLOCK
SNB pcCLOCK_out ; check if we are low
JMP INT_RX_CLOCK_HIGH ; yep set to high ( release line )
SNB _isStartBit ; check if this is the first bit ( start )
JMP INT_RX_START ; clear start bit and continue
SNB _isParity ; check if this is the parity bit ( or parity has been received )
JMP INT_RX_PAR ; yep check parity
JMP INT_RX_BIT ; ok just a 'normal' bit read it
INT_RX_PAR ; check parity
SNB _doRXAck ; check the handshake flag
JMP INT_RX_HNDSHK ; start handshake check
SB pcDATA_in ; is the input high ?
JMP INT_RX_PAR_HIGH ; yep
SNB KeyParity.0 ; is the parity '0' ( should be )
JMP INT_RX_PAR_ERR ; nope parity error
JMP INT_RX_ACK ; parity ok next should be ack ( we take data line low )
INT_RX_PAR_HIGH
SB KeyParity.0 ; check that parity bit is '1'
JMP INT_RX_PAR_ERR ; nope parity error
JMP INT_RX_ACK ; parity ok, next is ack ( we take data line low )
INT_RX_PAR_ERR
SETB _KeyError ; set error flag
INT_RX_ACK
SETB pcCLOCK_out ; ok to set clock low ( pull down )
SETB _doRXAck ; enable ack check
JMP INTX
INT_RX_HNDSHK
SB _RXEnd ; if we are done dont take data low
SETB pcCLOCK_out ; ok to set clock low ( pull down )
SNB _RXAckDone ; chek if hand shake ( ack ) is done ?
SETB _RXEnd ; ok we are now done just make one more clock pulse
JMP INTX ; exit
INT_RX_CLOCK_HIGH
CLRB pcCLOCK_out ; set high ( release line )
SB _RXAckDone ; are we done.. ?
JMP INTX
SB _RXDone ; finished ?
JMP INTX
CLRB _RX_Mode ; and clear rx flag..
JMP INT_EXIT_RX ; bye bye baby
INT_RX_DEC_CLOCK
DEC clkCount ; decrement clock counter ( so we toggle next time )
SB _doRXAck ; check if we are waiting for handshake
JMP INTX
SNB pcCLOCK_out ; check if the clock is low ( pulled down )
JMP 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.. ???
SNB _RXEnd ; are we done ?
JMP INT_RX_END
; handshake check if data line is free ( high )
SB pcDATA_in ; is data line free ?
JMP INTX ; nope
SETB pcDATA_out ; takeover data line
SETB _RXAckDone ; we are done..at next switchover from low-high we exit
JMP INTX ;
INT_RX_END
CLRB pcDATA_out ; release data line
SETB _RXDone ; we are now done
JMP INTX
INT_RX_START
CLRB _isStartBit ; clear start bit flag
SETB pcCLOCK_out ; ok to set clock low ( pull down )
JMP INTX
INT_RX_BIT
CLRB CurrKey.7
SB pcDATA_in ; is bit high
JMP INT_RX_NEXT ; nope , it's a '0'
SETB CurrKey.7 ; set highest bit to 1
INC KeyParity ; increase parity bit counter
INT_RX_NEXT
SETB pcCLOCK_out ; ok to set clock low ( pull down )
DECSZ bitCount ; decrement data bit counter
JMP INT_RX_NEXT_OK
SETB bitCount.0 ; just in case ( so we cannot overdecrement )
SETB _isParity ; next bit is parity
JMP INTX
INT_RX_NEXT_OK
CLRB C ; clear carry, so it doesnt affect receving byte
RR CurrKey ; rotate to make room for next bit
JMP INTX ; and exit
INT_EXIT_RX
; handle the recevied key ( if not it is an 'data' byte )
MOV W, #$C4 ; preset timer with 196 ( 256 - 60 = 196 )
; timetick = 0.4uS x 8 ( prescale ) x 60 + 8 ( int handling code )= 200 us
;
MOV RTCC, W ; this delay seems to be needed ( handshake ? )
; check if this is an data byte ( rate/delay led status etc )
TEST CommandData ; reload into itself ( affect zero flag )
SB Z ; check zero flag
JMP INT_STORE_DATA ; byte contains data ( rate/delay etc )
CALL CHECK_RX_KEY ; no data, handle recevied command
JMP INTX
INT_STORE_DATA
; store data byte in 'currkey',
; first reply with 'ack'
MOV W, #$FA ; keyboard ack
CALL ADD_KEY ;
SB _IsLedStatus ; is it led status byte ?
JMP INT_STORE_RATE ; nope check next
INT_STORE_NUM
; byte in 'currkey' is led status byte, store it
MOV W, CurrKey ; get byte
MOV KbLedStatus, W ; and store it
SNB _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
JMP INT_STORE_EXIT ; store it in local ram copy and exit
INT_STORE_RATE
SB _IsRateDelay ; is it rate/delay byte ?
JMP INT_STORE_EXIT ; nope then send ack end exit
; byte in 'currkey' is rate/delay byte, store it
MOV W, CurrKey ; get byte
MOV KbRateDelay, W ; and store it
INT_STORE_EXIT
CLR CommandData ; clear data byte flags
JMP 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
MOV W, #$64 ; start timer with 100 ( 256-100 = 156 )
; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
MOV RTCC, W
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
SB pcDATA_in ; is the data line high ( free )..
JMP INT_CHECK_RX ; Nope it's pulled down, check if rx is requested
SNB pcCLOCK_in ; Is the clk line low ( pulled down ) ?
JMP INT_CHECK_BUFF ; Nope, so check if we have any keys to send
JMP INT_IDLE ; clock is low , wait and buffer keys( i.e. no rx/tx )
INT_CHECK_RX ; pc ( probably ) wants to send something..
SB pcCLOCK_in ; wait until clock is released before we go into receving mode..
JMP INT_RX_IDLE ; nope still low
; clock now high test if we are set to start an rx seq.
SB _RxCanStart ; have we set the flag ?
JMP INT_WAIT_RX ; nope then set it
SNB pcDATA_in ; make sure that data still is low
JMP INT_ABORT_RX ; nope abort rx req, might been a 'glitch'
; initiate the rx seq.
CLR Comm_Flags ; used by both tx/rx routines ( and _RxCanStart bit !! )
CLR TRX_Flags ; clear tx/rx flags
CLR KeyParity ; clear parity counter
SETB _RX_Mode ; set rx mode flag..
SETB _isStartBit ; set that next sampling is start bit
; preset bit and clock counters
MOV W, #$2F ; = 47 dec, will toggle clock output every even number until zero
MOV clkCount, W ; preset clock pulse counter
MOV W, #$08 ; = 8 dec, number of bits to read
; then parity bit will be set instead
MOV bitCount, W ; 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'
MOV W, #$C4 ; preset timer with 196 ( 256 - 60 = 196 )
; timetick = 0.4uS x 8 ( prescale ) x 60 + 8us ( int handling code )= 200 us
;
MOV RTCC, W
JMP INTX ; exit, the next int will start an rx seq
INT_WAIT_RX:
SETB _RxCanStart ; set flag so we start rx next int ( 0.5 ms )
INT_RX_IDLE
; reload clock so we check more often
MOV W, #$F0 ; start timer with 240 ( 256-16 = 240 )
; timetick = 0.4uS x 8 ( prescale ) x 16 + 8/10us ( int handling code )= (about) 60 us
MOV RTCC, W
JMP INTX ;
INT_ABORT_RX
CLRB _RxCanStart ; clear flag ( forces a 'new' rx start delay )
JMP INT_IDLE ;
INT_CHECK_BUFF:
; check if we have any keys to send to pc
MOV W, KeyBufferTail ; get end of buffer to 'w'
MOV W, KeyBufferHead-w ; subtract start of buffer if head - tail = zero, no byte
SNB Z ; zero flag = no byte to send
JMP 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
MOV W, CurrKey
MOV LastKey, W ; store last sent key
; setup our tx/rx vars
CLR Comm_Flags ; used by both tx/rx routines ( and _RxCanStart bit !! )
CLR TRX_Flags ; clear tx/rx flags
CLR KeyParity ; clear parity counter
SETB _TX_Mode ; set tx mode flag..
; preset bit and clock counters
MOV W, #$2B ; = 43 dec, will toggle clock out put every even number until zero
MOV clkCount, W ; preset clock pulse counter
MOV W, #$12 ; = 18 dec, will shift data out every even number until zero
; then parity bit will be set instead
MOV bitCount, W ; preset bit counter
; data now set, initiate the clock to generate a 20 us tick ( rx/tx heart beat )
SETB pcDATA_out ; start bit, always 'low' ( we pull down )
MOV W, #$FA ; start timer with 252 ( 256-6 = 250 )
; timetick = 0.4uS x 8 ( prescale ) x 6 + 8 ( int handling code )= 27 us
MOV RTCC, W
JMP 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
DEC Divisor_10ms ; Count 0.5ms down to give 10 milli second tick
JNZ INTX ; Exit if divider not zeroed
MOV W, #20
MOV Divisor_10ms, W ; Preset the divide by 20
;+++
; 10 ms tick here
; Divide the 10 ms tick to give 100 ms second tick
INT_10MS
DEC Divisor_100ms ; Count 10ms down to give 100 milli second tick
JNZ INTX ; Exit if divider not zeroed
MOV W, #10
MOV Divisor_100ms, W ; 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.
SNB _IsFirstLedStatus ; if = 1 then we have not yet received numlock status
JMP INT_REPEAT_CHECK ; nope, then this is a consecutive byte, store as 'normal'
SB _WaitNumLock ; are we waiting for pc numlock reply ?
JMP INT_NUMLOCK_CHECK ; yep then do repeat check instead
DECSZ Temp_Var ;
JMP INT_REPEAT_CHECK
CALL RELEASE_NUMLOCK
INT_NUMLOCK_CHECK
SNB _LedNumLock ; is the led on ?
JMP INT_NUMLOCK_ON ; yep, then test our 'local' numlock state ( wanted numlock state )
; nope numlock is off, is our wanted state also off ?
SB _NumLock ; is wanted state off ?
JMP INT_REPEAT_CHECK ; yep continue
CALL PRESS_NUMLOCK ; nope then send numlock press/release code
JMP INT_REPEAT_CHECK
INT_NUMLOCK_ON
SNB _NumLock ; is wanted state also 'on' ?
JMP 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 )
SB _startRepeat ; start repeating a key ? ( delay !!! )
JMP INT_CHECK_KEY ; nope, then check if key should be repeated
DEC RepeatTimer ;
JNZ INT_500MS ; not zero yet, check timer instead
CLRB _startRepeat ; stop repeat timer ( delay is accomplished )
SETB _doRepeat ; and enable 'key' is still down check
MOV W, #02 ; start repeat send timer
MOV Divisor_Repeat, W ;
JMP INT_500MS ; do next timer check
INT_CHECK_KEY
SB _doRepeat ; key should be repeated ?
JMP 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',
SB C ; check carry
CLRB _doRepeat ; clear repeat bit, stop repeating the key
SB _doRepeat ; still pressed ?
JMP INT_500MS ; nope
DEC Divisor_Repeat ; should we send the key ?
;*** WARNING: MPASM macro BNZ is not supported yet. Replace manually.
BNZ INT_500MS ; nope
MOV W, #DELAY_RATE ; reload timer with key rate delay
; MOV W, #02 ; restart timer
MOV Divisor_Repeat, W ;
SETB _doSendKey ; set flag to send key, NOTE the actual sending ( putting into send buffer )
; is done inside mainloop.
INT_500MS
DEC Divisor_500ms ; Count 100ms down to give 500 milli second tick
JNZ INTX ; Exit if divider not zeroed
MOV W, #05
MOV Divisor_500ms, W ; Preset the divide by 5
;+++
; 500 ms tick here
INT_500_NEXT
TOGGLE_PIN O_led_KEYCOMM_ok ; toggle the disco light ;-)
SB _DoExitAltKeymap ; is the alt keymap toggle key pressed the second time ?
; if so skip timeout test and exit
SB _InAltKeymap ; are we in altkeymap ?
JMP INTX ; nope
; we are in altkeymap, decrement the lastkeytime
; and check if we are at zero then we exit
; the altkeymap.
DEC LastKeyTime ; decrease time
JNZ INTX ; exit, timer has not expired
; timer expired, get out of altkey map
SETB _ExitAltKeymap ;
; ***************** 'heart' beat code end ***************
INTX
; CLRB INTCON.T0IF ; Clear the calling flag
PULL ; Restore registers
RETI
; **************** end interrupt routine **************
;+++++
; Routines that will 'toggle' keyboard numlock status
; by sending numlock make/break code
;
PRESS_NUMLOCK:
MOV W, #$77 ; numlock key scancode, make
CALL ADD_KEY
MOV W, #$06 ; 6 x 100 ms = 600 ms ( release delay )
MOV Temp_Var, W ;
SETB _WaitNumLock ; we are waitin for numlock status reply from pc
RET
RELEASE_NUMLOCK:
MOV W, #BREAK ; break prefix
CALL ADD_KEY
MOV W, #$77 ; numlock key scancode
CALL ADD_KEY
CLRB _WaitNumLock
RET
; ***********************************************************************
;
; CHECK_RX_KEY - handles the received commands from pc
;
CHECK_RX_KEY
; check the key in 'currkey' ( command from pc )
CHECK_ED
MOV W, CurrKey ; move key buffer into W register
XOR W,$ED ; subtract value in W with 0xED
SB Z ; check if the zero bit is set
JMP CHECK_EE ; the result of the subtraction was not zero check next
; ok 'ED'=set status leds ( in next byte ) received
SETB _IsLedStatus ; set bit that next incoming byte is kb led staus
JMP CHECK_SEND_ACK ; send ack
CHECK_EE
MOV W, CurrKey ; move key buffer into W register
XOR W,$EE ; subtract value in W with 0xEE
SB Z ; check if the zero bit is set
JMP CHECK_F0 ; the result of the subtraction was not zero check next
; ok 'EE'= echo command received
JMP CHECK_SEND_EE ; send echo
CHECK_F0
MOV W, CurrKey ; move key buffer into W register
XOR W,$F0 ; subtract value in W with 0xF0
SB Z ; check if the zero bit is set
JMP CHECK_F2 ; the result of the subtraction was not zero check next
; ok 'F0'= scan code set ( in next commming byte ) received
SETB _SkipByte ; skip next incomming byte ( or dont interpret )
JMP CHECK_DONE ; do not send ack !
CHECK_F2
MOV W, CurrKey ; move key buffer into W register
XOR W,$F2 ; subtract value in W with 0xF0
SB Z ; check if the zero bit is set
JMP CHECK_F3 ; the result of the subtraction was not zero check next
; ok 'F2'= Read ID command responds with 'AB' '83'
JMP CHECK_SEND_ID ; send id bytes
CHECK_F3
MOV W, CurrKey ; move key buffer into W register
XOR W,$F3 ; subtract value in W with 0xF3
SB Z ; check if the zero bit is set
JMP CHECK_FE
; JMP CHECK_F4 ; the result of the subtraction was not zero check next
; ok 'F3'= set repeat rate ( in next commming byte ) received
SETB _IsRateDelay ; next incomming byte is rate/delay info
JMP 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
; MOV W, CurrKey ; move key buffer into W register
; XOR W,$F4 ; subtract value in W with 0xF4
; SB Z ; check if the zero bit is set
; JMP CHECK_F5 ; the result of the subtraction was not zero check next
; ok 'F4'= keyboard enable received
; JMP CHECK_SEND_ACK ; send ack
;CHECK_F5
; MOV W, CurrKey ; move key buffer into W register
; XOR W,$F5 ; subtract value in W with 0xF5
; SB Z ; check if the zero bit is set
; JMP CHECK_FE ; the result of the subtraction was not zero check next
; ok 'F5'= keyboard disable received
; JMP CHECK_SEND_ACK ; send ack
CHECK_FE
MOV W, CurrKey ; move key buffer into W register
XOR W,$FE ; subtract value in W with 0xFE
SB Z ; check if the zero bit is set
JMP CHECK_FF ; the result of the subtraction was not zero check next
; ok 'FE'= resend last sent byte
MOV W, LastKey ; get last key
CALL ADD_KEY ; and put it on the que
JMP CHECK_DONE
CHECK_FF
MOV W, CurrKey ; move key buffer into W register
XOR W,$FF ; subtract value in W with 0xFF
SB Z ; check if the zero bit is set
JMP CHECK_ERROR ; the result of the subtraction was not zero, unknown command
; ok 'FF'= reset keyboard received
JMP CHECK_SEND_AA ; send 'AA' power on self test passed
CHECK_ERROR ; unknown command ( or command not interpreted )
JMP CHECK_SEND_ACK
CHECK_SEND_ID
MOV W, #$FA ; keyboard ack
CALL ADD_KEY ;
MOV W, #$AB ; keyboard id first byte, always 0xAB
CALL ADD_KEY ;
MOV W, #$83 ; keyboard id second byte, always 0x83
CALL ADD_KEY ;
JMP CHECK_DONE
CHECK_SEND_ACK
MOV W, #$FA ; keyboard ack
CALL ADD_KEY ;
JMP CHECK_DONE
CHECK_SEND_AA
MOV W, #$FA ; keyboard ack
CALL ADD_KEY ;
MOV W, #$AA ; keyboard post passed
CALL ADD_KEY ;
JMP CHECK_DONE
CHECK_SEND_EE
MOV W, #$EE ; keyboard echo
CALL ADD_KEY ;
CHECK_DONE
RETW #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 !!!!!!!
CLRB INTCON.GIE ; disable global interrupts..
SNB INTCON.GIE ; check that is really was disabled
JMP ADD_STOP_INT ; nope try again
ADD_KEY ; inside interuppt we call this instead ( as we dont need to disable int :-) )
MOV BufTemp, W ; store key temporary
MOV W, KeyBufferHead ; move buffer head out of FSR temporarily
MOV Temp, W ; store in temp
MOV W, KeyBufferTail ; set FSR to buffer tail
MOV FSR, W ; set indirect file pointer
MOV W, BufTemp ; set W to new scancode to send
MOV INDF, W ; and put it in the buffer
MOV W, Temp ; get the head pointer back
MOV KeyBufferHead, W ;
INC KeyBufferTail
MOV W, #KbBufferMax ; check if at buffer max
MOV W, KeyBufferTail-W
MOV W, KeyBufferTail ; (reload value to w - doesn't affect C)
SB C ; if so (negative result)
MOV W, #KbBufferMin ; set to buffer min ( wrap around )
MOV KeyBufferTail, W
MOV W, KeyBufferHead-w ; see if we have any room ( head and tail have meet )
SNB Z ; if so (Z set)
CALL INC_KEY_HEAD ; dump oldest byte
; finally turn on interrupts again
MOV W, #%10100000 ; enable global & TMR0 interrupts
MOV INTCON, W
RET
; ***********************************************************************
;
; 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
MOV W, INDF ;put the byte to send into key buffer
MOV CurrKey, W
RET ; 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:
INC KeyBufferHead ; set to next byte in buffer
MOV W, #KbBufferMax
MOV W, KeyBufferHead-W ; check if at buffer max
MOV W, KeyBufferHead ; (reload value to w - doesn't affect C)
SB C ; if so (negative result)
MOV W, #KbBufferMin ; set to buffer min ( wrap around )
MOV KeyBufferHead, W ; and store ( in FSR )
RET ; 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
MOV W, LastMakeOffset ; get offset
AND W, #$18 ; mask out column bits
; lastmake offset has the following bits:
; '000yyxxx' where 'yy' is column no
; and 'xxx' is key num,
SNB Z ; zero = column 1 & 2
JMP CHECK_COL_12 ; it is in column 1
MOV repTemp, W ; save it temporary
XOR W,$08 ; subtract value in W with 0x08 ( columns 3 & 4 )
SNB Z ; check if the zero bit is set
JMP CHECK_COL_34 ; it is in column 3 & 4
MOV W, repTemp ; get the column bits back
XOR W,$10 ; subtract value in W with 0x10 ( columns 5 & 6 )
SNB Z ; check if the zero bit is set
JMP CHECK_COL_56 ; it is in column 5 & 6
CHECK_COL_78
MOV W, kbColumn78_Old ; get bit map ( key status ) for keys in column 7 & 8
MOV repKeyMap, W ; and store it
JMP CHECK_KEY ; and continue to check bit
CHECK_COL_56
MOV W, kbColumn56_Old ; get bit map ( key status ) for keys in column 5 & 6
MOV repKeyMap, W ; and store it
JMP CHECK_KEY ; and continue to check bit
CHECK_COL_34
MOV W, kbColumn34_Old ; get bit map ( key status ) for keys in column 3 & 4
MOV repKeyMap, W ; and store it
JMP CHECK_KEY ; and continue to check bit
CHECK_COL_12
MOV W, kbColumn12_Old ; get bit map ( key status ) for keys in column 1 & 2
MOV repKeyMap, W ; 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 )
MOV W, LastMakeOffset ; get key offset again
AND W, #$07 ; mask out column bits
XOR W,$02 ; check if its bit num 2 ( the enter 'alt keymap' key )
SB Z ; check if the zero bit is set
JMP CHECK_KEY ; nope than another key was the last
; skip altkeymap enable
; the altkeymap key was the last pressed !
; is key repeat disabled ?
SB I_jmp_NoRepeat ; check if repeat code is enabled ?
JMP CHECK_KEY ; yep, then skip altkeymap enable test
; enable altkeymap if key is still pressed
SNB repKeyMap.2 ; test bit 2 ( should be key 'F7' )
JMP CHECK_ENABLE_ALT ; ok enable altkeymap ( if we are not already in altkeymap )
JMP CHECK_KEY ; nope another key in column 1&2 continue check
CHECK_ENABLE_ALT
SNB _AltKeymap ; are we already in altkeymap ?
JMP CHECK_KEY ; yep then just continue
; We are just entering/enabling the alt. keymap
SETB _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
; MOV W, #BREAK ; send break prefix
; CALL ADD_KEY
; MOV W, #$83 ; and scancode for the enter alt keymap
; CALL ADD_KEY
; MOV W, #$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
; CLRB _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.
; SNB _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
MOV W, LastMakeOffset ; get offset again
AND W, #$07 ; mask out key number ( lowest 3 bits )
SNB Z ; bit num zero ?
JMP CHECK_KEY_DONE ; yep lowest bit, check and return
MOV repTemp, W ; and store it
CHECK_KEY_LOOP
RR repKeyMap ; rotate one step to right
DECSZ repTemp ; decrement bit counter
JMP CHECK_KEY_LOOP ; loop again
CHECK_KEY_DONE
; ok the key to test should now be the lowest bit in repKeyMap
CLRB C ; clear carry
SNB repKeyMap.0 ; check bit 0
SETB C ; ok key is pressed set carry
RET ; 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
MOV W, #$F0 ; wait 255 cycles
MOV kbTemp, W ; this var is 'safe' to be used in side mainloop
MOV W, #$03
MOV kbState, W
DELAY_LOOP
DECSZ kbTemp ; decrement
JMP $-1 ;
MOV W, #$F0
MOV kbTemp, W
DECSZ kbState
JMP DELAY_LOOP
RET
;---------------------------------------------------------------------------
;
; Initialisation
;
;---------------------------------------------------------------------------
INIT:
;+++
; Set up the ports
; PORT A
MODE $0F
BANK1
MOV W, #%00000110 ; Set port data directions RA1,RA2 inputs RA0,RA3,RA4 outputs
MOV RA!,W ; PC keyboard connections
; PORT B
; Used for our own 3x8 matrix keyboard
BANK1
MOV W, #%11111000 ; Set port data directions RB4-RB7 inputs rest outputs
MOV RB!, W
; Clear all registers on bank 0 ( memory )
BANK0
MOV W, #$0C
MOV FSR, W
INITMEM
CLR 0 ; Clear a register pointed to be FSR
INC FSR
CLR !WDT ; clear watchdog
MOV W, #$50 ; Test if at top of memory
MOV W, FSR-w
JNZ INITMEM ; Loop until all cleared
;+++
; Initiate the keybuffer pointers
INIT_BUFF:
MOV W, #KbBufferMin ; get adress of first buffer byte
MOV KeyBufferHead, W ; store in FSR
MOV KeyBufferTail, W ; and set last byte to the same ( no bytes in buffer )
;+++
; Preset the timer dividers
MOV W, #20
MOV Divisor_10ms, W
MOV W, #10
MOV Divisor_100ms, W
MOV W, #05
MOV Divisor_500ms, W
;+++
; Set up Timer 0.
; Set up TMR0 to generate a 0.5ms tick
; Pre scale of /8, post scale of /1
BANK1
MOV W, #%00000010 ; Initialisation of TMR0 prescale 8 '010'
; weak pullup enabled by latch values.
MOV !OPTION, W ; load option reg with prescale of 8
BANK0
MOV W, #$64 ; start timer with 100 ( 256-100 = 156 )
; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
MOV RTCC, W
;---------------------------------------------------------------------------
;
; the main 'program' loop ( starts really at MAIN_LOOP )
;
;---------------------------------------------------------------------------
MAIN:
SETB pcDATA_in
SETB pcCLOCK_in
CLRB pcDATA_out
CLRB pcCLOCK_out
CLR RB
MOV W, #$08 ; preset the column counter
MOV kbColumnCnt, W ;
SETB _NumLock ; default state is numlock = on
SETB _IsFirstLedStatus ; we have not yet recevied led status byte.
MOV W, #%10100000 ; enable global & TMR0 interrupts
MOV INTCON, W
CLR !WDT ; clear watchdog
SB O_led_KEYCOMM_ok
JMP $-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.
CLR !WDT ; clear watchdog
SNB O_led_KEYCOMM_ok
JMP $-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.
MOV W, #$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 :-)
CLR !WDT ; 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
CLR kbColumnVal
; get column counter / adress out
MOV W, kbColumnCnt
MOV RB, W ; set the columns adress to the 74HCT4051
; i.e. make column low
IFNDEF DEBUG
CALL DELAY_1ms ; wait 1 ms let pins stabilize
ENDIF
MOV W, /RB ; read back the pin values ( complement i.e. key pressed = '1' )
AND W, #%11110000 ; mask out unused pins
MOV kbColumnVal, W ; store the pin values
SWAP kbColumnVal ; swap nibbles ( low<->high ) to make room for next column
INC kbColumnCnt ; 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
MOV W, kbColumnCnt
MOV RB, W ; set the columns adress to the 74HCT4051
; i.e. make column low
IFNDEF DEBUG
CALL DELAY_1ms ; wait 1 ms
ENDIF
MOV W, /RB ; read back the pin values ( complement i.e. key pressed = '1' )
AND W, #%11110000 ; mask out unused pins
ADD kbColumnVal, W ; and store pin values
INC kbColumnCnt
; reset column counter check
; i.e. we are 'only' using adress 0 - 7
MOV W, kbColumnCnt
XOR W,$08 ; subtract value in W with 0x08
SB Z ; check if the zero bit is set
JMP MAIN_CHECK_DEBOUNCE ; nope continue
CLR 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:
SB I_jmp_NoRepeat ; check if repeat code is enabled ?
JMP MAIN_CHECK_REPEAT ; yep check key repeating
; keyrepeat disabled then do check on exit of altkeymap instead
SB _ExitAltKeymap ; we want to exit altkeymap ?
JMP MAIN_LOOP ; nope
; check that ALL keys are released
; before exiting the alt keymap
TEST kbColumn78_Old ; reload column 78 to itself ( affect zero flag )
SB Z ; check if zero ?
JMP MAIN_LOOP ; key/s still down in column 78
TEST kbColumn56_Old ; reload column 56 to itself ( affect zero flag )
SB Z ; check if zero ?
JMP MAIN_LOOP ; key/s still down in column 56
TEST kbColumn34_Old ; reload column 34 to itself ( affect zero flag )
SB Z ; check if zero ?
JMP MAIN_LOOP ; key/s still down in column 34
TEST kbColumn12_Old ; reload column 12 to itself ( affect zero flag )
SB Z ; check if zero ?
JMP MAIN_LOOP ; key/s still down in column 12
; all keys released !!
CLRB _AltKeymap ; exit altkeymap
CLRB _ExitAltKeymap ; exit release check
CLRB _InAltKeymap ; clear flag for second keypress check
CLRB _DoExitAltKeymap ;
JMP MAIN_LOOP
MAIN_CHECK_REPEAT
SB _doSendKey ; if we should send a repeated key
JMP MAIN_LOOP ; nope continue
; send the key in RepeatedKey but first check if its an extended key
SB _RepeatIsExt ; is it extended ?
JMP MAIN_SEND_REPEAT ; nope just send scan code
; last key pressed was extended send extended prefix
MOV W, #EXTENDED ; get extended code
CALL ADD_KEY_BUFFER ; and put it into the buffer
MAIN_SEND_REPEAT:
MOV W, RepeatKey ; get key code for the last pressed key
CALL ADD_KEY_BUFFER ; and put it into the buffer
CLRB _doSendKey ; and clear the flag, it will be set again
; inside int handler if key still is pressed
JMP 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:
MOV W, Offset ; get current offset
MOV TempOffset, W ; 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,
CLRB C ; clear carry so it dont affect byte rotation
RL Offset ; first rotate
RL Offset ; 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'
INC Offset ; add one, for the 'movwf pcl' at the start of the table
CLRB Offset.7 ; clear to bit, just in case so we dont
; 'overflow' the table, should not be needed !
CLRB _isExtended ; clear extended flag
MOV W, #LOW LOOKUP_KEY ; get low bit of table adress
ADD Offset, W ; 8 bit add
PAGE LOOKUP_KEY ; get high 5 bits
SNB C ; is page boundary crossed ?
PAGE LOOKUP_KEY+$200
MOV W, Offset ; load computed offset in w
CLRB C ; 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
MOV kbScan, W ; store scancode
; if carry is set then key is extended so first send extended code
; before any make or break code
SB C ; check carry flag
JMP KB_CHK_BREAK ; nope then check make/break status
SETB _isExtended ; set extended flag
MOV W, #EXTENDED ;
CALL ADD_KEY_BUFFER ; get extended code and put in in the buffer
KB_CHK_BREAK:
; check if it's make or break
SB _isBreak ; check if its pressed or released ?
JMP KB_DO_MAKE_ONLY ; send make code
CLRB _isBreak ; clear bit for next key
; break code, key is released
MOV W, #BREAK ; get break code
CALL ADD_KEY_BUFFER ; and put into buffer
JMP KB_DO_MAKE ; and send key code also
; key is pressed !
KB_DO_MAKE_ONLY:
CLRB _doSendKey ; stop repeat sending
CLRB _doRepeat ; and bit for repeat key send
SETB _startRepeat ; and set flag for start key repeat check
CLRB _RepeatIsExt ; clear repeat key extended flag ( just in case )
SNB _isExtended ; is it extended ?
SETB _RepeatIsExt ; set the flag
; save this key in 'last' pressed, to be used in key repeat code
MOV W, TempOffset ; get saved offset
MOV LastMakeOffset, W ; and store it
; if keyrepat = enabled, alternative mapping = disabled
SB I_jmp_NoRepeat ; check if repeat code is enabled ?
JMP KB_REP_NOR ; yep set normal delay ( 800 ms )
; else keyrepat = disabled, alternative mapping = enabled
MOV W, #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.
JMP KB_REP_SET ; and set it
KB_REP_NOR:
MOV W, #DELAY_REPEAT ; reload 'normal' repeat delay ( 800 ms )
KB_REP_SET:
MOV RepeatTimer, W ; and (re)start the timer for key repeat
MOV W, kbScan ; get key scan code
MOV RepeatKey, W ; and save it
KB_DO_MAKE:
; key pressed/released ( i.e. the scancode is sent both on make and break )
MOV W, kbScan ; 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.
MOV W, #DELAY_EXIT_ALTKEYMAP ; reload the delay for exiting the altkeymap when no
; key is pressed ( 7.5 sec )
MOV LastKeyTime, W ; (re)set lastkey timer ( used to get out of altkeymap )
RET
; ***********************************************************************
;
; 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)
TEST kbColumnCnt ; reload value into itself ( affect zero flag )
SNB Z ; is it zero ?
JMP KB_DEBOUNCE_78 ; debounce columns 7 & 8
MOV W, kbColumnCnt ; move column counter into W register
;*** WARNING: Manual replacement required for "SUBLW k" instruction (w = k - w). Check if previous instruction is a skip instruction.
SUBLW H'04' ; subtract value in W with 0x04 ( columns 5 & 6 )
SNB Z ; check if the zero bit is set
JMP KB_DEBOUNCE_34 ; debounce columns 3 & 4
MOV W, kbColumnCnt ; move column counter into W register
;*** WARNING: Manual replacement required for "SUBLW k" instruction (w = k - w). Check if previous instruction is a skip instruction.
SUBLW H'06' ; subtract value in W with 0x02 ( columns 3 & 4 )
SNB Z ; check if the zero bit is set
JMP 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
MOV W, kbColumn12_New ; get debounced sample
XOR W, kbColumn12_Old ; get changed bits
SNB Z ; check if zero = no change
RET ; 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'
MOV kbState, W ; save change bit/s
MOV W, #$07 ; preset bit counter
MOV kbBitCnt, W ; loop though all eight bits.
CLRB _LastColumn ; clear end seq bit ( set when are done with last bit )
MOV W, kbColumn12_New ; get new sample
MOV kbTemp, W ; and store it
KB_LOOP_12
CLR Offset ; clear offset counter ( for table read )
CLRB C ; clear carry
RL kbState ; rotate left, and store back
SB C ; check carry '1' = bit was high = change has occured
JMP KB_LOOP_12_SKIP ; nope, no change check next bit ( or exit )
; bit changed
MOV W, kbBitCnt ; get bit counter ( for offset calc. )
MOV Offset, W ; store bit num ( for offset )
CLRB C ; clear carry
RL kbTemp ; rotate left ( next bit )
SB C ; check carry '1' = key is down ( i.e. make )
SETB _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
JMP KB_LOOP_12_NEXT
KB_LOOP_12_SKIP
RL kbTemp ; rotate so we read next key
KB_LOOP_12_NEXT
SNB _LastColumn ; are we done ?
JMP KB_12_DONE ; yep, save new key bit map and exit
DECSZ kbBitCnt ; decrement bit counter
JMP KB_LOOP_12 ; bits left
SETB _LastColumn ; set bit so we break out after next run
JMP KB_LOOP_12
KB_12_DONE:
; and update our 'last known' status for the columns
MOV W, kbColumn12_New ; get new status
MOV kbColumn12_Old, W ; 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
;
SB _AltKeymap ; is altkeymap enabled ?
RET ; nope return
SNB _InAltKeymap ; are we in altkeymap ?
JMP KB_12_IN ; yep alt keymap key has been released once
; nope still waiting for first release
SB kbColumn12_Old.2 ; is key released ? ( first time )
JMP KB_12_ALT ; yep, reset timers and set bit variables
KB_12_IN
SNB _DoExitAltKeymap ; are we waiting for release ?
JMP KB_12_OUT ; yes
; the key has been released once test for second press
SNB kbColumn12_Old.2 ; is it still pressed ?
JMP KB_12_ALT2 ; yep
SB _DoExitAltKeymap ; are we now waiting for the last ( second ) release ?
RET ; nope
KB_12_OUT
SB kbColumn12_Old.2 ; check if key still pressed ?
SETB _ExitAltKeymap ; nope, then enable exit check that
; will exit alt keymap as soon as all key are released
KB_12_ALT2
SETB _DoExitAltKeymap ; check for second release
RET
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
MOV W, #$0F ; x0.5 sec = 7.5 sec
MOV LastKeyTime, W ; (re)set lastkey timer ( used to get out of altkeymap automaticly)
SETB _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 !
; SETB _NumLock ; and also force numlock to be 'on'
; as it is set to 'off' when we enter altkeymap
; we must set it 'back'
RET
KB_DEBOUNCE_34:
; debounce columns 3 & 4
DEBOUNCE_BYTE kbColumnVal,kbColumn34_New,kbColumn34Cnt,kbColumn34State
MOV W, kbColumn34_New ; get debounced sample
XOR W, kbColumn34_Old ; get changed bits
SNB Z ; check if zero = no change
RET ; 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'
MOV kbState, W ; save change bit/s
MOV W, #$07 ; preset bit counter
MOV kbBitCnt, W ; loop though all eight bits.
CLRB _LastColumn ; clear end seq bit ( set when are done with last bit )
MOV W, kbColumn34_New ; get new sample
MOV kbTemp, W ; and store it
KB_LOOP_34
CLR Offset ; clear offset counter ( for table read )
CLRB C ; clear carry
RL kbState ; rotate left, and store back
SB C ; check carry '1' = bit was high = change has occured
JMP KB_LOOP_34_SKIP ; nope, no change check next bit ( or exit )
; bit changed
MOV W, kbBitCnt ; get bit counter ( for offset calc. )
MOV Offset, W ; store bit num ( for offset )
SETB Offset.3 ; set bit 3 for table read ( column 3 & 4 )
; CLRB _isBreak ; clear break flag
CLRB C ; clear carry
RL kbTemp ; rotate left ( next bit )
SB C ; check carry '1' = key is down ( i.e. make )
SETB _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
JMP KB_LOOP_34_NEXT
KB_LOOP_34_SKIP
RL kbTemp ; rotate so we read next key
KB_LOOP_34_NEXT
SNB _LastColumn ; are we done ?
JMP KB_34_DONE ; yep, save new key bit map and exit
DECSZ kbBitCnt ; decrement bit counter
JMP KB_LOOP_34 ; bits left
SETB _LastColumn ; set bit so we break out after next run
JMP KB_LOOP_34
KB_34_DONE:
; and update our 'last known' status for the columns
MOV W, kbColumn34_New ; get new status
MOV kbColumn34_Old, W ; and store it..
RET
KB_DEBOUNCE_56:
; debounce columns 5 & 6
DEBOUNCE_BYTE kbColumnVal,kbColumn56_New,kbColumn56Cnt,kbColumn56State
MOV W, kbColumn56_New ; get debounced sample
XOR W, kbColumn56_Old ; get changed bits
SNB Z ; check if zero = no change
RET ; 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'
MOV kbState, W ; save change bit/s
MOV W, #$07 ; preset bit counter
MOV kbBitCnt, W ; loop though all eight bits.
CLRB _LastColumn ; clear end seq bit ( set when are done with last bit )
MOV W, kbColumn56_New ; get new sample
MOV kbTemp, W ; and store it
KB_LOOP_56
CLR Offset ; clear offset counter ( for table read )
CLRB C ; clear carry
RL kbState ; rotate left, and store back
SB C ; check carry '1' = bit was high = change has occured
JMP KB_LOOP_56_SKIP ; nope, no change check next bit ( or exit )
; bit changed
MOV W, kbBitCnt ; get bit counter ( for offset calc. )
MOV Offset, W ; store bit num ( for offset )
SETB Offset.4 ; set bit 4 for table read ( column 5 & 6 )
CLRB C ; clear carry
RL kbTemp ; rotate left ( next bit )
SB C ; check carry '1' = key is down ( i.e. make )
SETB _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
JMP KB_LOOP_56_NEXT
KB_LOOP_56_SKIP
RL kbTemp ; rotate so we read next key
KB_LOOP_56_NEXT
SNB _LastColumn ; are we done ?
JMP KB_56_DONE ; yep, save new key bit map and exit
DECSZ kbBitCnt ; decrement bit counter
JMP KB_LOOP_56 ; bits left
SETB _LastColumn ; set bit so we break out after next run
JMP KB_LOOP_56
KB_56_DONE:
; and update our 'last known' status for the columns
MOV W, kbColumn56_New ; get new status
MOV kbColumn56_Old, W ; and store it..
RET
KB_DEBOUNCE_78:
; debounce columns 7 & 8
DEBOUNCE_BYTE kbColumnVal,kbColumn78_New,kbColumn78Cnt,kbColumn78State
MOV W, kbColumn78_New ; get debounced sample
XOR W, kbColumn78_Old ; get changed bits
SNB Z ; check if zero = no change
RET ; 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'
MOV kbState, W ; save change bit/s
MOV W, #$07 ; preset bit counter
MOV kbBitCnt, W ; loop though all eight bits. ( 7-0 )
CLRB _LastColumn ; clear end seq bit ( set when are done with last bit )
MOV W, kbColumn78_New ; get new sample
MOV kbTemp, W ; and store it
KB_LOOP_78
CLR Offset ; clear offset counter ( for table read )
CLRB C ; clear carry
RL kbState ; rotate left, and store back
SB C ; check carry '1' = bit was high = change has occured
JMP KB_LOOP_78_SKIP ; nope, no change check next bit ( or exit )
; bit changed
MOV W, kbBitCnt ; get bit counter ( for offset calc. )
MOV Offset, W ; store bit num ( for offset )
SETB Offset.4 ; set bit 3,4 for table read ( column 7 & 8 )
SETB Offset.3 ;
CLRB C ; clear carry
RL kbTemp ; rotate left ( next bit )
SB C ; check carry '1' = key is down ( i.e. make )
SETB _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
JMP KB_LOOP_78_NEXT
KB_LOOP_78_SKIP
RL kbTemp ; rotate so we read next key
KB_LOOP_78_NEXT
SNB _LastColumn ; are we done ?
JMP KB_78_DONE ; yep, save new key bit map and exit
DECSZ kbBitCnt ; decrement bit counter
JMP KB_LOOP_78 ; bits left
SETB _LastColumn ; set bit so we break out after next run
JMP KB_LOOP_78
KB_78_DONE:
; and update our 'last known' status for the columns
MOV W, kbColumn78_New ; get new status
MOV kbColumn78_Old, W ; and store it..
RET
; ***********************************************************************
;
; 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
MOV PC, W ; add to program counter
; R1 - C1 i.e. key 1
NOP
NOP
NOP
RETW #$05 ; scan code 'F1'
; R2 - C1 i.e. key 2
NOP
NOP
NOP
RETW #$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
RETW #$83 ; scan code for F7 ( in 'normal' mode )
; R4 - C1 i.e. key 4
SNB _AltKeymap ; check for alternative keymap
RETW #$76 ; send scancode for 'ESC' instead
SETB C ; set carry ( i.e. extended code )
RETW #$6B ; scan code 'arrow left' '<-'
; R1 - C2 i.e. key 5
NOP
NOP
NOP
RETW #$06 ; scan code 'F2' hex06
; R2 - C2 i.e. key 6
NOP
NOP
NOP
RETW #$03 ; scan code 'F5'
; R3 - C2 i.e. key 7
SNB _AltKeymap ; check for alternative keymap
RETW #$0D ; send scancode for 'horizontaltab' HT instead
SETB C ; set carry ( i.e. extended code )
RETW #$75 ; scan code 'arrow up' '^'
; R4 - C2 i.e. key 8
SNB _AltKeymap ; check for alternative keymap
RETW #$14 ; send scancode for 'left ctrl' instead
SETB C ; set carry ( i.e. extended code )
RETW #$72 ; scan code 'arrow down'
; R1 - C3 i.e. key 9
NOP
NOP
NOP
RETW #$04 ; scan code 'F3'
; R2 - C3 i.e. key 10
NOP
NOP
NOP
RETW #$0B ; scan code 'F6'
; R3 - C3 i.e. key 11
NOP
NOP
NOP
RETW #$0A ; scan code 'F8'
; R4 - C3 i.e. key 12
SNB _AltKeymap ; check for alternative keymap
RETW #$11 ; send scancode for 'left alt' instead
SETB C ; set carry ( i.e. extended code )
RETW #$74 ; scan code 'arrow right' '->'
; R1 - C4 i.e. key 13
SNB _AltKeymap ; check for alternative keymap
RETW #$6C ; send scancode for numeric '7' instead
NOP
RETW #$3D ; scan code '7'
; R2 - C4 i.e. key 14
SNB _AltKeymap ; check for alternative keymap
RETW #$6B ; send scancode for numeric '4' instead
NOP
RETW #$25 ; scan code '4'
; R3 - C4 i.e. key 15
SNB _AltKeymap ; check for alternative keymap
RETW #$69 ; send scancode for numeric '1' instead
NOP
RETW #$16 ; scan code '1'
; R4 - C4 i.e. key 16
SNB _AltKeymap ; check for alternative keymap
RETW #$7B ; send scancode for numeric '-' instead
NOP
RETW #$4A ; scan code '-' minus ( swe kbd )
; R1 - C5 i.e. key 17
SNB _AltKeymap ; check for alternative keymap
RETW #$75 ; send scancode for numeric '8' instead
NOP
RETW #$3E ; scan code '8'
; R2 - C5 i.e. key 18
SNB _AltKeymap ; check for alternative keymap
RETW #$73 ; send scancode for numeric '5' instead
NOP
RETW #$2E ; scan code '5'
; R3 - C5 i.e. key 19
SNB _AltKeymap ; check for alternative keymap
RETW #$72 ; send scancode for numeric '2' instead
NOP
RETW #$1E ; scan code '2'
; R4 - C5 i.e. key 20
SB _AltKeymap ; check for alternative keymap
RETW #$45 ; scan code '0' ( from keypad ) normal key
SETB C ; set carry ( i.e. extended code )
RETW #$1F ; alt keycode ( windows start menu activate )
; R1 - C6 i.e. key 21
SNB _AltKeymap ; check for alternative keymap
RETW #$7D ; send scancode for numeric '9' instead
NOP
RETW #$46 ; scan code '9'
; R2 - C6 i.e. key 22
SNB _AltKeymap ; check for alternative keymap
RETW #$74 ; send scancode for numeric '6' instead
NOP
RETW #$36 ; scan code '6'
; R3 - C6 i.e. key 23
SNB _AltKeymap ; check for alternative keymap
RETW #$7A ; send scancode for numeric '3' instead
NOP
RETW #$26 ; scan code '3'
; R4 - C6 i.e. key 24
SB _AltKeymap ; check for alternative keymap
RETW #$49 ; scan code '.' ( swe kbd ) normal key
; use alternative keymap
SETB C ; set carry ( i.e. extended code )
RETW #$4A ; send scancode for numeric '/' instead
; R1 - C7 i.e. key 25
SNB _AltKeymap ; check for alternative keymap
RETW #$79 ; send scancode for numeric '+' instead
NOP
RETW #$4E ; scan code '+'
; R2 - C7 i.e. key 26
SB _AltKeymap ; check for alternative keymap
RETW #$66 ; scan code 'back space' BS, normal key
; use alternative keymap
SETB C ; set carry ( i.e. extended code )
RETW #$71 ; send scancode for 'delete' instead
; R3 - C7 i.e. key 27
SB _AltKeymap ; check for alternative keymap
RETW #$5A ; scan code 'enter', normal key
; use alternative keymap
SETB C ; set carry ( i.e. extended code )
RETW #$5A ; send scancode for numeric enter instead ( note ! extended )
; R4 - C7 i.e. key 28
SB _AltKeymap ; check for alternative keymap
RETW #$5A ; scan code 'enter', normal key
; use alternative keymap
SETB C ; set carry ( i.e. extended code )
RETW #$5A ; send scancode for numeric enter instead ( note ! extended )
; R1 - C8 i.e. key 29
NOP
NOP
NOP
RETW #$2C ; scan code 't'
; R2 - C8 i.e. key 30
NOP
NOP
NOP
RETW #$24 ; scan code 'e'
; R3 - C8 i.e. key 31
NOP
NOP
NOP
RETW #$1B ; scan code 's'
; R4 - C8 i.e. key 32
NOP
NOP
NOP
RETW #$2C ; scan code 't'
END