{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
file: /Techref/scenix/lib/io/dev/keys/picboardasm_sx.htm, 87KB, , updated: 2002/11/6 16:56, local time: 2024/11/14 14:13,
|
| ©2024 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions? <A HREF="http://linistepper.com/Techref/scenix/lib/io/dev/keys/picboardasm_sx.htm"> PiCBoard</A> |
Did you find what you needed?
|