Ir ao conteúdo
  • Cadastre-se
marcoxr365

PIC mplab build falhou!! codigo correto ou incorreto?

Posts recomendados

;
;               PC-Keyboard emulator using a PIC16F84 
;     Scan a 32 key keyboard ( with alternative mapping = 64 keys )
;			

;                 COPYRIGHT (c)1999 BY Tony Kübek
; This is kindly donated to the PIC community. It may be used freely, 
;     and you are forbidden to deprive others from those rights.   
;    Please read the FSF (Free Software Foundation) GNU statement.
;
;*********************************************************************
;
;			E-MAIL	tony.kubek@flintab.se
;		
;		
; DATE			2000-01-23
; ITERATION		1.0B
; FILE SAVED AS		PiCBoard.ASM	
; FOR			PIC16F84-10/P		
; CLOCK			10.00 MHz RESONATOR ( OSC-HS )
; INSTRUCTION CLOCK	2.50 MHz T= 0.4 us
; SETTINGS		WDT-ON, PWT-ON, CP-OFF
; REVISION HISTORY	
;			0.1b -	First beta, mainly for testing
;			1.0b - 	First public beta release
;
;
;
;
;***************************************************************************
;
;				PREFACE ;-)
;
;	This is NOT an tutorial on pc keyboards in general, there are quite
;	a few sites/etc that have that already covered. However i DID find
;	some minor ambiguities regarding the actual protocol used but nothing
;	that warrants me to rewrite an complete pc keyboard FAQ. 
;	So for 'general' pc keyboard info ( scancodes/protocol/pin config/etc )
;	here are some useful links:
;
;	http://www.senet.com.au/~cpeacock/ 	
;	http://ourworld.compuserve.com/homepages/steve_lawther/keybinfo.htm 
;	http://204.210.50.240/techref/default.asp?url=io/keyboard.htm
;	http://www.arne.si/~mauricio/PIC.HTM 
;	
;	PLEASE do not complain about code implementation, I know there are parts
;	in which is it 'a bit' messy and hard to follow, and other parts where
;	the code is not optimised. Take it as is. Futhermore I did hesitate
;	to include all of the functionality thats currently in, but decided to
;	keep most of it, this as the major complaint i had with the other available
;	pc-keyboard code was just that - 'is was incomplete'. Also do not 
;	be discoraged by the size and complexity of it if you are an beginner,
;	as a matter of fact this is only my SECOND program ever using a pic.
;	I think I managed to give credit were credit was due ( 'borrowed code' ).
;	But the originators of these snippets has nothing to do with this project
;	and are probably totally unaware of this, so please do not contact them
;	if you have problems with 'my' implementation. 
;
;	BTW ( shameless plug ) UltraEdit rules ! ( if you dont have it, get it ! )
;	http://www.ultraedit.com ( dont 'forget' the 'wordfile' for PIC, color indenting )
;	Without that I guess this file would be 'messy'.
;
;	Ok with that out of the way here we go:
;	
;
;***************************************************************************

;				DESCRIPTION ( short version ;-) )

;	A set of routines which forms a PC keyboard emulator.
;	Routines included are:
;	Interrupt controlled clock ( used for kb comm. and 'heart beat )
;	A 4x8 matrix keyboard scanner ( using an 74HCT4051 3 to 8 analogue multiplexer )
;	Communincation with PC keyboard controller, both send end recive.

; 	PC keyboard routines are using 4 pins (2 inputs & 2 outputs) to control
; 	keyboard's 2 bidirectional OC lines (CLK & DATA). The following
; 	'drawing' conceptually shows how to connect the related pins/lines
;
;	( ASCII art/info shamelessly 'borrowed' from http://www.arne.si/~mauricio/PIC.HTM )
;
;			     vcc	vcc
;			      | 	 |
;			      \ 	-+-
;			      / 2K2	/_\  1N4148
;			      \ 	 |
; pcCLOCK_in -----------------o---o------o---------   kbd CLOCK
;			      |   |	 |
;			 2N2222   |50pF -+-
; pcCLOCK_out ____/\/\/\____|/	 ===	/_\
;		    2K2     |\>   |	 |
;			      |   |	 |
;			     /// ///	///
;
; 	An identical circuit is used for the DATA line. 
;	Note: The 2N2222 transitors can be replaced with BC337 ( NPN ) which are cheaper
;	The keyboard matrix routines are using RB4-RB7 as inputs.
;	and RB0-RB2 as output to/from an 3 to 8 multiplexer 
;	so that it can read up to 4x8 keys ( 32 ).
;
;	RA4/TOCK1 is an input from an jumper, if low then keyrepeat is disabled and 
;	alt-key is enabled instead i.e. instead of repeating a key that has
;	been depressed a certain amount of time, a bit is set that can change the
;	scancode for a key ( of course, all keys can have an alternate scancode ).
;	To exit the alt. keymap press the 'enter alt. keymap' key once again or wait until
;	timeout. ( defined in the code )
;	NOTE !! The so called 'enter alt. keymap' key is hardcoded, i.e the key
;	i've choosen in this 'example' is Column 1 Row 2, if this is to be changed
;	code has to be changed/moved in the debounce and checkkeystate routines.
;	( marked with <-------Alt keymap code-------> )
; 	RB3 is currently used for a flashing diode. ( running )
;	Note my real keyboard ( KeyTronic ) uses a clock rate of 40 us, my implementation
;	uses about 50 us, easily to change.
;

;***************************************************************************

;			DESCRIPTION ( longer version ;-) )
;
;	Pin 	Used for
;	------------------------------------------------------------
;	RA0	Pc keyboard data out ( to pc )
;	RA1	Pc keyboard data in ( from pc )
;	RA2	Pc keyboard clock in 
;	RA3	Pc keyboard clock out
;	RA4	Flashing disco led 0.5s duty cycle( could be used for some more meaningful purpose )
;
;	RB0	Least significant bit in column adress to 4051 multiplexer ( own keyboard )
;	RB1	Middle...
;	RB2	Most significant bit -- || --
;	RB3	Input. Disable keyrepeat & enable alternative keymapping ( if = '0',ground)
;		OR if '1' ( free,+V ) enable keyrepeat & disable alternative keymapping
;	RB4	Own keyboard input row 1
;	RB5	--- || --- row 2
;	RB6	--- || --- row 3
;	RB7	--- || --- row 4
;
;	'Basic program structure':
;
;	Init	-	Initialise ports , ram, int, vars
;	Start delay - 	After init the timer int is enabled and the flashing led will
;			start to toggle ( flash ). Before I enter the mainloop 
;			( and send any keycodes ) I wait until the led has flashed
;			twice.	This is of course not really needed but I normally
;			like to have some kind of start delay ( I know 1 sec is a bit much :-) )
;	Time Int    -	The timer interrupt (BIG), runs in the background:
;			- 'Normal' rate 0.5 ms, when in rx/tx rate is 27 us
;			 - Performs an rx/tx check, then jumps to rx, tx or 'heart beat' code.
;			 - TX code sends a byte to pc, at a rate of 27us per int.
;			   The int rate is actually double the bit rate, as
;			   a bit is shifted out in the middle of the clock pulse,
;			   I've seen different implementations of this and I think
;			   that the bit is not sampled until clock goes low again BUT
;			   when logging my keyboard ( Keytronic ) this is the way that it
;		   	   does it. When all bits are sent, stopbit/parity is sent.
;			   And the key is removed from the buffer.
;			   After stopbit/parity is sent, a delay is inserted ( 0.5 ms )
;			   before next rx/tx check.
;			 - RX code recevies a byte from the pc, PIC is controlling clock !!
;			   Int rate ( 27 us ) is, again, double bit rate.
;			   Toggles clock and samples the data pin to read a byte from pc.
;			   When reception is finished an 'handshake' takes place.
;			   When a byte has been recevied a routine is called to check
;			   which command and/or data was received. If it was
;			   keyboard rate/delay or led status byte, it is stored in local ram
;			   variables. NOTE: The rate/delay is not actually used for
;			   key repeat as the code is right now ( I use my 'own' fixed rate/delay )
;			   however it is very easy to implement.
;			   After handshake a delay is inserted ( 0.5 ms )
;			   before next rx/tx check.
;			- 'Heart beat' ( idle code 0.5 ms tick ) performs:
;			 - Check clock/data lines to see if pc wants to send something or
;			   if tx is allowed.
;			 - If tx is possible it checks the keybuffer for an available key and
;			   if keys are in buffer then it initiates a tx seq. 
;			   and sets the int rate to 27 us.
;			 - If the pc wants to send something, an rx seq. is initiated
;			   ( there is some handshaking involved, during which
;			   the int rate is set to 60 us ) after that, the int rate is 
;			   set to 27 us and an rx seq is started.
;			 - Divides some clock counters to achive 10ms,100ms,500ms sections.
;			 - In 100 ms section it performes a numlock status check and
;			   keyrepeat check ( both rate and delay is local in 100 ms ticks,
;			   thats why I dont use the 'real' rate delay )
;			  - If numlock status is not the desired one code is called to
;			    toggle the numlock status.
;			  - If a key has been pressed long enough for repeat, an bit is set
;			    so we can repeat the key ( send the scancode again ) in the main loop.
;			- In 500 ms section the led is toggled on each loop
;			  - Some various alternative keymap checks to get out of
;			    alternative keymap. ( i'll get to that in a bit )
;
;	Main loop	- Outputs an adress to the 4051 multiplexer waits 1 ms
;			  reads the row inputs ( 4 bits/keys ), increments address
;			  and outputs the new adress, waits 1 ms and reads the input 
; 			  ( next 4 bits/keys ). Now using the address counter, calls a
;			  debounce/send routine that first debounces the input,
;			  ( four consecutive readings before current state is affected )
;			  and when a key is changed a make/break code is sent ( put in buffer ).
;			  In the next loop the next two columns are read etc. until all 
;			  4 column pairs are read.
;			- If keyrepeat is enabled ( see pin conf. above ) the 
;			  repeat flag is checked and if '1' the last pressed key scancode
;			  is sent again ( put in buffer ).
;			- If keyrepeat is not enabled( alternative keymap is enabled instead )
;			  then various checks to exit the alternative keymap are performed instead.
;
;	Scancodes for all key are located in a lookup table at the end of this file,
;	each key has four program rows, to make room for extended codes and alt. keymap codes.
;
; 	Explanation of 'alternative' keymap:
;	
;	Using this program ( or an heavily modified version of it anyway :-) )
; 	on a computer running Windows posed some small problems; namely:
;	-The keyboard ( mapping ) I used did not have any 'special' key such as 
;	 <alt>,<ctrl>,<tab> etc.
;	-In windows, things can go wrong, :-) if a dialog pops up or something similar
;	 there were just no way one could dispose of this with the keymapping i used.
;	- 'Only' 28 keys were implemented ( hardware wise ).
;	In this particular case the keyrepeat was disabled ( due to the nature of the application )
;	Therefore i came up with the solution to use the keyrepeat related routines and vars.
;	To handle a so called 'alternative' keymapping.
;	This means that an key is dedicated to be the alt. keymap toggle key,
;	when pressing this longer than the programmed repeat delay, instead of 
;	repeating the key a bit variable is set to use another set of scancodes for
;	the keyboard. This 'alternative' keymap is then enabled even though the 
;	alt. keymap toggle key is released, but it also incorporates an timeout 
;	that will return to normal keymap if no key is pressed within a certain time ( currently 7 sec )
;	Of course pressing the alt. keymap toggle key again ( while in alt. keymap )
;	will return the keyboard to the normal keymap.
;	NOTE !! the key choosen for the alt. keymap toggle is hardcoded, so if the scancode
;	for this is changed in the lookup table, changes have to be made in other routines
;	as well. ( namly : CHECK_KEY_STATE, and DEBOUNCE_COLUMN routines )
;	While in alt. keymap each key CAN have an alternative scancode ( see lookup table ).
;	Also by using the numlock bit, one can toggle numlock status 'on the fly' when entering
;	or exiting the alt keymap. 
;
;	Some notes about the local keyboard interface ( matrix ):
;	Although the hardware circuit and software allows virtually unlimited
;	simultaneosly pressed keys, the keyboard matrix itself normally poses
;	some limitations on this. If an keymatrix without any protective diodes
;	are used then one would have loops INSIDE the keymatrix itself when
;	multiple keys are pressed in different columns .
;	Look at the ( although horrible ;-) ) ASCII art below(internal weak pullup enabled):
;	0 - Key is free
;	1 - Key is pressed ( connection between hor/ver rows )
;	Three keys pressed adressing ( reading ) left column :
;
;	To pic1	--------0-------0------ ( row 1 )
;	        	|       |
;	To pic2	--------1-------1------ ( row 2 )
;            		|       |
;	To pic3	--------1-------0------ ( row 3 )
;               	|       |
;    	Column(4051)	0V	-	( Current column is set to 0V when adressing )
;
;	This works as intended, we can read a '0' on pic inputs 2,3 which
;	is what we expected. The current ( signal ) follows the route marked with '*':
;	( only the 'signal path' is shown for clarity )
;
;	To pic1	--------0-------0------ ( row 1 )
;	        	|       |
;	To pic2	*********-------1------ ( row 2 )
;            		*       |
;	To pic3	*********-------0------ ( row 3 )
;               	*       |
;    	Column(4051)	0V	-	( Current column is set to 0V when adressing )
;
;	However, when we now read ( address ) the right column instead we 
;	do not read what is expected ( same three keys still pressed ):
;
;	To pic1	--------0-------0------ ( row 1 )
;	        	|       |
;	To pic2	*****************------ ( row 2 )
;            		*<-     *
;	To pic3	*********-------*------ ( row 3 )
;               	|       *
;    	Column(4051)	-	0V	( Current read column is set to 0V when adressing )
;
;	As you can see the 'signal' travels in the keymatrix itself ( where the '<-' is )and
;	will cause a 'ghost' signal to be read on the pic. So instead
;	of having an '0' on input 2 only we also can read an '0' on input 3.
;	This is because the two keys in column 1 are interconnected ( when they are pressed ).
;	Keep this in mind if you are planning to support multiple pressed keys.
;	
;
;***************************************************************************
;
;	Some suggestions for 'improvements' or alternations
;
;	- Using the jumper 'disable-repeat' as a dedicated key for switching
;	  to alternative keymapping. 
;	- Enable repeat in alternative keymapping
;	- Clean up TX/RX code ( a bit messy )
;	- Using the led output ( or jumper input ) as an extra adress line 
;	  to the multiplexers ( in this case 2 pcs. 74HCT4051 ) we could have
;	  4x16 keys instead. Would require some heavy modifications though
;	  as there are not much ram/program space left. But if alternative
;  	  keymapping is discarded ( most likely if one has 64 keys ) each
;	  key in the lookup table only needs to be 2 lines instead of 4.
;	  That would 'only' require some modifications to preserv ram.
;	- Using the EERAM for 'macros' or similar ( not used at all now )
;
;***************************************************************************
;
;			LEDGEND
;
;	I tend to use the following when naming vars. etc. : 
;	( yes i DO like long names )
;	
;	For 'general' purpose pins:
;
;	An input pin is named I_xxx_Name where :
;
;		I_   - This is an input pin ;-)
;		xxx_ - Optional what type of input, jmp=jumper etc.
;		Name - Self explanatory
;
;	An output pin is named O_xxx_Name where:
;	
;		O_   - This is an output pin ;-)
;		xxx_ - Optional what type of output, led=LED etc.
;		Name - Self explanatory
;
;	Application(function) specific pins: 
;
;	An application(function) specific pin is named xxName where:
;		
;		xx   - What/Where, for example pc=To/From pc
;		Name - Self explanatory ( what does it control etc )
;
;	An #define/constant/equ (no pin or ram ) uses all capital letters e.x. #define BREAK 0xF0
;
;	A bit variable will always start with '_'. For example '_IsLedStatus'
;
;	All other ( mostly ramvars. ) are named in various ways.
;
;
;***************************************************************************





	TITLE "PC Keyboard emulator - By Tony K&uuml;bek"



		Processor       16F84
		Radix   DEC
		EXPAND

	

;***** HARDWARE DEFINITIONS ( processor type include file )

	INCLUDE <C:\PROGRAM\MPLAB\P16F84.INC>       	; this might need changing !

;***** CONFIGURATION BITS 

    __CONFIG _WDT_ON&_HS_OSC&_CP_OFF&_PWRTE_ON		; _WDT_OFF
    
    __IDLOCS 010Bh	; version 1.0B 

;***** CONSTANT DEFINITIONS 

	CONSTANT	BREAK = 0xF0		; the break key postfix ( when key is released )
	CONSTANT	EXTENDED = 0xE0 	; the extended key postfix

	; As i dont really use the rate/delay I receive from the pc ( easy to change )
	; this is the current rate/delay times i use:
	CONSTANT	DELAY_ENTER_ALTKEYMAP = 0x1E	; x100 ms , approx 3 seconds ( 30 x 100 ms )
							; how long the 'enter altkeymap' key must
							; be in pressed state before the altkeymap is enabled
	CONSTANT	DELAY_EXIT_ALTKEYMAP = 0x0F	; x0.5 sec , approx 7.5 sec
							; how long before we exit the alt keymap if no key is 
							; pressed.
	CONSTANT 	DELAY_REPEAT	= 0x08		; x100 ms, approx 800 ms 
							; how long before we START repeating a key
	CONSTANT	DELAY_RATE	= 0x02		; x100 ms, approx 200 ms repeat rate
							; how fast we are repeating a key ( after the delay above )
	
;***** CONSTANT DEFINITIONS ( pins )

;	For connection with PC keyboard

#define pcCLOCK_in  PORTA,2   ; Input PC keyboard clock
#define pcCLOCK_out PORTA,3   ; Output PC keyboard clock
#define pcDATA_in   PORTA,1   ; Input PC keyboard data
#define pcDATA_out  PORTA,0   ; Output PC Keyboard data


;	For connection (input) with our own keyboard 
; Note I actually dont use these (definitions!) in the program, but they might come in handy
; at one time or another ;-) ( I use the pins though.. )

#define kbROW_1   PORTB,7
#define kbROW_2   PORTB,6
#define kbROW_3   PORTB,5
#define kbROW_4   PORTB,4

;	Indications ( output )

#define O_led_KEYCOMM_ok	PORTA,4		; communication seems ok led ( flashing )

;	Disable/enable key repeat input jumper

#define I_jmp_NoRepeat	PORTB,3	; note: internal weak pullup enabled

;	For keybuffer ( using the indirect file selector FSR )

#define KeyBufferHead	FSR


;***** RAM ASSIGNMENT

        
		CBLOCK	0x0C
			KeyBufferTail	; where the last byte in buffer is..
			clkCount     	; used for clock timing 
			Offset		; used for table reads		
                        Saved_Pclath   	; Saved registers during interrrupt
			Saved_Status   	; -----
			Saved_w        	; -----
			CurrKey		; current key ( rx or tx )..
			KeyParity	; key parity storage ( inc. for every '1' )
			Divisor_10ms	; for the timer
			Divisor_100ms   ; ditto
			Divisor_500ms   ;
			Divisor_Repeat  ; timer for repeated key sends

			Flags		; various flags
			RepeatFlags     ; flags for repeating a key
			bitCount	; bitcounter for tx/rx
			Comm_Flags	; flags used by both rx and tx routines
			Temp_Var	; temp storage, can be used outside int loop
			TRX_Flags	; flags used by both rx and tx routines
			CommandData     ; bit map when receving data bytes from pc
					; for example led status/ delay / etc 
			KbLedStatus     ; to store status led bitmap for keyboard ( pc first sends 'ED' then this byte )
        				;  bit 0=Scroll lock  ( 1=on )
        				;  bit 1=Num lock
        				;  bit 2=Caps lock
        				;  bits 3-7 = unused 
			KbRateDelay	; to store repeat delay/rate ( pc first sends 'F3' then this byte )
        				;  bit 0-4 (rate) = '00000' is 30x/sec '11111' is 2x/sec ( i.e. value * 33 ms )
        				;  bit 5-7 (delay)= '00' is 250 ms, '11' is 1000 ms ( ie. value * 250 ms )   
					;  bit 7 = unused 
			BufTemp		; temp byte for storing scancode to put in buffer
			Temp		; temp byte, used locally in buffer routine
			Temp2		;
			LastKey		; stores the last sent key
			KbBufferMin	; where our keybuffer starts
			Kb1		; used in keybuffer
			Kb2		; used in keybuffer
			Kb3		; used in keybuffer
			Kb4		; used in keybuffer
			Kb5		; used in keybuffer
			Kb6		; used in keybuffer
			Kb7		; used in keybuffer
			Kb8		; used in keybuffer
			Kb9		; used in keybuffer
			Kb10		; used in keybuffer
			KbBufferMax	; end of keybuffer
			TempOffset	; temporary storage for key offset ( make/break )

			LastMakeOffset  ; storage of last pressed key ( offset in table )
			RepeatTimer	; timer to determine how long a key has been pressed 
			RepeatKey 	; the key to repeat
			repTemp		; temporary storage in repeat key calc.
			repKeyMap	; bit pattern for the column in which the repeat key is in
					; i.e. a copy of kbColumnXX_Old where 'XX' is the column 
			LastKeyTime	; counter when last key was pressed, used to get out of altkeymap
					; after a specific 'timeout'

			kbScan		; scan code for pressed/released key
			kbTemp		; temp storage for key states

			kbState		; which keys that has changed in current columns
			kbBitCnt	; bit counter for key check ( which key/bit )			
			
			kbColumnCnt	; column counter ( loops from 8 to 0 )
					; Used as output to multiplexer/decoder and in debounce routines

			kbColumnVal	; current value of the last 2 columns ( 2x4bits = 8 bits ) input pins ( keys ) 
					;
					; Note the kbColumnXX_New variables is not really needed
					; used it while making the program ( debugging ;-) ).
					; so if more free ram is needed change code using these to use
					; the current 'input' sample instead. ( kbColumnVal )
			kbColumn12_New  ; New debounced reading for column 1 & 2
			kbColumn12_Old  ; Latest known valid status of column 1 & 2
			kbColumn12Cnt	; Debounce counter for column 1 & 2
			kbColumn12State ; State of debounce for column 1 & 2

			kbColumn34_New  ; New debounced reading for column 3 & 4
			kbColumn34_Old  ; Latest known valid status of column 3 & 4
			kbColumn34Cnt	; Debounce counter for column 3 & 4
			kbColumn34State ; State of debounce for column 3 & 4

			kbColumn56_New  ; New debounced reading for column 5 & 6
			kbColumn56_Old  ; Latest known valid status of column 5 & 6
			kbColumn56Cnt	; Debounce counter for column 5 & 6
			kbColumn56State ; State of debounce for column 5 & 6

			kbColumn78_New  ; New debounced reading for column 7 & 8
			kbColumn78_Old  ; Latest known valid status of column 7 & 8
			kbColumn78Cnt	; Debounce counter for column 7 & 8
			kbColumn78State ; State of debounce for column 7 & 8

		ENDC

;******* END RAM

; *** flags used by both rx and tx routines
#define _isParity	Comm_Flags,0	; bit in rx/tx is parity bit
#define _KeyError	Comm_Flags,1	; set to '1' when an error is detected
#define _isStartBit	Comm_Flags,2	; set to '1' when bit in rx/tx is startbit
#define _isStopBit	Comm_Flags,3	; --- || --- but stopbit
#define _RxCanStart     Comm_Flags,4	; for handshaking, when negotiating an rx seq.


; *** dito used for rx and tx
#define _KeyReceived	TRX_Flags,0	; rx
#define _RX_Mode	TRX_Flags,1	; rx is in progress ( started )
#define _doRXAck	TRX_Flags,2	; do rx handshake
#define _RXAckDone	TRX_Flags,3	; rx handshake is done
#define _RXEnd		TRX_Flags,4	; rx seq is finished
#define _RXDone		TRX_Flags,5	; rx exit bit
#define _KeySent	TRX_Flags,6	; tx key has been succesfully sent
#define _TX_Mode	TRX_Flags,7	; tx is in progress ( started )


; *** flags to determine the meaning of an incomming data is 
; i.e. when then pc has sent a 'data follow' instruction
; these bits are set according to the command, 
; i.e. the next incomming byte is.....
#define _IsLedStatus    CommandData,0	; the next incoming byte contains kb led status
#define _IsRateDelay    CommandData,1	; the next incoming byte contains kb rate/delay
#define _SkipByte	CommandData,2	; other data not saved
 
; *** bit meaning in KbLedStatus ( the leds on the keyboard )
; i.e local ram copy of this byte
#define _LedScrollLock	KbLedStatus,0	; '1' led is on
#define _LedNumLock     KbLedStatus,1	; 
#define _LedCapsLock	KbLedStatus,2	;
#define _IsFirstLedStatus KbLedStatus,7	; set this to '1' at startup, to know that our local
;					; copy (led status) is not yet syncronised. Used to
;					; 'force' sync. the first time we set the local numlock bit.

; *** flags used for various purposes
#define _Timer		Flags,0 ; used for waiting
;#define _WrongPar	Flags,1	; ?? ( used during dev. to force wrong parity sends, removed )
#define _LastColumn	Flags,2	; for kb scan code
#define _isBreak	Flags,3 ; if '1' the currently handled key is break ( released ), else make ( pressed )
;#define _isFree         Flags,4 ; Not used ! ( temporary during dev. )
#define _AltKeymap      Flags,5 ; is we use alternative keymap ( other scancodes ) see below !
				; enabled ONLY when keyrepeat is disabled ( I_jmp_NoRepeat (RB3) is low )
#define _ExitAltKeymap  Flags,6 ; start checking if we should exit alternative keymap
				; if all keys are 'free' ( not pressed ) we exit altkeymap
#define _InAltKeymap    Flags,7 ; if we are waiting for second press/release to get out of altkeymap


#define _doRepeat	RepeatFlags,0 ; if the last pressed key should be 'repeated'
#define _doSendKey	RepeatFlags,1 ; send the key in RepeatKey to pc 	
#define _isExtended     RepeatFlags,2 ; temp flag if last key is extended
#define _RepeatIsExt	RepeatFlags,3 ; the key to repeat is extended key
#define _startRepeat    RepeatFlags,4 ; start repeat timer
#define _DoExitAltKeymap RepeatFlags,5 	; bit set when we are getting out of alternative keymap
#define _NumLock	RepeatFlags,6  	; 'mirror' of numlockstatus, by setting/clearing this bit
				       	; numlock status will be changed. 
					; I.e. there is no need to 'manually' send break/make code for numlock
					; key, by setting this bit to '1' numlock status will by automaticlly
					; ( inside int handler ) set to 'on'( within 100 ms ). Se code for example.
#define _WaitNumLock	RepeatFlags,7	; bit set when we have sent make/break numlock scancode
					; and waiting for numlock status byte reply.
					; ( to inhibit a new numlock scancode send )


;**************************************************************************	
;				Macros 			
;**************************************************************************	
	

;+++++
;	BANK0/1 selects register bank 0/1.
;	Leave set to BANK0 normally.

BANK0	MACRO
	BCF	STATUS,RP0
	ENDM

BANK1	MACRO
	BSF	STATUS,RP0
	ENDM

;+++++
;	PUSH/PULL save and restore W, PCLATH and STATUS registers -
;	used on interrupt entry/exit 

PUSH	MACRO
	MOVWF	Saved_w		; Save W register on current bank
	SWAPF	STATUS,W	; Swap status to be saved into W
	BANK0			; Select BANK0
	MOVWF	Saved_Status    ; Save STATUS register on bank 0
	MOVFW	PCLATH
	MOVWF	Saved_Pclath	; Save PCLATH on bank 0
	ENDM

PULL	MACRO
	BANK0			; Select BANK0
	MOVFW	Saved_Pclath
	MOVWF	PCLATH		; Restore PCLATH
	SWAPF	Saved_Status,W
	MOVWF	STATUS		; Restore STATUS register - restores bank
	SWAPF	Saved_w,F
	SWAPF	Saved_w,W	; Restore W register
	ENDM




;+++++        
; 	We define a macro that will switch an output pin on or off depending
; 	on its previous state. We must be on bank0 !!
;

TOGGLE_PIN	MACRO WHICH_PORT,WHICH_PIN
		LOCAL TOGGLE_PIN10, TOGGLE_END
		
		BTFSC	WHICH_PORT,WHICH_PIN	; is the pin high ?
		GOTO 	TOGGLE_PIN10		; yes, clear it
		BSF	WHICH_PORT,WHICH_PIN	; no, so set it										
	    	GOTO   	TOGGLE_END
TOGGLE_PIN10:
		BCF	WHICH_PORT,WHICH_PIN	; clear the pin		    
TOGGLE_END:
		ENDM


;************************************************************* 
; Credit for routine ( almost unchanged, expect it's now a macro ;-) )
; goes to Scott Dattalo http://www.interstice.com/~sdattalo/technical/software/software.html
; ( debouce routine at http://www.interstice.com/~sdattalo/technical/software/pic/debounce.html )
;
; 	DEBOUNCE_BYTE - 'debounces' 8 bits, when a bit state 
;	has been 'active' for 4 consecutive debounce loops
;	it's state is put into the 'debounced' sample ( i.e. output )
;
; The purpose of this routine is to debounce, i.e. digitally low pass filter
; inputs. The algorithm handles upto 8 bits at a time. An input is considered
; filtered if it has not changed states in the last 4 samples.
;
; 2-bit cyclic vertical counters count the 4 samples. As long as there is no
; change, the counters are held in the reset state of 00b. When a change is detected
; between the current sample and the filtered or debounced sample, the counters
; are incremented. The counting sequence is 00,01,10,11,00... When the counters
; roll over from 11b to 00b, the debounced state is updated. If the input changes
; back to the filtered state while the counters are counting, then the counters
; are re-initialized to the reset state and the filtered state is unaffected.
; In other words, a glitch or transient input has been filtered.
;
; Here's the C-psuedo code:
;
;static unsigned clock_A,clock_B,debounced_state
;
;debounce(unsigned new_sample)
;{
;  unsigned delta;
;
;  delta = new_sample ^ debounced_state;   //Find all of the changes;
;  clock_A ^= clock_B;                     //Increment the counters
;  clock_B  = ~clock_B;
;
;  clock_A &= delta;                       //Reset the counters if no changes
;  clock_B &= delta;                       //were detected.
;
;      //Preserve the state of those bits that are being filtered and simultaneously
;      //clear the states of those bits that are already filtered.
;  debounced_state &= (clock_A | clock_B);
;      //Re-write the bits that are already filtered.
;  debounced_state |= (~(clock_A | clock_B) & new_sample);
;}
;
; The 2-bit counters are arranged "vertically". In other words 8 counters
; are formed with 2 bytes such that the corresponding bits in the bytes are
; paired (e.g. MSBit of each byte is paired to form one counter).
; The counting sequence is 0,1,2,3,0,1,... And the state tables and Karnaugh
; maps are:
;
;State Table:     Karnaugh Maps:
;pres  next      B
; SS  SS         0   1
; AB  AB       +---+---+    +---+---+
;--------   A 0|   | 1 |    | 1 |   |
; 00  01       +---+---+    +---+---+
; 01  10      1| 1 |   |    | 1 |   |
; 10  11       +---+---+    +---+---+
; 11  00      A+ = A ^ B     B+ = ~B
;
; Here's the PIC code that implements the counter:
;       MOVF    SB,W    ;W = B
;       XORWF   SA,F    ;A+ = A ^ B
;       COMF    SB,F    ;B+ = ~B
;  14 instructions
;  15 cycles
; Inputs:
;   NewSample - The current sample
; Outputs
;   DebouncedSample - The current value (filtered version of NewSample)
;
; VARS used
;   DBCnt,
;   DBState - State variables for the 8 2-bit counters
;
	
DEBOUNCE_BYTE MACRO NewSample,DebouncedSample,DBCnt,DBState

    	;Increment the vertical counter        

	MOVF    DBState,W
        XORWF   DBCnt,F        
	COMF    DBState,F

    	;See if any changes occurred        

	MOVF    NewSample,W        
	XORWF   DebouncedSample,W

    	;Reset the counter if no change has occurred        

	ANDWF   DBState,F
        ANDWF   DBCnt,F    	;Determine the counter's state
        MOVF    DBState,W        
	IORWF   DBCnt,W

    	;Clear all bits that are filtered-or more accurately, save
    	;the state of those that are being filtered        

	ANDWF   DebouncedSample,F
        XORLW   0xff    	;Re-write the bits that haven't changed.
        ANDWF   NewSample,W        
	IORWF   DebouncedSample,F        
	
	ENDM

	

;**************************************************************************	
;			     Program Start
;**************************************************************************	


;	Reset Vector

	ORG	H'00'

	; For the sole purpose of squeezing every last byte of the programming mem
	; I actually use the 3 program positions before the interrupt vector
	; before jumping to the main program. Take note though that 
	; ONLY 3 instructions are allowed before the jump to main loop !!

	BANK0

        CLRF	PCLATH
	CLRF	INTCON	

	GOTO	INIT

;**************************************************************************	
;			     	Interrupt routine
; An humongously big int handler here ;-)
; but the keyboard handling is FULLY in the background, without any intervention
; in the main loop whatsoever. I like the solution anyway.
;
;**************************************************************************


;	Interrupt vector 

	ORG	H'04'

INT
	PUSH			; Save registers and set to BANK 0
				
	
	BTFSS   INTCON,T0IF    	; check if TMR0 interrupt
        GOTO    INTX            ; whoops ! 'unknown' int, should be disabled...
				; NOTE ! if an 'unknown' int triggers the int routine
				; the program will loop here for ever ;-) ( as the calling flag is not cleared )

	

	;+++
	; Timer (TMR0) timeout either heart beat or tx/rx mode
	; In 'heart beat mode' we monitor the clock and data lines
	; at ( roughly )= 0.5 ms interval, we also check the send buffer
	; if there are any keys to send to pc ( if clock/data levels allows us to )
	; In tx/rx mode we are controlling the clock/data line = 27 us tick
	; Note: This software works using both 8Mhz and 10Mhz resonators without modifications
	; however the 'timing' will then of course be 'off'.

INT_CHECK	
	BCF	INTCON,T0IF 	; Clear the calling flag !
	
 	BTFSC	_TX_Mode	; check if we are in tx mode
	GOTO	INT_TX		; yep, goto tx mode code..	
	BTFSC	_RX_Mode	; are we in rx mode ?
	GOTO	INT_RX		; yep goto rx mode code
	GOTO	INT_HEART_BEAT  ; nope just goto 'heart beat' mode

;*************** TX code start ***********************************

INT_TX
	MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
    	MOVWF   TMR0
 
	BTFSS	clkCount,0	; check if we should toggle clock
	GOTO	INT_DEC_CLOCK	; bit low,decrement and check if we should toggle data instead
	
	DECFSZ	clkCount,F	; decrement and check if we are at zero..
	GOTO	INT_CLOCK 	; not zero then toggle clock line
	
	GOTO	INT_EXIT_TX		

INT_CLOCK
	
	BTFSC	pcCLOCK_out	; check if we are low
	GOTO	INT_CLOCK_HIGH  ; yep set to high
				
	BTFSS	pcCLOCK_in	; check if pc is pulling the clock line low
				; i.e. it wants to abort and send instead..
	GOTO	INT_TX_CHECK_ABORT	; abort this transfer
	BSF	pcCLOCK_out	; ok to set clock low ( pull down )
	GOTO	INTX

INT_CLOCK_HIGH
	BCF	pcCLOCK_out	; set high ( release line )
				;BCF	_ClockHigh	;
	GOTO	INTX

INT_TX_CHECK_ABORT
	GOTO	INT_EXIT_TX

INT_DEC_CLOCK	
	DECF	clkCount,F	; decrement clock counter ( so we toggle next time )
INT_DATA
	BTFSS	bitCount,0	; check bit counter
	GOTO	INT_DATA_IDLE	; no data toggle

	DECFSZ	bitCount,F	; decrement bit counter
	GOTO	INT_DATA_NEXT	; next bit..

INT_NO_BITS
	BSF	bitCount,0	; just in case ( stupid code, not sure its needed, just
				; to make it impossible to overdecrement )***
	BTFSC	_isParity	; are we sending parity ?
	GOTO	INT_DATA_END	; exit
	
	; all bits sent 
	; delete the last key from the buffer
	CALL	INC_KEY_HEAD	; remove the ( last ) key form the buffer as is was sent ok..

	; all bits sent check parity
	
	BSF	_isParity	; set flag data is parity
	
	BTFSS	KeyParity, 0    ; is the last parity bit high? ( odd num of bits )
				; then parity should be high ( free )
	GOTO 	INT_DATA_HIGH_PAR  	; yes
	BSF	pcDATA_out	; no, parity should be 'low' ( pulled down )
	GOTO 	INTX		;

INT_DATA_HIGH_PAR:	
	BCF	pcDATA_out	; set parity bit high ( release data line )
	GOTO	INTX		; and exit..


INT_DATA_END	
	BTFSS	_isStopBit	; is the stopbit sent ?
	GOTO	INT_DATA_STOPB	; nope then set stopbit flag

	BCF	pcDATA_out	; parity bit sent, always release data line ( stop bit )		
	GOTO	INTX

INT_DATA_STOPB
	BSF	_isStopBit	; set the stopbit flag
	GOTO	INTX		; and exit

INT_DATA_IDLE
	DECF	bitCount,F	; decrement bit counter
	GOTO 	INTX		; no toggle of data line

INT_DATA_NEXT
	BTFSS 	CurrKey,0	; is the last bit of the key_buffer high?
	GOTO 	INT_DATA_LOW	; no, pull data low
	BCF  	pcDATA_out	; yes, release data line 
	INCF    KeyParity,F	; increment parity bit
	GOTO	INT_DATA_ROTATE	; rotate data 
	
INT_DATA_LOW	; last bit is low
	BSF	pcDATA_out	; set the bit

INT_DATA_ROTATE
	RRF	CurrKey,F	; rotate right by 1 bit	
	GOTO	INTX

INT_EXIT_TX
	; setup the timer so we accomplish an delay after an tx seq
	
	BCF	_TX_Mode	; clear tx mode flag
	BCF	pcCLOCK_out	; release clock line
	BCF	pcDATA_out	; and data line
;
	MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0

	GOTO	INTX


;************** TX code end *****************

;************** RX code start ***************

INT_RX
	MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
    	MOVWF   TMR0


	BTFSS	clkCount,0	; check if we should toggle clock
	GOTO	INT_RX_DEC_CLOCK	; bit low,decrement and check if we should read data instead
	
	DECFSZ	clkCount,F	; decrement and check if we are at zero..
	GOTO	INT_RX_CLOCK 	; not zero then toggle clock line
	
	BCF	pcCLOCK_out	; release the clock line we are done..
	BCF	_RX_Mode	; clear rx mode bit ( go over to heart beat mode )
	GOTO	INT_EXIT_RX			

INT_RX_CLOCK

	BTFSC	pcCLOCK_out	; check if we are low
	GOTO	INT_RX_CLOCK_HIGH 	; yep set to high ( release line )
	
	BTFSC	_isStartBit	; check if this is the first bit ( start )
	GOTO	INT_RX_START	; clear start bit and continue
	
	BTFSC	_isParity	; check if this is the parity bit ( or parity has been received )
	GOTO	INT_RX_PAR	; yep check parity

	GOTO	INT_RX_BIT	; ok just a 'normal' bit read it
	
	
INT_RX_PAR			; check parity
	BTFSC	_doRXAck	; check the handshake flag
	GOTO	INT_RX_HNDSHK	; start handshake check

	BTFSS	pcDATA_in	; is the input high ?
	GOTO	INT_RX_PAR_HIGH	; yep
	BTFSC	KeyParity,0	; is the parity '0' ( should be )
	GOTO	INT_RX_PAR_ERR	; nope parity error
	GOTO	INT_RX_ACK	; parity ok next should be ack ( we take data line low )

INT_RX_PAR_HIGH
	BTFSS	KeyParity,0	; check that parity bit is '1'
	GOTO	INT_RX_PAR_ERR	; nope parity error
	GOTO	INT_RX_ACK	; parity ok, next is ack ( we take data line low )

INT_RX_PAR_ERR
	BSF	_KeyError	; set error flag

INT_RX_ACK
	BSF	pcCLOCK_out	; ok to set clock low ( pull down )
	BSF	_doRXAck	; enable ack check
	GOTO	INTX

INT_RX_HNDSHK
	BTFSS	_RXEnd		; if we are done dont take data low
	BSF	pcCLOCK_out	; ok to set clock low ( pull down )
	
	BTFSC	_RXAckDone	; chek if hand shake ( ack ) is done ?
	BSF	_RXEnd		; ok we are now done just make one more clock pulse

	GOTO	INTX		; exit	
		

INT_RX_CLOCK_HIGH

	BCF	pcCLOCK_out	; set high ( release line )
	BTFSS	_RXAckDone	; are we done.. ?
	GOTO	INTX
	BTFSS	_RXDone		; finished ?
	GOTO	INTX

	BCF	_RX_Mode	; and clear rx flag..
	GOTO	INT_EXIT_RX	; bye bye baby

INT_RX_DEC_CLOCK	

	DECF	clkCount,F	; decrement clock counter ( so we toggle next time )
	BTFSS	_doRXAck	; check if we are waiting for handshake
	GOTO	INTX
	

 	BTFSC	pcCLOCK_out     ; check if the clock is low ( pulled down )
	GOTO	INTX		; nope we are pulling down then exit
				; we only take over the data line if
				; the clock is high ( idle )
				; not sure about this though.. ???

 	
	BTFSC	_RXEnd		; are we done ?
	GOTO	INT_RX_END

	; handshake check if data line is free ( high )
	BTFSS	pcDATA_in	; is data line free ?
	GOTO	INTX		; nope

	BSF	pcDATA_out	; takeover data line
	BSF	_RXAckDone	; we are done..at next switchover from low-high we exit
	GOTO	INTX		; 

INT_RX_END
	BCF	pcDATA_out	; release data line
	BSF	_RXDone		; we are now done
	GOTO	INTX

INT_RX_START
	BCF	_isStartBit	; clear start bit flag
	BSF	pcCLOCK_out	; ok to set clock low ( pull down )
	GOTO	INTX
INT_RX_BIT
	BCF	CurrKey,7
	BTFSS	pcDATA_in	; is bit high
	GOTO	INT_RX_NEXT	; nope , it's a '0'
	BSF	CurrKey,7	; set highest bit to 1
	INCF	KeyParity,F	; increase parity bit counter
	
INT_RX_NEXT
	BSF	pcCLOCK_out	; ok to set clock low ( pull down )

	DECFSZ	bitCount,F	; decrement data bit counter	
	GOTO	INT_RX_NEXT_OK

	BSF	bitCount,0	; just in case ( so we cannot overdecrement )
	BSF	_isParity	; next bit is parity
	GOTO	INTX

INT_RX_NEXT_OK
	CLRC			; clear carry, so it doesnt affect receving byte
	RRF 	CurrKey,F	; rotate to make room for next bit
	GOTO	INTX		; and exit

INT_EXIT_RX	

	; handle the recevied key ( if not it is an 'data' byte )

	MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8 ( int handling code )= 200 us
				; 
    	MOVWF   TMR0		; this delay seems to be needed ( handshake ? )
	
	; check if this is an data byte ( rate/delay led status etc )
	
	MOVF	CommandData,F	; reload into itself ( affect zero flag )

	BTFSS	STATUS,Z	; check zero flag
	GOTO	INT_STORE_DATA	; byte contains data ( rate/delay etc )
	
	CALL	CHECK_RX_KEY	; no data, handle recevied command 
	GOTO	INTX

INT_STORE_DATA
	; store data byte in 'currkey', 
	; first reply with 'ack'
	
	MOVLW	H'FA'		; keyboard ack
	CALL	ADD_KEY		;

	BTFSS	_IsLedStatus	; is it led status byte ?
	GOTO	INT_STORE_RATE  ; nope check next
	
INT_STORE_NUM
	; byte in 'currkey' is led status byte, store it
	MOVF	CurrKey,W	; get byte
	MOVWF	KbLedStatus	; and store it
	BTFSC	_WaitNumLock	; was this something we were waiting for ?
				; i.e. we sent the make scancode for numlock.
	CALL	RELEASE_NUMLOCK	; yep, then send release code for 'soft' numlock

	GOTO	INT_STORE_EXIT	; store it in local ram copy and exit

INT_STORE_RATE

	BTFSS	_IsRateDelay	; is it rate/delay byte ?
	GOTO	INT_STORE_EXIT  ; nope then send ack end exit
	; byte in 'currkey' is rate/delay byte, store it
	MOVF	CurrKey,W	; get byte
	MOVWF	KbRateDelay	; and store it
	
INT_STORE_EXIT
	
	CLRF	CommandData	; clear data byte flags
	GOTO	INTX


;******************* 'heart' beat code ( 0.5 ms tick ) *********
; Note: As I 'mess' with the int timer this 'heart beat' is by no means
; an accurate clock. However it can work as a rough estimate for local time.
;
INT_HEART_BEAT

	MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0


INT_CHECK_CLKDTA:  

	; CLOCK DATA   Action
	;-----------+--------
	;   L    L  |  wait ?
	;   L    H  |  wait, buffer keys
	;   H    L  |  start an rx sequence
	;   H    H  |  keyboard can tx
		
	BTFSS	pcDATA_in	; is the data line high ( free )..
	GOTO	INT_CHECK_RX	; Nope it's pulled down, check if rx is requested
		
	BTFSC	pcCLOCK_in	; Is the clk line low  ( pulled down ) ?		
	GOTO	INT_CHECK_BUFF  ; Nope, so check if we have any keys to send

	GOTO	INT_IDLE	; clock is low , wait and buffer keys( i.e. no rx/tx )

		
						
INT_CHECK_RX   		; pc ( probably ) wants to send something..
		
	BTFSS	pcCLOCK_in	; wait until clock is released before we go into receving mode..
	GOTO	INT_RX_IDLE	; nope still low

	; clock now high test if we are set to start an rx seq. 
	BTFSS	_RxCanStart     ; have we set the flag ?
	GOTO	INT_WAIT_RX	; nope then set it  	
			
	BTFSC	pcDATA_in	; make sure that data still is low
	GOTO	INT_ABORT_RX	; nope abort rx req, might been a 'glitch'

	; initiate the rx seq.

	CLRF	Comm_Flags	; used by both tx/rx routines ( and _RxCanStart bit !! )
	CLRF	TRX_Flags	; clear tx/rx flags
	CLRF	KeyParity	; clear parity counter

	BSF	_RX_Mode      	; set rx mode flag..
	BSF	_isStartBit	; set that next sampling is start bit
	
	; preset bit and clock counters 

	MOVLW	H'2F'		; = 47 dec, will toggle clock output every even number until zero
	MOVWF	clkCount	; preset clock pulse counter

	MOVLW	H'08'		; = 8 dec, number of bits to read
				; then parity bit will be set instead

	MOVWF	bitCount	; preset bit counter

	; note as we are starting the clock here we allow a longer time before we start
	; the actual 20 us tick, this the first time we wait about 200 us before the first clock 'tick'     

	MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8us ( int handling code )= 200 us
				; 
    	MOVWF   TMR0
	
	GOTO	INTX		; exit, the next int will start an rx seq

INT_WAIT_RX:
	BSF	_RxCanStart	; set flag so we start rx next int ( 0.5 ms )
INT_RX_IDLE
	; reload clock so we check more often
	MOVLW   H'F0'           ; start timer with 240 ( 256-16  = 240 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 16 + 8/10us ( int handling code )= (about) 60 us
    	MOVWF   TMR0

	GOTO	INTX		;

INT_ABORT_RX
	BCF	_RxCanStart	; clear flag ( forces a 'new' rx start delay )
	GOTO	INT_IDLE	;
  		 									

INT_CHECK_BUFF:
	; check if we have any keys to send to pc

        MOVF 	KeyBufferTail,W ; get end of buffer to 'w'
        SUBWF 	KeyBufferHead,W ; subtract start of buffer if head - tail = zero, no byte
        BTFSC   STATUS, Z       ; zero flag = no byte to send         
        GOTO	INT_IDLE	; then do the 'idle' stuff
		 
INT_SEND_KEY
	;key in buffer, get it and initiate an tx seq...
	CALL	GET_KEY_BUFFER	; get the key into CurrKey
	MOVF	CurrKey,W
	MOVWF	LastKey		; store last sent key

	; setup our tx/rx vars 

	CLRF	Comm_Flags	; used by both tx/rx routines ( and _RxCanStart bit !! )
	CLRF	TRX_Flags	; clear tx/rx flags
	CLRF	KeyParity	; clear parity counter

	BSF	_TX_Mode      	; set tx mode flag..
	
	; preset bit and clock counters

	MOVLW	H'2B'		; = 43 dec, will toggle clock out put every even number until zero
	MOVWF	clkCount	; preset clock pulse counter

	MOVLW	H'12'		; = 18 dec, will shift data out every even number until zero
				; then parity bit will be set instead
	MOVWF	bitCount	; preset bit counter

	; data now set, initiate the clock to generate a 20 us tick ( rx/tx heart beat )

	BSF	pcDATA_out	; start bit, always 'low' ( we pull down )
     
	MOVLW   H'FA'           ; start timer with 252 ( 256-6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8 ( int handling code )= 27 us
        MOVWF   TMR0
	
	GOTO	INTX		; exit, the next int will start an tx

INT_IDLE:
	
	; 'Idle' code i.e. no rx or tx possible AND/OR nothing to send

	DECF	Divisor_10ms,F  ; Count 0.5ms down to give 10 milli second tick
	BNZ	INTX		; Exit if divider not zeroed
	MOVLW	.20
	MOVWF	Divisor_10ms     ; Preset the divide by 20

	;+++
	; 10 ms tick here
	


	; Divide the 10 ms tick to give 100 ms second tick
INT_10MS
	DECF	Divisor_100ms,F	; Count 10ms down to give 100 milli second tick
	BNZ	INTX		; Exit if divider not zeroed
	MOVLW	.10
	MOVWF	Divisor_100ms	; Preset the divide by 10

        ;+++
 	; 100 ms tick here

INT_100MS


	; numlock check !! bit variable _Numlock does not actually set the numlock status directly.
	; However, by setting this bit to '1' we make a test against the current numlock led status
	; ( bit no 1 in variable KbLedStatus ), if that is different from this variable
	; we send a 'numlock' press/release ( to toggle numlock status )
	; so in essence, setting this bit to '1' will force numlock to be 'on' etc.

	BTFSC	_IsFirstLedStatus ;  if = 1 then we have not yet received numlock status
	GOTO	INT_REPEAT_CHECK	; nope, then this is a consecutive byte, store as 'normal'

	BTFSS	_WaitNumLock	; are we waiting for pc numlock reply ?
	GOTO	INT_NUMLOCK_CHECK ; yep then do repeat check instead
	
	DECFSZ	Temp_Var,F	;
	GOTO	INT_REPEAT_CHECK

	CALL	RELEASE_NUMLOCK

INT_NUMLOCK_CHECK

	BTFSC	_LedNumLock	; is the led on ?
	GOTO	INT_NUMLOCK_ON	; yep, then test our 'local' numlock state ( wanted numlock state )

	; nope numlock is off, is our wanted state also off ?
	BTFSS	_NumLock	; is wanted state off ?
	GOTO	INT_REPEAT_CHECK ; yep continue   

	CALL	PRESS_NUMLOCK	; nope then send numlock press/release code

	GOTO	INT_REPEAT_CHECK
			

INT_NUMLOCK_ON
	BTFSC	_NumLock	; is wanted state also 'on' ?
	GOTO	INT_REPEAT_CHECK ; yep

	CALL	PRESS_NUMLOCK	; nope then toggle numlock state

INT_REPEAT_CHECK

	; check if a key should be 'repeated' ( when pressed longer than 500 ms )
	BTFSS	_startRepeat	; start repeating a key ? ( delay !!! )
	GOTO	INT_CHECK_KEY	; nope, then check if key should be repeated
	DECF	RepeatTimer,F	;
	BNZ	INT_500MS	; not zero yet, check timer instead 

	BCF	_startRepeat	; stop repeat timer ( delay is accomplished )
	BSF	_doRepeat	; and enable 'key' is still down check
	MOVLW	.02		; start repeat send timer
	MOVWF	Divisor_Repeat  ;

	GOTO	INT_500MS	; do next timer check

INT_CHECK_KEY
	BTFSS	_doRepeat	; key should be repeated ?
	GOTO	INT_500MS	; nope
	
	; ok key should be repeated, check if it still pressed ?
	CALL	CHECK_KEY_STATE	; uses MakeKeyOffset to calculate which key that was
				; the last pressed, and then check if it's still pressed
				; if still pressed carry = '1', 

	BTFSS	STATUS,C	; check carry
	BCF	_doRepeat	; clear repeat bit, stop repeating the key 

	BTFSS	_doRepeat	; still pressed ?
	GOTO	INT_500MS	; nope

	DECF	Divisor_Repeat,F  ; should we send the key ?
	BNZ	INT_500MS	; nope

	MOVLW	DELAY_RATE	; reload timer with key rate delay
	;MOVLW	.02		; restart timer
	MOVWF	Divisor_Repeat  ;
	
	BSF	_doSendKey	; set flag to send key, NOTE the actual sending ( putting into send buffer )
				; is done inside mainloop.
 	
	
INT_500MS
	
	DECF	Divisor_500ms,F	; Count 100ms down to give 500 milli second tick
	BNZ	INTX		; Exit if divider not zeroed
	MOVLW	.05
	MOVWF	Divisor_500ms	; Preset the divide by 5

        ;+++
 	; 500 ms tick here


INT_500_NEXT
	
	TOGGLE_PIN O_led_KEYCOMM_ok	; toggle the disco light ;-) 

	BTFSS	_DoExitAltKeymap	; is the alt keymap toggle key pressed the second time ?
					; if so skip timeout test and exit
	BTFSS	_InAltKeymap	; are we in altkeymap ?
	GOTO	INTX		; nope 
	
	; we are in altkeymap, decrement the lastkeytime
	; and check if we are at zero then we exit 
	; the altkeymap.

	DECF	LastKeyTime,F	; decrease time
	BNZ	INTX		; exit, timer has not expired
	; timer expired, get out of altkey map
	BSF	_ExitAltKeymap	;



; ***************** 'heart' beat code end ***************

INTX
	;BCF	INTCON,T0IF 	; Clear the calling flag

	PULL			; Restore registers
	RETFIE
	
; **************** end interrupt routine **************


;+++++        
; 	Routines that will 'toggle' keyboard numlock status
; 	by sending numlock make/break code 
;

PRESS_NUMLOCK:	 
		MOVLW	H'77' 		; numlock key scancode, make
		CALL	ADD_KEY
		MOVLW	H'06' 		; 6 x 100 ms = 600 ms ( release delay )
		MOVWF	Temp_Var	;
		BSF	_WaitNumLock    ; we are waitin for numlock status reply from pc
		RETURN

RELEASE_NUMLOCK:
		MOVLW	BREAK		; break prefix
		CALL	ADD_KEY	
		MOVLW	H'77' 		; numlock key scancode 
		CALL	ADD_KEY
		BCF	_WaitNumLock
		RETURN
	
; ***********************************************************************
;
;  CHECK_RX_KEY - handles the received commands from pc
;
CHECK_RX_KEY	
	; check the key in 'currkey' ( command from pc )

CHECK_ED
	MOVF	CurrKey,W	; move key buffer into W register
	SUBLW	H'ED'		; subtract value in W with 0xED
	BTFSS   STATUS, Z	; check if the zero bit is set
	GOTO	CHECK_EE	; the result of the subtraction was not zero check next
	; ok 'ED'=set status leds ( in next byte ) received
	BSF	_IsLedStatus	; set bit that next incoming byte is kb led staus 
	GOTO	CHECK_SEND_ACK	; send ack

CHECK_EE	
	MOVF	CurrKey,W	; move key buffer into W register
	SUBLW	H'EE'		; subtract value in W with 0xEE
	BTFSS   STATUS, Z	; check if the zero bit is set
	GOTO	CHECK_F0	; the result of the subtraction was not zero check next
	; ok 'EE'= echo command received
	GOTO	CHECK_SEND_EE	; send echo

CHECK_F0	
	MOVF	CurrKey,W	; move key buffer into W register
	SUBLW	H'F0'		; subtract value in W with 0xF0
	BTFSS   STATUS, Z	; check if the zero bit is set
	GOTO	CHECK_F2	; the result of the subtraction was not zero check next
	; ok 'F0'= scan code set ( in next commming byte ) received
	BSF	_SkipByte	; skip next incomming byte ( or dont interpret )
	GOTO	CHECK_DONE	; do not send ack !
CHECK_F2	
	MOVF	CurrKey,W	; move key buffer into W register
	SUBLW	H'F2'		; subtract value in W with 0xF0
	BTFSS   STATUS, Z	; check if the zero bit is set
	GOTO	CHECK_F3	; the result of the subtraction was not zero check next
	; ok 'F2'= Read ID command responds with 'AB' '83'
	GOTO	CHECK_SEND_ID	; send id bytes

CHECK_F3	
	MOVF	CurrKey,W	; move key buffer into W register
	SUBLW	H'F3'		; subtract value in W with 0xF3
	BTFSS   STATUS, Z	; check if the zero bit is set
	GOTO	CHECK_FE
;	GOTO	CHECK_F4	; the result of the subtraction was not zero check next
	; ok 'F3'= set repeat rate ( in next commming byte ) received
	BSF	_IsRateDelay	; next incomming byte is rate/delay info
	GOTO	CHECK_SEND_ACK	; send ack

; **** Note ! removed from test as I 'don't care' ********
; **** i.e. I dont disable or enable the keyboard at any time.

;CHECK_F4	
;	MOVF	CurrKey,W	; move key buffer into W register
;	SUBLW	H'F4'		; subtract value in W with 0xF4
;	BTFSS   STATUS, Z	; check if the zero bit is set
;	GOTO	CHECK_F5	; the result of the subtraction was not zero check next
	; ok 'F4'= keyboard enable received
;	GOTO	CHECK_SEND_ACK	; send ack
;CHECK_F5	
;	MOVF	CurrKey,W	; move key buffer into W register
;	SUBLW	H'F5'		; subtract value in W with 0xF5
;	BTFSS   STATUS, Z	; check if the zero bit is set
;	GOTO	CHECK_FE	; the result of the subtraction was not zero check next
	; ok 'F5'= keyboard disable received
;	GOTO	CHECK_SEND_ACK	; send ack
CHECK_FE	
	MOVF	CurrKey,W	; move key buffer into W register
	SUBLW	H'FE'		; subtract value in W with 0xFE
	BTFSS   STATUS, Z	; check if the zero bit is set
	GOTO	CHECK_FF	; the result of the subtraction was not zero check next
	; ok 'FE'= resend last sent byte
	MOVF	LastKey,W	; get last key
	CALL	ADD_KEY		; and put it on the que
	GOTO	CHECK_DONE

CHECK_FF
	MOVF	CurrKey,W	; move key buffer into W register
	SUBLW	H'FF'		; subtract value in W with 0xFF
	BTFSS   STATUS, Z	; check if the zero bit is set
	GOTO	CHECK_ERROR	; the result of the subtraction was not zero, unknown command
	; ok 'FF'= reset keyboard received
	
	GOTO	CHECK_SEND_AA	; send 'AA' power on self test passed

CHECK_ERROR			; unknown command ( or command not interpreted )
	GOTO	CHECK_SEND_ACK

CHECK_SEND_ID
	MOVLW	H'FA'		; keyboard ack
	CALL	ADD_KEY		;

	MOVLW	H'AB'		; keyboard id first byte, always 0xAB
	CALL	ADD_KEY		;

	MOVLW	H'83'		; keyboard id second byte, always 0x83
	CALL	ADD_KEY		;

	GOTO	CHECK_DONE

CHECK_SEND_ACK

	MOVLW	H'FA'		; keyboard ack
	CALL	ADD_KEY		;
	GOTO	CHECK_DONE

CHECK_SEND_AA
	MOVLW	H'FA'		; keyboard ack
	CALL	ADD_KEY		; 

	MOVLW	H'AA'		; keyboard post passed
	CALL	ADD_KEY		;
	GOTO	CHECK_DONE

CHECK_SEND_EE
	MOVLW	H'EE'		; keyboard echo
	CALL	ADD_KEY		;

CHECK_DONE
	RETLW	0		; and we are done

; ***********************************************************************
;  Buffer code ( a bit modified ) from Stewe Lawther 
;  http://ourworld.compuserve.com/homepages/steve_lawther/ucindex.htm
;  And of course source of the exellent keyboard viewer. !! ( without which, this 
;  project would have been close to impossible ) ( and of course my nifty 
;  memory oscilloscope )
;
;  ADD_KEY_BUFFER - (outside int)add the key in CurrKey to our keybuffer que in the first
;		    free position. If there is no more room the oldest byte is 
;		    'dumped'.
;  ADD_KEY	  - Same but to be used inside int routine.( just skips int disable code )

ADD_KEY_BUFFER

ADD_STOP_INT			; first stop all interrupts !!!!!!! 
	BCF	INTCON,GIE	; disable global interrupts..
	BTFSC	INTCON,GIE	; check that is really was disabled
	GOTO	ADD_STOP_INT	; nope try again
ADD_KEY				; inside interuppt we call this instead ( as we dont need to disable int :-) )
	MOVWF	BufTemp		; store key temporary
	MOVF    KeyBufferHead,W ; move buffer head out of FSR temporarily
        MOVWF   Temp            ; store in temp  
        MOVF    KeyBufferTail,W ; set FSR to buffer tail
        MOVWF   FSR		; set indirect file pointer
        MOVF    BufTemp,W       ; set W to new scancode to send
        MOVWF   INDF            ; and put it in the buffer
        MOVF    Temp,W		; get the head pointer back
        MOVWF   KeyBufferHead	;

        INCF    KeyBufferTail,W ; get the end of the buffer
        SUBLW   KbBufferMax     ; check if at buffer max
        INCF    KeyBufferTail,W ; (reload value to w - doesn't affect C) 
        BTFSS   STATUS, C       ; if so (negative result)
        MOVLW   KbBufferMin     ; set to buffer min ( wrap around )

        MOVWF   KeyBufferTail	
        SUBWF   KeyBufferHead,W ; see if we have any room ( head and tail have meet ) 
        BTFSC   STATUS, Z       ; if so (Z set)
        CALL    INC_KEY_HEAD    ; dump oldest byte

				; finally turn on interrupts again
        MOVLW   b'10100000'     ; enable global & TMR0 interrupts
        MOVWF   INTCON

	RETURN


; ***********************************************************************
;
;  GET_KEY_BUFFER - 	Gets a char from the buffer, and puts it into KeyBuffer
;			NOTE: Does not increase buffer pointers ( dump this key ).
;			A call to INC_KEY_HEAD will do this if the key is sent ok
GET_KEY_BUFFER	
	  	MOVF    INDF, W         ;put the byte to send into key buffer
                MOVWF   CurrKey
		RETURN			; and go back, NOTE ! the key is not 
					; removed from the buffer until a call
					; to INC_KEY_HEAD is done.
					
; ***********************************************************************
;
;  INC_KEY_HEAD - 	dump oldest byte in keybuffer, Do not call if byte
;			has not been fetched before ( GET_KEY_BUFFER )
;			
INC_KEY_HEAD    INCF    KeyBufferHead,W ; set to next byte in buffer
                SUBLW   KbBufferMax     ; check if at buffer max
                INCF    KeyBufferHead,W ; (reload value to w - doesn't affect C) 
                BTFSS   STATUS, C       ; if so (negative result)
                MOVLW   KbBufferMin     ; set to buffer min ( wrap around )
                MOVWF   KeyBufferHead   ; and store ( in FSR )
                RETURN			; go back
                
		

; ***********************************************************************
;
;  CHECK_KEY_STATE - Check if the last pressed key is still pressed 
;		     Returns with carry = '1' if still pressed
;		     else carry = '0' ( or error )
;

CHECK_KEY_STATE:
	; uses LastMakeOffset to calculate which key to test
	
	MOVF	LastMakeOffset,W	; get offset 
	ANDLW	H'18'		; mask out column bits  
				; lastmake offset has the following bits:
				; '000yyxxx' where 'yy' is column no
				; and 'xxx' is key num, 
	BTFSC	STATUS,Z	; zero = column 1 & 2
	GOTO	CHECK_COL_12	; it is in column 1

	MOVWF	repTemp		; save it temporary
	SUBLW	H'08'		; subtract value in W with 0x08 ( columns 3 & 4 )
	BTFSC   STATUS, Z	; check if the zero bit is set
	GOTO	CHECK_COL_34	; it is in column 3 & 4

	MOVF	repTemp,W	; get the column bits back
	SUBLW	H'10'		; subtract value in W with 0x10 ( columns 5 & 6 )
	BTFSC   STATUS, Z	; check if the zero bit is set
	GOTO	CHECK_COL_56	; it is in column 5 & 6
	
CHECK_COL_78
	MOVF	kbColumn78_Old,W ; get bit map ( key status ) for keys in column 7 & 8
	MOVWF	repKeyMap	; and store it
	GOTO	CHECK_KEY	; and continue to check bit

CHECK_COL_56
	MOVF	kbColumn56_Old,W ; get bit map ( key status ) for keys in column 5 & 6
	MOVWF	repKeyMap	; and store it
	GOTO	CHECK_KEY	; and continue to check bit

CHECK_COL_34
	MOVF	kbColumn34_Old,W ; get bit map ( key status ) for keys in column 3 & 4
	MOVWF	repKeyMap	; and store it
	GOTO	CHECK_KEY	; and continue to check bit

CHECK_COL_12
	MOVF	kbColumn12_Old,W ; get bit map ( key status ) for keys in column 1 & 2
	MOVWF	repKeyMap	; and store it

;<-------Alt keymap code-------> 
	
	;Checks ONLY column 1&2 bitmap ( keymap ) ( as it is now )
	; this code has to be moved/changed if another column is choosen as the alt. keymap toggle key

	; alternative keymap handling if key r3 c1 is pressed ( bit 2 in column 1&2 )
	; then enable alternative keymap ( only if keyrepeat is disabled )
	
	; check if this was the last key pressed

	; check bit representing the alt. keymap key ( i've choosen key 2 )

	MOVF	LastMakeOffset,W ; get key offset again
	ANDLW	H'07' 		; mask out column bits
	SUBLW   H'02'		; check if its bit num 2 ( the enter 'alt keymap' key )

	BTFSS   STATUS, Z	; check if the zero bit is set	GOTO	CHECK_KEY	; nope than another key was the last
				; skip altkeymap enable

	; the altkeymap key was the last pressed !
	; is key repeat disabled ?

	BTFSS	I_jmp_NoRepeat	; check if repeat code is enabled ?
	GOTO	CHECK_KEY 	; yep, then skip altkeymap enable test

	; enable altkeymap if key is still pressed
	
	BTFSC	repKeyMap,2	; test bit 2 ( should be key 'F7' )
	GOTO	CHECK_ENABLE_ALT ; ok enable altkeymap ( if we are not already in altkeymap )
	GOTO	CHECK_KEY	; nope another key in column 1&2 continue check 

CHECK_ENABLE_ALT
	BTFSC	_AltKeymap	; are we already in altkeymap ?
	GOTO	CHECK_KEY	; yep then just continue

	; We are just entering/enabling the alt. keymap

	BSF	_AltKeymap	; enable alternative keymap

	; Example of using an 'advanced' alt keymap handling	
	; not enabled, to avoid intial confusion.

	; I.E This snippet would only be called once when we
	; are just entering(enabling) the alternative keymapping !

	; This example code will 'soft' release the current altkeymap key ( it is in pressed state ! )
	; ( i.e send release code for the enter alt. keymap key 'F7' )
	; send the make scancode for left <alt> key instead.
	; and force numlock to be off.
	
	; Do not use if you dont understand the implifications !
		
	; Also note that the scancodes are hardcoded here !
	; ( i.e do not use the lookup table definition of the key/s )
	
	; ***** start snippet 
	
	;MOVLW	BREAK	; send break prefix
	;CALL	ADD_KEY
	;MOVLW	H'83'   ; and scancode for the enter alt keymap
	;CALL	ADD_KEY
	;MOVLW	H'11' 	; send make code for the left <alt> key 
	;CALL	ADD_KEY
	
	; example of forcing the numlock status to a particular state
	; the numlockstatus will change ( be checked ) inside the int routine
	; See also at the end of KB_DEBOUNCE_12 where the numlock status
	; will be restored when we release the key
	
	;BCF	_NumLock	; 'force' numlock to be off
	
	; This bit MUST also be checked as we do not know if we have recevied 
	; first numlock status byte yet ( pc does not send numlock/led status
	; after intial poweron, if not one of the numlock/capslock/scrolllock are pressed )
	; i.e. if you connect this keyboard to a 'running' pc, the numlock status
	; will be unknown.However if connected before poweron, it will be updated
	; as numlock status is sent during pre-boot seq.
	
	;BTFSC	_IsFirstLedStatus ; have we recevied numlock status yet ?
	;CALL	PRESS_NUMLOCK
	
	; ***** end snippet 

CHECK_KEY
	; 'normal' key down check
	; column for pressed key is now in repKeyMap
	MOVF	LastMakeOffset,W ; get offset again
	ANDLW	H'07'		; mask out key number ( lowest 3 bits )
	
 	BTFSC	STATUS,Z	; bit num zero ?
	GOTO	CHECK_KEY_DONE	; yep lowest bit, check and return
	
   	MOVWF	repTemp		; and store it
CHECK_KEY_LOOP
	RRF	repKeyMap,F	; rotate one step to right
	DECFSZ	repTemp,F		; decrement bit counter
	GOTO	CHECK_KEY_LOOP	; loop again

CHECK_KEY_DONE
	; ok the key to test should now be the lowest bit in repKeyMap
	CLRC			; clear carry
	BTFSC	repKeyMap,0	; check bit 0
	BSF	STATUS,C	; ok key is pressed set carry
	RETURN			; and we are done..

; ***********************************************************************
;
;  DELAY_1ms - 	Delay routine ! used when scanning our own keyboard
; 		Delay is between output of adress to 4051 and reading of inputs
; 		Increase to have a slower 'scan' rate or decrease to have a higher scan rate.
;

DELAY_1ms
		MOVLW	H'F0'		; wait 255 cycles
		MOVWF	kbTemp		; this var is 'safe' to be used in side mainloop
		MOVLW	H'03'
		MOVWF	kbState
DELAY_LOOP
		DECFSZ	kbTemp,F	; decrement
		GOTO	$-1		;
		
		MOVLW	H'F0'
		MOVWF	kbTemp
		DECFSZ	kbState,F
		GOTO	DELAY_LOOP

		RETURN


;---------------------------------------------------------------------------
;
;		Initialisation
;
;---------------------------------------------------------------------------
        
        
INIT:  

	;+++
	;	Set up the ports

		; PORT A

	BANK1
        MOVLW	b'00000110'		; Set port data directions RA1,RA2 inputs RA0,RA3,RA4 outputs
	MOVWF	TRISA			; PC keyboard connections


		; PORT B

		; Used for our own 3x8 matrix keyboard
	
        BANK1
        MOVLW	b'11111000'		; Set port data directions RB4-RB7 inputs rest outputs
	MOVWF	TRISB


	;	Clear all registers on bank 0 ( memory )
	
	BANK0
	MOVLW	H'0C'
	MOVWF	FSR
	
INITMEM
	CLRF	0		; Clear a register pointed to be FSR
	INCF	FSR,F
	CLRWDT			; clear watchdog
	MOVLW	H'50'		; Test if at top of memory
	SUBWF	FSR,W
	BNZ	INITMEM		; Loop until all cleared

	;+++ 	
	;	Initiate the keybuffer pointers

INIT_BUFF:

	MOVLW   KbBufferMin	; get adress of first buffer byte
        MOVWF	KeyBufferHead	; store in FSR
        MOVWF	KeyBufferTail	; and set last byte to the same ( no bytes in buffer )

	;+++
	;	Preset the timer dividers

	MOVLW	.20
	MOVWF	Divisor_10ms
	MOVLW	.10
	MOVWF	Divisor_100ms
	MOVLW	.05
	MOVWF	Divisor_500ms

	;+++
	;	Set up Timer 0.

	;	Set up TMR0 to generate a 0.5ms tick
	;	Pre scale of /8, post scale of /1
	
	BANK1
        
	MOVLW   b'00000010'     ; Initialisation of TMR0 prescale 8 '010'
                                ; weak pullup enabled by latch values.

        MOVWF   OPTION_REG      ; load option reg with prescale of 8
                
        BANK0

	MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0
        

		
;---------------------------------------------------------------------------
;
;		the main 'program' loop ( starts really at MAIN_LOOP )
;
;---------------------------------------------------------------------------

MAIN:    
		BSF	pcDATA_in
		BSF	pcCLOCK_in
		BCF	pcDATA_out
		BCF	pcCLOCK_out
		CLRF	PORTB

		MOVLW	H'08'		; preset the column counter
		MOVWF	kbColumnCnt	;
		
 		BSF	_NumLock	; default state is numlock = on 
		BSF	_IsFirstLedStatus ; we have not yet recevied led status byte.

	        MOVLW   b'10100000'     ; enable global & TMR0 interrupts
	        MOVWF   INTCON

		CLRWDT			; clear watchdog
		BTFSS	O_led_KEYCOMM_ok
		GOTO    $-2		; make an 0.5 second delay here 
					; i.e. the led will come on when 0.5 seconds has passed
					; set inside the timer int.

		CLRWDT			; clear watchdog
		BTFSC	O_led_KEYCOMM_ok
		GOTO    $-2		; make an additional 0.5 second delay here 
					; i.e. the led will be dark when 0.5 seconds has passed
					; set inside the timer int.


		MOVLW	H'AA' 		; post passed :-), always 0xAA
		CALL	ADD_KEY_BUFFER
		
		; now go into infinite loop, the pc kb interface runs in the background ( as an int )
		; where we continuously monitor the pcCLOCK/DATA_in lines

 		
MAIN_LOOP:
		; check whatever :-)
		CLRWDT			; clear watchdog

MAIN_CHECK_COL_1:
		; scan our own keyboard, first four bits

		; address and read column, read as complement so key pressed = '1'
		; since we pull down when key is pressed ( weak pullup enabled )
		; ( i.e. pressed key has level 0V ) to make it more 'logical' to work with

		CLRF	kbColumnVal
		
		; get column counter / adress out
		MOVF	kbColumnCnt,W  		
		
		MOVWF	PORTB		; set the columns adress to the 74HCT4051
					; i.e. make column low
		IFNDEF	DEBUG
		CALL	DELAY_1ms	; wait 1 ms let pins stabilize
		ENDIF

		COMF	PORTB,W		; read back the pin values ( complement i.e. key pressed = '1' )
		ANDLW   b'11110000'	; mask out unused pins
		
		MOVWF	kbColumnVal	; store the pin values

		SWAPF	kbColumnVal,F	; swap nibbles ( low<->high ) to make room for next column

		INCF	kbColumnCnt,F     ; inc column adress
MAIN_CHECK_COL_2:
		; read next four bits
		; put out adress and read next column, read as complement so key pressed = '1'
		; this as we pull down when key is pressed ( weak pullup enabled )

		; get column counter / adress out
		MOVF	kbColumnCnt,W  		
		
		MOVWF	PORTB		; set the columns adress to the 74HCT4051
					; i.e. make column low

		IFNDEF	DEBUG
		CALL	DELAY_1ms	; wait 1 ms
		ENDIF

		COMF	PORTB,W		; read back the pin values ( complement i.e. key pressed = '1' )
		ANDLW   b'11110000'	; mask out unused pins
		
		ADDWF	kbColumnVal,F	; and store pin values

		INCF	kbColumnCnt,F   

		; reset column counter check
		; i.e. we are 'only' using adress 0 - 7 
		MOVF	kbColumnCnt,W
		SUBLW	H'08'		; subtract value in W with 0x08
		BTFSS   STATUS, Z	; check if the zero bit is set
		GOTO	MAIN_CHECK_DEBOUNCE ; nope continue

		CLRF	kbColumnCnt     ; reset counter/adress

MAIN_CHECK_DEBOUNCE:
		CALL	KB_DEBOUNCE	; do debouncing on the current values and send make/break
					; for any key that has changed
					; NOTE uses the current column adress to determine which
					; columns to debounce!
MAIN_REPEAT:
		BTFSS	I_jmp_NoRepeat	; check if repeat code is enabled ?
		GOTO	MAIN_CHECK_REPEAT ; yep check key repeating
		
		; keyrepeat disabled then do check on exit of altkeymap instead

		BTFSS	_ExitAltKeymap	; we want to exit altkeymap ?
		GOTO	MAIN_LOOP	; nope

		
		; check that ALL keys are released
		; before exiting the alt keymap
		MOVF	kbColumn78_Old,F ; reload column 78 to itself ( affect zero flag )
		BTFSS	STATUS,Z	; check if zero ?
		GOTO	MAIN_LOOP	; key/s still down in column 78

		MOVF	kbColumn56_Old,F ; reload column 56 to itself ( affect zero flag )
		BTFSS	STATUS,Z	; check if zero ?
		GOTO	MAIN_LOOP	; key/s still down in column 56

		MOVF	kbColumn34_Old,F ; reload column 34 to itself ( affect zero flag )
		BTFSS	STATUS,Z	; check if zero ?
		GOTO	MAIN_LOOP	; key/s still down in column 34

		MOVF	kbColumn12_Old,F ; reload column 12 to itself ( affect zero flag )
		BTFSS	STATUS,Z	; check if zero ?
		GOTO	MAIN_LOOP	; key/s still down in column 12
	
		; all keys released !! 
		BCF	_AltKeymap	; exit altkeymap
		BCF	_ExitAltKeymap	; exit release check
		BCF	_InAltKeymap    ; clear flag for second keypress check
		BCF	_DoExitAltKeymap ;
		GOTO	MAIN_LOOP	


MAIN_CHECK_REPEAT
		BTFSS	_doSendKey	; if we should send a repeated key
		GOTO	MAIN_LOOP	; nope continue

		; send the key in RepeatedKey but first check if its an extended key
		BTFSS 	_RepeatIsExt	; is it extended ?
		GOTO	MAIN_SEND_REPEAT ; nope just send scan code
		
		; last key pressed was extended send extended prefix
		MOVLW	EXTENDED	; get extended code
		CALL	ADD_KEY_BUFFER	; and put it into the buffer
		
MAIN_SEND_REPEAT:
		MOVF	RepeatKey,W	; get key code for the last pressed key
		CALL	ADD_KEY_BUFFER	; and put it into the buffer
		BCF	_doSendKey	; and clear the flag, it will be set again 
					; inside int handler if key still is pressed	
		
		GOTO	MAIN_LOOP	; and return
	

; ***********************************************************************
;
;  KB_SEND_KEY - uses the Offset stored in var 'Offset' to fetch a key from our
;		 key lookup table.
;	  	 then checks the bit var _isBreak to see if make or break codes should be sent
;		 It then puts the code/s into the key buffer ( for sending later, in int routine )

KB_SEND_KEY:

	MOVF	Offset,W	; get current offset
	MOVWF	TempOffset	; save it ( to be used in key repeat code, is its 'make' )
				; temp offset has the following bits:
				; '000yyxxx' where 'yy' is column offset
				; and 'xxx' is key num, 


	CLRC			; clear carry so it dont affect byte rotation
	RLF	Offset,F	; first rotate
	RLF	Offset,F	; second rotate

				; offset no have the following bits:
				; '0yyxxx01' where 'yy' is column offset
				; and 'xxx' is key num, 
				; as each key in table has 4 bytes of 'space'

	INCF	Offset,F	; add one, for the 'movwf pcl' at the start of the table

	BCF	Offset,7	; clear to bit, just in case so we dont
				; 'overflow' the table, should not be needed !

	BCF	_isExtended     ; clear extended flag

	MOVLW   LOW LOOKUP_KEY	; get low bit of table adress
	ADDWF	Offset,F	; 8 bit add
	MOVLW	HIGH LOOKUP_KEY	; get high 5 bits
	BTFSC	STATUS,C	; is page boundary crossed ?
	ADDLW	1		; yep, then inc high adress
	MOVWF	PCLATH		; load high adress in latch
	MOVF	Offset,W	; load computed offset in w
	CLRC		        ; clear carry ( default= key is not extended )
				; if key is extended then carry is set in jumptable lookup_key

	CALL	LOOKUP_KEY	; get key scan code/s for this key
				; key scan code/s are saved in 
				; W - scancode, should go into kbScan
				; carry set - extend code 
				; carry clear - not extended code

	MOVWF	kbScan		; store scancode
	; if carry is set then key is extended so first send extended code 
	; before any make or break code

	BTFSS	STATUS,C	; check carry flag
	GOTO	KB_CHK_BREAK	; nope then check make/break status

	BSF	_isExtended	; set extended flag
	MOVLW	EXTENDED	; 
	CALL	ADD_KEY_BUFFER	; get extended code and put in in the buffer

KB_CHK_BREAK:
	
	; check if it's make or break
	BTFSS	_isBreak	; check if its pressed or released ? 
	GOTO	KB_DO_MAKE_ONLY	; send make code 

	BCF	_isBreak	; clear bit for next key

	; break code, key is released
	MOVLW	BREAK		; get break code
	CALL	ADD_KEY_BUFFER	; and put into buffer
	GOTO	KB_DO_MAKE	; and send key code also

	; key is pressed !
KB_DO_MAKE_ONLY:
	BCF	_doSendKey	; stop repeat sending
	BCF	_doRepeat	; and bit for repeat key send
	BSF	_startRepeat	; and set flag for start key repeat check
	BCF	_RepeatIsExt    ; clear repeat key extended flag ( just in case )

	BTFSC 	_isExtended     ; is it extended ?
	BSF	_RepeatIsExt	; set the flag 
	
	; save this key in 'last' pressed, to be used in key repeat code

	MOVF	TempOffset,W	; get saved offset
	MOVWF	LastMakeOffset  ; and store it 

	; if keyrepat = enabled, alternative mapping = disabled
	BTFSS	I_jmp_NoRepeat	; check if repeat code is enabled ?
	GOTO	KB_REP_NOR	; yep set normal delay ( 800 ms )
	
	; else keyrepat = disabled, alternative mapping = enabled
	MOVLW	DELAY_ENTER_ALTKEYMAP ;reload delay before entering the altkeymap ( 3 sec )
				; i.e how long the enter altkeymap key must be pressed before 
				; we enable altkey keymap codes.
							
	GOTO	KB_REP_SET	; and set it
KB_REP_NOR:	
	
	MOVLW	DELAY_REPEAT	; reload 'normal' repeat delay ( 800 ms )
			
KB_REP_SET:
	MOVWF	RepeatTimer	; and (re)start the timer for key repeat
	MOVF	kbScan,W	; get key scan code	
	MOVWF	RepeatKey	; and save it

KB_DO_MAKE:
	; key pressed/released ( i.e. the scancode is sent both on make and break )
	MOVF	kbScan,W	; get scan code into w
	CALL	ADD_KEY_BUFFER	; and add to send buffer
	

	; reset the 'get out of alt. keymap timer for each keypress 
	; note don't care if we are 'in' alt. keymap. Reset this timer anyway
	; as the code for checking if we are currently in alt. key map
	; would be as long as it takes to reset the timer.

	MOVLW	DELAY_EXIT_ALTKEYMAP ; reload the delay for exiting the altkeymap when no
				; key is pressed ( 7.5 sec )
	MOVWF	LastKeyTime	; (re)set lastkey timer ( used to get out of altkeymap )	
	RETURN

; ***********************************************************************
;
;  KB_DEBOUNCE - debounces two column readings from our keyboard
;		 If a bit 'state' has been 'stable' for 4 consecutive debounces
;	  	 the 'new' byte is updated with the new state 
;		 'normal' loop time ( no tx/rx/key press ) is about 2-3 ms
;		 so from 'key' down until 'new' is updated it takes about 8-10 ms
;		 ( as we are scanning columns two by two, the whole keyboard needs
;		 4 loops to be fully updated, then 4 debounce samples for each 'pair' )	

KB_DEBOUNCE:
	; debounce current column(s) 
	MOVF	kbColumnCnt,F	; reload value into itself ( affect zero flag )
	BTFSC	STATUS,Z	; is it zero ?
	GOTO	KB_DEBOUNCE_78	; debounce columns 7 & 8

	MOVF	kbColumnCnt,W	; move column counter into W register
	SUBLW	H'04'		; subtract value in W with 0x04 ( columns 5 & 6 )
	BTFSC   STATUS, Z	; check if the zero bit is set
	GOTO	KB_DEBOUNCE_34	; debounce columns 3 & 4

	MOVF	kbColumnCnt,W	; move column counter into W register
	SUBLW	H'06'		; subtract value in W with 0x02 ( columns 3 & 4 )
	BTFSC   STATUS, Z	; check if the zero bit is set
	GOTO	KB_DEBOUNCE_56	; ok column 1 & 2 debounce

	; all above tests 'failed'
	; columns to debouce are 1 & 2

KB_DEBOUNCE_12:	
	; debounce columns 1 & 2
	DEBOUNCE_BYTE	kbColumnVal,kbColumn12_New,kbColumn12Cnt,kbColumn12State

	MOVF	kbColumn12_New,W	; get debounced sample
	XORWF	kbColumn12_Old,W	; get changed bits
	
	BTFSC	STATUS,Z	; check if zero = no change
	RETURN			; no change. return
	
	; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
	; Note ! Not the actual state of the key, only 'change has occured' = '1'

	MOVWF	kbState		; save change bit/s
	MOVLW	H'07'           ; preset bit counter
	MOVWF	kbBitCnt	; loop though all eight bits.
	BCF	_LastColumn	; clear end seq bit ( set when are done with last bit )

	MOVF	kbColumn12_New,W ; get new sample
	MOVWF	kbTemp		; and store it

KB_LOOP_12	
	CLRF	Offset		; clear offset counter ( for table read )

	CLRC			; clear carry
	RLF	kbState,F	; rotate left, and store back
	BTFSS	STATUS,C 	; check carry '1' = bit was high = change has occured
	GOTO	KB_LOOP_12_SKIP	; nope, no change check next bit ( or exit )
		
	; bit changed
	MOVF	kbBitCnt,W	; get bit counter ( for offset calc. )
	MOVWF	Offset		; store bit num ( for offset )
		
	CLRC			; clear carry
	RLF	kbTemp,F	; rotate left ( next bit )
	BTFSS	STATUS,C 	; check carry '1' = key is down ( i.e. make )
	BSF     _isBreak 	; c = '0' = send break code, i.e. key is released

	CALL	KB_SEND_KEY	; send key code/s make/break uses 
				; Offset, and _isBreak vars


	GOTO	KB_LOOP_12_NEXT	

KB_LOOP_12_SKIP
	RLF	kbTemp,F	; rotate so we read next key
	
KB_LOOP_12_NEXT
	BTFSC	_LastColumn	; are we done ?
	GOTO	KB_12_DONE	; yep, save new key bit map and exit

	DECFSZ	kbBitCnt,F	; decrement bit counter
	GOTO	KB_LOOP_12	; bits left
	BSF	_LastColumn	; set bit so we break out after next run
	GOTO	KB_LOOP_12

KB_12_DONE:
	; and update our 'last known' status for the columns
	MOVF	kbColumn12_New,W ; get new status
	MOVWF	kbColumn12_Old	; and store it..

;<-------Alt keymap code------->

	; ***** alternative keymap handling
	; The alternative keymap is enabled by pressing key r4 c1 ( i.e. bit 3 in column 12 )
	; Here, we enable a check to turn off alternative keymap if
	; that key and all others are released ( bit is cleared ). 
	; ( else no (alternative)break codes would be sent for those keys that are still pressed )
	; NOTE: _Altkeymap is set inside int routine when checking
	; keyrepeat so there is a 'variable' delay before the altkeymap is active
	;

	BTFSS	_AltKeymap		; is altkeymap enabled ?
	RETURN				; nope return


	BTFSC   _InAltKeymap		; are we in altkeymap ?
	GOTO    KB_12_IN		; yep alt keymap key has been released once
	
	; nope still waiting for first release
	BTFSS   kbColumn12_Old,2	; is key released ? ( first time )
	GOTO	KB_12_ALT		; yep, reset timers and set bit variables

KB_12_IN
	BTFSC	_DoExitAltKeymap	; are we waiting for release ?
	GOTO	KB_12_OUT		; yes

	; the key has been released once test for second press
	BTFSC   kbColumn12_Old,2	; is it still pressed ?
	GOTO	KB_12_ALT2		; yep

	BTFSS	_DoExitAltKeymap        ; are we now waiting for the last ( second ) release ?
	RETURN				; nope

KB_12_OUT
	BTFSS	kbColumn12_Old,2	; check if key still pressed ?
	BSF	_ExitAltKeymap		; nope, then enable exit check that
					; will exit alt keymap as soon as all key are released
	
KB_12_ALT2
	BSF	_DoExitAltKeymap	; check for second release
	RETURN
KB_12_ALT
	; first release of the enter alt keymap key
	; reset 'get out' timer and set bit variables to enable check
	; for second press/release

	MOVLW	H'0F'		; x0.5 sec = 7.5 sec
	MOVWF	LastKeyTime	; (re)set lastkey timer ( used to get out of altkeymap automaticly)	

	BSF	_InAltKeymap	; yep the first time, then set flag that we are now
				; waiting for a second press/release to exit alt key map
				; all keys are released before exiting altkeymap

	;***** Example snippet(one line) to be paired with code in CHECK_KEY_STATE where I
	; forced numlock status to be off while enetering the alt keymap
	; but have not yet released the alt keymap toggle key.
	; this code will be called at the first release of this key. Used
	; to restore numlock status.
	; As said before, do not use if implifications are not known !

	;BSF	_NumLock	; and also force numlock to be 'on'
				; as it is set to 'off' when we enter altkeymap
				; we must set it 'back'
	
	RETURN	
		
KB_DEBOUNCE_34:	
	; debounce columns 3 & 4
	DEBOUNCE_BYTE	kbColumnVal,kbColumn34_New,kbColumn34Cnt,kbColumn34State

	MOVF	kbColumn34_New,W	; get debounced sample
	XORWF	kbColumn34_Old,W	; get changed bits
	
	BTFSC	STATUS,Z	; check if zero = no change
	RETURN			; no change. return
	
	; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
	; Note ! Not the actual state of the key, only 'change has occured' = '1'

	MOVWF	kbState		; save change bit/s
	MOVLW	H'07'           ; preset bit counter
	MOVWF	kbBitCnt	; loop though all eight bits.
	BCF	_LastColumn	; clear end seq bit ( set when are done with last bit )

	MOVF	kbColumn34_New,W ; get new sample
	MOVWF	kbTemp		; and store it

KB_LOOP_34	
	CLRF	Offset		; clear offset counter ( for table read )

	CLRC			; clear carry
	RLF	kbState,F	; rotate left, and store back
	BTFSS	STATUS,C 	; check carry '1' = bit was high = change has occured
	GOTO	KB_LOOP_34_SKIP	; nope, no change check next bit ( or exit )
		
	; bit changed
	MOVF	kbBitCnt,W	; get bit counter ( for offset calc. )
	MOVWF	Offset		; store bit num ( for offset )
	
	BSF	Offset,3	; set bit 3 for table read ( column 3 & 4 )

	;BCF	_isBreak	; clear break flag
	CLRC			; clear carry
	RLF	kbTemp,F	; rotate left ( next bit )
	BTFSS	STATUS,C 	; check carry '1' = key is down ( i.e. make )
	BSF     _isBreak 	; c = '0' = send break code, i.e. key is released

	CALL	KB_SEND_KEY	; send key code/s make/break uses 
				; Offset, and _isBreak vars


	GOTO	KB_LOOP_34_NEXT

KB_LOOP_34_SKIP
	RLF	kbTemp,F	; rotate so we read next key
	
KB_LOOP_34_NEXT
	BTFSC	_LastColumn	; are we done ?
	GOTO	KB_34_DONE	; yep, save new key bit map and exit

	DECFSZ	kbBitCnt,F	; decrement bit counter
	GOTO	KB_LOOP_34	; bits left
	BSF	_LastColumn	; set bit so we break out after next run
	GOTO	KB_LOOP_34

KB_34_DONE:
	; and update our 'last known' status for the columns
	MOVF	kbColumn34_New,W ; get new status
	MOVWF	kbColumn34_Old	; and store it..
	RETURN	


KB_DEBOUNCE_56:	
	; debounce columns 5 & 6
	DEBOUNCE_BYTE	kbColumnVal,kbColumn56_New,kbColumn56Cnt,kbColumn56State

	MOVF	kbColumn56_New,W	; get debounced sample
	XORWF	kbColumn56_Old,W	; get changed bits
	
	BTFSC	STATUS,Z	; check if zero = no change
	RETURN			; no change. return
	
	; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
	; Note ! Not the actual state of the key, only that 'a change has occured' = '1'

	MOVWF	kbState		; save change bit/s
	MOVLW	H'07'           ; preset bit counter
	MOVWF	kbBitCnt	; loop though all eight bits.
	BCF	_LastColumn	; clear end seq bit ( set when are done with last bit )

	MOVF	kbColumn56_New,W ; get new sample
	MOVWF	kbTemp		; and store it

KB_LOOP_56	
	CLRF	Offset		; clear offset counter ( for table read )

	CLRC			; clear carry
	RLF	kbState,F	; rotate left, and store back
	BTFSS	STATUS,C 	; check carry '1' = bit was high = change has occured
	GOTO	KB_LOOP_56_SKIP	; nope, no change check next bit ( or exit )
		
	; bit changed
	MOVF	kbBitCnt,W	; get bit counter ( for offset calc. )
	MOVWF	Offset		; store bit num ( for offset )
	
	BSF	Offset,4	; set bit 4 for table read ( column 5 & 6 )
	
	CLRC			; clear carry
	RLF	kbTemp,F	; rotate left ( next bit )
	BTFSS	STATUS,C 	; check carry '1' = key is down ( i.e. make )
	BSF     _isBreak 	; c = '0' = send break code, i.e. key is released

	CALL	KB_SEND_KEY	; send key code/s make/break uses 
				; Offset, and _isBreak vars


	GOTO	KB_LOOP_56_NEXT

KB_LOOP_56_SKIP
	RLF	kbTemp,F	; rotate so we read next key

KB_LOOP_56_NEXT
	BTFSC	_LastColumn	; are we done ?
	GOTO	KB_56_DONE	; yep, save new key bit map and exit

	DECFSZ	kbBitCnt,F	; decrement bit counter
	GOTO	KB_LOOP_56	; bits left
	BSF	_LastColumn	; set bit so we break out after next run
	GOTO	KB_LOOP_56

KB_56_DONE:
	; and update our 'last known' status for the columns
	MOVF	kbColumn56_New,W ; get new status
	MOVWF	kbColumn56_Old	; and store it..
	RETURN	

KB_DEBOUNCE_78:	
	; debounce columns 7 & 8
	DEBOUNCE_BYTE	kbColumnVal,kbColumn78_New,kbColumn78Cnt,kbColumn78State
	
	MOVF	kbColumn78_New,W	; get debounced sample
	XORWF	kbColumn78_Old,W	; get changed bits
	
	BTFSC	STATUS,Z	; check if zero = no change
	RETURN			; no change. return
	
	; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
	; Note ! Not the actual state of the key, only 'change has occured' = '1'

	MOVWF	kbState		; save change bit/s
	MOVLW	H'07'           ; preset bit counter
	MOVWF	kbBitCnt	; loop though all eight bits. ( 7-0 )
	BCF	_LastColumn	; clear end seq bit ( set when are done with last bit )

	MOVF	kbColumn78_New,W ; get new sample
	MOVWF	kbTemp		; and store it

KB_LOOP_78	
	CLRF	Offset		; clear offset counter ( for table read )
	CLRC			; clear carry
	RLF	kbState,F	; rotate left, and store back
	BTFSS	STATUS,C 	; check carry '1' = bit was high = change has occured
	GOTO	KB_LOOP_78_SKIP	; nope, no change check next bit ( or exit )
		
	; bit changed
	MOVF	kbBitCnt,W	; get bit counter ( for offset calc. )
	MOVWF	Offset		; store bit num ( for offset )
	
	BSF	Offset,4	; set bit 3,4 for table read ( column 7 & 8 )
	BSF	Offset,3	; 
	
	CLRC			; clear carry
	RLF	kbTemp,F	; rotate left ( next bit )
	BTFSS	STATUS,C 	; check carry '1' = key is down ( i.e. make )
	BSF     _isBreak 	; c = '0' = send break code, i.e. key is released


	CALL	KB_SEND_KEY	; send key code/s make/break uses 
				; Offset, and _isBreak vars

	GOTO	KB_LOOP_78_NEXT
	
KB_LOOP_78_SKIP
	RLF	kbTemp,F	; rotate so we read next key
	
KB_LOOP_78_NEXT
	BTFSC	_LastColumn	; are we done ?
	GOTO	KB_78_DONE	; yep, save new key bit map and exit

	DECFSZ	kbBitCnt,F	; decrement bit counter
	GOTO	KB_LOOP_78	; bits left
	BSF	_LastColumn	; set bit so we break out after next run
	GOTO	KB_LOOP_78

KB_78_DONE:
	; and update our 'last known' status for the columns
	MOVF	kbColumn78_New,W ; get new status
	MOVWF	kbColumn78_Old	; and store it..
	RETURN	

	
; ***********************************************************************
; 
;  LOOKUP_KEY -  lookup table for key scancodes.
;		 Returns a scancode in w 
;		 Sets carry if key is extended
;		 NOTE: If key R3 C1 has been pressed longer than keyrepeat delay 
;		 AND keyrepeat is disabled ( jumper on pin RB3 ) THEN the 
; 		 bit _AltKeymap is set and we can return an alternative scancode in W
;		

LOOKUP_KEY	; lookup table for the keys ( 32 keys x 4 lines + 1 = 129 lines )
		; keys are labelled Rx - Cy where x = row number and y = column number
		; handles a 4 row x 8 column keyboard = 32 keys
		
	MOVWF    PCL	  ; add to program counter  		
; R1 - C1 i.e. key 1
	NOP
	NOP
	NOP	
	RETLW	H'05'	; scan code 'F1'
; R2 - C1 i.e. key 2
	NOP
	NOP
	NOP	
	RETLW	H'0C'	; scan code 'F4'
; R3 - C1 i.e. key 3
	; The famous alternative keymap toggle key !!!  ;-)
	; It is adviced that this key does not use an alt scancode
	; makes things cleaner and simplified.
	; IF USED though, remember that a 'soft' release code must be sent
	; for this key when entering the altkeymap !( + 'soft' make code for the alternative key )
	; This as the key is pressed when entering altkeymap
	; which makes the bit _Altkeymap be set, and hence when released
	; the release code for this 'normal' key will never be sent
	; instead the release code for the alternative key will be sent.
	; To 'fix' this put the release code at the end of CHECK_KEY_STATE ( where the
	; alt keymap bit is set ).i.e. break prefix+scancode for normal key.
	NOP
	NOP
	NOP
	RETLW	H'83'		; scan code for F7 ( in 'normal' mode )
; R4 - C1 i.e. key 4
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'76'		; send scancode for 'ESC'  instead
	BSF	STATUS,C 	; set carry ( i.e. extended code )
	RETLW	H'6B'		; scan code 'arrow left' '<-'
; R1 - C2 i.e. key 5
	NOP
	NOP
	NOP	
	RETLW	H'06'	;  scan code 'F2' hex06
; R2 - C2 i.e. key 6
	NOP
	NOP
	NOP	
	RETLW	H'03'	; scan code 'F5'
; R3 - C2 i.e. key 7
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'0D'		; send scancode for 'horizontaltab' HT instead
	BSF	STATUS,C 	; set carry ( i.e. extended code )
	RETLW	H'75'		; scan code 'arrow up' '^'
; R4 - C2 i.e. key 8
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'14'		; send scancode for 'left ctrl' instead
	BSF	STATUS,C 	; set carry ( i.e. extended code )
	RETLW	H'72'		; scan code 'arrow down' 
; R1 - C3 i.e. key 9
	NOP	
	NOP	
	NOP	
	RETLW	H'04'	; scan code 'F3'
; R2 - C3 i.e. key 10
	NOP	
	NOP	
	NOP	
	RETLW	H'0B'	; scan code 'F6'
; R3 - C3 i.e. key 11
	NOP	
	NOP	
	NOP	
	RETLW	H'0A'	; scan code 'F8'
; R4 - C3 i.e. key 12
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'11'		; send scancode for 'left alt' instead
	BSF	STATUS,C ; set carry ( i.e. extended code )
	RETLW	H'74'	; scan code 'arrow right' '->'
; R1 - C4 i.e. key 13
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'6C'		; send scancode for numeric '7' instead
	NOP	
	RETLW	H'3D'	; scan code '7'
; R2 - C4 i.e. key 14
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'6B'		; send scancode for numeric '4' instead
	NOP	
	RETLW	H'25'	; scan code '4'
; R3 - C4 i.e. key 15
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'69'		; send scancode for numeric '1' instead
	NOP	
	RETLW	H'16'	; scan code '1'
; R4 - C4 i.e. key 16
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'7B'		; send scancode for numeric '-' instead
	NOP	
	RETLW	H'4A'	; scan code '-' minus ( swe kbd )
; R1 - C5 i.e. key 17
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'75'		; send scancode for numeric '8' instead
	NOP	
	RETLW	H'3E'	; scan code '8'
; R2 - C5 i.e. key 18
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'73'		; send scancode for numeric '5' instead
	NOP	
	RETLW	H'2E'	; scan code '5'
; R3 - C5 i.e. key 19
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'72'		; send scancode for numeric '2' instead
	NOP	
	RETLW	H'1E'	; scan code '2'
; R4 - C5 i.e. key 20
	BTFSS	_AltKeymap	; check for alternative keymap
	RETLW	H'45'		; scan code '0' ( from keypad ) normal key 
	BSF	STATUS,C 	; set carry ( i.e. extended code ) 
	RETLW	H'1F'		; alt keycode ( windows start menu activate )
; R1 - C6 i.e. key 21
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'7D'		; send scancode for numeric '9' instead
	NOP	
	RETLW	H'46'	; scan code '9'
; R2 - C6 i.e. key 22
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'74'		; send scancode for numeric '6' instead
	NOP	
	RETLW	H'36'	; scan code '6'
; R3 - C6 i.e. key 23
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'7A'		; send scancode for numeric '3' instead
	NOP	
	RETLW	H'26'	; scan code '3'
; R4 - C6 i.e. key 24
	BTFSS	_AltKeymap	; check for alternative keymap
	RETLW	H'49'		; scan code '.' ( swe kbd ) normal key
				; use alternative keymap
	BSF	STATUS,C	; set carry ( i.e. extended code )
	RETLW	H'4A'	; send scancode for numeric '/' instead
; R1 - C7 i.e. key 25
	BTFSC	_AltKeymap	; check for alternative keymap
	RETLW	H'79'		; send scancode for numeric '+' instead
	NOP	
	RETLW	H'4E'	; scan code '+'
; R2 - C7 i.e. key 26
	BTFSS	_AltKeymap	; check for alternative keymap
	RETLW	H'66'		; scan code 'back space' BS, normal key
				; use alternative keymap
	BSF	STATUS,C	; set carry ( i.e. extended code )
	RETLW	H'71'	; send scancode for 'delete' instead
; R3 - C7 i.e. key 27
	BTFSS	_AltKeymap	; check for alternative keymap
	RETLW	H'5A'		; scan code 'enter', normal key
				; use alternative keymap
	BSF	STATUS,C	; set carry ( i.e. extended code )
	RETLW	H'5A'	; send scancode for numeric enter instead ( note ! extended )
; R4 - C7 i.e. key 28
	BTFSS	_AltKeymap	; check for alternative keymap
	RETLW	H'5A'		; scan code 'enter', normal key
				; use alternative keymap
	BSF	STATUS,C	; set carry ( i.e. extended code )
	RETLW	H'5A'	; send scancode for numeric enter instead ( note ! extended )
; R1 - C8 i.e. key 29
	NOP	
	NOP	
	NOP	
	RETLW	H'2C'	; scan code 't'
; R2 - C8 i.e. key 30
	NOP	
	NOP	
	NOP	
	RETLW	H'24'	; scan code 'e'
; R3 - C8 i.e. key 31
	NOP	
	NOP	
	NOP	
	RETLW	H'1B'	; scan code 's'
; R4 - C8 i.e. key 32
	NOP	
	NOP	
	NOP	
	RETLW	H'2C'	; scan code 't'

    END

Será que algum programador pode me ajudar? minha duvida é a seguinte ...eu encontrei um projeto na internet ae eu copiei o codigo mas quando eu coloco o codigo no mplab eu não consigo fazer o make ele da erro!! build falhou!! será que alguem pode me ajudar?? será que esse codigo ta errado!! se algum programador poder me ajudar eu agradeço!!!

 

;
;               PC-Keyboard emulator using a PIC16F84 
;     Scan a 32 key keyboard ( with alternative mapping = 64 keys )
;            

;                 COPYRIGHT (c)1999 BY Tony K&uuml;bek
; This is kindly donated to the PIC community. It may be used freely, 
;     and you are forbidden to deprive others from those rights.   
;    Please read the FSF (Free Software Foundation) GNU statement.
;
;*********************************************************************
;
;            E-MAIL    tony.kubek@flintab.se
;        
;        
; DATE            2000-01-23
; ITERATION        1.0B
; FILE SAVED AS        PiCBoard.ASM    
; FOR            PIC16F84-10/P        
; CLOCK            10.00 MHz RESONATOR ( OSC-HS )
; INSTRUCTION CLOCK    2.50 MHz T= 0.4 us
; SETTINGS        WDT-ON, PWT-ON, CP-OFF
; REVISION HISTORY    
;            0.1b -    First beta, mainly for testing
;            1.0b -     First public beta release
;
;
;
;
;***************************************************************************
;
;                PREFACE 😉
;
;    This is NOT an tutorial on pc keyboards in general, there are quite
;    a few sites/etc that have that already covered. However i DID find
;    some minor ambiguities regarding the actual protocol used but nothing
;    that warrants me to rewrite an complete pc keyboard FAQ. 
;    So for 'general' pc keyboard info ( scancodes/protocol/pin config/etc )
;    here are some useful links:
;
;   http://www.senet.com.au/~cpeacock/     
;   http://ourworld.compuserve.com/homepages/steve_lawther/keybinfo.htm
;   http://204.210.50.240/techref/default.asp?url=io/keyboard.htm
;   http://www.arne.si/~mauricio/PIC.HTM
;    
;    PLEASE do not complain about code implementation, I know there are parts
;    in which is it 'a bit' messy and hard to follow, and other parts where
;    the code is not optimised. Take it as is. Futhermore I did hesitate
;    to include all of the functionality thats currently in, but decided to
;    keep most of it, this as the major complaint i had with the other available
;    pc-keyboard code was just that - 'is was incomplete'. Also do not 
;    be discoraged by the size and complexity of it if you are an beginner,
;    as a matter of fact this is only my SECOND program ever using a pic.
;    I think I managed to give credit were credit was due ( 'borrowed code' ).
;    But the originators of these snippets has nothing to do with this project
;    and are probably totally unaware of this, so please do not contact them
;    if you have problems with 'my' implementation. 
;
;    BTW ( shameless plug ) UltraEdit rules ! ( if you dont have it, get it ! )
;   http://www.ultraedit.com ( dont 'forget' the 'wordfile' for PIC, color indenting )
;    Without that I guess this file would be 'messy'.
;
;    Ok with that out of the way here we go:
;    
;
;***************************************************************************

;                DESCRIPTION ( short version 😉 )

;    A set of routines which forms a PC keyboard emulator.
;    Routines included are:
;    Interrupt controlled clock ( used for kb comm. and 'heart beat )
;    A 4x8 matrix keyboard scanner ( using an 74HCT4051 3 to 8 analogue multiplexer )
;    Communincation with PC keyboard controller, both send end recive.

;     PC keyboard routines are using 4 pins (2 inputs & 2 outputs) to control
;     keyboard's 2 bidirectional OC lines (CLK & DATA). The following
;     'drawing' conceptually shows how to connect the related pins/lines
;
;    ( ASCII art/info shamelessly 'borrowed' from http://www.arne.si/~mauricio/PIC.HTM )
;
;                 vcc    vcc
;                  |      |
;                  \     -+-
;                  / 2K2    /_\  1N4148
;                  \      |
; pcCLOCK_in -----------------o---o------o---------   kbd CLOCK
;                  |   |     |
;             2N2222   |50pF -+-
; pcCLOCK_out ____/\/\/\____|/     ===    /_\
;            2K2     |\>   |     |
;                  |   |     |
;                 /// ///    ///
;
;     An identical circuit is used for the DATA line. 
;    Note: The 2N2222 transitors can be replaced with BC337 ( NPN ) which are cheaper
;    The keyboard matrix routines are using RB4-RB7 as inputs.
;    and RB0-RB2 as output to/from an 3 to 8 multiplexer 
;    so that it can read up to 4x8 keys ( 32 ).
;
;    RA4/TOCK1 is an input from an jumper, if low then keyrepeat is disabled and 
;    alt-key is enabled instead i.e. instead of repeating a key that has
;    been depressed a certain amount of time, a bit is set that can change the
;    scancode for a key ( of course, all keys can have an alternate scancode ).
;    To exit the alt. keymap press the 'enter alt. keymap' key once again or wait until
;    timeout. ( defined in the code )
;    NOTE !! The so called 'enter alt. keymap' key is hardcoded, i.e the key
;    i've choosen in this 'example' is Column 1 Row 2, if this is to be changed
;    code has to be changed/moved in the debounce and checkkeystate routines.
;    ( marked with <-------Alt keymap code-------> )
;     RB3 is currently used for a flashing diode. ( running )
;    Note my real keyboard ( KeyTronic ) uses a clock rate of 40 us, my implementation
;    uses about 50 us, easily to change.
;

;***************************************************************************

;            DESCRIPTION ( longer version 😉 )
;
;    Pin     Used for
;    ------------------------------------------------------------
;    RA0    Pc keyboard data out ( to pc )
;    RA1    Pc keyboard data in ( from pc )
;    RA2    Pc keyboard clock in 
;    RA3    Pc keyboard clock out
;    RA4    Flashing disco led 0.5s duty cycle( could be used for some more meaningful purpose )
;
;    RB0    Least significant bit in column adress to 4051 multiplexer ( own keyboard )
;    RB1    Middle...
;    RB2    Most significant bit -- || --
;    RB3    Input. Disable keyrepeat & enable alternative keymapping ( if = '0',ground)
;        OR if '1' ( free,+V ) enable keyrepeat & disable alternative keymapping
;    RB4    Own keyboard input row 1
;    RB5    --- || --- row 2
;    RB6    --- || --- row 3
;    RB7    --- || --- row 4
;
;    'Basic program structure':
;
;    Init    -    Initialise ports , ram, int, vars
;    Start delay -     After init the timer int is enabled and the flashing led will
;            start to toggle ( flash ). Before I enter the mainloop 
;            ( and send any keycodes ) I wait until the led has flashed
;            twice.    This is of course not really needed but I normally
;            like to have some kind of start delay ( I know 1 sec is a bit much 🙂 )
;    Time Int    -    The timer interrupt (BIG), runs in the background:
;            - 'Normal' rate 0.5 ms, when in rx/tx rate is 27 us
;             - Performs an rx/tx check, then jumps to rx, tx or 'heart beat' code.
;             - TX code sends a byte to pc, at a rate of 27us per int.
;               The int rate is actually double the bit rate, as
;               a bit is shifted out in the middle of the clock pulse,
;               I've seen different implementations of this and I think
;               that the bit is not sampled until clock goes low again BUT
;               when logging my keyboard ( Keytronic ) this is the way that it
;                  does it. When all bits are sent, stopbit/parity is sent.
;               And the key is removed from the buffer.
;               After stopbit/parity is sent, a delay is inserted ( 0.5 ms )
;               before next rx/tx check.
;             - RX code recevies a byte from the pc, PIC is controlling clock !!
;               Int rate ( 27 us ) is, again, double bit rate.
;               Toggles clock and samples the data pin to read a byte from pc.
;               When reception is finished an 'handshake' takes place.
;               When a byte has been recevied a routine is called to check
;               which command and/or data was received. If it was
;               keyboard rate/delay or led status byte, it is stored in local ram
;               variables. NOTE: The rate/delay is not actually used for
;               key repeat as the code is right now ( I use my 'own' fixed rate/delay )
;               however it is very easy to implement.
;               After handshake a delay is inserted ( 0.5 ms )
;               before next rx/tx check.
;            - 'Heart beat' ( idle code 0.5 ms tick ) performs:
;             - Check clock/data lines to see if pc wants to send something or
;               if tx is allowed.
;             - If tx is possible it checks the keybuffer for an available key and
;               if keys are in buffer then it initiates a tx seq. 
;               and sets the int rate to 27 us.
;             - If the pc wants to send something, an rx seq. is initiated
;               ( there is some handshaking involved, during which
;               the int rate is set to 60 us ) after that, the int rate is 
;               set to 27 us and an rx seq is started.
;             - Divides some clock counters to achive 10ms,100ms,500ms sections.
;             - In 100 ms section it performes a numlock status check and
;               keyrepeat check ( both rate and delay is local in 100 ms ticks,
;               thats why I dont use the 'real' rate delay )
;              - If numlock status is not the desired one code is called to
;                toggle the numlock status.
;              - If a key has been pressed long enough for repeat, an bit is set
;                so we can repeat the key ( send the scancode again ) in the main loop.
;            - In 500 ms section the led is toggled on each loop
;              - Some various alternative keymap checks to get out of
;                alternative keymap. ( i'll get to that in a bit )
;
;    Main loop    - Outputs an adress to the 4051 multiplexer waits 1 ms
;              reads the row inputs ( 4 bits/keys ), increments address
;              and outputs the new adress, waits 1 ms and reads the input 
;               ( next 4 bits/keys ). Now using the address counter, calls a
;              debounce/send routine that first debounces the input,
;              ( four consecutive readings before current state is affected )
;              and when a key is changed a make/break code is sent ( put in buffer ).
;              In the next loop the next two columns are read etc. until all 
;              4 column pairs are read.
;            - If keyrepeat is enabled ( see pin conf. above ) the 
;              repeat flag is checked and if '1' the last pressed key scancode
;              is sent again ( put in buffer ).
;            - If keyrepeat is not enabled( alternative keymap is enabled instead )
;              then various checks to exit the alternative keymap are performed instead.
;
;    Scancodes for all key are located in a lookup table at the end of this file,
;    each key has four program rows, to make room for extended codes and alt. keymap codes.
;
;     Explanation of 'alternative' keymap:
;    
;    Using this program ( or an heavily modified version of it anyway 🙂 )
;     on a computer running Windows posed some small problems; namely:
;    -The keyboard ( mapping ) I used did not have any 'special' key such as 
;     <alt>,<ctrl>,<tab> etc.
;    -In windows, things can go wrong, 🙂 if a dialog pops up or something similar
;     there were just no way one could dispose of this with the keymapping i used.
;    - 'Only' 28 keys were implemented ( hardware wise ).
;    In this particular case the keyrepeat was disabled ( due to the nature of the application )
;    Therefore i came up with the solution to use the keyrepeat related routines and vars.
;    To handle a so called 'alternative' keymapping.
;    This means that an key is dedicated to be the alt. keymap toggle key,
;    when pressing this longer than the programmed repeat delay, instead of 
;    repeating the key a bit variable is set to use another set of scancodes for
;    the keyboard. This 'alternative' keymap is then enabled even though the 
;    alt. keymap toggle key is released, but it also incorporates an timeout 
;    that will return to normal keymap if no key is pressed within a certain time ( currently 7 sec )
;    Of course pressing the alt. keymap toggle key again ( while in alt. keymap )
;    will return the keyboard to the normal keymap.
;    NOTE !! the key choosen for the alt. keymap toggle is hardcoded, so if the scancode
;    for this is changed in the lookup table, changes have to be made in other routines
;    as well. ( namly : CHECK_KEY_STATE, and DEBOUNCE_COLUMN routines )
;    While in alt. keymap each key CAN have an alternative scancode ( see lookup table ).
;    Also by using the numlock bit, one can toggle numlock status 'on the fly' when entering
;    or exiting the alt keymap. 
;
;    Some notes about the local keyboard interface ( matrix 😞
;    Although the hardware circuit and software allows virtually unlimited
;    simultaneosly pressed keys, the keyboard matrix itself normally poses
;    some limitations on this. If an keymatrix without any protective diodes
;    are used then one would have loops INSIDE the keymatrix itself when
;    multiple keys are pressed in different columns .
;    Look at the ( although horrible 😉 ) ASCII art below(internal weak pullup enabled):
;    0 - Key is free
;    1 - Key is pressed ( connection between hor/ver rows )
;    Three keys pressed adressing ( reading ) left column :
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    --------1-------1------ ( row 2 )
;                    |       |
;    To pic3    --------1-------0------ ( row 3 )
;                   |       |
;        Column(4051)    0V    -    ( Current column is set to 0V when adressing )
;
;    This works as intended, we can read a '0' on pic inputs 2,3 which
;    is what we expected. The current ( signal ) follows the route marked with '*':
;    ( only the 'signal path' is shown for clarity )
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    *********-------1------ ( row 2 )
;                    *       |
;    To pic3    *********-------0------ ( row 3 )
;                   *       |
;        Column(4051)    0V    -    ( Current column is set to 0V when adressing )
;
;    However, when we now read ( address ) the right column instead we 
;    do not read what is expected ( same three keys still pressed 😞
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    *****************------ ( row 2 )
;                    *<-     *
;    To pic3    *********-------*------ ( row 3 )
;                   |       *
;        Column(4051)    -    0V    ( Current read column is set to 0V when adressing )
;
;    As you can see the 'signal' travels in the keymatrix itself ( where the '<-' is )and
;    will cause a 'ghost' signal to be read on the pic. So instead
;    of having an '0' on input 2 only we also can read an '0' on input 3.
;    This is because the two keys in column 1 are interconnected ( when they are pressed ).
;    Keep this in mind if you are planning to support multiple pressed keys.
;    
;
;***************************************************************************
;
;    Some suggestions for 'improvements' or alternations
;
;    - Using the jumper 'disable-repeat' as a dedicated key for switching
;      to alternative keymapping. 
;    - Enable repeat in alternative keymapping
;    - Clean up TX/RX code ( a bit messy )
;    - Using the led output ( or jumper input ) as an extra adress line 
;      to the multiplexers ( in this case 2 pcs. 74HCT4051 ) we could have
;      4x16 keys instead. Would require some heavy modifications though
;      as there are not much ram/program space left. But if alternative
;        keymapping is discarded ( most likely if one has 64 keys ) each
;      key in the lookup table only needs to be 2 lines instead of 4.
;      That would 'only' require some modifications to preserv ram.
;    - Using the EERAM for 'macros' or similar ( not used at all now )
;
;***************************************************************************
;
;            LEDGEND
;
;    I tend to use the following when naming vars. etc. : 
;    ( yes i DO like long names )
;    
;    For 'general' purpose pins:
;
;    An input pin is named I_xxx_Name where :
;
;        I_   - This is an input pin 😉
;        xxx_ - Optional what type of input, jmp=jumper etc.
;        Name - Self explanatory
;
;    An output pin is named O_xxx_Name where:
;    
;        O_   - This is an output pin 😉
;        xxx_ - Optional what type of output, led=LED etc.
;        Name - Self explanatory
;
;    Application(function) specific pins: 
;
;    An application(function) specific pin is named xxName where:
;        
;        xx   - What/Where, for example pc=To/From pc
;        Name - Self explanatory ( what does it control etc )
;
;    An #define/constant/equ (no pin or ram ) uses all capital letters e.x. #define BREAK 0xF0
;
;    A bit variable will always start with '_'. For example '_IsLedStatus'
;
;    All other ( mostly ramvars. ) are named in various ways.
;
;
;***************************************************************************

    TITLE "PC Keyboard emulator - By Tony K&uuml;bek"

        Processor       16F84
        Radix   DEC
        EXPAND

    

;***** HARDWARE DEFINITIONS ( processor type include file )

    INCLUDE <C:\PROGRAM\MPLAB\P16F84.INC>           ; this might need changing !

;***** CONFIGURATION BITS 

    __CONFIG _WDT_ON&_HS_OSC&_CP_OFF&_PWRTE_ON        ; _WDT_OFF
    
    __IDLOCS 010Bh    ; version 1.0B 

;***** CONSTANT DEFINITIONS 

    CONSTANT    BREAK = 0xF0        ; the break key postfix ( when key is released )
    CONSTANT    EXTENDED = 0xE0     ; the extended key postfix

    ; As i dont really use the rate/delay I receive from the pc ( easy to change )
    ; this is the current rate/delay times i use:
    CONSTANT    DELAY_ENTER_ALTKEYMAP = 0x1E    ; x100 ms , approx 3 seconds ( 30 x 100 ms )
                            ; how long the 'enter altkeymap' key must
                            ; be in pressed state before the altkeymap is enabled
    CONSTANT    DELAY_EXIT_ALTKEYMAP = 0x0F    ; x0.5 sec , approx 7.5 sec
                            ; how long before we exit the alt keymap if no key is 
                            ; pressed.
    CONSTANT     DELAY_REPEAT    = 0x08        ; x100 ms, approx 800 ms 
                            ; how long before we START repeating a key
    CONSTANT    DELAY_RATE    = 0x02        ; x100 ms, approx 200 ms repeat rate
                            ; how fast we are repeating a key ( after the delay above )
    
;***** CONSTANT DEFINITIONS ( pins )

;    For connection with PC keyboard

#define pcCLOCK_in  PORTA,2   ; Input PC keyboard clock
#define pcCLOCK_out PORTA,3   ; Output PC keyboard clock
#define pcDATA_in   PORTA,1   ; Input PC keyboard data
#define pcDATA_out  PORTA,0   ; Output PC Keyboard data


;    For connection (input) with our own keyboard 
; Note I actually dont use these (definitions!) in the program, but they might come in handy
; at one time or another 😉 ( I use the pins though.. )

#define kbROW_1   PORTB,7
#define kbROW_2   PORTB,6
#define kbROW_3   PORTB,5
#define kbROW_4   PORTB,4

;    Indications ( output )

#define O_led_KEYCOMM_ok    PORTA,4        ; communication seems ok led ( flashing )

;    Disable/enable key repeat input jumper

#define I_jmp_NoRepeat    PORTB,3    ; note: internal weak pullup enabled

;    For keybuffer ( using the indirect file selector FSR )

#define KeyBufferHead    FSR


;***** RAM ASSIGNMENT

        
        CBLOCK    0x0C
            KeyBufferTail    ; where the last byte in buffer is..
            clkCount         ; used for clock timing 
            Offset        ; used for table reads        
                        Saved_Pclath       ; Saved registers during interrrupt
            Saved_Status       ; -----
            Saved_w            ; -----
            CurrKey        ; current key ( rx or tx )..
            KeyParity    ; key parity storage ( inc. for every '1' )
            Divisor_10ms    ; for the timer
            Divisor_100ms   ; ditto
            Divisor_500ms   ;
            Divisor_Repeat  ; timer for repeated key sends

            Flags        ; various flags
            RepeatFlags     ; flags for repeating a key
            bitCount    ; bitcounter for tx/rx
            Comm_Flags    ; flags used by both rx and tx routines
            Temp_Var    ; temp storage, can be used outside int loop
            TRX_Flags    ; flags used by both rx and tx routines
            CommandData     ; bit map when receving data bytes from pc
                    ; for example led status/ delay / etc 
            KbLedStatus     ; to store status led bitmap for keyboard ( pc first sends 'ED' then this byte )
                        ;  bit 0=Scroll lock  ( 1=on )
                        ;  bit 1=Num lock
                        ;  bit 2=Caps lock
                        ;  bits 3-7 = unused 
            KbRateDelay    ; to store repeat delay/rate ( pc first sends 'F3' then this byte )
                        ;  bit 0-4 (rate) = '00000' is 30x/sec '11111' is 2x/sec ( i.e. value * 33 ms )
                        ;  bit 5-7 (delay)= '00' is 250 ms, '11' is 1000 ms ( ie. value * 250 ms )   
                    ;  bit 7 = unused 
            BufTemp        ; temp byte for storing scancode to put in buffer
            Temp        ; temp byte, used locally in buffer routine
            Temp2        ;
            LastKey        ; stores the last sent key
            KbBufferMin    ; where our keybuffer starts
            Kb1        ; used in keybuffer
            Kb2        ; used in keybuffer
            Kb3        ; used in keybuffer
            Kb4        ; used in keybuffer
            Kb5        ; used in keybuffer
            Kb6        ; used in keybuffer
            Kb7        ; used in keybuffer
            Kb8        ; used in keybuffer
            Kb9        ; used in keybuffer
            Kb10        ; used in keybuffer
            KbBufferMax    ; end of keybuffer
            TempOffset    ; temporary storage for key offset ( make/break )

            LastMakeOffset  ; storage of last pressed key ( offset in table )
            RepeatTimer    ; timer to determine how long a key has been pressed 
            RepeatKey     ; the key to repeat
            repTemp        ; temporary storage in repeat key calc.
            repKeyMap    ; bit pattern for the column in which the repeat key is in
                    ; i.e. a copy of kbColumnXX_Old where 'XX' is the column 
            LastKeyTime    ; counter when last key was pressed, used to get out of altkeymap
                    ; after a specific 'timeout'

            kbScan        ; scan code for pressed/released key
            kbTemp        ; temp storage for key states

            kbState        ; which keys that has changed in current columns
            kbBitCnt    ; bit counter for key check ( which key/bit )            
            
            kbColumnCnt    ; column counter ( loops from 8 to 0 )
                    ; Used as output to multiplexer/decoder and in debounce routines

            kbColumnVal    ; current value of the last 2 columns ( 2x4bits = 8 bits ) input pins ( keys ) 
                    ;
                    ; Note the kbColumnXX_New variables is not really needed
                    ; used it while making the program ( debugging 😉 ).
                    ; so if more free ram is needed change code using these to use
                    ; the current 'input' sample instead. ( kbColumnVal )
            kbColumn12_New  ; New debounced reading for column 1 & 2
            kbColumn12_Old  ; Latest known valid status of column 1 & 2
            kbColumn12Cnt    ; Debounce counter for column 1 & 2
            kbColumn12State ; State of debounce for column 1 & 2

            kbColumn34_New  ; New debounced reading for column 3 & 4
            kbColumn34_Old  ; Latest known valid status of column 3 & 4
            kbColumn34Cnt    ; Debounce counter for column 3 & 4
            kbColumn34State ; State of debounce for column 3 & 4

            kbColumn56_New  ; New debounced reading for column 5 & 6
            kbColumn56_Old  ; Latest known valid status of column 5 & 6
            kbColumn56Cnt    ; Debounce counter for column 5 & 6
            kbColumn56State ; State of debounce for column 5 & 6

            kbColumn78_New  ; New debounced reading for column 7 & 8
            kbColumn78_Old  ; Latest known valid status of column 7 & 8
            kbColumn78Cnt    ; Debounce counter for column 7 & 8
            kbColumn78State ; State of debounce for column 7 & 8

        ENDC

;******* END RAM

; *** flags used by both rx and tx routines
#define _isParity    Comm_Flags,0    ; bit in rx/tx is parity bit
#define _KeyError    Comm_Flags,1    ; set to '1' when an error is detected
#define _isStartBit    Comm_Flags,2    ; set to '1' when bit in rx/tx is startbit
#define _isStopBit    Comm_Flags,3    ; --- || --- but stopbit
#define _RxCanStart     Comm_Flags,4    ; for handshaking, when negotiating an rx seq.


; *** dito used for rx and tx
#define _KeyReceived    TRX_Flags,0    ; rx
#define _RX_Mode    TRX_Flags,1    ; rx is in progress ( started )
#define _doRXAck    TRX_Flags,2    ; do rx handshake
#define _RXAckDone    TRX_Flags,3    ; rx handshake is done
#define _RXEnd        TRX_Flags,4    ; rx seq is finished
#define _RXDone        TRX_Flags,5    ; rx exit bit
#define _KeySent    TRX_Flags,6    ; tx key has been succesfully sent
#define _TX_Mode    TRX_Flags,7    ; tx is in progress ( started )


; *** flags to determine the meaning of an incomming data is 
; i.e. when then pc has sent a 'data follow' instruction
; these bits are set according to the command, 
; i.e. the next incomming byte is.....
#define _IsLedStatus    CommandData,0    ; the next incoming byte contains kb led status
#define _IsRateDelay    CommandData,1    ; the next incoming byte contains kb rate/delay
#define _SkipByte    CommandData,2    ; other data not saved
 
; *** bit meaning in KbLedStatus ( the leds on the keyboard )
; i.e local ram copy of this byte
#define _LedScrollLock    KbLedStatus,0    ; '1' led is on
#define _LedNumLock     KbLedStatus,1    ; 
#define _LedCapsLock    KbLedStatus,2    ;
#define _IsFirstLedStatus KbLedStatus,7    ; set this to '1' at startup, to know that our local
;                    ; copy (led status) is not yet syncronised. Used to
;                    ; 'force' sync. the first time we set the local numlock bit.

; *** flags used for various purposes
#define _Timer        Flags,0 ; used for waiting
;#define _WrongPar    Flags,1    ; ?? ( used during dev. to force wrong parity sends, removed )
#define _LastColumn    Flags,2    ; for kb scan code
#define _isBreak    Flags,3 ; if '1' the currently handled key is break ( released ), else make ( pressed )
;#define _isFree         Flags,4 ; Not used ! ( temporary during dev. )
#define _AltKeymap      Flags,5 ; is we use alternative keymap ( other scancodes ) see below !
                ; enabled ONLY when keyrepeat is disabled ( I_jmp_NoRepeat (RB3) is low )
#define _ExitAltKeymap  Flags,6 ; start checking if we should exit alternative keymap
                ; if all keys are 'free' ( not pressed ) we exit altkeymap
#define _InAltKeymap    Flags,7 ; if we are waiting for second press/release to get out of altkeymap


#define _doRepeat    RepeatFlags,0 ; if the last pressed key should be 'repeated'
#define _doSendKey    RepeatFlags,1 ; send the key in RepeatKey to pc     
#define _isExtended     RepeatFlags,2 ; temp flag if last key is extended
#define _RepeatIsExt    RepeatFlags,3 ; the key to repeat is extended key
#define _startRepeat    RepeatFlags,4 ; start repeat timer
#define _DoExitAltKeymap RepeatFlags,5     ; bit set when we are getting out of alternative keymap
#define _NumLock    RepeatFlags,6      ; 'mirror' of numlockstatus, by setting/clearing this bit
                           ; numlock status will be changed. 
                    ; I.e. there is no need to 'manually' send break/make code for numlock
                    ; key, by setting this bit to '1' numlock status will by automaticlly
                    ; ( inside int handler ) set to 'on'( within 100 ms ). Se code for example.
#define _WaitNumLock    RepeatFlags,7    ; bit set when we have sent make/break numlock scancode
                    ; and waiting for numlock status byte reply.
                    ; ( to inhibit a new numlock scancode send )


;**************************************************************************    
;                Macros             
;**************************************************************************    
    

;+++++
;    BANK0/1 selects register bank 0/1.
;    Leave set to BANK0 normally.

BANK0    MACRO
    BCF    STATUS,RP0
    ENDM

BANK1    MACRO
    BSF    STATUS,RP0
    ENDM

;+++++
;    PUSH/PULL save and restore W, PCLATH and STATUS registers -
;    used on interrupt entry/exit 

PUSH    MACRO
    MOVWF    Saved_w        ; Save W register on current bank
    SWAPF    STATUS,W    ; Swap status to be saved into W
    BANK0            ; Select BANK0
    MOVWF    Saved_Status    ; Save STATUS register on bank 0
    MOVFW    PCLATH
    MOVWF    Saved_Pclath    ; Save PCLATH on bank 0
    ENDM

PULL    MACRO
    BANK0            ; Select BANK0
    MOVFW    Saved_Pclath
    MOVWF    PCLATH        ; Restore PCLATH
    SWAPF    Saved_Status,W
    MOVWF    STATUS        ; Restore STATUS register - restores bank
    SWAPF    Saved_w,F
    SWAPF    Saved_w,W    ; Restore W register
    ENDM


;+++++        
;     We define a macro that will switch an output pin on or off depending
;     on its previous state. We must be on bank0 !!
;

TOGGLE_PIN    MACRO WHICH_PORT,WHICH_PIN
        LOCAL TOGGLE_PIN10, TOGGLE_END
        
        BTFSC    WHICH_PORT,WHICH_PIN    ; is the pin high ?
        GOTO     TOGGLE_PIN10        ; yes, clear it
        BSF    WHICH_PORT,WHICH_PIN    ; no, so set it                                        
            GOTO       TOGGLE_END
TOGGLE_PIN10:
        BCF    WHICH_PORT,WHICH_PIN    ; clear the pin            
TOGGLE_END:
        ENDM


;************************************************************* 
; Credit for routine ( almost unchanged, expect it's now a macro 😉 )
; goes to Scott Dattalo http://www.interstice.com/~sdattalo/technical/software/software.html
; ( debouce routine at http://www.interstice.com/~sdattalo/technical/software/pic/debounce.html )
;
;     DEBOUNCE_BYTE - 'debounces' 8 bits, when a bit state 
;    has been 'active' for 4 consecutive debounce loops
;    it's state is put into the 'debounced' sample ( i.e. output )
;
; The purpose of this routine is to debounce, i.e. digitally low pass filter
; inputs. The algorithm handles upto 8 bits at a time. An input is considered
; filtered if it has not changed states in the last 4 samples.
;
; 2-bit cyclic vertical counters count the 4 samples. As long as there is no
; change, the counters are held in the reset state of 00b. When a change is detected
; between the current sample and the filtered or debounced sample, the counters
; are incremented. The counting sequence is 00,01,10,11,00... When the counters
; roll over from 11b to 00b, the debounced state is updated. If the input changes
; back to the filtered state while the counters are counting, then the counters
; are re-initialized to the reset state and the filtered state is unaffected.
; In other words, a glitch or transient input has been filtered.
;
; Here's the C-psuedo code:
;
;static unsigned clock_A,clock_B,debounced_state
;
;debounce(unsigned new_sample)
;{
;  unsigned delta;
;
;  delta = new_sample ^ debounced_state;   //Find all of the changes;
;  clock_A ^= clock_B;                     //Increment the counters
;  clock_B  = ~clock_B;
;
;  clock_A &= delta;                       //Reset the counters if no changes
;  clock_B &= delta;                       //were detected.
;
;      //Preserve the state of those bits that are being filtered and simultaneously
;      //clear the states of those bits that are already filtered.
;  debounced_state &= (clock_A | clock_B);
;      //Re-write the bits that are already filtered.
;  debounced_state |= (~(clock_A | clock_B) & new_sample);
;}
;
; The 2-bit counters are arranged "vertically". In other words 8 counters
; are formed with 2 bytes such that the corresponding bits in the bytes are
; paired (e.g. MSBit of each byte is paired to form one counter).
; The counting sequence is 0,1,2,3,0,1,... And the state tables and Karnaugh
; maps are:
;
;State Table:     Karnaugh Maps:
;pres  next      B
; SS  SS         0   1
; AB  AB       +---+---+    +---+---+
;--------   A 0|   | 1 |    | 1 |   |
; 00  01       +---+---+    +---+---+
; 01  10      1| 1 |   |    | 1 |   |
; 10  11       +---+---+    +---+---+
; 11  00      A+ = A ^ B     B+ = ~B
;
; Here's the PIC code that implements the counter:
;       MOVF    SB,W    ;W = B
;       XORWF   SA,F    ;A+ = A ^ B
;       COMF    SB,F    ;B+ = ~B
;  14 instructions
;  15 cycles
; Inputs:
;   NewSample - The current sample
; Outputs
;   DebouncedSample - The current value (filtered version of NewSample)
;
; VARS used
;   DBCnt,
;   DBState - State variables for the 8 2-bit counters
;
    
DEBOUNCE_BYTE MACRO NewSample,DebouncedSample,DBCnt,DBState

        ;Increment the vertical counter        

    MOVF    DBState,W
        XORWF   DBCnt,F        
    COMF    DBState,F

        ;See if any changes occurred        

    MOVF    NewSample,W        
    XORWF   DebouncedSample,W

        ;Reset the counter if no change has occurred        

    ANDWF   DBState,F
        ANDWF   DBCnt,F        ;Determine the counter's state
        MOVF    DBState,W        
    IORWF   DBCnt,W

        ;Clear all bits that are filtered-or more accurately, save
        ;the state of those that are being filtered        

    ANDWF   DebouncedSample,F
        XORLW   0xff        ;Re-write the bits that haven't changed.
        ANDWF   NewSample,W        
    IORWF   DebouncedSample,F        
    
    ENDM

    

;**************************************************************************    
;                 Program Start
;**************************************************************************    


;    Reset Vector

    ORG    H'00'

    ; For the sole purpose of squeezing every last byte of the programming mem
    ; I actually use the 3 program positions before the interrupt vector
    ; before jumping to the main program. Take note though that 
    ; ONLY 3 instructions are allowed before the jump to main loop !!

    BANK0

        CLRF    PCLATH
    CLRF    INTCON    

    GOTO    INIT

;**************************************************************************    
;                     Interrupt routine
; An humongously big int handler here 😉
; but the keyboard handling is FULLY in the background, without any intervention
; in the main loop whatsoever. I like the solution anyway.
;
;**************************************************************************


;    Interrupt vector 

    ORG    H'04'

INT
    PUSH            ; Save registers and set to BANK 0
                
    
    BTFSS   INTCON,T0IF        ; check if TMR0 interrupt
        GOTO    INTX            ; whoops ! 'unknown' int, should be disabled...
                ; NOTE ! if an 'unknown' int triggers the int routine
                ; the program will loop here for ever 😉 ( as the calling flag is not cleared )

    

    ;+++
    ; Timer (TMR0) timeout either heart beat or tx/rx mode
    ; In 'heart beat mode' we monitor the clock and data lines
    ; at ( roughly )= 0.5 ms interval, we also check the send buffer
    ; if there are any keys to send to pc ( if clock/data levels allows us to )
    ; In tx/rx mode we are controlling the clock/data line = 27 us tick
    ; Note: This software works using both 8Mhz and 10Mhz resonators without modifications
    ; however the 'timing' will then of course be 'off'.

INT_CHECK    
    BCF    INTCON,T0IF     ; Clear the calling flag !
    
     BTFSC    _TX_Mode    ; check if we are in tx mode
    GOTO    INT_TX        ; yep, goto tx mode code..    
    BTFSC    _RX_Mode    ; are we in rx mode ?
    GOTO    INT_RX        ; yep goto rx mode code
    GOTO    INT_HEART_BEAT  ; nope just goto 'heart beat' mode

;*************** TX code start ***********************************

INT_TX
    MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
        MOVWF   TMR0
 
    BTFSS    clkCount,0    ; check if we should toggle clock
    GOTO    INT_DEC_CLOCK    ; bit low,decrement and check if we should toggle data instead
    
    DECFSZ    clkCount,F    ; decrement and check if we are at zero..
    GOTO    INT_CLOCK     ; not zero then toggle clock line
    
    GOTO    INT_EXIT_TX        

INT_CLOCK
    
    BTFSC    pcCLOCK_out    ; check if we are low
    GOTO    INT_CLOCK_HIGH  ; yep set to high
                
    BTFSS    pcCLOCK_in    ; check if pc is pulling the clock line low
                ; i.e. it wants to abort and send instead..
    GOTO    INT_TX_CHECK_ABORT    ; abort this transfer
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    GOTO    INTX

INT_CLOCK_HIGH
    BCF    pcCLOCK_out    ; set high ( release line )
                ;BCF    _ClockHigh    ;
    GOTO    INTX

INT_TX_CHECK_ABORT
    GOTO    INT_EXIT_TX

INT_DEC_CLOCK    
    DECF    clkCount,F    ; decrement clock counter ( so we toggle next time )
INT_DATA
    BTFSS    bitCount,0    ; check bit counter
    GOTO    INT_DATA_IDLE    ; no data toggle

    DECFSZ    bitCount,F    ; decrement bit counter
    GOTO    INT_DATA_NEXT    ; next bit..

INT_NO_BITS
    BSF    bitCount,0    ; just in case ( stupid code, not sure its needed, just
                ; to make it impossible to overdecrement )***
    BTFSC    _isParity    ; are we sending parity ?
    GOTO    INT_DATA_END    ; exit
    
    ; all bits sent 
    ; delete the last key from the buffer
    CALL    INC_KEY_HEAD    ; remove the ( last ) key form the buffer as is was sent ok..

    ; all bits sent check parity
    
    BSF    _isParity    ; set flag data is parity
    
    BTFSS    KeyParity, 0    ; is the last parity bit high? ( odd num of bits )
                ; then parity should be high ( free )
    GOTO     INT_DATA_HIGH_PAR      ; yes
    BSF    pcDATA_out    ; no, parity should be 'low' ( pulled down )
    GOTO     INTX        ;

INT_DATA_HIGH_PAR:    
    BCF    pcDATA_out    ; set parity bit high ( release data line )
    GOTO    INTX        ; and exit..


INT_DATA_END    
    BTFSS    _isStopBit    ; is the stopbit sent ?
    GOTO    INT_DATA_STOPB    ; nope then set stopbit flag

    BCF    pcDATA_out    ; parity bit sent, always release data line ( stop bit )        
    GOTO    INTX

INT_DATA_STOPB
    BSF    _isStopBit    ; set the stopbit flag
    GOTO    INTX        ; and exit

INT_DATA_IDLE
    DECF    bitCount,F    ; decrement bit counter
    GOTO     INTX        ; no toggle of data line

INT_DATA_NEXT
    BTFSS     CurrKey,0    ; is the last bit of the key_buffer high?
    GOTO     INT_DATA_LOW    ; no, pull data low
    BCF      pcDATA_out    ; yes, release data line 
    INCF    KeyParity,F    ; increment parity bit
    GOTO    INT_DATA_ROTATE    ; rotate data 
    
INT_DATA_LOW    ; last bit is low
    BSF    pcDATA_out    ; set the bit

INT_DATA_ROTATE
    RRF    CurrKey,F    ; rotate right by 1 bit    
    GOTO    INTX

INT_EXIT_TX
    ; setup the timer so we accomplish an delay after an tx seq
    
    BCF    _TX_Mode    ; clear tx mode flag
    BCF    pcCLOCK_out    ; release clock line
    BCF    pcDATA_out    ; and data line
;
    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0

    GOTO    INTX


;************** TX code end *****************

;************** RX code start ***************

INT_RX
    MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
        MOVWF   TMR0


    BTFSS    clkCount,0    ; check if we should toggle clock
    GOTO    INT_RX_DEC_CLOCK    ; bit low,decrement and check if we should read data instead
    
    DECFSZ    clkCount,F    ; decrement and check if we are at zero..
    GOTO    INT_RX_CLOCK     ; not zero then toggle clock line
    
    BCF    pcCLOCK_out    ; release the clock line we are done..
    BCF    _RX_Mode    ; clear rx mode bit ( go over to heart beat mode )
    GOTO    INT_EXIT_RX            

INT_RX_CLOCK

    BTFSC    pcCLOCK_out    ; check if we are low
    GOTO    INT_RX_CLOCK_HIGH     ; yep set to high ( release line )
    
    BTFSC    _isStartBit    ; check if this is the first bit ( start )
    GOTO    INT_RX_START    ; clear start bit and continue
    
    BTFSC    _isParity    ; check if this is the parity bit ( or parity has been received )
    GOTO    INT_RX_PAR    ; yep check parity

    GOTO    INT_RX_BIT    ; ok just a 'normal' bit read it
    
    
INT_RX_PAR            ; check parity
    BTFSC    _doRXAck    ; check the handshake flag
    GOTO    INT_RX_HNDSHK    ; start handshake check

    BTFSS    pcDATA_in    ; is the input high ?
    GOTO    INT_RX_PAR_HIGH    ; yep
    BTFSC    KeyParity,0    ; is the parity '0' ( should be )
    GOTO    INT_RX_PAR_ERR    ; nope parity error
    GOTO    INT_RX_ACK    ; parity ok next should be ack ( we take data line low )

INT_RX_PAR_HIGH
    BTFSS    KeyParity,0    ; check that parity bit is '1'
    GOTO    INT_RX_PAR_ERR    ; nope parity error
    GOTO    INT_RX_ACK    ; parity ok, next is ack ( we take data line low )

INT_RX_PAR_ERR
    BSF    _KeyError    ; set error flag

INT_RX_ACK
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    BSF    _doRXAck    ; enable ack check
    GOTO    INTX

INT_RX_HNDSHK
    BTFSS    _RXEnd        ; if we are done dont take data low
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    
    BTFSC    _RXAckDone    ; chek if hand shake ( ack ) is done ?
    BSF    _RXEnd        ; ok we are now done just make one more clock pulse

    GOTO    INTX        ; exit    
        

INT_RX_CLOCK_HIGH

    BCF    pcCLOCK_out    ; set high ( release line )
    BTFSS    _RXAckDone    ; are we done.. ?
    GOTO    INTX
    BTFSS    _RXDone        ; finished ?
    GOTO    INTX

    BCF    _RX_Mode    ; and clear rx flag..
    GOTO    INT_EXIT_RX    ; bye bye baby

INT_RX_DEC_CLOCK    

    DECF    clkCount,F    ; decrement clock counter ( so we toggle next time )
    BTFSS    _doRXAck    ; check if we are waiting for handshake
    GOTO    INTX
    

     BTFSC    pcCLOCK_out     ; check if the clock is low ( pulled down )
    GOTO    INTX        ; nope we are pulling down then exit
                ; we only take over the data line if
                ; the clock is high ( idle )
                ; not sure about this though.. ???

     
    BTFSC    _RXEnd        ; are we done ?
    GOTO    INT_RX_END

    ; handshake check if data line is free ( high )
    BTFSS    pcDATA_in    ; is data line free ?
    GOTO    INTX        ; nope

    BSF    pcDATA_out    ; takeover data line
    BSF    _RXAckDone    ; we are done..at next switchover from low-high we exit
    GOTO    INTX        ; 

INT_RX_END
    BCF    pcDATA_out    ; release data line
    BSF    _RXDone        ; we are now done
    GOTO    INTX

INT_RX_START
    BCF    _isStartBit    ; clear start bit flag
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    GOTO    INTX
INT_RX_BIT
    BCF    CurrKey,7
    BTFSS    pcDATA_in    ; is bit high
    GOTO    INT_RX_NEXT    ; nope , it's a '0'
    BSF    CurrKey,7    ; set highest bit to 1
    INCF    KeyParity,F    ; increase parity bit counter
    
INT_RX_NEXT
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )

    DECFSZ    bitCount,F    ; decrement data bit counter    
    GOTO    INT_RX_NEXT_OK

    BSF    bitCount,0    ; just in case ( so we cannot overdecrement )
    BSF    _isParity    ; next bit is parity
    GOTO    INTX

INT_RX_NEXT_OK
    CLRC            ; clear carry, so it doesnt affect receving byte
    RRF     CurrKey,F    ; rotate to make room for next bit
    GOTO    INTX        ; and exit

INT_EXIT_RX    

    ; handle the recevied key ( if not it is an 'data' byte )

    MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8 ( int handling code )= 200 us
                ; 
        MOVWF   TMR0        ; this delay seems to be needed ( handshake ? )
    
    ; check if this is an data byte ( rate/delay led status etc )
    
    MOVF    CommandData,F    ; reload into itself ( affect zero flag )

    BTFSS    STATUS,Z    ; check zero flag
    GOTO    INT_STORE_DATA    ; byte contains data ( rate/delay etc )
    
    CALL    CHECK_RX_KEY    ; no data, handle recevied command 
    GOTO    INTX

INT_STORE_DATA
    ; store data byte in 'currkey', 
    ; first reply with 'ack'
    
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    BTFSS    _IsLedStatus    ; is it led status byte ?
    GOTO    INT_STORE_RATE  ; nope check next
    
INT_STORE_NUM
    ; byte in 'currkey' is led status byte, store it
    MOVF    CurrKey,W    ; get byte
    MOVWF    KbLedStatus    ; and store it
    BTFSC    _WaitNumLock    ; was this something we were waiting for ?
                ; i.e. we sent the make scancode for numlock.
    CALL    RELEASE_NUMLOCK    ; yep, then send release code for 'soft' numlock

    GOTO    INT_STORE_EXIT    ; store it in local ram copy and exit

INT_STORE_RATE

    BTFSS    _IsRateDelay    ; is it rate/delay byte ?
    GOTO    INT_STORE_EXIT  ; nope then send ack end exit
    ; byte in 'currkey' is rate/delay byte, store it
    MOVF    CurrKey,W    ; get byte
    MOVWF    KbRateDelay    ; and store it
    
INT_STORE_EXIT
    
    CLRF    CommandData    ; clear data byte flags
    GOTO    INTX


;******************* 'heart' beat code ( 0.5 ms tick ) *********
; Note: As I 'mess' with the int timer this 'heart beat' is by no means
; an accurate clock. However it can work as a rough estimate for local time.
;
INT_HEART_BEAT

    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0


INT_CHECK_CLKDTA:  

    ; CLOCK DATA   Action
    ;-----------+--------
    ;   L    L  |  wait ?
    ;   L    H  |  wait, buffer keys
    ;   H    L  |  start an rx sequence
    ;   H    H  |  keyboard can tx
        
    BTFSS    pcDATA_in    ; is the data line high ( free )..
    GOTO    INT_CHECK_RX    ; Nope it's pulled down, check if rx is requested
        
    BTFSC    pcCLOCK_in    ; Is the clk line low  ( pulled down ) ?        
    GOTO    INT_CHECK_BUFF  ; Nope, so check if we have any keys to send

    GOTO    INT_IDLE    ; clock is low , wait and buffer keys( i.e. no rx/tx )

        
                        
INT_CHECK_RX           ; pc ( probably ) wants to send something..
        
    BTFSS    pcCLOCK_in    ; wait until clock is released before we go into receving mode..
    GOTO    INT_RX_IDLE    ; nope still low

    ; clock now high test if we are set to start an rx seq. 
    BTFSS    _RxCanStart     ; have we set the flag ?
    GOTO    INT_WAIT_RX    ; nope then set it      
            
    BTFSC    pcDATA_in    ; make sure that data still is low
    GOTO    INT_ABORT_RX    ; nope abort rx req, might been a 'glitch'

    ; initiate the rx seq.

    CLRF    Comm_Flags    ; used by both tx/rx routines ( and _RxCanStart bit !! )
    CLRF    TRX_Flags    ; clear tx/rx flags
    CLRF    KeyParity    ; clear parity counter

    BSF    _RX_Mode          ; set rx mode flag..
    BSF    _isStartBit    ; set that next sampling is start bit
    
    ; preset bit and clock counters 

    MOVLW    H'2F'        ; = 47 dec, will toggle clock output every even number until zero
    MOVWF    clkCount    ; preset clock pulse counter

    MOVLW    H'08'        ; = 8 dec, number of bits to read
                ; then parity bit will be set instead

    MOVWF    bitCount    ; preset bit counter

    ; note as we are starting the clock here we allow a longer time before we start
    ; the actual 20 us tick, this the first time we wait about 200 us before the first clock 'tick'     

    MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8us ( int handling code )= 200 us
                ; 
        MOVWF   TMR0
    
    GOTO    INTX        ; exit, the next int will start an rx seq

INT_WAIT_RX:
    BSF    _RxCanStart    ; set flag so we start rx next int ( 0.5 ms )
INT_RX_IDLE
    ; reload clock so we check more often
    MOVLW   H'F0'           ; start timer with 240 ( 256-16  = 240 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 16 + 8/10us ( int handling code )= (about) 60 us
        MOVWF   TMR0

    GOTO    INTX        ;

INT_ABORT_RX
    BCF    _RxCanStart    ; clear flag ( forces a 'new' rx start delay )
    GOTO    INT_IDLE    ;
                                               

INT_CHECK_BUFF:
    ; check if we have any keys to send to pc

        MOVF     KeyBufferTail,W ; get end of buffer to 'w'
        SUBWF     KeyBufferHead,W ; subtract start of buffer if head - tail = zero, no byte
        BTFSC   STATUS, Z       ; zero flag = no byte to send         
        GOTO    INT_IDLE    ; then do the 'idle' stuff
         
INT_SEND_KEY
    ;key in buffer, get it and initiate an tx seq...
    CALL    GET_KEY_BUFFER    ; get the key into CurrKey
    MOVF    CurrKey,W
    MOVWF    LastKey        ; store last sent key

    ; setup our tx/rx vars 

    CLRF    Comm_Flags    ; used by both tx/rx routines ( and _RxCanStart bit !! )
    CLRF    TRX_Flags    ; clear tx/rx flags
    CLRF    KeyParity    ; clear parity counter

    BSF    _TX_Mode          ; set tx mode flag..
    
    ; preset bit and clock counters

    MOVLW    H'2B'        ; = 43 dec, will toggle clock out put every even number until zero
    MOVWF    clkCount    ; preset clock pulse counter

    MOVLW    H'12'        ; = 18 dec, will shift data out every even number until zero
                ; then parity bit will be set instead
    MOVWF    bitCount    ; preset bit counter

    ; data now set, initiate the clock to generate a 20 us tick ( rx/tx heart beat )

    BSF    pcDATA_out    ; start bit, always 'low' ( we pull down )
     
    MOVLW   H'FA'           ; start timer with 252 ( 256-6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8 ( int handling code )= 27 us
        MOVWF   TMR0
    
    GOTO    INTX        ; exit, the next int will start an tx

INT_IDLE:
    
    ; 'Idle' code i.e. no rx or tx possible AND/OR nothing to send

    DECF    Divisor_10ms,F  ; Count 0.5ms down to give 10 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .20
    MOVWF    Divisor_10ms     ; Preset the divide by 20

    ;+++
    ; 10 ms tick here
    


    ; Divide the 10 ms tick to give 100 ms second tick
INT_10MS
    DECF    Divisor_100ms,F    ; Count 10ms down to give 100 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .10
    MOVWF    Divisor_100ms    ; Preset the divide by 10

        ;+++
     ; 100 ms tick here

INT_100MS


    ; numlock check !! bit variable _Numlock does not actually set the numlock status directly.
    ; However, by setting this bit to '1' we make a test against the current numlock led status
    ; ( bit no 1 in variable KbLedStatus ), if that is different from this variable
    ; we send a 'numlock' press/release ( to toggle numlock status )
    ; so in essence, setting this bit to '1' will force numlock to be 'on' etc.

    BTFSC    _IsFirstLedStatus ;  if = 1 then we have not yet received numlock status
    GOTO    INT_REPEAT_CHECK    ; nope, then this is a consecutive byte, store as 'normal'

    BTFSS    _WaitNumLock    ; are we waiting for pc numlock reply ?
    GOTO    INT_NUMLOCK_CHECK ; yep then do repeat check instead
    
    DECFSZ    Temp_Var,F    ;
    GOTO    INT_REPEAT_CHECK

    CALL    RELEASE_NUMLOCK

INT_NUMLOCK_CHECK

    BTFSC    _LedNumLock    ; is the led on ?
    GOTO    INT_NUMLOCK_ON    ; yep, then test our 'local' numlock state ( wanted numlock state )

    ; nope numlock is off, is our wanted state also off ?
    BTFSS    _NumLock    ; is wanted state off ?
    GOTO    INT_REPEAT_CHECK ; yep continue   

    CALL    PRESS_NUMLOCK    ; nope then send numlock press/release code

    GOTO    INT_REPEAT_CHECK
            

INT_NUMLOCK_ON
    BTFSC    _NumLock    ; is wanted state also 'on' ?
    GOTO    INT_REPEAT_CHECK ; yep

    CALL    PRESS_NUMLOCK    ; nope then toggle numlock state

INT_REPEAT_CHECK

    ; check if a key should be 'repeated' ( when pressed longer than 500 ms )
    BTFSS    _startRepeat    ; start repeating a key ? ( delay !!! )
    GOTO    INT_CHECK_KEY    ; nope, then check if key should be repeated
    DECF    RepeatTimer,F    ;
    BNZ    INT_500MS    ; not zero yet, check timer instead 

    BCF    _startRepeat    ; stop repeat timer ( delay is accomplished )
    BSF    _doRepeat    ; and enable 'key' is still down check
    MOVLW    .02        ; start repeat send timer
    MOVWF    Divisor_Repeat  ;

    GOTO    INT_500MS    ; do next timer check

INT_CHECK_KEY
    BTFSS    _doRepeat    ; key should be repeated ?
    GOTO    INT_500MS    ; nope
    
    ; ok key should be repeated, check if it still pressed ?
    CALL    CHECK_KEY_STATE    ; uses MakeKeyOffset to calculate which key that was
                ; the last pressed, and then check if it's still pressed
                ; if still pressed carry = '1', 

    BTFSS    STATUS,C    ; check carry
    BCF    _doRepeat    ; clear repeat bit, stop repeating the key 

    BTFSS    _doRepeat    ; still pressed ?
    GOTO    INT_500MS    ; nope

    DECF    Divisor_Repeat,F  ; should we send the key ?
    BNZ    INT_500MS    ; nope

    MOVLW    DELAY_RATE    ; reload timer with key rate delay
    ;MOVLW    .02        ; restart timer
    MOVWF    Divisor_Repeat  ;
    
    BSF    _doSendKey    ; set flag to send key, NOTE the actual sending ( putting into send buffer )
                ; is done inside mainloop.
     
    
INT_500MS
    
    DECF    Divisor_500ms,F    ; Count 100ms down to give 500 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .05
    MOVWF    Divisor_500ms    ; Preset the divide by 5

        ;+++
     ; 500 ms tick here


INT_500_NEXT
    
    TOGGLE_PIN O_led_KEYCOMM_ok    ; toggle the disco light 😉

    BTFSS    _DoExitAltKeymap    ; is the alt keymap toggle key pressed the second time ?
                    ; if so skip timeout test and exit
    BTFSS    _InAltKeymap    ; are we in altkeymap ?
    GOTO    INTX        ; nope 
    
    ; we are in altkeymap, decrement the lastkeytime
    ; and check if we are at zero then we exit 
    ; the altkeymap.

    DECF    LastKeyTime,F    ; decrease time
    BNZ    INTX        ; exit, timer has not expired
    ; timer expired, get out of altkey map
    BSF    _ExitAltKeymap    ;

; ***************** 'heart' beat code end ***************

INTX
    ;BCF    INTCON,T0IF     ; Clear the calling flag

    PULL            ; Restore registers
    RETFIE
    
; **************** end interrupt routine **************


;+++++        
;     Routines that will 'toggle' keyboard numlock status
;     by sending numlock make/break code 
;

PRESS_NUMLOCK:     
        MOVLW    H'77'         ; numlock key scancode, make
        CALL    ADD_KEY
        MOVLW    H'06'         ; 6 x 100 ms = 600 ms ( release delay )
        MOVWF    Temp_Var    ;
        BSF    _WaitNumLock    ; we are waitin for numlock status reply from pc
        RETURN

RELEASE_NUMLOCK:
        MOVLW    BREAK        ; break prefix
        CALL    ADD_KEY    
        MOVLW    H'77'         ; numlock key scancode 
        CALL    ADD_KEY
        BCF    _WaitNumLock
        RETURN
    
; ***********************************************************************
;
;  CHECK_RX_KEY - handles the received commands from pc
;
CHECK_RX_KEY    
    ; check the key in 'currkey' ( command from pc )

CHECK_ED
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'ED'        ; subtract value in W with 0xED
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_EE    ; the result of the subtraction was not zero check next
    ; ok 'ED'=set status leds ( in next byte ) received
    BSF    _IsLedStatus    ; set bit that next incoming byte is kb led staus 
    GOTO    CHECK_SEND_ACK    ; send ack

CHECK_EE    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'EE'        ; subtract value in W with 0xEE
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F0    ; the result of the subtraction was not zero check next
    ; ok 'EE'= echo command received
    GOTO    CHECK_SEND_EE    ; send echo

CHECK_F0    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F0'        ; subtract value in W with 0xF0
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F2    ; the result of the subtraction was not zero check next
    ; ok 'F0'= scan code set ( in next commming byte ) received
    BSF    _SkipByte    ; skip next incomming byte ( or dont interpret )
    GOTO    CHECK_DONE    ; do not send ack !
CHECK_F2    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F2'        ; subtract value in W with 0xF0
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F3    ; the result of the subtraction was not zero check next
    ; ok 'F2'= Read ID command responds with 'AB' '83'
    GOTO    CHECK_SEND_ID    ; send id bytes

CHECK_F3    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F3'        ; subtract value in W with 0xF3
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_FE
;    GOTO    CHECK_F4    ; the result of the subtraction was not zero check next
    ; ok 'F3'= set repeat rate ( in next commming byte ) received
    BSF    _IsRateDelay    ; next incomming byte is rate/delay info
    GOTO    CHECK_SEND_ACK    ; send ack

; **** Note ! removed from test as I 'don't care' ********
; **** i.e. I dont disable or enable the keyboard at any time.

;CHECK_F4    
;    MOVF    CurrKey,W    ; move key buffer into W register
;    SUBLW    H'F4'        ; subtract value in W with 0xF4
;    BTFSS   STATUS, Z    ; check if the zero bit is set
;    GOTO    CHECK_F5    ; the result of the subtraction was not zero check next
    ; ok 'F4'= keyboard enable received
;    GOTO    CHECK_SEND_ACK    ; send ack
;CHECK_F5    
;    MOVF    CurrKey,W    ; move key buffer into W register
;    SUBLW    H'F5'        ; subtract value in W with 0xF5
;    BTFSS   STATUS, Z    ; check if the zero bit is set
;    GOTO    CHECK_FE    ; the result of the subtraction was not zero check next
    ; ok 'F5'= keyboard disable received
;    GOTO    CHECK_SEND_ACK    ; send ack
CHECK_FE    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'FE'        ; subtract value in W with 0xFE
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_FF    ; the result of the subtraction was not zero check next
    ; ok 'FE'= resend last sent byte
    MOVF    LastKey,W    ; get last key
    CALL    ADD_KEY        ; and put it on the que
    GOTO    CHECK_DONE

CHECK_FF
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'FF'        ; subtract value in W with 0xFF
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_ERROR    ; the result of the subtraction was not zero, unknown command
    ; ok 'FF'= reset keyboard received
    
    GOTO    CHECK_SEND_AA    ; send 'AA' power on self test passed

CHECK_ERROR            ; unknown command ( or command not interpreted )
    GOTO    CHECK_SEND_ACK

CHECK_SEND_ID
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    MOVLW    H'AB'        ; keyboard id first byte, always 0xAB
    CALL    ADD_KEY        ;

    MOVLW    H'83'        ; keyboard id second byte, always 0x83
    CALL    ADD_KEY        ;

    GOTO    CHECK_DONE

CHECK_SEND_ACK

    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;
    GOTO    CHECK_DONE

CHECK_SEND_AA
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ; 

    MOVLW    H'AA'        ; keyboard post passed
    CALL    ADD_KEY        ;
    GOTO    CHECK_DONE

CHECK_SEND_EE
    MOVLW    H'EE'        ; keyboard echo
    CALL    ADD_KEY        ;

CHECK_DONE
    RETLW    0        ; and we are done

; ***********************************************************************
;  Buffer code ( a bit modified ) from Stewe Lawther 
; http://ourworld.compuserve.com/homepages/steve_lawther/ucindex.htm
;  And of course source of the exellent keyboard viewer. !! ( without which, this 
;  project would have been close to impossible ) ( and of course my nifty 
;  memory oscilloscope )
;
;  ADD_KEY_BUFFER - (outside int)add the key in CurrKey to our keybuffer que in the first
;            free position. If there is no more room the oldest byte is 
;            'dumped'.
;  ADD_KEY      - Same but to be used inside int routine.( just skips int disable code )

ADD_KEY_BUFFER

ADD_STOP_INT            ; first stop all interrupts !!!!!!! 
    BCF    INTCON,GIE    ; disable global interrupts..
    BTFSC    INTCON,GIE    ; check that is really was disabled
    GOTO    ADD_STOP_INT    ; nope try again
ADD_KEY                ; inside interuppt we call this instead ( as we dont need to disable int 🙂 )
    MOVWF    BufTemp        ; store key temporary
    MOVF    KeyBufferHead,W ; move buffer head out of FSR temporarily
        MOVWF   Temp            ; store in temp  
        MOVF    KeyBufferTail,W ; set FSR to buffer tail
        MOVWF   FSR        ; set indirect file pointer
        MOVF    BufTemp,W       ; set W to new scancode to send
        MOVWF   INDF            ; and put it in the buffer
        MOVF    Temp,W        ; get the head pointer back
        MOVWF   KeyBufferHead    ;

        INCF    KeyBufferTail,W ; get the end of the buffer
        SUBLW   KbBufferMax     ; check if at buffer max
        INCF    KeyBufferTail,W ; (reload value to w - doesn't affect C) 
        BTFSS   STATUS, C       ; if so (negative result)
        MOVLW   KbBufferMin     ; set to buffer min ( wrap around )

        MOVWF   KeyBufferTail    
        SUBWF   KeyBufferHead,W ; see if we have any room ( head and tail have meet ) 
        BTFSC   STATUS, Z       ; if so (Z set)
        CALL    INC_KEY_HEAD    ; dump oldest byte

                ; finally turn on interrupts again
        MOVLW   b'10100000'     ; enable global & TMR0 interrupts
        MOVWF   INTCON

    RETURN


; ***********************************************************************
;
;  GET_KEY_BUFFER -     Gets a char from the buffer, and puts it into KeyBuffer
;            NOTE: Does not increase buffer pointers ( dump this key ).
;            A call to INC_KEY_HEAD will do this if the key is sent ok
GET_KEY_BUFFER    
          MOVF    INDF, W         ;put the byte to send into key buffer
                MOVWF   CurrKey
        RETURN            ; and go back, NOTE ! the key is not 
                    ; removed from the buffer until a call
                    ; to INC_KEY_HEAD is done.
                    
; ***********************************************************************
;
;  INC_KEY_HEAD -     dump oldest byte in keybuffer, Do not call if byte
;            has not been fetched before ( GET_KEY_BUFFER )
;            
INC_KEY_HEAD    INCF    KeyBufferHead,W ; set to next byte in buffer
                SUBLW   KbBufferMax     ; check if at buffer max
                INCF    KeyBufferHead,W ; (reload value to w - doesn't affect C) 
                BTFSS   STATUS, C       ; if so (negative result)
                MOVLW   KbBufferMin     ; set to buffer min ( wrap around )
                MOVWF   KeyBufferHead   ; and store ( in FSR )
                RETURN            ; go back
                
        

; ***********************************************************************
;
;  CHECK_KEY_STATE - Check if the last pressed key is still pressed 
;             Returns with carry = '1' if still pressed
;             else carry = '0' ( or error )
;

CHECK_KEY_STATE:
    ; uses LastMakeOffset to calculate which key to test
    
    MOVF    LastMakeOffset,W    ; get offset 
    ANDLW    H'18'        ; mask out column bits  
                ; lastmake offset has the following bits:
                ; '000yyxxx' where 'yy' is column no
                ; and 'xxx' is key num, 
    BTFSC    STATUS,Z    ; zero = column 1 & 2
    GOTO    CHECK_COL_12    ; it is in column 1

    MOVWF    repTemp        ; save it temporary
    SUBLW    H'08'        ; subtract value in W with 0x08 ( columns 3 & 4 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_COL_34    ; it is in column 3 & 4

    MOVF    repTemp,W    ; get the column bits back
    SUBLW    H'10'        ; subtract value in W with 0x10 ( columns 5 & 6 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_COL_56    ; it is in column 5 & 6
    
CHECK_COL_78
    MOVF    kbColumn78_Old,W ; get bit map ( key status ) for keys in column 7 & 8
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_56
    MOVF    kbColumn56_Old,W ; get bit map ( key status ) for keys in column 5 & 6
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_34
    MOVF    kbColumn34_Old,W ; get bit map ( key status ) for keys in column 3 & 4
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_12
    MOVF    kbColumn12_Old,W ; get bit map ( key status ) for keys in column 1 & 2
    MOVWF    repKeyMap    ; and store it

;<-------Alt keymap code-------> 
    
    ;Checks ONLY column 1&2 bitmap ( keymap ) ( as it is now )
    ; this code has to be moved/changed if another column is choosen as the alt. keymap toggle key

    ; alternative keymap handling if key r3 c1 is pressed ( bit 2 in column 1&2 )
    ; then enable alternative keymap ( only if keyrepeat is disabled )
    
    ; check if this was the last key pressed

    ; check bit representing the alt. keymap key ( i've choosen key 2 )

    MOVF    LastMakeOffset,W ; get key offset again
    ANDLW    H'07'         ; mask out column bits
    SUBLW   H'02'        ; check if its bit num 2 ( the enter 'alt keymap' key )

    BTFSS   STATUS, Z    ; check if the zero bit is set    GOTO    CHECK_KEY    ; nope than another key was the last
                ; skip altkeymap enable

    ; the altkeymap key was the last pressed !
    ; is key repeat disabled ?

    BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
    GOTO    CHECK_KEY     ; yep, then skip altkeymap enable test

    ; enable altkeymap if key is still pressed
    
    BTFSC    repKeyMap,2    ; test bit 2 ( should be key 'F7' )
    GOTO    CHECK_ENABLE_ALT ; ok enable altkeymap ( if we are not already in altkeymap )
    GOTO    CHECK_KEY    ; nope another key in column 1&2 continue check 

CHECK_ENABLE_ALT
    BTFSC    _AltKeymap    ; are we already in altkeymap ?
    GOTO    CHECK_KEY    ; yep then just continue

    ; We are just entering/enabling the alt. keymap

    BSF    _AltKeymap    ; enable alternative keymap

    ; Example of using an 'advanced' alt keymap handling    
    ; not enabled, to avoid intial confusion.

    ; I.E This snippet would only be called once when we
    ; are just entering(enabling) the alternative keymapping !

    ; This example code will 'soft' release the current altkeymap key ( it is in pressed state ! )
    ; ( i.e send release code for the enter alt. keymap key 'F7' )
    ; send the make scancode for left <alt> key instead.
    ; and force numlock to be off.
    
    ; Do not use if you dont understand the implifications !
        
    ; Also note that the scancodes are hardcoded here !
    ; ( i.e do not use the lookup table definition of the key/s )
    
    ; ***** start snippet 
    
    ;MOVLW    BREAK    ; send break prefix
    ;CALL    ADD_KEY
    ;MOVLW    H'83'   ; and scancode for the enter alt keymap
    ;CALL    ADD_KEY
    ;MOVLW    H'11'     ; send make code for the left <alt> key 
    ;CALL    ADD_KEY
    
    ; example of forcing the numlock status to a particular state
    ; the numlockstatus will change ( be checked ) inside the int routine
    ; See also at the end of KB_DEBOUNCE_12 where the numlock status
    ; will be restored when we release the key
    
    ;BCF    _NumLock    ; 'force' numlock to be off
    
    ; This bit MUST also be checked as we do not know if we have recevied 
    ; first numlock status byte yet ( pc does not send numlock/led status
    ; after intial poweron, if not one of the numlock/capslock/scrolllock are pressed )
    ; i.e. if you connect this keyboard to a 'running' pc, the numlock status
    ; will be unknown.However if connected before poweron, it will be updated
    ; as numlock status is sent during pre-boot seq.
    
    ;BTFSC    _IsFirstLedStatus ; have we recevied numlock status yet ?
    ;CALL    PRESS_NUMLOCK
    
    ; ***** end snippet 

CHECK_KEY
    ; 'normal' key down check
    ; column for pressed key is now in repKeyMap
    MOVF    LastMakeOffset,W ; get offset again
    ANDLW    H'07'        ; mask out key number ( lowest 3 bits )
    
     BTFSC    STATUS,Z    ; bit num zero ?
    GOTO    CHECK_KEY_DONE    ; yep lowest bit, check and return
    
       MOVWF    repTemp        ; and store it
CHECK_KEY_LOOP
    RRF    repKeyMap,F    ; rotate one step to right
    DECFSZ    repTemp,F        ; decrement bit counter
    GOTO    CHECK_KEY_LOOP    ; loop again

CHECK_KEY_DONE
    ; ok the key to test should now be the lowest bit in repKeyMap
    CLRC            ; clear carry
    BTFSC    repKeyMap,0    ; check bit 0
    BSF    STATUS,C    ; ok key is pressed set carry
    RETURN            ; and we are done..

; ***********************************************************************
;
;  DELAY_1ms -     Delay routine ! used when scanning our own keyboard
;         Delay is between output of adress to 4051 and reading of inputs
;         Increase to have a slower 'scan' rate or decrease to have a higher scan rate.
;

DELAY_1ms
        MOVLW    H'F0'        ; wait 255 cycles
        MOVWF    kbTemp        ; this var is 'safe' to be used in side mainloop
        MOVLW    H'03'
        MOVWF    kbState
DELAY_LOOP
        DECFSZ    kbTemp,F    ; decrement
        GOTO    $-1        ;
        
        MOVLW    H'F0'
        MOVWF    kbTemp
        DECFSZ    kbState,F
        GOTO    DELAY_LOOP

        RETURN


;---------------------------------------------------------------------------
;
;        Initialisation
;
;---------------------------------------------------------------------------
        
        
INIT:  

    ;+++
    ;    Set up the ports

        ; PORT A

    BANK1
        MOVLW    b'00000110'        ; Set port data directions RA1,RA2 inputs RA0,RA3,RA4 outputs
    MOVWF    TRISA            ; PC keyboard connections


        ; PORT B

        ; Used for our own 3x8 matrix keyboard
    
        BANK1
        MOVLW    b'11111000'        ; Set port data directions RB4-RB7 inputs rest outputs
    MOVWF    TRISB


    ;    Clear all registers on bank 0 ( memory )
    
    BANK0
    MOVLW    H'0C'
    MOVWF    FSR
    
INITMEM
    CLRF    0        ; Clear a register pointed to be FSR
    INCF    FSR,F
    CLRWDT            ; clear watchdog
    MOVLW    H'50'        ; Test if at top of memory
    SUBWF    FSR,W
    BNZ    INITMEM        ; Loop until all cleared

    ;+++     
    ;    Initiate the keybuffer pointers

INIT_BUFF:

    MOVLW   KbBufferMin    ; get adress of first buffer byte
        MOVWF    KeyBufferHead    ; store in FSR
        MOVWF    KeyBufferTail    ; and set last byte to the same ( no bytes in buffer )

    ;+++
    ;    Preset the timer dividers

    MOVLW    .20
    MOVWF    Divisor_10ms
    MOVLW    .10
    MOVWF    Divisor_100ms
    MOVLW    .05
    MOVWF    Divisor_500ms

    ;+++
    ;    Set up Timer 0.

    ;    Set up TMR0 to generate a 0.5ms tick
    ;    Pre scale of /8, post scale of /1
    
    BANK1
        
    MOVLW   b'00000010'     ; Initialisation of TMR0 prescale 8 '010'
                                ; weak pullup enabled by latch values.

        MOVWF   OPTION_REG      ; load option reg with prescale of 8
                
        BANK0

    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0
        

        
;---------------------------------------------------------------------------
;
;        the main 'program' loop ( starts really at MAIN_LOOP )
;
;---------------------------------------------------------------------------

MAIN:    
        BSF    pcDATA_in
        BSF    pcCLOCK_in
        BCF    pcDATA_out
        BCF    pcCLOCK_out
        CLRF    PORTB

        MOVLW    H'08'        ; preset the column counter
        MOVWF    kbColumnCnt    ;
        
         BSF    _NumLock    ; default state is numlock = on 
        BSF    _IsFirstLedStatus ; we have not yet recevied led status byte.

            MOVLW   b'10100000'     ; enable global & TMR0 interrupts
            MOVWF   INTCON

        CLRWDT            ; clear watchdog
        BTFSS    O_led_KEYCOMM_ok
        GOTO    $-2        ; make an 0.5 second delay here 
                    ; i.e. the led will come on when 0.5 seconds has passed
                    ; set inside the timer int.

        CLRWDT            ; clear watchdog
        BTFSC    O_led_KEYCOMM_ok
        GOTO    $-2        ; make an additional 0.5 second delay here 
                    ; i.e. the led will be dark when 0.5 seconds has passed
                    ; set inside the timer int.


        MOVLW    H'AA'         ; post passed :-), always 0xAA
        CALL    ADD_KEY_BUFFER
        
        ; now go into infinite loop, the pc kb interface runs in the background ( as an int )
        ; where we continuously monitor the pcCLOCK/DATA_in lines

         
MAIN_LOOP:
        ; check whatever 🙂
        CLRWDT            ; clear watchdog

MAIN_CHECK_COL_1:
        ; scan our own keyboard, first four bits

        ; address and read column, read as complement so key pressed = '1'
        ; since we pull down when key is pressed ( weak pullup enabled )
        ; ( i.e. pressed key has level 0V ) to make it more 'logical' to work with

        CLRF    kbColumnVal
        
        ; get column counter / adress out
        MOVF    kbColumnCnt,W          
        
        MOVWF    PORTB        ; set the columns adress to the 74HCT4051
                    ; i.e. make column low
        IFNDEF    DEBUG
        CALL    DELAY_1ms    ; wait 1 ms let pins stabilize
        ENDIF

        COMF    PORTB,W        ; read back the pin values ( complement i.e. key pressed = '1' )
        ANDLW   b'11110000'    ; mask out unused pins
        
        MOVWF    kbColumnVal    ; store the pin values

        SWAPF    kbColumnVal,F    ; swap nibbles ( low<->high ) to make room for next column

        INCF    kbColumnCnt,F     ; inc column adress
MAIN_CHECK_COL_2:
        ; read next four bits
        ; put out adress and read next column, read as complement so key pressed = '1'
        ; this as we pull down when key is pressed ( weak pullup enabled )

        ; get column counter / adress out
        MOVF    kbColumnCnt,W          
        
        MOVWF    PORTB        ; set the columns adress to the 74HCT4051
                    ; i.e. make column low

        IFNDEF    DEBUG
        CALL    DELAY_1ms    ; wait 1 ms
        ENDIF

        COMF    PORTB,W        ; read back the pin values ( complement i.e. key pressed = '1' )
        ANDLW   b'11110000'    ; mask out unused pins
        
        ADDWF    kbColumnVal,F    ; and store pin values

        INCF    kbColumnCnt,F   

        ; reset column counter check
        ; i.e. we are 'only' using adress 0 - 7 
        MOVF    kbColumnCnt,W
        SUBLW    H'08'        ; subtract value in W with 0x08
        BTFSS   STATUS, Z    ; check if the zero bit is set
        GOTO    MAIN_CHECK_DEBOUNCE ; nope continue

        CLRF    kbColumnCnt     ; reset counter/adress

MAIN_CHECK_DEBOUNCE:
        CALL    KB_DEBOUNCE    ; do debouncing on the current values and send make/break
                    ; for any key that has changed
                    ; NOTE uses the current column adress to determine which
                    ; columns to debounce!
MAIN_REPEAT:
        BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
        GOTO    MAIN_CHECK_REPEAT ; yep check key repeating
        
        ; keyrepeat disabled then do check on exit of altkeymap instead

        BTFSS    _ExitAltKeymap    ; we want to exit altkeymap ?
        GOTO    MAIN_LOOP    ; nope

        
        ; check that ALL keys are released
        ; before exiting the alt keymap
        MOVF    kbColumn78_Old,F ; reload column 78 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 78

        MOVF    kbColumn56_Old,F ; reload column 56 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 56

        MOVF    kbColumn34_Old,F ; reload column 34 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 34

        MOVF    kbColumn12_Old,F ; reload column 12 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 12
    
        ; all keys released !! 
        BCF    _AltKeymap    ; exit altkeymap
        BCF    _ExitAltKeymap    ; exit release check
        BCF    _InAltKeymap    ; clear flag for second keypress check
        BCF    _DoExitAltKeymap ;
        GOTO    MAIN_LOOP    


MAIN_CHECK_REPEAT
        BTFSS    _doSendKey    ; if we should send a repeated key
        GOTO    MAIN_LOOP    ; nope continue

        ; send the key in RepeatedKey but first check if its an extended key
        BTFSS     _RepeatIsExt    ; is it extended ?
        GOTO    MAIN_SEND_REPEAT ; nope just send scan code
        
        ; last key pressed was extended send extended prefix
        MOVLW    EXTENDED    ; get extended code
        CALL    ADD_KEY_BUFFER    ; and put it into the buffer
        
MAIN_SEND_REPEAT:
        MOVF    RepeatKey,W    ; get key code for the last pressed key
        CALL    ADD_KEY_BUFFER    ; and put it into the buffer
        BCF    _doSendKey    ; and clear the flag, it will be set again 
                    ; inside int handler if key still is pressed    
        
        GOTO    MAIN_LOOP    ; and return
    

; ***********************************************************************
;
;  KB_SEND_KEY - uses the Offset stored in var 'Offset' to fetch a key from our
;         key lookup table.
;           then checks the bit var _isBreak to see if make or break codes should be sent
;         It then puts the code/s into the key buffer ( for sending later, in int routine )

KB_SEND_KEY:

    MOVF    Offset,W    ; get current offset
    MOVWF    TempOffset    ; save it ( to be used in key repeat code, is its 'make' )
                ; temp offset has the following bits:
                ; '000yyxxx' where 'yy' is column offset
                ; and 'xxx' is key num, 


    CLRC            ; clear carry so it dont affect byte rotation
    RLF    Offset,F    ; first rotate
    RLF    Offset,F    ; second rotate

                ; offset no have the following bits:
                ; '0yyxxx01' where 'yy' is column offset
                ; and 'xxx' is key num, 
                ; as each key in table has 4 bytes of 'space'

    INCF    Offset,F    ; add one, for the 'movwf pcl' at the start of the table

    BCF    Offset,7    ; clear to bit, just in case so we dont
                ; 'overflow' the table, should not be needed !

    BCF    _isExtended     ; clear extended flag

    MOVLW   LOW LOOKUP_KEY    ; get low bit of table adress
    ADDWF    Offset,F    ; 8 bit add
    MOVLW    HIGH LOOKUP_KEY    ; get high 5 bits
    BTFSC    STATUS,C    ; is page boundary crossed ?
    ADDLW    1        ; yep, then inc high adress
    MOVWF    PCLATH        ; load high adress in latch
    MOVF    Offset,W    ; load computed offset in w
    CLRC                ; clear carry ( default= key is not extended )
                ; if key is extended then carry is set in jumptable lookup_key

    CALL    LOOKUP_KEY    ; get key scan code/s for this key
                ; key scan code/s are saved in 
                ; W - scancode, should go into kbScan
                ; carry set - extend code 
                ; carry clear - not extended code

    MOVWF    kbScan        ; store scancode
    ; if carry is set then key is extended so first send extended code 
    ; before any make or break code

    BTFSS    STATUS,C    ; check carry flag
    GOTO    KB_CHK_BREAK    ; nope then check make/break status

    BSF    _isExtended    ; set extended flag
    MOVLW    EXTENDED    ; 
    CALL    ADD_KEY_BUFFER    ; get extended code and put in in the buffer

KB_CHK_BREAK:
    
    ; check if it's make or break
    BTFSS    _isBreak    ; check if its pressed or released ? 
    GOTO    KB_DO_MAKE_ONLY    ; send make code 

    BCF    _isBreak    ; clear bit for next key

    ; break code, key is released
    MOVLW    BREAK        ; get break code
    CALL    ADD_KEY_BUFFER    ; and put into buffer
    GOTO    KB_DO_MAKE    ; and send key code also

    ; key is pressed !
KB_DO_MAKE_ONLY:
    BCF    _doSendKey    ; stop repeat sending
    BCF    _doRepeat    ; and bit for repeat key send
    BSF    _startRepeat    ; and set flag for start key repeat check
    BCF    _RepeatIsExt    ; clear repeat key extended flag ( just in case )

    BTFSC     _isExtended     ; is it extended ?
    BSF    _RepeatIsExt    ; set the flag 
    
    ; save this key in 'last' pressed, to be used in key repeat code

    MOVF    TempOffset,W    ; get saved offset
    MOVWF    LastMakeOffset  ; and store it 

    ; if keyrepat = enabled, alternative mapping = disabled
    BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
    GOTO    KB_REP_NOR    ; yep set normal delay ( 800 ms )
    
    ; else keyrepat = disabled, alternative mapping = enabled
    MOVLW    DELAY_ENTER_ALTKEYMAP ;reload delay before entering the altkeymap ( 3 sec )
                ; i.e how long the enter altkeymap key must be pressed before 
                ; we enable altkey keymap codes.
                            
    GOTO    KB_REP_SET    ; and set it
KB_REP_NOR:    
    
    MOVLW    DELAY_REPEAT    ; reload 'normal' repeat delay ( 800 ms )
            
KB_REP_SET:
    MOVWF    RepeatTimer    ; and (re)start the timer for key repeat
    MOVF    kbScan,W    ; get key scan code    
    MOVWF    RepeatKey    ; and save it

KB_DO_MAKE:
    ; key pressed/released ( i.e. the scancode is sent both on make and break )
    MOVF    kbScan,W    ; get scan code into w
    CALL    ADD_KEY_BUFFER    ; and add to send buffer
    

    ; reset the 'get out of alt. keymap timer for each keypress 
    ; note don't care if we are 'in' alt. keymap. Reset this timer anyway
    ; as the code for checking if we are currently in alt. key map
    ; would be as long as it takes to reset the timer.

    MOVLW    DELAY_EXIT_ALTKEYMAP ; reload the delay for exiting the altkeymap when no
                ; key is pressed ( 7.5 sec )
    MOVWF    LastKeyTime    ; (re)set lastkey timer ( used to get out of altkeymap )    
    RETURN

; ***********************************************************************
;
;  KB_DEBOUNCE - debounces two column readings from our keyboard
;         If a bit 'state' has been 'stable' for 4 consecutive debounces
;           the 'new' byte is updated with the new state 
;         'normal' loop time ( no tx/rx/key press ) is about 2-3 ms
;         so from 'key' down until 'new' is updated it takes about 8-10 ms
;         ( as we are scanning columns two by two, the whole keyboard needs
;         4 loops to be fully updated, then 4 debounce samples for each 'pair' )    

KB_DEBOUNCE:
    ; debounce current column(s) 
    MOVF    kbColumnCnt,F    ; reload value into itself ( affect zero flag )
    BTFSC    STATUS,Z    ; is it zero ?
    GOTO    KB_DEBOUNCE_78    ; debounce columns 7 & 8

    MOVF    kbColumnCnt,W    ; move column counter into W register
    SUBLW    H'04'        ; subtract value in W with 0x04 ( columns 5 & 6 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    KB_DEBOUNCE_34    ; debounce columns 3 & 4

    MOVF    kbColumnCnt,W    ; move column counter into W register
    SUBLW    H'06'        ; subtract value in W with 0x02 ( columns 3 & 4 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    KB_DEBOUNCE_56    ; ok column 1 & 2 debounce

    ; all above tests 'failed'
    ; columns to debouce are 1 & 2

KB_DEBOUNCE_12:    
    ; debounce columns 1 & 2
    DEBOUNCE_BYTE    kbColumnVal,kbColumn12_New,kbColumn12Cnt,kbColumn12State

    MOVF    kbColumn12_New,W    ; get debounced sample
    XORWF    kbColumn12_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn12_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_12    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_12_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
        
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses 
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_12_NEXT    

KB_LOOP_12_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_12_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_12_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_12    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_12

KB_12_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn12_New,W ; get new status
    MOVWF    kbColumn12_Old    ; and store it..

;<-------Alt keymap code------->

    ; ***** alternative keymap handling
    ; The alternative keymap is enabled by pressing key r4 c1 ( i.e. bit 3 in column 12 )
    ; Here, we enable a check to turn off alternative keymap if
    ; that key and all others are released ( bit is cleared ). 
    ; ( else no (alternative)break codes would be sent for those keys that are still pressed )
    ; NOTE: _Altkeymap is set inside int routine when checking
    ; keyrepeat so there is a 'variable' delay before the altkeymap is active
    ;

    BTFSS    _AltKeymap        ; is altkeymap enabled ?
    RETURN                ; nope return


    BTFSC   _InAltKeymap        ; are we in altkeymap ?
    GOTO    KB_12_IN        ; yep alt keymap key has been released once
    
    ; nope still waiting for first release
    BTFSS   kbColumn12_Old,2    ; is key released ? ( first time )
    GOTO    KB_12_ALT        ; yep, reset timers and set bit variables

KB_12_IN
    BTFSC    _DoExitAltKeymap    ; are we waiting for release ?
    GOTO    KB_12_OUT        ; yes

    ; the key has been released once test for second press
    BTFSC   kbColumn12_Old,2    ; is it still pressed ?
    GOTO    KB_12_ALT2        ; yep

    BTFSS    _DoExitAltKeymap        ; are we now waiting for the last ( second ) release ?
    RETURN                ; nope

KB_12_OUT
    BTFSS    kbColumn12_Old,2    ; check if key still pressed ?
    BSF    _ExitAltKeymap        ; nope, then enable exit check that
                    ; will exit alt keymap as soon as all key are released
    
KB_12_ALT2
    BSF    _DoExitAltKeymap    ; check for second release
    RETURN
KB_12_ALT
    ; first release of the enter alt keymap key
    ; reset 'get out' timer and set bit variables to enable check
    ; for second press/release

    MOVLW    H'0F'        ; x0.5 sec = 7.5 sec
    MOVWF    LastKeyTime    ; (re)set lastkey timer ( used to get out of altkeymap automaticly)    

    BSF    _InAltKeymap    ; yep the first time, then set flag that we are now
                ; waiting for a second press/release to exit alt key map
                ; all keys are released before exiting altkeymap

    ;***** Example snippet(one line) to be paired with code in CHECK_KEY_STATE where I
    ; forced numlock status to be off while enetering the alt keymap
    ; but have not yet released the alt keymap toggle key.
    ; this code will be called at the first release of this key. Used
    ; to restore numlock status.
    ; As said before, do not use if implifications are not known !

    ;BSF    _NumLock    ; and also force numlock to be 'on'
                ; as it is set to 'off' when we enter altkeymap
                ; we must set it 'back'
    
    RETURN    
        
KB_DEBOUNCE_34:    
    ; debounce columns 3 & 4
    DEBOUNCE_BYTE    kbColumnVal,kbColumn34_New,kbColumn34Cnt,kbColumn34State

    MOVF    kbColumn34_New,W    ; get debounced sample
    XORWF    kbColumn34_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn34_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_34    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_34_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,3    ; set bit 3 for table read ( column 3 & 4 )

    ;BCF    _isBreak    ; clear break flag
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses 
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_34_NEXT

KB_LOOP_34_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_34_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_34_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_34    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_34

KB_34_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn34_New,W ; get new status
    MOVWF    kbColumn34_Old    ; and store it..
    RETURN    


KB_DEBOUNCE_56:    
    ; debounce columns 5 & 6
    DEBOUNCE_BYTE    kbColumnVal,kbColumn56_New,kbColumn56Cnt,kbColumn56State

    MOVF    kbColumn56_New,W    ; get debounced sample
    XORWF    kbColumn56_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only that 'a change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn56_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_56    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_56_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,4    ; set bit 4 for table read ( column 5 & 6 )
    
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses 
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_56_NEXT

KB_LOOP_56_SKIP
    RLF    kbTemp,F    ; rotate so we read next key

KB_LOOP_56_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_56_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_56    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_56

KB_56_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn56_New,W ; get new status
    MOVWF    kbColumn56_Old    ; and store it..
    RETURN    

KB_DEBOUNCE_78:    
    ; debounce columns 7 & 8
    DEBOUNCE_BYTE    kbColumnVal,kbColumn78_New,kbColumn78Cnt,kbColumn78State
    
    MOVF    kbColumn78_New,W    ; get debounced sample
    XORWF    kbColumn78_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits. ( 7-0 )
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn78_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_78    
    CLRF    Offset        ; clear offset counter ( for table read )
    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_78_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,4    ; set bit 3,4 for table read ( column 7 & 8 )
    BSF    Offset,3    ; 
    
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released


    CALL    KB_SEND_KEY    ; send key code/s make/break uses 
                ; Offset, and _isBreak vars

    GOTO    KB_LOOP_78_NEXT
    
KB_LOOP_78_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_78_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_78_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_78    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_78

KB_78_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn78_New,W ; get new status
    MOVWF    kbColumn78_Old    ; and store it..
    RETURN    

    
; ***********************************************************************

;  LOOKUP_KEY -  lookup table for key scancodes.
;         Returns a scancode in w 
;         Sets carry if key is extended
;         NOTE: If key R3 C1 has been pressed longer than keyrepeat delay 
;         AND keyrepeat is disabled ( jumper on pin RB3 ) THEN the 
;          bit _AltKeymap is set and we can return an alternative scancode in W
;        

LOOKUP_KEY    ; lookup table for the keys ( 32 keys x 4 lines + 1 = 129 lines )
        ; keys are labelled Rx - Cy where x = row number and y = column number
        ; handles a 4 row x 8 column keyboard = 32 keys
        
    MOVWF    PCL      ; add to program counter          
; R1 - C1 i.e. key 1
    NOP
    NOP
    NOP    
    RETLW    H'05'    ; scan code 'F1'
; R2 - C1 i.e. key 2
    NOP
    NOP
    NOP    
    RETLW    H'0C'    ; scan code 'F4'
; R3 - C1 i.e. key 3
    ; The famous alternative keymap toggle key !!! 😉
    ; It is adviced that this key does not use an alt scancode
    ; makes things cleaner and simplified.
    ; IF USED though, remember that a 'soft' release code must be sent
    ; for this key when entering the altkeymap !( + 'soft' make code for the alternative key )
    ; This as the key is pressed when entering altkeymap
    ; which makes the bit _Altkeymap be set, and hence when released
    ; the release code for this 'normal' key will never be sent
    ; instead the release code for the alternative key will be sent.
    ; To 'fix' this put the release code at the end of CHECK_KEY_STATE ( where the
    ; alt keymap bit is set ).i.e. break prefix+scancode for normal key.
    NOP
    NOP
    NOP
    RETLW    H'83'        ; scan code for F7 ( in 'normal' mode )
; R4 - C1 i.e. key 4
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'76'        ; send scancode for 'ESC'  instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'6B'        ; scan code 'arrow left' '<-'
; R1 - C2 i.e. key 5
    NOP
    NOP
    NOP    
    RETLW    H'06'    ;  scan code 'F2' hex06
; R2 - C2 i.e. key 6
    NOP
    NOP
    NOP    
    RETLW    H'03'    ; scan code 'F5'
; R3 - C2 i.e. key 7
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'0D'        ; send scancode for 'horizontaltab' HT instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'75'        ; scan code 'arrow up' '^'
; R4 - C2 i.e. key 8
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'14'        ; send scancode for 'left ctrl' instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'72'        ; scan code 'arrow down' 
; R1 - C3 i.e. key 9
    NOP    
    NOP    
    NOP    
    RETLW    H'04'    ; scan code 'F3'
; R2 - C3 i.e. key 10
    NOP    
    NOP    
    NOP    
    RETLW    H'0B'    ; scan code 'F6'
; R3 - C3 i.e. key 11
    NOP    
    NOP    
    NOP    
    RETLW    H'0A'    ; scan code 'F8'
; R4 - C3 i.e. key 12
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'11'        ; send scancode for 'left alt' instead
    BSF    STATUS,C ; set carry ( i.e. extended code )
    RETLW    H'74'    ; scan code 'arrow right' '->'
; R1 - C4 i.e. key 13
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'6C'        ; send scancode for numeric '7' instead
    NOP    
    RETLW    H'3D'    ; scan code '7'
; R2 - C4 i.e. key 14
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'6B'        ; send scancode for numeric '4' instead
    NOP    
    RETLW    H'25'    ; scan code '4'
; R3 - C4 i.e. key 15
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'69'        ; send scancode for numeric '1' instead
    NOP    
    RETLW    H'16'    ; scan code '1'
; R4 - C4 i.e. key 16
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7B'        ; send scancode for numeric '-' instead
    NOP    
    RETLW    H'4A'    ; scan code '-' minus ( swe kbd )
; R1 - C5 i.e. key 17
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'75'        ; send scancode for numeric '8' instead
    NOP    
    RETLW    H'3E'    ; scan code '8'
; R2 - C5 i.e. key 18
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'73'        ; send scancode for numeric '5' instead
    NOP    
    RETLW    H'2E'    ; scan code '5'
; R3 - C5 i.e. key 19
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'72'        ; send scancode for numeric '2' instead
    NOP    
    RETLW    H'1E'    ; scan code '2'
; R4 - C5 i.e. key 20
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'45'        ; scan code '0' ( from keypad ) normal key 
    BSF    STATUS,C     ; set carry ( i.e. extended code ) 
    RETLW    H'1F'        ; alt keycode ( windows start menu activate )
; R1 - C6 i.e. key 21
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7D'        ; send scancode for numeric '9' instead
    NOP    
    RETLW    H'46'    ; scan code '9'
; R2 - C6 i.e. key 22
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'74'        ; send scancode for numeric '6' instead
    NOP    
    RETLW    H'36'    ; scan code '6'
; R3 - C6 i.e. key 23
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7A'        ; send scancode for numeric '3' instead
    NOP    
    RETLW    H'26'    ; scan code '3'
; R4 - C6 i.e. key 24
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'49'        ; scan code '.' ( swe kbd ) normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'4A'    ; send scancode for numeric '/' instead
; R1 - C7 i.e. key 25
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'79'        ; send scancode for numeric '+' instead
    NOP    
    RETLW    H'4E'    ; scan code '+'
; R2 - C7 i.e. key 26
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'66'        ; scan code 'back space' BS, normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'71'    ; send scancode for 'delete' instead
; R3 - C7 i.e. key 27
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'5A'        ; scan code 'enter', normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'5A'    ; send scancode for numeric enter instead ( note ! extended )
; R4 - C7 i.e. key 28
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'5A'        ; scan code 'enter', normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'5A'    ; send scancode for numeric enter instead ( note ! extended )
; R1 - C8 i.e. key 29
    NOP    
    NOP    
    NOP    
    RETLW    H'2C'    ; scan code 't'
; R2 - C8 i.e. key 30
    NOP    
    NOP    
    NOP    
    RETLW    H'24'    ; scan code 'e'
; R3 - C8 i.e. key 31
    NOP    
    NOP    
    NOP    
    RETLW    H'1B'    ; scan code 's'
; R4 - C8 i.e. key 32
    NOP    
    NOP    
    NOP    
    RETLW    H'2C'    ; scan code 't'

    END

Compartilhar este post


Link para o post
Compartilhar em outros sites

Algum motivo pra não mencionar a falha que o mplab mostra?

Se alguém analisar o assembly todo deste código pra localizar a falha pra você, este alguém é só mesmo o chucky norris.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Isadora é o que eu queria saber porque quando eu coloco esse codigo no mplab ele da erro ......eu peguei esse codigo em um site e copiei e colei ele no mplab mas eu não consigo fazer build ele falha!!! eu queria saber o que to fazendo de errado pois to iniciando me programação!! isadora com todo respeito você é linda meu amor e queria que você me ajuda-se pois ja vai fazer meses e ninguem me ajuda!! fico super triste pois eu quero aprender...eu t peço ...te imploro ajuda ....se você é programadora me ajuda por favor ja vai fazer tempo que to tentado obter esse codigo mas ninguem explica nada a respeito disso!!! obrigado 

Compartilhar este post


Link para o post
Compartilhar em outros sites
4 horas atrás, marcoxr365 disse:

eu quero aprender

Excelente decisão amigo mas escolheste o código errado pra iniciar. Penso que você está atropelando a etapa do pisca led. Este código daí é bem complexo pro 'nosso' nível muito mais em assembly...

 

4 horas atrás, marcoxr365 disse:

ele falha!!!

pois é amigo.. xeu explicar melhor pra você fazer o mesmo... tenta publicar o que o mplab diz. De repente dá uma noção.

Compartilhar este post


Link para o post
Compartilhar em outros sites

esse                                      1qq.jpg2a.jpg.a765481adb0efbe8bb449bc1a441348a.jpg

Compartilhar este post


Link para o post
Compartilhar em outros sites

Amigo é sério: faça primeiro um led piscar. Mas vejamos...

O programa sequer achou o #include e por isso não consegue definir os nomes dos registros. Prestenção pois vou explicar uma vez só...

INCLUDE <C:\PROGRAM\MPLAB\P16F84.INC> ; this might need changing !

Além do + ele precisar ser alterado ou adaptado pra 'realidade' do seu mc

 

Ou seja você precisa de noções mais elementares que acho que teremos dificuldades pois na minha idade não consigo mais ... trocar fraldas kk... 😁. Calma... brinc... pero no mucho. Outra coisa era que você poderia copiar e colar o texto e não necessariamente a tela. Por acaso notou que falta informações? Tá vendo? 💩 kk

 

Mais uma vez...

led.gif

Compartilhar este post


Link para o post
Compartilhar em outros sites

Mais um detalhe,esquece o 16F84,não fabrica a anos...tente o 16F628.

O MPLAB funciona com 'projeto',não adianta colar o código no corpo vazio,até compila,mas voce tem que ter conhecimento.

Aprenda a usar o CCS.

 

  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites

Trocar fraldas kk ......kkkkk eu t amo!!! mas meu amor como falei to começando!!

eu ja coloquei varios leds para piscar mas o meu problema é que tava atrás desse codigo de teclado emulador ps2 ae encontrei ele na internet só que não to conseguindo fazer o make e concluir passando o hex para o pic e terminar 

ainda mais que peguei essa droga de chicongunha e to mal meu amor rsrs

Isadora faz pra mim por favor faz o hex pra mim passar pro pic e me diz em qual setor mudar os numeros e letras do teclado por favor meu amor to t implorando me ajuda! e fala pro VTRX que to estudando pra ficar fera igual a ele rsrsr

 

 

Mais um detalhe,esquece o 16F84,não fabrica a anos...tente o 16F628.

O MPLAB funciona com 'projeto',não adianta colar o código no corpo vazio,até compila,mas voce tem que ter conhecimento.

Aprenda a usar o CCS.___VTRX você é o Professor e a Isadora Tb rs me ajuda com esse codigo por favor eu to implorando a vcs me ajudem ......abraço pra vcs porque to mal com essa chicongunha Dói tudo 

 

image.png

Compartilhar este post


Link para o post
Compartilhar em outros sites

Fiz só pela adrenalina...

 

1.png

Sucesso mas (sempre tem um mas) tem alguns warnings: Não aloca determinada área de memória...

3.png

Se achar que deve, continue os estudos, algo como tenta outra área de memória, outro banco e etc.

2.png

 

Ou, caso o amigo @vtrx sentir vontade, poderá te orientar no assembly. Na verdade, na configuração do mplab que penso que foi atualizado seu assembler. @vtrx perceba que é o 9º byte do começo da alocação que ele acha que é inválido. Será que aloca só de 8 em 8? Bem... só percebi esta peculiaridade...

 

Anexo o zip com tudo contudo não deve dar muito certo. Deu uns errinhos no ctrl-c ctrl-v e tive que ajustar. Consulte mais o site de onde veio isso. Contate o autor e etc.

Já está ajustado pro pic16f628A. Tenta a sorte aí. E modificar pra você, sem chance. Contente-se com esta mamadeira! E esqueça que eu existo!

Sucessos e boa recuperação😄

 

 

 

cdh.zip

  • Obrigado 1

Compartilhar este post


Link para o post
Compartilhar em outros sites

Update...

Chance de sucesso ultrapassa 99%. Remapeei o endereço de acordo com d.s. do 628. A ram começa em 0x20 e não em 0x0c. Troque aí no fonte e reassemble.

 

;
;               PC-Keyboard emulator using a PIC16F628A ...
;     Scan a 32 key keyboard ( with alternative mapping = 64 keys )
;            

;                 COPYRIGHT (c)1999 BY Tony K&uuml;bek && I.F.  CDH 2019
; This is kindly donated to the PIC community. It may be used freely,
;     and you are forbidden to deprive others from those rights.   
;    Please read the FSF (Free Software Foundation) GNU statement.
;
;*********************************************************************
;
;            E-MAIL    tony.kubek@flintab.se
;        
;        
; DATE            2000-01-23
; ITERATION        1.0B
; FILE SAVED AS        PiCBoard.ASM    
; FOR            PIC16F84-10/P        
; CLOCK            10.00 MHz RESONATOR ( OSC-HS )
; INSTRUCTION CLOCK    2.50 MHz T= 0.4 us
; SETTINGS        WDT-ON, PWT-ON, CP-OFF
; REVISION HISTORY    
;            0.1b -    First beta, mainly for testing
;            1.0b -     First public beta release
;
;
;
;
;***************************************************************************
;
;                PREFACE 😉
;
;    This is NOT an tutorial on pc keyboards in general, there are quite
;    a few sites/etc that have that already covered. However i DID find
;    some minor ambiguities regarding the actual protocol used but nothing
;    that warrants me to rewrite an complete pc keyboard FAQ.
;    So for 'general' pc keyboard info ( scancodes/protocol/pin config/etc )
;    here are some useful links:
;
;   http://www.senet.com.au/~cpeacock/     
;   http://ourworld.compuserve.com/homepages/steve_lawther/keybinfo.htm
;   http://204.210.50.240/techref/default.asp?url=io/keyboard.htm
;   http://www.arne.si/~mauricio/PIC.HTM
;    
;    PLEASE do not complain about code implementation, I know there are parts
;    in which is it 'a bit' messy and hard to follow, and other parts where
;    the code is not optimised. Take it as is. Futhermore I did hesitate
;    to include all of the functionality thats currently in, but decided to
;    keep most of it, this as the major complaint i had with the other available
;    pc-keyboard code was just that - 'is was incomplete'. Also do not
;    be discoraged by the size and complexity of it if you are an beginner,
;    as a matter of fact this is only my SECOND program ever using a pic.
;    I think I managed to give credit were credit was due ( 'borrowed code' ).
;    But the originators of these snippets has nothing to do with this project
;    and are probably totally unaware of this, so please do not contact them
;     if you have problems with 'my' implementation.
;
;    BTW ( shameless plug ) UltraEdit rules ! ( if you dont have it, get it ! )
;   http://www.ultraedit.com ( dont 'forget' the 'wordfile' for PIC, color indenting )
;    Without that I guess this file would be 'messy'.
;
;    Ok with that out of the way here we go:
;    
;
;***************************************************************************

;                DESCRIPTION ( short version 😉 )

;    A set of routines which forms a PC keyboard emulator.
;    Routines included are:
;    Interrupt controlled clock ( used for kb comm. and 'heart beat )
;    A 4x8 matrix keyboard scanner ( using an 74HCT4051 3 to 8 analogue multiplexer )
;    Communincation with PC keyboard controller, both send end recive.

;     PC keyboard routines are using 4 pins (2 inputs & 2 outputs) to control
;     keyboard's 2 bidirectional OC lines (CLK & DATA). The following
;     'drawing' conceptually shows how to connect the related pins/lines
;
;    ( ASCII art/info shamelessly 'borrowed' from http://www.arne.si/~mauricio/PIC.HTM )
;
;                 vcc    vcc
;                  |      |
;                  \     -+-
;                  / 2K2    /_\  1N4148
;                  \      |
; pcCLOCK_in -----------------o---o------o---------   kbd CLOCK
;                  |   |     |
;             2N2222   |50pF -+-
; pcCLOCK_out ____/\/\/\____|/     ===    /_\
;            2K2     |\>   |     |
;                  |   |     |
;                 /// ///    ///
;
;     An identical circuit is used for the DATA line.
;    Note: The 2N2222 transitors can be replaced with BC337 ( NPN ) which are cheaper
;    The keyboard matrix routines are using RB4-RB7 as inputs.
;    and RB0-RB2 as output to/from an 3 to 8 multiplexer
;    so that it can read up to 4x8 keys ( 32 ).
;
;    RA4/TOCK1 is an input from an jumper, if low then keyrepeat is disabled and
;    alt-key is enabled instead i.e. instead of repeating a key that has
;    been depressed a certain amount of time, a bit is set that can change the
;    scancode for a key ( of course, all keys can have an alternate scancode ).
;    To exit the alt. keymap press the 'enter alt. keymap' key once again or wait until
;    timeout. ( defined in the code )
;    NOTE !! The so called 'enter alt. keymap' key is hardcoded, i.e the key
;    i've choosen in this 'example' is Column 1 Row 2, if this is to be changed
;    code has to be changed/moved in the debounce and checkkeystate routines.
;    ( marked with <-------Alt keymap code-------> )
;     RB3 is currently used for a flashing diode. ( running )
;    Note my real keyboard ( KeyTronic ) uses a clock rate of 40 us, my implementation
;     uses about 50 us, easily to change.
;

;***************************************************************************

;            DESCRIPTION ( longer version 😉 )
;
;    Pin     Used for
;    ------------------------------------------------------------
;    RA0    Pc keyboard data out ( to pc )
;    RA1    Pc keyboard data in ( from pc )
;    RA2    Pc keyboard clock in
;    RA3    Pc keyboard clock out
;    RA4    Flashing disco led 0.5s duty cycle( could be used for some more meaningful purpose )
;
;    RB0    Least significant bit in column adress to 4051 multiplexer ( own keyboard )
;    RB1    Middle...
;    RB2    Most significant bit -- || --
;    RB3    Input. Disable keyrepeat & enable alternative keymapping ( if = '0',ground)
;        OR if '1' ( free,+V ) enable keyrepeat & disable alternative keymapping
;    RB4    Own keyboard input row 1
;    RB5    --- || --- row 2
;    RB6    --- || --- row 3
;    RB7    --- || --- row 4
;
;    'Basic program structure':
;
;    Init    -    Initialise ports , ram, int, vars
;    Start delay -     After init the timer int is enabled and the flashing led will
;            start to toggle ( flash ). Before I enter the mainloop
;            ( and send any keycodes ) I wait until the led has flashed
;            twice.    This is of course not really needed but I normally
;            like to have some kind of start delay ( I know 1 sec is a bit much 🙂 )
;    Time Int    -    The timer interrupt (BIG), runs in the background:
;            - 'Normal' rate 0.5 ms, when in rx/tx rate is 27 us
;             - Performs an rx/tx check, then jumps to rx, tx or 'heart beat' code.
;             - TX code sends a byte to pc, at a rate of 27us per int.
;               The int rate is actually double the bit rate, as
;               a bit is shifted out in the middle of the clock pulse,
;               I've seen different implementations of this and I think
;               that the bit is not sampled until clock goes low again BUT
;               when logging my keyboard ( Keytronic ) this is the way that it
;                  does it. When all bits are sent, stopbit/parity is sent.
;               And the key is removed from the buffer.
;               After stopbit/parity is sent, a delay is inserted ( 0.5 ms )
;               before next rx/tx check.
;             - RX code recevies a byte from the pc, PIC is controlling clock !!
;               Int rate ( 27 us ) is, again, double bit rate.
;               Toggles clock and samples the data pin to read a byte from pc.
;               When reception is finished an 'handshake' takes place.
;               When a byte has been recevied a routine is called to check
;               which command and/or data was received. If it was
;               keyboard rate/delay or led status byte, it is stored in local ram
;               variables. NOTE: The rate/delay is not actually used for
;               key repeat as the code is right now ( I use my 'own' fixed rate/delay )
;               however it is very easy to implement.
;               After handshake a delay is inserted ( 0.5 ms )
;               before next rx/tx check.
;            - 'Heart beat' ( idle code 0.5 ms tick ) performs:
;             - Check clock/data lines to see if pc wants to send something or
;               if tx is allowed.
;             - If tx is possible it checks the keybuffer for an available key and
;               if keys are in buffer then it initiates a tx seq.
;               and sets the int rate to 27 us.
;             - If the pc wants to send something, an rx seq. is initiated
;               ( there is some handshaking involved, during which
;               the int rate is set to 60 us ) after that, the int rate is
;               set to 27 us and an rx seq is started.
;             - Divides some clock counters to achive 10ms,100ms,500ms sections.
;             - In 100 ms section it performes a numlock status check and
;               keyrepeat check ( both rate and delay is local in 100 ms ticks,
;               thats why I dont use the 'real' rate delay )
;              - If numlock status is not the desired one code is called to
;                toggle the numlock status.
;              - If a key has been pressed long enough for repeat, an bit is set
;                so we can repeat the key ( send the scancode again ) in the main loop.
;            - In 500 ms section the led is toggled on each loop
;              - Some various alternative keymap checks to get out of
;                alternative keymap. ( i'll get to that in a bit )
;
;    Main loop    - Outputs an adress to the 4051 multiplexer waits 1 ms
;              reads the row inputs ( 4 bits/keys ), increments address
;              and outputs the new adress, waits 1 ms and reads the input
;               ( next 4 bits/keys ). Now using the address counter, calls a
;               debounce/send routine that first debounces the input,
;              ( four consecutive readings before current state is affected )
;              and when a key is changed a make/break code is sent ( put in buffer ).
;              In the next loop the next two columns are read etc. until all
;              4 column pairs are read.
;            - If keyrepeat is enabled ( see pin conf. above ) the
;              repeat flag is checked and if '1' the last pressed key scancode
;              is sent again ( put in buffer ).
;            - If keyrepeat is not enabled( alternative keymap is enabled instead )
;              then various checks to exit the alternative keymap are performed instead.
;
;    Scancodes for all key are located in a lookup table at the end of this file,
;    each key has four program rows, to make room for extended codes and alt. keymap codes.
;
;     Explanation of 'alternative' keymap:
;    
;    Using this program ( or an heavily modified version of it anyway 🙂 )
;     on a computer running Windows posed some small problems; namely:
;    -The keyboard ( mapping ) I used did not have any 'special' key such as
;     <alt>,<ctrl>,<tab> etc.
;    -In windows, things can go wrong, 🙂 if a dialog pops up or something similar
;     there were just no way one could dispose of this with the keymapping i used.
;    - 'Only' 28 keys were implemented ( hardware wise ).
;    In this particular case the keyrepeat was disabled ( due to the nature of the application )
;    Therefore i came up with the solution to use the keyrepeat related routines and vars.
;    To handle a so called 'alternative' keymapping.
;    This means that an key is dedicated to be the alt. keymap toggle key,
;    when pressing this longer than the programmed repeat delay, instead of
;    repeating the key a bit variable is set to use another set of scancodes for
;    the keyboard. This 'alternative' keymap is then enabled even though the
;    alt. keymap toggle key is released, but it also incorporates an timeout
;    that will return to normal keymap if no key is pressed within a certain time ( currently 7 sec )
;    Of course pressing the alt. keymap toggle key again ( while in alt. keymap )
;    will return the keyboard to the normal keymap.
;    NOTE !! the key choosen for the alt. keymap toggle is hardcoded, so if the scancode
;    for this is changed in the lookup table, changes have to be made in other routines
;    as well. ( namly : CHECK_KEY_STATE, and DEBOUNCE_COLUMN routines )
;    While in alt. keymap each key CAN have an alternative scancode ( see lookup table ).
;    Also by using the numlock bit, one can toggle numlock status 'on the fly' when entering
;    or exiting the alt keymap.
;
;    Some notes about the local keyboard interface ( matrix 😞
;    Although the hardware circuit and software allows virtually unlimited
;    simultaneosly pressed keys, the keyboard matrix itself normally poses
;    some limitations on this. If an keymatrix without any protective diodes
;    are used then one would have loops INSIDE the keymatrix itself when
;    multiple keys are pressed in different columns .
;    Look at the ( although horrible 😉 ) ASCII art below(internal weak pullup enabled):
;    0 - Key is free
;    1 - Key is pressed ( connection between hor/ver rows )
;    Three keys pressed adressing ( reading ) left column :
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    --------1-------1------ ( row 2 )
;                    |       |
;     To pic3    --------1-------0------ ( row 3 )
;                   |       |
;        Column(4051)    0V    -    ( Current column is set to 0V when adressing )
;
;    This works as intended, we can read a '0' on pic inputs 2,3 which
;    is what we expected. The current ( signal ) follows the route marked with '*':
;    ( only the 'signal path' is shown for clarity )
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    *********-------1------ ( row 2 )
;                    *       |
;    To pic3    *********-------0------ ( row 3 )
;                   *       |
;        Column(4051)    0V    -    ( Current column is set to 0V when adressing )
;
;    However, when we now read ( address ) the right column instead we
;    do not read what is expected ( same three keys still pressed 😞
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    *****************------ ( row 2 )
;                    *<-     *
;    To pic3    *********-------*------ ( row 3 )
;                   |       *
;        Column(4051)    -    0V    ( Current read column is set to 0V when adressing )
;
;    As you can see the 'signal' travels in the keymatrix itself ( where the '<-' is )and
;     will cause a 'ghost' signal to be read on the pic. So instead
;    of having an '0' on input 2 only we also can read an '0' on input 3.
;    This is because the two keys in column 1 are interconnected ( when they are pressed ).
;    Keep this in mind if you are planning to support multiple pressed keys.
;    
;
;***************************************************************************
;
;    Some suggestions for 'improvements' or alternations
;
;    - Using the jumper 'disable-repeat' as a dedicated key for switching
;      to alternative keymapping.
;    - Enable repeat in alternative keymapping
;    - Clean up TX/RX code ( a bit messy )
;    - Using the led output ( or jumper input ) as an extra adress line
;      to the multiplexers ( in this case 2 pcs. 74HCT4051 ) we could have
;      4x16 keys instead. Would require some heavy modifications though
;      as there are not much ram/program space left. But if alternative
;        keymapping is discarded ( most likely if one has 64 keys ) each
;      key in the lookup table only needs to be 2 lines instead of 4.
;      That would 'only' require some modifications to preserv ram.
;    - Using the EERAM for 'macros' or similar ( not used at all now )
;
;***************************************************************************
;
;            LEDGEND
;
;    I tend to use the following when naming vars. etc. :
;    ( yes i DO like long names )
;    
;    For 'general' purpose pins:
;
;    An input pin is named I_xxx_Name where :
;
;        I_   - This is an input pin 😉
;        xxx_ - Optional what type of input, jmp=jumper etc.
;        Name - Self explanatory
;
;    An output pin is named O_xxx_Name where:
;    
;        O_   - This is an output pin 😉
;        xxx_ - Optional what type of output, led=LED etc.
;        Name - Self explanatory
;
;    Application(function) specific pins:
;
;    An application(function) specific pin is named xxName where:
;        
;        xx   - What/Where, for example pc=To/From pc
;        Name - Self explanatory ( what does it control etc )
;
;    An #define/constant/equ (no pin or ram ) uses all capital letters e.x. #define BREAK 0xF0
;
;    A bit variable will always start with '_'. For example '_IsLedStatus'
;
;    All other ( mostly ramvars. ) are named in various ways.
;
;
;***************************************************************************

    TITLE "PC Keyboard emulator - By Tony K&uuml;bek"

;        Processor       16F628a
;        Radix   DEC
;        EXPAND

    

;***** HARDWARE DEFINITIONS ( processor type include file )

    INCLUDE <C:\Arquivos de programas\Microchip\MPASM Suite\p16f628a.inc>           ; this might need changing !

;***** CONFIGURATION BITS

    __CONFIG _WDT_ON&_HS_OSC&_CP_OFF&_PWRTE_ON        ; _WDT_OFF
    
    __IDLOCS 010Bh    ; version 1.0B

;***** CONSTANT DEFINITIONS

    CONSTANT    BREAK = 0xF0        ; the break key postfix ( when key is released )
    CONSTANT    EXTENDED = 0xE0     ; the extended key postfix

    ; As i dont really use the rate/delay I receive from the pc ( easy to change )
    ; this is the current rate/delay times i use:
    CONSTANT    DELAY_ENTER_ALTKEYMAP = 0x1E    ; x100 ms , approx 3 seconds ( 30 x 100 ms )
                            ; how long the 'enter altkeymap' key must
                            ; be in pressed state before the altkeymap is enabled
    CONSTANT    DELAY_EXIT_ALTKEYMAP = 0x0F    ; x0.5 sec , approx 7.5 sec
                            ; how long before we exit the alt keymap if no key is
                            ; pressed.
    CONSTANT     DELAY_REPEAT    = 0x08        ; x100 ms, approx 800 ms
                            ; how long before we START repeating a key
    CONSTANT    DELAY_RATE    = 0x02        ; x100 ms, approx 200 ms repeat rate
                            ; how fast we are repeating a key ( after the delay above )
    
;***** CONSTANT DEFINITIONS ( pins )

;    For connection with PC keyboard

#define pcCLOCK_in  PORTA,2   ; Input PC keyboard clock
#define pcCLOCK_out PORTA,3   ; Output PC keyboard clock
#define pcDATA_in   PORTA,1   ; Input PC keyboard data
#define pcDATA_out  PORTA,0   ; Output PC Keyboard data


;     For connection (input) with our own keyboard
; Note I actually dont use these (definitions!) in the program, but they might come in handy
; at one time or another 😉 ( I use the pins though.. )

#define kbROW_1   PORTB,7
#define kbROW_2   PORTB,6
#define kbROW_3   PORTB,5
#define kbROW_4   PORTB,4

;    Indications ( output )

#define O_led_KEYCOMM_ok    PORTA,4        ; communication seems ok led ( flashing )

;    Disable/enable key repeat input jumper

#define I_jmp_NoRepeat    PORTB,3    ; note: internal weak pullup enabled

;    For keybuffer ( using the indirect file selector FSR )

#define KeyBufferHead    FSR


;***** RAM ASSIGNMENT

        
        CBLOCK    0x20
            KeyBufferTail    ; where the last byte in buffer is..
            clkCount         ; used for clock timing
            Offset        ; used for table reads        
            Saved_Pclath       ; Saved registers during interrrupt
            Saved_Status       ; -----
            Saved_w            ; -----
            CurrKey        ; current key ( rx or tx )..
            KeyParity    ; key parity storage ( inc. for every '1' )
            Divisor_10ms    ; for the timer
            Divisor_100ms   ; ditto
            Divisor_500ms   ;
            Divisor_Repeat  ; timer for repeated key sends

            Flags        ; various flags
            RepeatFlags     ; flags for repeating a key
            bitCount    ; bitcounter for tx/rx
            Comm_Flags    ; flags used by both rx and tx routines
            Temp_Var    ; temp storage, can be used outside int loop
            TRX_Flags    ; flags used by both rx and tx routines
            CommandData     ; bit map when receving data bytes from pc
                    ; for example led status/ delay / etc
            KbLedStatus     ; to store status led bitmap for keyboard ( pc first sends 'ED' then this byte )
                        ;  bit 0=Scroll lock  ( 1=on )
                        ;  bit 1=Num lock
                        ;  bit 2=Caps lock
                        ;  bits 3-7 = unused
            KbRateDelay    ; to store repeat delay/rate ( pc first sends 'F3' then this byte )
                        ;  bit 0-4 (rate) = '00000' is 30x/sec '11111' is 2x/sec ( i.e. value * 33 ms )
                        ;  bit 5-7 (delay)= '00' is 250 ms, '11' is 1000 ms ( ie. value * 250 ms )   
                    ;  bit 7 = unused
            BufTemp        ; temp byte for storing scancode to put in buffer
            Temp        ; temp byte, used locally in buffer routine
            Temp2        ;
            LastKey        ; stores the last sent key
            KbBufferMin    ; where our keybuffer starts
            Kb1        ; used in keybuffer
            Kb2        ; used in keybuffer
            Kb3        ; used in keybuffer
            Kb4        ; used in keybuffer
            Kb5        ; used in keybuffer
            Kb6        ; used in keybuffer
            Kb7        ; used in keybuffer
            Kb8        ; used in keybuffer
            Kb9        ; used in keybuffer
            Kb10        ; used in keybuffer
            KbBufferMax    ; end of keybuffer
            TempOffset    ; temporary storage for key offset ( make/break )

            LastMakeOffset  ; storage of last pressed key ( offset in table )
            RepeatTimer    ; timer to determine how long a key has been pressed
            RepeatKey     ; the key to repeat
            repTemp        ; temporary storage in repeat key calc.
            repKeyMap    ; bit pattern for the column in which the repeat key is in
                    ; i.e. a copy of kbColumnXX_Old where 'XX' is the column
            LastKeyTime    ; counter when last key was pressed, used to get out of altkeymap
                    ; after a specific 'timeout'

            kbScan        ; scan code for pressed/released key
            kbTemp        ; temp storage for key states

            kbState        ; which keys that has changed in current columns
            kbBitCnt    ; bit counter for key check ( which key/bit )            
            
            kbColumnCnt    ; column counter ( loops from 8 to 0 )
                    ; Used as output to multiplexer/decoder and in debounce routines

            kbColumnVal    ; current value of the last 2 columns ( 2x4bits = 8 bits ) input pins ( keys )
                    ;
                    ; Note the kbColumnXX_New variables is not really needed
                    ; used it while making the program ( debugging 😉 ).
                    ; so if more free ram is needed change code using these to use
                    ; the current 'input' sample instead. ( kbColumnVal )
            kbColumn12_New  ; New debounced reading for column 1 & 2
            kbColumn12_Old  ; Latest known valid status of column 1 & 2
            kbColumn12Cnt    ; Debounce counter for column 1 & 2
            kbColumn12State ; State of debounce for column 1 & 2

            kbColumn34_New  ; New debounced reading for column 3 & 4
            kbColumn34_Old  ; Latest known valid status of column 3 & 4
            kbColumn34Cnt    ; Debounce counter for column 3 & 4
            kbColumn34State ; State of debounce for column 3 & 4

            kbColumn56_New  ; New debounced reading for column 5 & 6
            kbColumn56_Old  ; Latest known valid status of column 5 & 6
            kbColumn56Cnt    ; Debounce counter for column 5 & 6
            kbColumn56State ; State of debounce for column 5 & 6

            kbColumn78_New  ; New debounced reading for column 7 & 8
            kbColumn78_Old  ; Latest known valid status of column 7 & 8
            kbColumn78Cnt    ; Debounce counter for column 7 & 8
            kbColumn78State ; State of debounce for column 7 & 8

        ENDC

;******* END RAM

; *** flags used by both rx and tx routines
#define _isParity    Comm_Flags,0    ; bit in rx/tx is parity bit
#define _KeyError    Comm_Flags,1    ; set to '1' when an error is detected
#define _isStartBit    Comm_Flags,2    ; set to '1' when bit in rx/tx is startbit
#define _isStopBit    Comm_Flags,3    ; --- || --- but stopbit
#define _RxCanStart     Comm_Flags,4    ; for handshaking, when negotiating an rx seq.


; *** dito used for rx and tx
#define _KeyReceived    TRX_Flags,0    ; rx
#define _RX_Mode    TRX_Flags,1    ; rx is in progress ( started )
#define _doRXAck    TRX_Flags,2    ; do rx handshake
#define _RXAckDone    TRX_Flags,3    ; rx handshake is done
#define _RXEnd        TRX_Flags,4    ; rx seq is finished
#define _RXDone        TRX_Flags,5    ; rx exit bit
#define _KeySent    TRX_Flags,6    ; tx key has been succesfully sent
#define _TX_Mode    TRX_Flags,7    ; tx is in progress ( started )


; *** flags to determine the meaning of an incomming data is
; i.e. when then pc has sent a 'data follow' instruction
; these bits are set according to the command,
; i.e. the next incomming byte is.....
#define _IsLedStatus    CommandData,0    ; the next incoming byte contains kb led status
#define _IsRateDelay    CommandData,1    ; the next incoming byte contains kb rate/delay
#define _SkipByte    CommandData,2    ; other data not saved
 
; *** bit meaning in KbLedStatus ( the leds on the keyboard )
; i.e local ram copy of this byte
#define _LedScrollLock    KbLedStatus,0    ; '1' led is on
#define _LedNumLock     KbLedStatus,1    ;
#define _LedCapsLock    KbLedStatus,2    ;
#define _IsFirstLedStatus KbLedStatus,7    ; set this to '1' at startup, to know that our local
;                    ; copy (led status) is not yet syncronised. Used to
;                    ; 'force' sync. the first time we set the local numlock bit.

; *** flags used for various purposes
#define _Timer        Flags,0 ; used for waiting
;#define _WrongPar    Flags,1    ; ?? ( used during dev. to force wrong parity sends, removed )
#define _LastColumn    Flags,2    ; for kb scan code
#define _isBreak    Flags,3 ; if '1' the currently handled key is break ( released ), else make ( pressed )
;#define _isFree         Flags,4 ; Not used ! ( temporary during dev. )
#define _AltKeymap      Flags,5 ; is we use alternative keymap ( other scancodes ) see below !
                ; enabled ONLY when keyrepeat is disabled ( I_jmp_NoRepeat (RB3) is low )
#define _ExitAltKeymap  Flags,6 ; start checking if we should exit alternative keymap
                ; if all keys are 'free' ( not pressed ) we exit altkeymap
#define _InAltKeymap    Flags,7 ; if we are waiting for second press/release to get out of altkeymap


#define _doRepeat    RepeatFlags,0 ; if the last pressed key should be 'repeated'
#define _doSendKey    RepeatFlags,1 ; send the key in RepeatKey to pc     
#define _isExtended     RepeatFlags,2 ; temp flag if last key is extended
#define _RepeatIsExt    RepeatFlags,3 ; the key to repeat is extended key
#define _startRepeat    RepeatFlags,4 ; start repeat timer
#define _DoExitAltKeymap RepeatFlags,5     ; bit set when we are getting out of alternative keymap
#define _NumLock    RepeatFlags,6      ; 'mirror' of numlockstatus, by setting/clearing this bit
                           ; numlock status will be changed.
                    ; I.e. there is no need to 'manually' send break/make code for numlock
                    ; key, by setting this bit to '1' numlock status will by automaticlly
                    ; ( inside int handler ) set to 'on'( within 100 ms ). Se code for example.
#define _WaitNumLock    RepeatFlags,7    ; bit set when we have sent make/break numlock scancode
                    ; and waiting for numlock status byte reply.
                    ; ( to inhibit a new numlock scancode send )


;**************************************************************************    
;                Macros             
;**************************************************************************    
    

;+++++
;    BANK0/1 selects register bank 0/1.
;    Leave set to BANK0 normally.

BANK0    MACRO
    BCF    STATUS,RP0
    ENDM

BANK1    MACRO
    BSF    STATUS,RP0
    ENDM

;+++++
;    PUSH/PULL save and restore W, PCLATH and STATUS registers -
;    used on interrupt entry/exit

PUSH    MACRO
    MOVWF    Saved_w        ; Save W register on current bank
    SWAPF    STATUS,W    ; Swap status to be saved into W
    BANK0            ; Select BANK0
    MOVWF    Saved_Status    ; Save STATUS register on bank 0
    MOVFW    PCLATH
    MOVWF    Saved_Pclath    ; Save PCLATH on bank 0
    ENDM

PULL    MACRO
    BANK0            ; Select BANK0
    MOVFW    Saved_Pclath
    MOVWF    PCLATH        ; Restore PCLATH
    SWAPF    Saved_Status,W
    MOVWF    STATUS        ; Restore STATUS register - restores bank
    SWAPF    Saved_w,F
    SWAPF    Saved_w,W    ; Restore W register
    ENDM


;+++++        
;     We define a macro that will switch an output pin on or off depending
;     on its previous state. We must be on bank0 !!
;

TOGGLE_PIN    MACRO WHICH_PORT,WHICH_PIN
        LOCAL TOGGLE_PIN10, TOGGLE_END
        
        BTFSC    WHICH_PORT,WHICH_PIN    ; is the pin high ?
        GOTO     TOGGLE_PIN10        ; yes, clear it
        BSF    WHICH_PORT,WHICH_PIN    ; no, so set it                                        
            GOTO       TOGGLE_END
TOGGLE_PIN10:
        BCF    WHICH_PORT,WHICH_PIN    ; clear the pin            
TOGGLE_END:
        ENDM


;*************************************************************
; Credit for routine ( almost unchanged, expect it's now a macro 😉 )
; goes to Scott Dattalo http://www.interstice.com/~sdattalo/technical/software/software.html
; ( debouce routine at http://www.interstice.com/~sdattalo/technical/software/pic/debounce.html )
;
;     DEBOUNCE_BYTE - 'debounces' 8 bits, when a bit state
;    has been 'active' for 4 consecutive debounce loops
;    it's state is put into the 'debounced' sample ( i.e. output )
;
; The purpose of this routine is to debounce, i.e. digitally low pass filter
; inputs. The algorithm handles upto 8 bits at a time. An input is considered
; filtered if it has not changed states in the last 4 samples.
;
; 2-bit cyclic vertical counters count the 4 samples. As long as there is no
; change, the counters are held in the reset state of 00b. When a change is detected
; between the current sample and the filtered or debounced sample, the counters
; are incremented. The counting sequence is 00,01,10,11,00... When the counters
; roll over from 11b to 00b, the debounced state is updated. If the input changes
; back to the filtered state while the counters are counting, then the counters
; are re-initialized to the reset state and the filtered state is unaffected.
; In other words, a glitch or transient input has been filtered.
;
; Here's the C-psuedo code:
;
;static unsigned clock_A,clock_B,debounced_state
;
;debounce(unsigned new_sample)
;{
;  unsigned delta;
;
;  delta = new_sample ^ debounced_state;   //Find all of the changes;
;  clock_A ^= clock_B;                     //Increment the counters
;  clock_B  = ~clock_B;
;
;  clock_A &= delta;                       //Reset the counters if no changes
;  clock_B &= delta;                       //were detected.
;
;      //Preserve the state of those bits that are being filtered and simultaneously
;      //clear the states of those bits that are already filtered.
;  debounced_state &= (clock_A | clock_B);
;      //Re-write the bits that are already filtered.
;  debounced_state |= (~(clock_A | clock_B) & new_sample);
;}
;
; The 2-bit counters are arranged "vertically". In other words 8 counters
; are formed with 2 bytes such that the corresponding bits in the bytes are
; paired (e.g. MSBit of each byte is paired to form one counter).
; The counting sequence is 0,1,2,3,0,1,... And the state tables and Karnaugh
; maps are:
;
;State Table:     Karnaugh Maps:
;pres  next      B
; SS  SS         0   1
; AB  AB       +---+---+    +---+---+
;--------   A 0|   | 1 |    | 1 |   |
; 00  01       +---+---+    +---+---+
; 01  10      1| 1 |   |    | 1 |   |
; 10  11       +---+---+    +---+---+
; 11  00      A+ = A ^ B     B+ = ~B
;
; Here's the PIC code that implements the counter:
;       MOVF    SB,W    ;W = B
;       XORWF   SA,F    ;A+ = A ^ B
;       COMF    SB,F    ;B+ = ~B
;  14 instructions
;  15 cycles
; Inputs:
;   NewSample - The current sample
; Outputs
;   DebouncedSample - The current value (filtered version of NewSample)
;
; VARS used
;   DBCnt,
;   DBState - State variables for the 8 2-bit counters
;
    
DEBOUNCE_BYTE MACRO NewSample,DebouncedSample,DBCnt,DBState

        ;Increment the vertical counter        

    MOVF    DBState,W
        XORWF   DBCnt,F        
    COMF    DBState,F

        ;See if any changes occurred        

    MOVF    NewSample,W        
    XORWF   DebouncedSample,W

        ;Reset the counter if no change has occurred        

    ANDWF   DBState,F
        ANDWF   DBCnt,F        ;Determine the counter's state
        MOVF    DBState,W        
    IORWF   DBCnt,W

        ;Clear all bits that are filtered-or more accurately, save
        ;the state of those that are being filtered        

    ANDWF   DebouncedSample,F
        XORLW   0xff        ;Re-write the bits that haven't changed.
        ANDWF   NewSample,W        
    IORWF   DebouncedSample,F        
    
    ENDM

    

;**************************************************************************    
;                 Program Start
;**************************************************************************    


;    Reset Vector

    ORG    H'00'

    ; For the sole purpose of squeezing every last byte of the programming mem
    ; I actually use the 3 program positions before the interrupt vector
    ; before jumping to the main program. Take note though that
    ; ONLY 3 instructions are allowed before the jump to main loop !!

    BANK0

        CLRF    PCLATH
    CLRF    INTCON    

    GOTO    INIT

;**************************************************************************    
;                     Interrupt routine
; An humongously big int handler here 😉
; but the keyboard handling is FULLY in the background, without any intervention
; in the main loop whatsoever. I like the solution anyway.
;
;**************************************************************************


;    Interrupt vector

    ORG    H'04'

INT
    PUSH            ; Save registers and set to BANK 0
                
    
    BTFSS   INTCON,T0IF        ; check if TMR0 interrupt
        GOTO    INTX            ; whoops ! 'unknown' int, should be disabled...
                ; NOTE ! if an 'unknown' int triggers the int routine
                ; the program will loop here for ever 😉 ( as the calling flag is not cleared )

    

    ;+++
    ; Timer (TMR0) timeout either heart beat or tx/rx mode
    ; In 'heart beat mode' we monitor the clock and data lines
    ; at ( roughly )= 0.5 ms interval, we also check the send buffer
    ; if there are any keys to send to pc ( if clock/data levels allows us to )
    ; In tx/rx mode we are controlling the clock/data line = 27 us tick
    ; Note: This software works using both 8Mhz and 10Mhz resonators without modifications
    ; however the 'timing' will then of course be 'off'.

INT_CHECK    
    BCF    INTCON,T0IF     ; Clear the calling flag !
    
     BTFSC    _TX_Mode    ; check if we are in tx mode
    GOTO    INT_TX        ; yep, goto tx mode code..    
    BTFSC    _RX_Mode    ; are we in rx mode ?
    GOTO    INT_RX        ; yep goto rx mode code
    GOTO    INT_HEART_BEAT  ; nope just goto 'heart beat' mode

;*************** TX code start ***********************************

INT_TX
    MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
        MOVWF   TMR0
 
    BTFSS    clkCount,0    ; check if we should toggle clock
    GOTO    INT_DEC_CLOCK    ; bit low,decrement and check if we should toggle data instead
    
    DECFSZ    clkCount,F    ; decrement and check if we are at zero..
    GOTO    INT_CLOCK     ; not zero then toggle clock line
    
    GOTO    INT_EXIT_TX        

INT_CLOCK
    
    BTFSC    pcCLOCK_out    ; check if we are low
    GOTO    INT_CLOCK_HIGH  ; yep set to high
                
    BTFSS    pcCLOCK_in    ; check if pc is pulling the clock line low
                ; i.e. it wants to abort and send instead..
    GOTO    INT_TX_CHECK_ABORT    ; abort this transfer
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    GOTO    INTX

INT_CLOCK_HIGH
    BCF    pcCLOCK_out    ; set high ( release line )
                ;BCF    _ClockHigh    ;
    GOTO    INTX

INT_TX_CHECK_ABORT
    GOTO    INT_EXIT_TX

INT_DEC_CLOCK    
    DECF    clkCount,F    ; decrement clock counter ( so we toggle next time )
INT_DATA
    BTFSS    bitCount,0    ; check bit counter
    GOTO    INT_DATA_IDLE    ; no data toggle

    DECFSZ    bitCount,F    ; decrement bit counter
    GOTO    INT_DATA_NEXT    ; next bit..

INT_NO_BITS
    BSF    bitCount,0    ; just in case ( stupid code, not sure its needed, just
                ; to make it impossible to overdecrement )***
    BTFSC    _isParity    ; are we sending parity ?
    GOTO    INT_DATA_END    ; exit
    
    ; all bits sent
    ; delete the last key from the buffer
    CALL    INC_KEY_HEAD    ; remove the ( last ) key form the buffer as is was sent ok..

    ; all bits sent check parity
    
    BSF    _isParity    ; set flag data is parity
    
    BTFSS    KeyParity, 0    ; is the last parity bit high? ( odd num of bits )
                ; then parity should be high ( free )
    GOTO     INT_DATA_HIGH_PAR      ; yes
    BSF    pcDATA_out    ; no, parity should be 'low' ( pulled down )
    GOTO     INTX        ;

INT_DATA_HIGH_PAR:    
    BCF    pcDATA_out    ; set parity bit high ( release data line )
    GOTO    INTX        ; and exit..


INT_DATA_END    
    BTFSS    _isStopBit    ; is the stopbit sent ?
    GOTO    INT_DATA_STOPB    ; nope then set stopbit flag

    BCF    pcDATA_out    ; parity bit sent, always release data line ( stop bit )        
    GOTO    INTX

INT_DATA_STOPB
    BSF    _isStopBit    ; set the stopbit flag
    GOTO    INTX        ; and exit

INT_DATA_IDLE
    DECF    bitCount,F    ; decrement bit counter
    GOTO     INTX        ; no toggle of data line

INT_DATA_NEXT
    BTFSS     CurrKey,0    ; is the last bit of the key_buffer high?
    GOTO     INT_DATA_LOW    ; no, pull data low
    BCF      pcDATA_out    ; yes, release data line
    INCF    KeyParity,F    ; increment parity bit
    GOTO    INT_DATA_ROTATE    ; rotate data
    
INT_DATA_LOW    ; last bit is low
    BSF    pcDATA_out    ; set the bit

INT_DATA_ROTATE
    RRF    CurrKey,F    ; rotate right by 1 bit    
    GOTO    INTX

INT_EXIT_TX
    ; setup the timer so we accomplish an delay after an tx seq
    
    BCF    _TX_Mode    ; clear tx mode flag
    BCF    pcCLOCK_out    ; release clock line
    BCF    pcDATA_out    ; and data line
;
    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0

    GOTO    INTX


;************** TX code end *****************

;************** RX code start ***************

INT_RX
    MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
        MOVWF   TMR0


    BTFSS    clkCount,0    ; check if we should toggle clock
    GOTO    INT_RX_DEC_CLOCK    ; bit low,decrement and check if we should read data instead
    
    DECFSZ    clkCount,F    ; decrement and check if we are at zero..
    GOTO    INT_RX_CLOCK     ; not zero then toggle clock line
    
    BCF    pcCLOCK_out    ; release the clock line we are done..
    BCF    _RX_Mode    ; clear rx mode bit ( go over to heart beat mode )
    GOTO    INT_EXIT_RX            

INT_RX_CLOCK

    BTFSC    pcCLOCK_out    ; check if we are low
    GOTO    INT_RX_CLOCK_HIGH     ; yep set to high ( release line )
    
    BTFSC    _isStartBit    ; check if this is the first bit ( start )
    GOTO    INT_RX_START    ; clear start bit and continue
    
    BTFSC    _isParity    ; check if this is the parity bit ( or parity has been received )
    GOTO    INT_RX_PAR    ; yep check parity

    GOTO    INT_RX_BIT    ; ok just a 'normal' bit read it
    
    
INT_RX_PAR            ; check parity
    BTFSC    _doRXAck    ; check the handshake flag
    GOTO    INT_RX_HNDSHK    ; start handshake check

    BTFSS    pcDATA_in    ; is the input high ?
    GOTO    INT_RX_PAR_HIGH    ; yep
    BTFSC    KeyParity,0    ; is the parity '0' ( should be )
    GOTO    INT_RX_PAR_ERR    ; nope parity error
    GOTO    INT_RX_ACK    ; parity ok next should be ack ( we take data line low )

INT_RX_PAR_HIGH
    BTFSS    KeyParity,0    ; check that parity bit is '1'
    GOTO    INT_RX_PAR_ERR    ; nope parity error
    GOTO    INT_RX_ACK    ; parity ok, next is ack ( we take data line low )

INT_RX_PAR_ERR
    BSF    _KeyError    ; set error flag

INT_RX_ACK
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    BSF    _doRXAck    ; enable ack check
    GOTO    INTX

INT_RX_HNDSHK
    BTFSS    _RXEnd        ; if we are done dont take data low
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    
    BTFSC    _RXAckDone    ; chek if hand shake ( ack ) is done ?
    BSF    _RXEnd        ; ok we are now done just make one more clock pulse

    GOTO    INTX        ; exit    
        

INT_RX_CLOCK_HIGH

    BCF    pcCLOCK_out    ; set high ( release line )
    BTFSS    _RXAckDone    ; are we done.. ?
    GOTO    INTX
    BTFSS    _RXDone        ; finished ?
    GOTO    INTX

    BCF    _RX_Mode    ; and clear rx flag..
    GOTO    INT_EXIT_RX    ; bye bye baby

INT_RX_DEC_CLOCK    

    DECF    clkCount,F    ; decrement clock counter ( so we toggle next time )
    BTFSS    _doRXAck    ; check if we are waiting for handshake
    GOTO    INTX
    

     BTFSC    pcCLOCK_out     ; check if the clock is low ( pulled down )
    GOTO    INTX        ; nope we are pulling down then exit
                ; we only take over the data line if
                ; the clock is high ( idle )
                ; not sure about this though.. ???

     
    BTFSC    _RXEnd        ; are we done ?
    GOTO    INT_RX_END

    ; handshake check if data line is free ( high )
    BTFSS    pcDATA_in    ; is data line free ?
    GOTO    INTX        ; nope

    BSF    pcDATA_out    ; takeover data line
    BSF    _RXAckDone    ; we are done..at next switchover from low-high we exit
    GOTO    INTX        ;

INT_RX_END
    BCF    pcDATA_out    ; release data line
    BSF    _RXDone        ; we are now done
    GOTO    INTX

INT_RX_START
    BCF    _isStartBit    ; clear start bit flag
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    GOTO    INTX
INT_RX_BIT
    BCF    CurrKey,7
    BTFSS    pcDATA_in    ; is bit high
    GOTO    INT_RX_NEXT    ; nope , it's a '0'
    BSF    CurrKey,7    ; set highest bit to 1
    INCF    KeyParity,F    ; increase parity bit counter
    
INT_RX_NEXT
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )

    DECFSZ    bitCount,F    ; decrement data bit counter    
    GOTO    INT_RX_NEXT_OK

    BSF    bitCount,0    ; just in case ( so we cannot overdecrement )
    BSF    _isParity    ; next bit is parity
    GOTO    INTX

INT_RX_NEXT_OK
    CLRC            ; clear carry, so it doesnt affect receving byte
    RRF     CurrKey,F    ; rotate to make room for next bit
    GOTO    INTX        ; and exit

INT_EXIT_RX    

    ; handle the recevied key ( if not it is an 'data' byte )

    MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8 ( int handling code )= 200 us
                ;
        MOVWF   TMR0        ; this delay seems to be needed ( handshake ? )
    
    ; check if this is an data byte ( rate/delay led status etc )
    
    MOVF    CommandData,F    ; reload into itself ( affect zero flag )

    BTFSS    STATUS,Z    ; check zero flag
    GOTO    INT_STORE_DATA    ; byte contains data ( rate/delay etc )
    
    CALL    CHECK_RX_KEY    ; no data, handle recevied command
    GOTO    INTX

INT_STORE_DATA
    ; store data byte in 'currkey',
    ; first reply with 'ack'
    
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    BTFSS    _IsLedStatus    ; is it led status byte ?
    GOTO    INT_STORE_RATE  ; nope check next
    
INT_STORE_NUM
    ; byte in 'currkey' is led status byte, store it
    MOVF    CurrKey,W    ; get byte
    MOVWF    KbLedStatus    ; and store it
    BTFSC    _WaitNumLock    ; was this something we were waiting for ?
                ; i.e. we sent the make scancode for numlock.
    CALL    RELEASE_NUMLOCK    ; yep, then send release code for 'soft' numlock

    GOTO    INT_STORE_EXIT    ; store it in local ram copy and exit

INT_STORE_RATE

    BTFSS    _IsRateDelay    ; is it rate/delay byte ?
    GOTO    INT_STORE_EXIT  ; nope then send ack end exit
    ; byte in 'currkey' is rate/delay byte, store it
    MOVF    CurrKey,W    ; get byte
    MOVWF    KbRateDelay    ; and store it
    
INT_STORE_EXIT
    
    CLRF    CommandData    ; clear data byte flags
    GOTO    INTX


;******************* 'heart' beat code ( 0.5 ms tick ) *********
; Note: As I 'mess' with the int timer this 'heart beat' is by no means
; an accurate clock. However it can work as a rough estimate for local time.
;
INT_HEART_BEAT

    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0


INT_CHECK_CLKDTA:  

    ; CLOCK DATA   Action
    ;-----------+--------
    ;   L    L  |  wait ?
    ;   L    H  |  wait, buffer keys
    ;   H    L  |  start an rx sequence
    ;   H    H  |  keyboard can tx
        
    BTFSS    pcDATA_in    ; is the data line high ( free )..
    GOTO    INT_CHECK_RX    ; Nope it's pulled down, check if rx is requested
        
    BTFSC    pcCLOCK_in    ; Is the clk line low  ( pulled down ) ?        
    GOTO    INT_CHECK_BUFF  ; Nope, so check if we have any keys to send

    GOTO    INT_IDLE    ; clock is low , wait and buffer keys( i.e. no rx/tx )

        
                        
INT_CHECK_RX           ; pc ( probably ) wants to send something..
        
    BTFSS    pcCLOCK_in    ; wait until clock is released before we go into receving mode..
    GOTO    INT_RX_IDLE    ; nope still low

    ; clock now high test if we are set to start an rx seq.
    BTFSS    _RxCanStart     ; have we set the flag ?
    GOTO    INT_WAIT_RX    ; nope then set it      
            
    BTFSC    pcDATA_in    ; make sure that data still is low
    GOTO    INT_ABORT_RX    ; nope abort rx req, might been a 'glitch'

    ; initiate the rx seq.

    CLRF    Comm_Flags    ; used by both tx/rx routines ( and _RxCanStart bit !! )
    CLRF    TRX_Flags    ; clear tx/rx flags
    CLRF    KeyParity    ; clear parity counter

    BSF    _RX_Mode          ; set rx mode flag..
    BSF    _isStartBit    ; set that next sampling is start bit
    
    ; preset bit and clock counters

    MOVLW    H'2F'        ; = 47 dec, will toggle clock output every even number until zero
    MOVWF    clkCount    ; preset clock pulse counter

    MOVLW    H'08'        ; = 8 dec, number of bits to read
                ; then parity bit will be set instead

    MOVWF    bitCount    ; preset bit counter

    ; note as we are starting the clock here we allow a longer time before we start
    ; the actual 20 us tick, this the first time we wait about 200 us before the first clock 'tick'     

    MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8us ( int handling code )= 200 us
                ;
        MOVWF   TMR0
    
    GOTO    INTX        ; exit, the next int will start an rx seq

INT_WAIT_RX:
    BSF    _RxCanStart    ; set flag so we start rx next int ( 0.5 ms )
INT_RX_IDLE
    ; reload clock so we check more often
    MOVLW   H'F0'           ; start timer with 240 ( 256-16  = 240 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 16 + 8/10us ( int handling code )= (about) 60 us
        MOVWF   TMR0

    GOTO    INTX        ;

INT_ABORT_RX
    BCF    _RxCanStart    ; clear flag ( forces a 'new' rx start delay )
    GOTO    INT_IDLE    ;
                                               

INT_CHECK_BUFF:
    ; check if we have any keys to send to pc

        MOVF     KeyBufferTail,W ; get end of buffer to 'w'
        SUBWF     KeyBufferHead,W ; subtract start of buffer if head - tail = zero, no byte
        BTFSC   STATUS, Z       ; zero flag = no byte to send         
        GOTO    INT_IDLE    ; then do the 'idle' stuff
        
INT_SEND_KEY
    ;key in buffer, get it and initiate an tx seq...
    CALL    GET_KEY_BUFFER    ; get the key into CurrKey
    MOVF    CurrKey,W
    MOVWF    LastKey        ; store last sent key

    ; setup our tx/rx vars

    CLRF    Comm_Flags    ; used by both tx/rx routines ( and _RxCanStart bit !! )
    CLRF    TRX_Flags    ; clear tx/rx flags
    CLRF    KeyParity    ; clear parity counter

    BSF    _TX_Mode          ; set tx mode flag..
    
    ; preset bit and clock counters

    MOVLW    H'2B'        ; = 43 dec, will toggle clock out put every even number until zero
    MOVWF    clkCount    ; preset clock pulse counter

    MOVLW    H'12'        ; = 18 dec, will shift data out every even number until zero
                ; then parity bit will be set instead
    MOVWF    bitCount    ; preset bit counter

    ; data now set, initiate the clock to generate a 20 us tick ( rx/tx heart beat )

    BSF    pcDATA_out    ; start bit, always 'low' ( we pull down )
     
    MOVLW   H'FA'           ; start timer with 252 ( 256-6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8 ( int handling code )= 27 us
        MOVWF   TMR0
    
    GOTO    INTX        ; exit, the next int will start an tx

INT_IDLE:
    
    ; 'Idle' code i.e. no rx or tx possible AND/OR nothing to send

    DECF    Divisor_10ms,F  ; Count 0.5ms down to give 10 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .20
    MOVWF    Divisor_10ms     ; Preset the divide by 20

    ;+++
    ; 10 ms tick here
    


    ; Divide the 10 ms tick to give 100 ms second tick
INT_10MS
    DECF    Divisor_100ms,F    ; Count 10ms down to give 100 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .10
    MOVWF    Divisor_100ms    ; Preset the divide by 10

        ;+++
     ; 100 ms tick here

INT_100MS


    ; numlock check !! bit variable _Numlock does not actually set the numlock status directly.
    ; However, by setting this bit to '1' we make a test against the current numlock led status
    ; ( bit no 1 in variable KbLedStatus ), if that is different from this variable
    ; we send a 'numlock' press/release ( to toggle numlock status )
    ; so in essence, setting this bit to '1' will force numlock to be 'on' etc.

    BTFSC    _IsFirstLedStatus ;  if = 1 then we have not yet received numlock status
    GOTO    INT_REPEAT_CHECK    ; nope, then this is a consecutive byte, store as 'normal'

    BTFSS    _WaitNumLock    ; are we waiting for pc numlock reply ?
    GOTO    INT_NUMLOCK_CHECK ; yep then do repeat check instead
    
    DECFSZ    Temp_Var,F    ;
    GOTO    INT_REPEAT_CHECK

    CALL    RELEASE_NUMLOCK

INT_NUMLOCK_CHECK

    BTFSC    _LedNumLock    ; is the led on ?
    GOTO    INT_NUMLOCK_ON    ; yep, then test our 'local' numlock state ( wanted numlock state )

    ; nope numlock is off, is our wanted state also off ?
    BTFSS    _NumLock    ; is wanted state off ?
    GOTO    INT_REPEAT_CHECK ; yep continue   

    CALL    PRESS_NUMLOCK    ; nope then send numlock press/release code

    GOTO    INT_REPEAT_CHECK
            

INT_NUMLOCK_ON
    BTFSC    _NumLock    ; is wanted state also 'on' ?
    GOTO    INT_REPEAT_CHECK ; yep

    CALL    PRESS_NUMLOCK    ; nope then toggle numlock state

INT_REPEAT_CHECK

    ; check if a key should be 'repeated' ( when pressed longer than 500 ms )
    BTFSS    _startRepeat    ; start repeating a key ? ( delay !!! )
    GOTO    INT_CHECK_KEY    ; nope, then check if key should be repeated
    DECF    RepeatTimer,F    ;
    BNZ    INT_500MS    ; not zero yet, check timer instead

    BCF    _startRepeat    ; stop repeat timer ( delay is accomplished )
    BSF    _doRepeat    ; and enable 'key' is still down check
    MOVLW    .02        ; start repeat send timer
    MOVWF    Divisor_Repeat  ;

    GOTO    INT_500MS    ; do next timer check

INT_CHECK_KEY
    BTFSS    _doRepeat    ; key should be repeated ?
    GOTO    INT_500MS    ; nope
    
    ; ok key should be repeated, check if it still pressed ?
    CALL    CHECK_KEY_STATE    ; uses MakeKeyOffset to calculate which key that was
                ; the last pressed, and then check if it's still pressed
                ; if still pressed carry = '1',

    BTFSS    STATUS,C    ; check carry
    BCF    _doRepeat    ; clear repeat bit, stop repeating the key

    BTFSS    _doRepeat    ; still pressed ?
    GOTO    INT_500MS    ; nope

    DECF    Divisor_Repeat,F  ; should we send the key ?
    BNZ    INT_500MS    ; nope

    MOVLW    DELAY_RATE    ; reload timer with key rate delay
    ;MOVLW    .02        ; restart timer
    MOVWF    Divisor_Repeat  ;
    
    BSF    _doSendKey    ; set flag to send key, NOTE the actual sending ( putting into send buffer )
                ; is done inside mainloop.
     
    
INT_500MS
    
    DECF    Divisor_500ms,F    ; Count 100ms down to give 500 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .05
    MOVWF    Divisor_500ms    ; Preset the divide by 5

        ;+++
     ; 500 ms tick here


INT_500_NEXT
    
    TOGGLE_PIN O_led_KEYCOMM_ok    ; toggle the disco light 😉

    BTFSS    _DoExitAltKeymap    ; is the alt keymap toggle key pressed the second time ?
                    ; if so skip timeout test and exit
    BTFSS    _InAltKeymap    ; are we in altkeymap ?
    GOTO    INTX        ; nope
    
    ; we are in altkeymap, decrement the lastkeytime
    ; and check if we are at zero then we exit
    ; the altkeymap.

    DECF    LastKeyTime,F    ; decrease time
    BNZ    INTX        ; exit, timer has not expired
    ; timer expired, get out of altkey map
    BSF    _ExitAltKeymap    ;

; ***************** 'heart' beat code end ***************

INTX
    ;BCF    INTCON,T0IF     ; Clear the calling flag

    PULL            ; Restore registers
    RETFIE
    
; **************** end interrupt routine **************


;+++++        
;     Routines that will 'toggle' keyboard numlock status
;     by sending numlock make/break code
;

PRESS_NUMLOCK:    
        MOVLW    H'77'         ; numlock key scancode, make
        CALL    ADD_KEY
        MOVLW    H'06'         ; 6 x 100 ms = 600 ms ( release delay )
        MOVWF    Temp_Var    ;
        BSF    _WaitNumLock    ; we are waitin for numlock status reply from pc
        RETURN

RELEASE_NUMLOCK:
        MOVLW    BREAK        ; break prefix
        CALL    ADD_KEY    
        MOVLW    H'77'         ; numlock key scancode
        CALL    ADD_KEY
        BCF    _WaitNumLock
        RETURN
    
; ***********************************************************************
;
;  CHECK_RX_KEY - handles the received commands from pc
;
CHECK_RX_KEY    
    ; check the key in 'currkey' ( command from pc )

CHECK_ED
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'ED'        ; subtract value in W with 0xED
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_EE    ; the result of the subtraction was not zero check next
    ; ok 'ED'=set status leds ( in next byte ) received
    BSF    _IsLedStatus    ; set bit that next incoming byte is kb led staus
    GOTO    CHECK_SEND_ACK    ; send ack

CHECK_EE    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'EE'        ; subtract value in W with 0xEE
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F0    ; the result of the subtraction was not zero check next
    ; ok 'EE'= echo command received
    GOTO    CHECK_SEND_EE    ; send echo

CHECK_F0    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F0'        ; subtract value in W with 0xF0
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F2    ; the result of the subtraction was not zero check next
    ; ok 'F0'= scan code set ( in next commming byte ) received
    BSF    _SkipByte    ; skip next incomming byte ( or dont interpret )
    GOTO    CHECK_DONE    ; do not send ack !
CHECK_F2    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F2'        ; subtract value in W with 0xF0
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F3    ; the result of the subtraction was not zero check next
    ; ok 'F2'= Read ID command responds with 'AB' '83'
    GOTO    CHECK_SEND_ID    ; send id bytes

CHECK_F3    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F3'        ; subtract value in W with 0xF3
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_FE
;    GOTO    CHECK_F4    ; the result of the subtraction was not zero check next
    ; ok 'F3'= set repeat rate ( in next commming byte ) received
    BSF    _IsRateDelay    ; next incomming byte is rate/delay info
    GOTO    CHECK_SEND_ACK    ; send ack

; **** Note ! removed from test as I 'don't care' ********
; **** i.e. I dont disable or enable the keyboard at any time.

;CHECK_F4    
;    MOVF    CurrKey,W    ; move key buffer into W register
;    SUBLW    H'F4'        ; subtract value in W with 0xF4
;    BTFSS   STATUS, Z    ; check if the zero bit is set
;    GOTO    CHECK_F5    ; the result of the subtraction was not zero check next
    ; ok 'F4'= keyboard enable received
;    GOTO    CHECK_SEND_ACK    ; send ack
;CHECK_F5    
;    MOVF    CurrKey,W    ; move key buffer into W register
;    SUBLW    H'F5'        ; subtract value in W with 0xF5
;    BTFSS   STATUS, Z    ; check if the zero bit is set
;    GOTO    CHECK_FE    ; the result of the subtraction was not zero check next
    ; ok 'F5'= keyboard disable received
;    GOTO    CHECK_SEND_ACK    ; send ack
CHECK_FE    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'FE'        ; subtract value in W with 0xFE
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_FF    ; the result of the subtraction was not zero check next
    ; ok 'FE'= resend last sent byte
    MOVF    LastKey,W    ; get last key
    CALL    ADD_KEY        ; and put it on the que
    GOTO    CHECK_DONE

CHECK_FF
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'FF'        ; subtract value in W with 0xFF
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_ERROR    ; the result of the subtraction was not zero, unknown command
    ; ok 'FF'= reset keyboard received
    
    GOTO    CHECK_SEND_AA    ; send 'AA' power on self test passed

CHECK_ERROR            ; unknown command ( or command not interpreted )
    GOTO    CHECK_SEND_ACK

CHECK_SEND_ID
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    MOVLW    H'AB'        ; keyboard id first byte, always 0xAB
    CALL    ADD_KEY        ;

    MOVLW    H'83'        ; keyboard id second byte, always 0x83
    CALL    ADD_KEY        ;

    GOTO    CHECK_DONE

CHECK_SEND_ACK

    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;
    GOTO    CHECK_DONE

CHECK_SEND_AA
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    MOVLW    H'AA'        ; keyboard post passed
    CALL    ADD_KEY        ;
    GOTO    CHECK_DONE

CHECK_SEND_EE
    MOVLW    H'EE'        ; keyboard echo
    CALL    ADD_KEY        ;

CHECK_DONE
    RETLW    0        ; and we are done

; ***********************************************************************
;  Buffer code ( a bit modified ) from Stewe Lawther
http://ourworld.compuserve.com/homepages/steve_lawther/ucindex.htm
;  And of course source of the exellent keyboard viewer. !! ( without which, this
;  project would have been close to impossible ) ( and of course my nifty
;  memory oscilloscope )
;
;  ADD_KEY_BUFFER - (outside int)add the key in CurrKey to our keybuffer que in the first
;            free position. If there is no more room the oldest byte is
;            'dumped'.
;  ADD_KEY      - Same but to be used inside int routine.( just skips int disable code )

ADD_KEY_BUFFER

ADD_STOP_INT            ; first stop all interrupts !!!!!!!
    BCF    INTCON,GIE    ; disable global interrupts..
    BTFSC    INTCON,GIE    ; check that is really was disabled
    GOTO    ADD_STOP_INT    ; nope try again
ADD_KEY                ; inside interuppt we call this instead ( as we dont need to disable int 🙂 )
    MOVWF    BufTemp        ; store key temporary
    MOVF    KeyBufferHead,W ; move buffer head out of FSR temporarily
        MOVWF   Temp            ; store in temp  
        MOVF    KeyBufferTail,W ; set FSR to buffer tail
        MOVWF   FSR        ; set indirect file pointer
        MOVF    BufTemp,W       ; set W to new scancode to send
        MOVWF   INDF            ; and put it in the buffer
        MOVF    Temp,W        ; get the head pointer back
        MOVWF   KeyBufferHead    ;

        INCF    KeyBufferTail,W ; get the end of the buffer
        SUBLW   KbBufferMax     ; check if at buffer max
        INCF    KeyBufferTail,W ; (reload value to w - doesn't affect C)
        BTFSS   STATUS, C       ; if so (negative result)
        MOVLW   KbBufferMin     ; set to buffer min ( wrap around )

        MOVWF   KeyBufferTail    
        SUBWF   KeyBufferHead,W ; see if we have any room ( head and tail have meet )
        BTFSC   STATUS, Z       ; if so (Z set)
        CALL    INC_KEY_HEAD    ; dump oldest byte

                ; finally turn on interrupts again
        MOVLW   b'10100000'     ; enable global & TMR0 interrupts
        MOVWF   INTCON

    RETURN


; ***********************************************************************
;
;  GET_KEY_BUFFER -     Gets a char from the buffer, and puts it into KeyBuffer
;            NOTE: Does not increase buffer pointers ( dump this key ).
;            A call to INC_KEY_HEAD will do this if the key is sent ok
GET_KEY_BUFFER    
          MOVF    INDF, W         ;put the byte to send into key buffer
                MOVWF   CurrKey
        RETURN            ; and go back, NOTE ! the key is not
                    ; removed from the buffer until a call
                    ; to INC_KEY_HEAD is done.
                    
; ***********************************************************************
;
;  INC_KEY_HEAD -     dump oldest byte in keybuffer, Do not call if byte
;            has not been fetched before ( GET_KEY_BUFFER )
;            
INC_KEY_HEAD    INCF    KeyBufferHead,W ; set to next byte in buffer
                SUBLW   KbBufferMax     ; check if at buffer max
                INCF    KeyBufferHead,W ; (reload value to w - doesn't affect C)
                BTFSS   STATUS, C       ; if so (negative result)
                MOVLW   KbBufferMin     ; set to buffer min ( wrap around )
                MOVWF   KeyBufferHead   ; and store ( in FSR )
                RETURN            ; go back
                
        

; ***********************************************************************
;
;  CHECK_KEY_STATE - Check if the last pressed key is still pressed
;             Returns with carry = '1' if still pressed
;             else carry = '0' ( or error )
;

CHECK_KEY_STATE:
    ; uses LastMakeOffset to calculate which key to test
    
    MOVF    LastMakeOffset,W    ; get offset
    ANDLW    H'18'        ; mask out column bits  
                ; lastmake offset has the following bits:
                ; '000yyxxx' where 'yy' is column no
                ; and 'xxx' is key num,
    BTFSC    STATUS,Z    ; zero = column 1 & 2
    GOTO    CHECK_COL_12    ; it is in column 1

    MOVWF    repTemp        ; save it temporary
    SUBLW    H'08'        ; subtract value in W with 0x08 ( columns 3 & 4 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_COL_34    ; it is in column 3 & 4

    MOVF    repTemp,W    ; get the column bits back
    SUBLW    H'10'        ; subtract value in W with 0x10 ( columns 5 & 6 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_COL_56    ; it is in column 5 & 6
    
CHECK_COL_78
    MOVF    kbColumn78_Old,W ; get bit map ( key status ) for keys in column 7 & 8
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_56
    MOVF    kbColumn56_Old,W ; get bit map ( key status ) for keys in column 5 & 6
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_34
    MOVF    kbColumn34_Old,W ; get bit map ( key status ) for keys in column 3 & 4
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_12
    MOVF    kbColumn12_Old,W ; get bit map ( key status ) for keys in column 1 & 2
    MOVWF    repKeyMap    ; and store it

;<-------Alt keymap code------->
    
    ;Checks ONLY column 1&2 bitmap ( keymap ) ( as it is now )
    ; this code has to be moved/changed if another column is choosen as the alt. keymap toggle key

    ; alternative keymap handling if key r3 c1 is pressed ( bit 2 in column 1&2 )
    ; then enable alternative keymap ( only if keyrepeat is disabled )
    
    ; check if this was the last key pressed

    ; check bit representing the alt. keymap key ( i've choosen key 2 )

    MOVF    LastMakeOffset,W ; get key offset again
    ANDLW    H'07'         ; mask out column bits
    SUBLW   H'02'        ; check if its bit num 2 ( the enter 'alt keymap' key )

    BTFSS   STATUS, Z    ; check if the zero bit is set    GOTO    CHECK_KEY    ; nope than another key was the last
                ; skip altkeymap enable

    ; the altkeymap key was the last pressed !
    ; is key repeat disabled ?

    BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
    GOTO    CHECK_KEY     ; yep, then skip altkeymap enable test

    ; enable altkeymap if key is still pressed
    
    BTFSC    repKeyMap,2    ; test bit 2 ( should be key 'F7' )
    GOTO    CHECK_ENABLE_ALT ; ok enable altkeymap ( if we are not already in altkeymap )
    GOTO    CHECK_KEY    ; nope another key in column 1&2 continue check

CHECK_ENABLE_ALT
    BTFSC    _AltKeymap    ; are we already in altkeymap ?
    GOTO    CHECK_KEY    ; yep then just continue

    ; We are just entering/enabling the alt. keymap

    BSF    _AltKeymap    ; enable alternative keymap

    ; Example of using an 'advanced' alt keymap handling    
    ; not enabled, to avoid intial confusion.

    ; I.E This snippet would only be called once when we
    ; are just entering(enabling) the alternative keymapping !

    ; This example code will 'soft' release the current altkeymap key ( it is in pressed state ! )
    ; ( i.e send release code for the enter alt. keymap key 'F7' )
    ; send the make scancode for left <alt> key instead.
    ; and force numlock to be off.
    
    ; Do not use if you dont understand the implifications !
        
    ; Also note that the scancodes are hardcoded here !
    ; ( i.e do not use the lookup table definition of the key/s )
    
    ; ***** start snippet
    
    ;MOVLW    BREAK    ; send break prefix
    ;CALL    ADD_KEY
    ;MOVLW    H'83'   ; and scancode for the enter alt keymap
    ;CALL    ADD_KEY
    ;MOVLW    H'11'     ; send make code for the left <alt> key
    ;CALL    ADD_KEY
    
    ; example of forcing the numlock status to a particular state
    ; the numlockstatus will change ( be checked ) inside the int routine
    ; See also at the end of KB_DEBOUNCE_12 where the numlock status
    ; will be restored when we release the key
    
    ;BCF    _NumLock    ; 'force' numlock to be off
    
    ; This bit MUST also be checked as we do not know if we have recevied
    ; first numlock status byte yet ( pc does not send numlock/led status
    ; after intial poweron, if not one of the numlock/capslock/scrolllock are pressed )
    ; i.e. if you connect this keyboard to a 'running' pc, the numlock status
    ; will be unknown.However if connected before poweron, it will be updated
    ; as numlock status is sent during pre-boot seq.
    
    ;BTFSC    _IsFirstLedStatus ; have we recevied numlock status yet ?
    ;CALL    PRESS_NUMLOCK
    
    ; ***** end snippet

CHECK_KEY
    ; 'normal' key down check
    ; column for pressed key is now in repKeyMap
    MOVF    LastMakeOffset,W ; get offset again
    ANDLW    H'07'        ; mask out key number ( lowest 3 bits )
    
     BTFSC    STATUS,Z    ; bit num zero ?
    GOTO    CHECK_KEY_DONE    ; yep lowest bit, check and return
    
       MOVWF    repTemp        ; and store it
CHECK_KEY_LOOP
    RRF    repKeyMap,F    ; rotate one step to right
    DECFSZ    repTemp,F        ; decrement bit counter
    GOTO    CHECK_KEY_LOOP    ; loop again

CHECK_KEY_DONE
    ; ok the key to test should now be the lowest bit in repKeyMap
    CLRC            ; clear carry
    BTFSC    repKeyMap,0    ; check bit 0
    BSF    STATUS,C    ; ok key is pressed set carry
    RETURN            ; and we are done..

; ***********************************************************************
;
;  DELAY_1ms -     Delay routine ! used when scanning our own keyboard
;         Delay is between output of adress to 4051 and reading of inputs
;         Increase to have a slower 'scan' rate or decrease to have a higher scan rate.
;

DELAY_1ms
        MOVLW    H'F0'        ; wait 255 cycles
        MOVWF    kbTemp        ; this var is 'safe' to be used in side mainloop
        MOVLW    H'03'
        MOVWF    kbState
DELAY_LOOP
        DECFSZ    kbTemp,F    ; decrement
        GOTO    $-1        ;
        
        MOVLW    H'F0'
        MOVWF    kbTemp
        DECFSZ    kbState,F
        GOTO    DELAY_LOOP

        RETURN


;---------------------------------------------------------------------------
;
;        Initialisation
;
;---------------------------------------------------------------------------
        
        
INIT:  

    ;+++
    ;    Set up the ports

        ; PORT A

    BANK1 ;
    MOVLW    b'00000110'        ; Set port data directions RA1,RA2 inputs RA0,RA3,RA4 outputs
    MOVWF    TRISA            ; PC keyboard connections


        ; PORT B

        ; Used for our own 3x8 matrix keyboard
    
    BANK1;
    MOVLW    b'11111000'        ; Set port data directions RB4-RB7 inputs rest outputs
    MOVWF    TRISB


    ;    Clear all registers on bank 0 ( memory )
    
    BANK0
    MOVLW    H'0C'
    MOVWF    FSR
    
INITMEM
    CLRF    0        ; Clear a register pointed to be FSR
    INCF    FSR,F
    CLRWDT            ; clear watchdog
    MOVLW    H'50'        ; Test if at top of memory
    SUBWF    FSR,W
    BNZ    INITMEM        ; Loop until all cleared

    ;+++     
    ;    Initiate the keybuffer pointers

INIT_BUFF:

    MOVLW   KbBufferMin    ; get adress of first buffer byte
    MOVWF    KeyBufferHead    ; store in FSR
    MOVWF    KeyBufferTail    ; and set last byte to the same ( no bytes in buffer )

    ;+++
    ;    Preset the timer dividers

    MOVLW    .20
    MOVWF    Divisor_10ms
    MOVLW    .10
    MOVWF    Divisor_100ms
    MOVLW    .05
    MOVWF    Divisor_500ms

    ;+++
    ;    Set up Timer 0.

    ;    Set up TMR0 to generate a 0.5ms tick
    ;    Pre scale of /8, post scale of /1
    
    BANK1
        
    MOVLW   b'00000010'     ; Initialisation of TMR0 prescale 8 '010'
                                ; weak pullup enabled by latch values.

    MOVWF   OPTION_REG      ; load option reg with prescale of 8
                
    BANK0

    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
    MOVWF   TMR0
        

        
;---------------------------------------------------------------------------
;
;        the main 'program' loop ( starts really at MAIN_LOOP )
;
;---------------------------------------------------------------------------

MAIN:    
        BSF    pcDATA_in
        BSF    pcCLOCK_in
        BCF    pcDATA_out
        BCF    pcCLOCK_out
        CLRF    PORTB

        MOVLW    H'08'        ; preset the column counter
        MOVWF    kbColumnCnt    ;
        
         BSF    _NumLock    ; default state is numlock = on
        BSF    _IsFirstLedStatus ; we have not yet recevied led status byte.

            MOVLW   b'10100000'     ; enable global & TMR0 interrupts
            MOVWF   INTCON

        CLRWDT            ; clear watchdog
        BTFSS    O_led_KEYCOMM_ok
        GOTO    $-2        ; make an 0.5 second delay here
                    ; i.e. the led will come on when 0.5 seconds has passed
                    ; set inside the timer int.

        CLRWDT            ; clear watchdog
        BTFSC    O_led_KEYCOMM_ok
        GOTO    $-2        ; make an additional 0.5 second delay here
                    ; i.e. the led will be dark when 0.5 seconds has passed
                    ; set inside the timer int.


        MOVLW    H'AA'         ; post passed :-), always 0xAA
        CALL    ADD_KEY_BUFFER
        
        ; now go into infinite loop, the pc kb interface runs in the background ( as an int )
        ; where we continuously monitor the pcCLOCK/DATA_in lines

         
MAIN_LOOP:
        ; check whatever 🙂
        CLRWDT            ; clear watchdog

MAIN_CHECK_COL_1:
        ; scan our own keyboard, first four bits

        ; address and read column, read as complement so key pressed = '1'
        ; since we pull down when key is pressed ( weak pullup enabled )
        ; ( i.e. pressed key has level 0V ) to make it more 'logical' to work with

        CLRF    kbColumnVal
        
        ; get column counter / adress out
        MOVF    kbColumnCnt,W          
        
        MOVWF    PORTB        ; set the columns adress to the 74HCT4051
                    ; i.e. make column low
        IFNDEF    DEBUG
        CALL    DELAY_1ms    ; wait 1 ms let pins stabilize
        ENDIF

        COMF    PORTB,W        ; read back the pin values ( complement i.e. key pressed = '1' )
        ANDLW   b'11110000'    ; mask out unused pins
        
        MOVWF    kbColumnVal    ; store the pin values

        SWAPF    kbColumnVal,F    ; swap nibbles ( low<->high ) to make room for next column

        INCF    kbColumnCnt,F     ; inc column adress
MAIN_CHECK_COL_2:
        ; read next four bits
        ; put out adress and read next column, read as complement so key pressed = '1'
        ; this as we pull down when key is pressed ( weak pullup enabled )

        ; get column counter / adress out
        MOVF    kbColumnCnt,W          
        
        MOVWF    PORTB        ; set the columns adress to the 74HCT4051
                    ; i.e. make column low

        IFNDEF    DEBUG
        CALL    DELAY_1ms    ; wait 1 ms
        ENDIF

        COMF    PORTB,W        ; read back the pin values ( complement i.e. key pressed = '1' )
        ANDLW   b'11110000'    ; mask out unused pins
        
        ADDWF    kbColumnVal,F    ; and store pin values

        INCF    kbColumnCnt,F   

        ; reset column counter check
        ; i.e. we are 'only' using adress 0 - 7
        MOVF    kbColumnCnt,W
        SUBLW    H'08'        ; subtract value in W with 0x08
        BTFSS   STATUS, Z    ; check if the zero bit is set
        GOTO    MAIN_CHECK_DEBOUNCE ; nope continue

        CLRF    kbColumnCnt     ; reset counter/adress

MAIN_CHECK_DEBOUNCE:
        CALL    KB_DEBOUNCE    ; do debouncing on the current values and send make/break
                    ; for any key that has changed
                    ; NOTE uses the current column adress to determine which
                    ; columns to debounce!
MAIN_REPEAT:
        BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
        GOTO    MAIN_CHECK_REPEAT ; yep check key repeating
        
        ; keyrepeat disabled then do check on exit of altkeymap instead

        BTFSS    _ExitAltKeymap    ; we want to exit altkeymap ?
        GOTO    MAIN_LOOP    ; nope

        
        ; check that ALL keys are released
        ; before exiting the alt keymap
        MOVF    kbColumn78_Old,F ; reload column 78 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 78

        MOVF    kbColumn56_Old,F ; reload column 56 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 56

        MOVF    kbColumn34_Old,F ; reload column 34 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 34

        MOVF    kbColumn12_Old,F ; reload column 12 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 12
    
        ; all keys released !!
        BCF    _AltKeymap    ; exit altkeymap
        BCF    _ExitAltKeymap    ; exit release check
        BCF    _InAltKeymap    ; clear flag for second keypress check
        BCF    _DoExitAltKeymap ;
        GOTO    MAIN_LOOP    


MAIN_CHECK_REPEAT
        BTFSS    _doSendKey    ; if we should send a repeated key
        GOTO    MAIN_LOOP    ; nope continue

        ; send the key in RepeatedKey but first check if its an extended key
        BTFSS     _RepeatIsExt    ; is it extended ?
        GOTO    MAIN_SEND_REPEAT ; nope just send scan code
        
        ; last key pressed was extended send extended prefix
        MOVLW    EXTENDED    ; get extended code
        CALL    ADD_KEY_BUFFER    ; and put it into the buffer
        
MAIN_SEND_REPEAT:
        MOVF    RepeatKey,W    ; get key code for the last pressed key
        CALL    ADD_KEY_BUFFER    ; and put it into the buffer
        BCF    _doSendKey    ; and clear the flag, it will be set again
                    ; inside int handler if key still is pressed    
        
        GOTO    MAIN_LOOP    ; and return
    

; ***********************************************************************
;
;  KB_SEND_KEY - uses the Offset stored in var 'Offset' to fetch a key from our
;         key lookup table.
;           then checks the bit var _isBreak to see if make or break codes should be sent
;         It then puts the code/s into the key buffer ( for sending later, in int routine )

KB_SEND_KEY:

    MOVF    Offset,W    ; get current offset
    MOVWF    TempOffset    ; save it ( to be used in key repeat code, is its 'make' )
                ; temp offset has the following bits:
                ; '000yyxxx' where 'yy' is column offset
                ; and 'xxx' is key num,


    CLRC            ; clear carry so it dont affect byte rotation
    RLF    Offset,F    ; first rotate
    RLF    Offset,F    ; second rotate

                ; offset no have the following bits:
                ; '0yyxxx01' where 'yy' is column offset
                ; and 'xxx' is key num,
                ; as each key in table has 4 bytes of 'space'

    INCF    Offset,F    ; add one, for the 'movwf pcl' at the start of the table

    BCF    Offset,7    ; clear to bit, just in case so we dont
                ; 'overflow' the table, should not be needed !

    BCF    _isExtended     ; clear extended flag

    MOVLW   LOW LOOKUP_KEY    ; get low bit of table adress
    ADDWF    Offset,F    ; 8 bit add
    MOVLW    HIGH LOOKUP_KEY    ; get high 5 bits
    BTFSC    STATUS,C    ; is page boundary crossed ?
    ADDLW    1        ; yep, then inc high adress
    MOVWF    PCLATH        ; load high adress in latch
    MOVF    Offset,W    ; load computed offset in w
    CLRC                ; clear carry ( default= key is not extended )
                ; if key is extended then carry is set in jumptable lookup_key

    CALL    LOOKUP_KEY    ; get key scan code/s for this key
                ; key scan code/s are saved in
                ; W - scancode, should go into kbScan
                ; carry set - extend code
                ; carry clear - not extended code

    MOVWF    kbScan        ; store scancode
    ; if carry is set then key is extended so first send extended code
    ; before any make or break code

    BTFSS    STATUS,C    ; check carry flag
    GOTO    KB_CHK_BREAK    ; nope then check make/break status

    BSF    _isExtended    ; set extended flag
    MOVLW    EXTENDED    ;
    CALL    ADD_KEY_BUFFER    ; get extended code and put in in the buffer

KB_CHK_BREAK:
    
    ; check if it's make or break
    BTFSS    _isBreak    ; check if its pressed or released ?
    GOTO    KB_DO_MAKE_ONLY    ; send make code

    BCF    _isBreak    ; clear bit for next key

    ; break code, key is released
    MOVLW    BREAK        ; get break code
    CALL    ADD_KEY_BUFFER    ; and put into buffer
    GOTO    KB_DO_MAKE    ; and send key code also

    ; key is pressed !
KB_DO_MAKE_ONLY:
    BCF    _doSendKey    ; stop repeat sending
    BCF    _doRepeat    ; and bit for repeat key send
    BSF    _startRepeat    ; and set flag for start key repeat check
    BCF    _RepeatIsExt    ; clear repeat key extended flag ( just in case )

    BTFSC     _isExtended     ; is it extended ?
    BSF    _RepeatIsExt    ; set the flag
    
    ; save this key in 'last' pressed, to be used in key repeat code

    MOVF    TempOffset,W    ; get saved offset
    MOVWF    LastMakeOffset  ; and store it

    ; if keyrepat = enabled, alternative mapping = disabled
    BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
    GOTO    KB_REP_NOR    ; yep set normal delay ( 800 ms )
    
    ; else keyrepat = disabled, alternative mapping = enabled
    MOVLW    DELAY_ENTER_ALTKEYMAP ;reload delay before entering the altkeymap ( 3 sec )
                ; i.e how long the enter altkeymap key must be pressed before
                ; we enable altkey keymap codes.
                            
    GOTO    KB_REP_SET    ; and set it
KB_REP_NOR:    
    
    MOVLW    DELAY_REPEAT    ; reload 'normal' repeat delay ( 800 ms )
            
KB_REP_SET:
    MOVWF    RepeatTimer    ; and (re)start the timer for key repeat
    MOVF    kbScan,W    ; get key scan code    
    MOVWF    RepeatKey    ; and save it

KB_DO_MAKE:
    ; key pressed/released ( i.e. the scancode is sent both on make and break )
    MOVF    kbScan,W    ; get scan code into w
    CALL    ADD_KEY_BUFFER    ; and add to send buffer
    

    ; reset the 'get out of alt. keymap timer for each keypress
    ; note don't care if we are 'in' alt. keymap. Reset this timer anyway
    ; as the code for checking if we are currently in alt. key map
    ; would be as long as it takes to reset the timer.

    MOVLW    DELAY_EXIT_ALTKEYMAP ; reload the delay for exiting the altkeymap when no
                ; key is pressed ( 7.5 sec )
    MOVWF    LastKeyTime    ; (re)set lastkey timer ( used to get out of altkeymap )    
    RETURN

; ***********************************************************************
;
;  KB_DEBOUNCE - debounces two column readings from our keyboard
;         If a bit 'state' has been 'stable' for 4 consecutive debounces
;           the 'new' byte is updated with the new state
;         'normal' loop time ( no tx/rx/key press ) is about 2-3 ms
;         so from 'key' down until 'new' is updated it takes about 8-10 ms
;         ( as we are scanning columns two by two, the whole keyboard needs
;         4 loops to be fully updated, then 4 debounce samples for each 'pair' )    

KB_DEBOUNCE:
    ; debounce current column(s)
    MOVF    kbColumnCnt,F    ; reload value into itself ( affect zero flag )
    BTFSC    STATUS,Z    ; is it zero ?
    GOTO    KB_DEBOUNCE_78    ; debounce columns 7 & 8

    MOVF    kbColumnCnt,W    ; move column counter into W register
    SUBLW    H'04'        ; subtract value in W with 0x04 ( columns 5 & 6 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    KB_DEBOUNCE_34    ; debounce columns 3 & 4

    MOVF    kbColumnCnt,W    ; move column counter into W register
    SUBLW    H'06'        ; subtract value in W with 0x02 ( columns 3 & 4 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    KB_DEBOUNCE_56    ; ok column 1 & 2 debounce

    ; all above tests 'failed'
    ; columns to debouce are 1 & 2

KB_DEBOUNCE_12:    
    ; debounce columns 1 & 2
    DEBOUNCE_BYTE    kbColumnVal,kbColumn12_New,kbColumn12Cnt,kbColumn12State

    MOVF    kbColumn12_New,W    ; get debounced sample
    XORWF    kbColumn12_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn12_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_12    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_12_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
        
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_12_NEXT    

KB_LOOP_12_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_12_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_12_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_12    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_12

KB_12_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn12_New,W ; get new status
    MOVWF    kbColumn12_Old    ; and store it..

;<-------Alt keymap code------->

    ; ***** alternative keymap handling
    ; The alternative keymap is enabled by pressing key r4 c1 ( i.e. bit 3 in column 12 )
    ; Here, we enable a check to turn off alternative keymap if
    ; that key and all others are released ( bit is cleared ).
    ; ( else no (alternative)break codes would be sent for those keys that are still pressed )
    ; NOTE: _Altkeymap is set inside int routine when checking
    ; keyrepeat so there is a 'variable' delay before the altkeymap is active
    ;

    BTFSS    _AltKeymap        ; is altkeymap enabled ?
    RETURN                ; nope return


    BTFSC   _InAltKeymap        ; are we in altkeymap ?
    GOTO    KB_12_IN        ; yep alt keymap key has been released once
    
    ; nope still waiting for first release
    BTFSS   kbColumn12_Old,2    ; is key released ? ( first time )
    GOTO    KB_12_ALT        ; yep, reset timers and set bit variables

KB_12_IN
    BTFSC    _DoExitAltKeymap    ; are we waiting for release ?
    GOTO    KB_12_OUT        ; yes

    ; the key has been released once test for second press
    BTFSC   kbColumn12_Old,2    ; is it still pressed ?
    GOTO    KB_12_ALT2        ; yep

    BTFSS    _DoExitAltKeymap        ; are we now waiting for the last ( second ) release ?
    RETURN                ; nope

KB_12_OUT
    BTFSS    kbColumn12_Old,2    ; check if key still pressed ?
    BSF    _ExitAltKeymap        ; nope, then enable exit check that
                    ; will exit alt keymap as soon as all key are released
    
KB_12_ALT2
    BSF    _DoExitAltKeymap    ; check for second release
    RETURN
KB_12_ALT
    ; first release of the enter alt keymap key
    ; reset 'get out' timer and set bit variables to enable check
    ; for second press/release

    MOVLW    H'0F'        ; x0.5 sec = 7.5 sec
    MOVWF    LastKeyTime    ; (re)set lastkey timer ( used to get out of altkeymap automaticly)    

    BSF    _InAltKeymap    ; yep the first time, then set flag that we are now
                ; waiting for a second press/release to exit alt key map
                ; all keys are released before exiting altkeymap

    ;***** Example snippet(one line) to be paired with code in CHECK_KEY_STATE where I
    ; forced numlock status to be off while enetering the alt keymap
    ; but have not yet released the alt keymap toggle key.
    ; this code will be called at the first release of this key. Used
    ; to restore numlock status.
    ; As said before, do not use if implifications are not known !

    ;BSF    _NumLock    ; and also force numlock to be 'on'
                ; as it is set to 'off' when we enter altkeymap
                ; we must set it 'back'
    
    RETURN    
        
KB_DEBOUNCE_34:    
    ; debounce columns 3 & 4
    DEBOUNCE_BYTE    kbColumnVal,kbColumn34_New,kbColumn34Cnt,kbColumn34State

    MOVF    kbColumn34_New,W    ; get debounced sample
    XORWF    kbColumn34_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn34_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_34    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_34_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,3    ; set bit 3 for table read ( column 3 & 4 )

    ;BCF    _isBreak    ; clear break flag
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_34_NEXT

KB_LOOP_34_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_34_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_34_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_34    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_34

KB_34_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn34_New,W ; get new status
    MOVWF    kbColumn34_Old    ; and store it..
    RETURN    


KB_DEBOUNCE_56:    
    ; debounce columns 5 & 6
    DEBOUNCE_BYTE    kbColumnVal,kbColumn56_New,kbColumn56Cnt,kbColumn56State

    MOVF    kbColumn56_New,W    ; get debounced sample
    XORWF    kbColumn56_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only that 'a change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn56_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_56    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_56_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,4    ; set bit 4 for table read ( column 5 & 6 )
    
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_56_NEXT

KB_LOOP_56_SKIP
    RLF    kbTemp,F    ; rotate so we read next key

KB_LOOP_56_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_56_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_56    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_56

KB_56_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn56_New,W ; get new status
    MOVWF    kbColumn56_Old    ; and store it..
    RETURN    

KB_DEBOUNCE_78:    
    ; debounce columns 7 & 8
    DEBOUNCE_BYTE    kbColumnVal,kbColumn78_New,kbColumn78Cnt,kbColumn78State
    
    MOVF    kbColumn78_New,W    ; get debounced sample
    XORWF    kbColumn78_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits. ( 7-0 )
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn78_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_78    
    CLRF    Offset        ; clear offset counter ( for table read )
    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_78_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,4    ; set bit 3,4 for table read ( column 7 & 8 )
    BSF    Offset,3    ;
    
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released


    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars

    GOTO    KB_LOOP_78_NEXT
    
KB_LOOP_78_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_78_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_78_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_78    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_78

KB_78_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn78_New,W ; get new status
    MOVWF    kbColumn78_Old    ; and store it..
    RETURN    

    
; ***********************************************************************
;
;  LOOKUP_KEY -  lookup table for key scancodes.
;         Returns a scancode in w
;         Sets carry if key is extended
;         NOTE: If key R3 C1 has been pressed longer than keyrepeat delay
;         AND keyrepeat is disabled ( jumper on pin RB3 ) THEN the
;          bit _AltKeymap is set and we can return an alternative scancode in W
;        

LOOKUP_KEY    ; lookup table for the keys ( 32 keys x 4 lines + 1 = 129 lines )
        ; keys are labelled Rx - Cy where x = row number and y = column number
        ; handles a 4 row x 8 column keyboard = 32 keys
        
    MOVWF    PCL      ; add to program counter          
; R1 - C1 i.e. key 1
    NOP
    NOP
    NOP    
    RETLW    H'05'    ; scan code 'F1'
; R2 - C1 i.e. key 2
    NOP
    NOP
    NOP    
    RETLW    H'0C'    ; scan code 'F4'
; R3 - C1 i.e. key 3
    ; The famous alternative keymap toggle key !!!  😉
    ; It is adviced that this key does not use an alt scancode
    ; makes things cleaner and simplified.
    ; IF USED though, remember that a 'soft' release code must be sent
    ; for this key when entering the altkeymap !( + 'soft' make code for the alternative key )
    ; This as the key is pressed when entering altkeymap
    ; which makes the bit _Altkeymap be set, and hence when released
    ; the release code for this 'normal' key will never be sent
    ; instead the release code for the alternative key will be sent.
    ; To 'fix' this put the release code at the end of CHECK_KEY_STATE ( where the
    ; alt keymap bit is set ).i.e. break prefix+scancode for normal key.
    NOP
    NOP
    NOP
    RETLW    H'83'        ; scan code for F7 ( in 'normal' mode )
; R4 - C1 i.e. key 4
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'76'        ; send scancode for 'ESC'  instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'6B'        ; scan code 'arrow left' '<-'
; R1 - C2 i.e. key 5
    NOP
    NOP
    NOP    
    RETLW    H'06'    ;  scan code 'F2' hex06
; R2 - C2 i.e. key 6
    NOP
    NOP
    NOP    
    RETLW    H'03'    ; scan code 'F5'
; R3 - C2 i.e. key 7
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'0D'        ; send scancode for 'horizontaltab' HT instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'75'        ; scan code 'arrow up' '^'
; R4 - C2 i.e. key 8
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'14'        ; send scancode for 'left ctrl' instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'72'        ; scan code 'arrow down'
; R1 - C3 i.e. key 9
    NOP    
    NOP    
    NOP    
    RETLW    H'04'    ; scan code 'F3'
; R2 - C3 i.e. key 10
    NOP    
    NOP    
    NOP    
    RETLW    H'0B'    ; scan code 'F6'
; R3 - C3 i.e. key 11
    NOP    
    NOP    
    NOP    
    RETLW    H'0A'    ; scan code 'F8'
; R4 - C3 i.e. key 12
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'11'        ; send scancode for 'left alt' instead
    BSF    STATUS,C ; set carry ( i.e. extended code )
    RETLW    H'74'    ; scan code 'arrow right' '->'
; R1 - C4 i.e. key 13
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'6C'        ; send scancode for numeric '7' instead
    NOP    
    RETLW    H'3D'    ; scan code '7'
; R2 - C4 i.e. key 14
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'6B'        ; send scancode for numeric '4' instead
    NOP    
    RETLW    H'25'    ; scan code '4'
; R3 - C4 i.e. key 15
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'69'        ; send scancode for numeric '1' instead
    NOP    
    RETLW    H'16'    ; scan code '1'
; R4 - C4 i.e. key 16
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7B'        ; send scancode for numeric '-' instead
    NOP    
    RETLW    H'4A'    ; scan code '-' minus ( swe kbd )
; R1 - C5 i.e. key 17
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'75'        ; send scancode for numeric '8' instead
    NOP    
    RETLW    H'3E'    ; scan code '8'
; R2 - C5 i.e. key 18
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'73'        ; send scancode for numeric '5' instead
    NOP    
    RETLW    H'2E'    ; scan code '5'
; R3 - C5 i.e. key 19
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'72'        ; send scancode for numeric '2' instead
    NOP    
    RETLW    H'1E'    ; scan code '2'
; R4 - C5 i.e. key 20
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'45'        ; scan code '0' ( from keypad ) normal key
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'1F'        ; alt keycode ( windows start menu activate )
; R1 - C6 i.e. key 21
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7D'        ; send scancode for numeric '9' instead
    NOP    
    RETLW    H'46'    ; scan code '9'
; R2 - C6 i.e. key 22
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'74'        ; send scancode for numeric '6' instead
    NOP    
    RETLW    H'36'    ; scan code '6'
; R3 - C6 i.e. key 23
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7A'        ; send scancode for numeric '3' instead
    NOP    
    RETLW    H'26'    ; scan code '3'
; R4 - C6 i.e. key 24
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'49'        ; scan code '.' ( swe kbd ) normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'4A'    ; send scancode for numeric '/' instead
; R1 - C7 i.e. key 25
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'79'        ; send scancode for numeric '+' instead
    NOP    
    RETLW    H'4E'    ; scan code '+'
; R2 - C7 i.e. key 26
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'66'        ; scan code 'back space' BS, normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'71'    ; send scancode for 'delete' instead
; R3 - C7 i.e. key 27
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'5A'        ; scan code 'enter', normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'5A'    ; send scancode for numeric enter instead ( note ! extended )
; R4 - C7 i.e. key 28
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'5A'        ; scan code 'enter', normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'5A'    ; send scancode for numeric enter instead ( note ! extended )
; R1 - C8 i.e. key 29
    NOP    
    NOP    
    NOP    
    RETLW    H'2C'    ; scan code 't'
; R2 - C8 i.e. key 30
    NOP    
    NOP    
    NOP    
    RETLW    H'24'    ; scan code 'e'
; R3 - C8 i.e. key 31
    NOP    
    NOP    
    NOP    
    RETLW    H'1B'    ; scan code 's'
; R4 - C8 i.e. key 32
    NOP    
    NOP    
    NOP    
    RETLW    H'2C'    ; scan code 't'

    END

Será que algum programador pode me ajudar? minha duvida é a seguinte ...eu encontrei um projeto na internet ae eu copiei o codigo mas quando eu coloco o codigo no mplab eu não consigo fazer o make ele da erro!! build falhou!! será que alguem pode me ajudar?? será que esse codigo ta errado!! se algum programador poder me ajudar eu agradeço!!!

 

;
;               PC-Keyboard emulator using a PIC16F84
;     Scan a 32 key keyboard ( with alternative mapping = 64 keys )
;            

;                 COPYRIGHT (c)1999 BY Tony K&uuml;bek
; This is kindly donated to the PIC community. It may be used freely,
;     and you are forbidden to deprive others from those rights.   
;    Please read the FSF (Free Software Foundation) GNU statement.
;
;*********************************************************************
;
;            E-MAIL    tony.kubek@flintab.se
;        
;        
; DATE            2000-01-23
; ITERATION        1.0B
; FILE SAVED AS        PiCBoard.ASM    
; FOR            PIC16F84-10/P        
; CLOCK            10.00 MHz RESONATOR ( OSC-HS )
; INSTRUCTION CLOCK    2.50 MHz T= 0.4 us
; SETTINGS        WDT-ON, PWT-ON, CP-OFF
; REVISION HISTORY    
;            0.1b -    First beta, mainly for testing
;            1.0b -     First public beta release
;
;
;
;
;***************************************************************************
;
;                PREFACE 😉
;
;    This is NOT an tutorial on pc keyboards in general, there are quite
;    a few sites/etc that have that already covered. However i DID find
;    some minor ambiguities regarding the actual protocol used but nothing
;    that warrants me to rewrite an complete pc keyboard FAQ.
;    So for 'general' pc keyboard info ( scancodes/protocol/pin config/etc )
;    here are some useful links:
;
;   http://www.senet.com.au/~cpeacock/  
;   http://ourworld.compuserve.com/homepages/steve_lawther/keybinfo.htm
;   http://204.210.50.240/techref/default.asp?url=io/keyboard.htm
;   http://www.arne.si/~mauricio/PIC.HTM
;    
;    PLEASE do not complain about code implementation, I know there are parts
;    in which is it 'a bit' messy and hard to follow, and other parts where
;    the code is not optimised. Take it as is. Futhermore I did hesitate
;    to include all of the functionality thats currently in, but decided to
;    keep most of it, this as the major complaint i had with the other available
;    pc-keyboard code was just that - 'is was incomplete'. Also do not
;    be discoraged by the size and complexity of it if you are an beginner,
;    as a matter of fact this is only my SECOND program ever using a pic.
;    I think I managed to give credit were credit was due ( 'borrowed code' ).
;    But the originators of these snippets has nothing to do with this project
;    and are probably totally unaware of this, so please do not contact them
;    if you have problems with 'my' implementation.
;
;    BTW ( shameless plug ) UltraEdit rules ! ( if you dont have it, get it ! )
;   http://www.ultraedit.com ( dont 'forget' the 'wordfile' for PIC, color indenting )
;    Without that I guess this file would be 'messy'.
;
;    Ok with that out of the way here we go:
;    
;
;***************************************************************************

;                DESCRIPTION ( short version 😉 )

;    A set of routines which forms a PC keyboard emulator.
;    Routines included are:
;    Interrupt controlled clock ( used for kb comm. and 'heart beat )
;    A 4x8 matrix keyboard scanner ( using an 74HCT4051 3 to 8 analogue multiplexer )
;    Communincation with PC keyboard controller, both send end recive.

;     PC keyboard routines are using 4 pins (2 inputs & 2 outputs) to control
;     keyboard's 2 bidirectional OC lines (CLK & DATA). The following
;     'drawing' conceptually shows how to connect the related pins/lines
;
;    ( ASCII art/info shamelessly 'borrowed' from http://www.arne.si/~mauricio/PIC.HTM )
;
;                 vcc    vcc
;                  |      |
;                  \     -+-
;                  / 2K2    /_\  1N4148
;                  \      |
; pcCLOCK_in -----------------o---o------o---------   kbd CLOCK
;                  |   |     |
;             2N2222   |50pF -+-
; pcCLOCK_out ____/\/\/\____|/     ===    /_\
;            2K2     |\>   |     |
;                  |   |     |
;                 /// ///    ///
;
;     An identical circuit is used for the DATA line.
;    Note: The 2N2222 transitors can be replaced with BC337 ( NPN ) which are cheaper
;    The keyboard matrix routines are using RB4-RB7 as inputs.
;    and RB0-RB2 as output to/from an 3 to 8 multiplexer
;    so that it can read up to 4x8 keys ( 32 ).
;
;    RA4/TOCK1 is an input from an jumper, if low then keyrepeat is disabled and
;    alt-key is enabled instead i.e. instead of repeating a key that has
;    been depressed a certain amount of time, a bit is set that can change the
;    scancode for a key ( of course, all keys can have an alternate scancode ).
;    To exit the alt. keymap press the 'enter alt. keymap' key once again or wait until
;    timeout. ( defined in the code )
;    NOTE !! The so called 'enter alt. keymap' key is hardcoded, i.e the key
;    i've choosen in this 'example' is Column 1 Row 2, if this is to be changed
;    code has to be changed/moved in the debounce and checkkeystate routines.
;    ( marked with <-------Alt keymap code-------> )
;     RB3 is currently used for a flashing diode. ( running )
;    Note my real keyboard ( KeyTronic ) uses a clock rate of 40 us, my implementation
;    uses about 50 us, easily to change.
;

;***************************************************************************

;            DESCRIPTION ( longer version 😉 )
;
;    Pin     Used for
;    ------------------------------------------------------------
;    RA0    Pc keyboard data out ( to pc )
;    RA1    Pc keyboard data in ( from pc )
;    RA2    Pc keyboard clock in
;    RA3    Pc keyboard clock out
;    RA4    Flashing disco led 0.5s duty cycle( could be used for some more meaningful purpose )
;
;    RB0    Least significant bit in column adress to 4051 multiplexer ( own keyboard )
;    RB1    Middle...
;    RB2    Most significant bit -- || --
;    RB3    Input. Disable keyrepeat & enable alternative keymapping ( if = '0',ground)
;        OR if '1' ( free,+V ) enable keyrepeat & disable alternative keymapping
;    RB4    Own keyboard input row 1
;    RB5    --- || --- row 2
;    RB6    --- || --- row 3
;    RB7    --- || --- row 4
;
;    'Basic program structure':
;
;    Init    -    Initialise ports , ram, int, vars
;    Start delay -     After init the timer int is enabled and the flashing led will
;            start to toggle ( flash ). Before I enter the mainloop
;            ( and send any keycodes ) I wait until the led has flashed
;            twice.    This is of course not really needed but I normally
;            like to have some kind of start delay ( I know 1 sec is a bit much 🙂 )
;    Time Int    -    The timer interrupt (BIG), runs in the background:
;            - 'Normal' rate 0.5 ms, when in rx/tx rate is 27 us
;             - Performs an rx/tx check, then jumps to rx, tx or 'heart beat' code.
;             - TX code sends a byte to pc, at a rate of 27us per int.
;               The int rate is actually double the bit rate, as
;               a bit is shifted out in the middle of the clock pulse,
;               I've seen different implementations of this and I think
;               that the bit is not sampled until clock goes low again BUT
;               when logging my keyboard ( Keytronic ) this is the way that it
;                  does it. When all bits are sent, stopbit/parity is sent.
;               And the key is removed from the buffer.
;               After stopbit/parity is sent, a delay is inserted ( 0.5 ms )
;               before next rx/tx check.
;             - RX code recevies a byte from the pc, PIC is controlling clock !!
;               Int rate ( 27 us ) is, again, double bit rate.
;               Toggles clock and samples the data pin to read a byte from pc.
;               When reception is finished an 'handshake' takes place.
;               When a byte has been recevied a routine is called to check
;               which command and/or data was received. If it was
;               keyboard rate/delay or led status byte, it is stored in local ram
;               variables. NOTE: The rate/delay is not actually used for
;               key repeat as the code is right now ( I use my 'own' fixed rate/delay )
;               however it is very easy to implement.
;               After handshake a delay is inserted ( 0.5 ms )
;               before next rx/tx check.
;            - 'Heart beat' ( idle code 0.5 ms tick ) performs:
;             - Check clock/data lines to see if pc wants to send something or
;               if tx is allowed.
;             - If tx is possible it checks the keybuffer for an available key and
;               if keys are in buffer then it initiates a tx seq.
;               and sets the int rate to 27 us.
;             - If the pc wants to send something, an rx seq. is initiated
;               ( there is some handshaking involved, during which
;               the int rate is set to 60 us ) after that, the int rate is
;               set to 27 us and an rx seq is started.
;             - Divides some clock counters to achive 10ms,100ms,500ms sections.
;             - In 100 ms section it performes a numlock status check and
;               keyrepeat check ( both rate and delay is local in 100 ms ticks,
;               thats why I dont use the 'real' rate delay )
;              - If numlock status is not the desired one code is called to
;                 toggle the numlock status.
;              - If a key has been pressed long enough for repeat, an bit is set
;                so we can repeat the key ( send the scancode again ) in the main loop.
;            - In 500 ms section the led is toggled on each loop
;              - Some various alternative keymap checks to get out of
;                alternative keymap. ( i'll get to that in a bit )
;
;    Main loop    - Outputs an adress to the 4051 multiplexer waits 1 ms
;              reads the row inputs ( 4 bits/keys ), increments address
;              and outputs the new adress, waits 1 ms and reads the input
;               ( next 4 bits/keys ). Now using the address counter, calls a
;              debounce/send routine that first debounces the input,
;              ( four consecutive readings before current state is affected )
;              and when a key is changed a make/break code is sent ( put in buffer ).
;              In the next loop the next two columns are read etc. until all
;              4 column pairs are read.
;            - If keyrepeat is enabled ( see pin conf. above ) the
;              repeat flag is checked and if '1' the last pressed key scancode
;              is sent again ( put in buffer ).
;            - If keyrepeat is not enabled( alternative keymap is enabled instead )
;              then various checks to exit the alternative keymap are performed instead.
;
;    Scancodes for all key are located in a lookup table at the end of this file,
;    each key has four program rows, to make room for extended codes and alt. keymap codes.
;
;     Explanation of 'alternative' keymap:
;    
;    Using this program ( or an heavily modified version of it anyway 🙂 )
;     on a computer running Windows posed some small problems; namely:
;    -The keyboard ( mapping ) I used did not have any 'special' key such as
;     <alt>,<ctrl>,<tab> etc.
;    -In windows, things can go wrong, 🙂 if a dialog pops up or something similar
;     there were just no way one could dispose of this with the keymapping i used.
;    - 'Only' 28 keys were implemented ( hardware wise ).
;    In this particular case the keyrepeat was disabled ( due to the nature of the application )
;    Therefore i came up with the solution to use the keyrepeat related routines and vars.
;    To handle a so called 'alternative' keymapping.
;    This means that an key is dedicated to be the alt. keymap toggle key,
;    when pressing this longer than the programmed repeat delay, instead of
;    repeating the key a bit variable is set to use another set of scancodes for
;    the keyboard. This 'alternative' keymap is then enabled even though the
;    alt. keymap toggle key is released, but it also incorporates an timeout
;    that will return to normal keymap if no key is pressed within a certain time ( currently 7 sec )
;    Of course pressing the alt. keymap toggle key again ( while in alt. keymap )
;    will return the keyboard to the normal keymap.
;    NOTE !! the key choosen for the alt. keymap toggle is hardcoded, so if the scancode
;    for this is changed in the lookup table, changes have to be made in other routines
;    as well. ( namly : CHECK_KEY_STATE, and DEBOUNCE_COLUMN routines )
;    While in alt. keymap each key CAN have an alternative scancode ( see lookup table ).
;    Also by using the numlock bit, one can toggle numlock status 'on the fly' when entering
;    or exiting the alt keymap.
;
;    Some notes about the local keyboard interface ( matrix 😞
;    Although the hardware circuit and software allows virtually unlimited
;    simultaneosly pressed keys, the keyboard matrix itself normally poses
;    some limitations on this. If an keymatrix without any protective diodes
;    are used then one would have loops INSIDE the keymatrix itself when
;    multiple keys are pressed in different columns .
;    Look at the ( although horrible 😉 ) ASCII art below(internal weak pullup enabled):
;    0 - Key is free
;    1 - Key is pressed ( connection between hor/ver rows )
;    Three keys pressed adressing ( reading ) left column :
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    --------1-------1------ ( row 2 )
;                    |       |
;    To pic3    --------1-------0------ ( row 3 )
;                   |       |
;        Column(4051)    0V    -    ( Current column is set to 0V when adressing )
;
;    This works as intended, we can read a '0' on pic inputs 2,3 which
;    is what we expected. The current ( signal ) follows the route marked with '*':
;    ( only the 'signal path' is shown for clarity )
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    *********-------1------ ( row 2 )
;                    *       |
;    To pic3    *********-------0------ ( row 3 )
;                   *       |
;        Column(4051)    0V    -    ( Current column is set to 0V when adressing )
;
;    However, when we now read ( address ) the right column instead we
;    do not read what is expected ( same three keys still pressed 😞
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    *****************------ ( row 2 )
;                    *<-     *
;    To pic3    *********-------*------ ( row 3 )
;                   |       *
;        Column(4051)    -    0V    ( Current read column is set to 0V when adressing )
;
;    As you can see the 'signal' travels in the keymatrix itself ( where the '<-' is )and
;    will cause a 'ghost' signal to be read on the pic. So instead
;    of having an '0' on input 2 only we also can read an '0' on input 3.
;    This is because the two keys in column 1 are interconnected ( when they are pressed ).
;    Keep this in mind if you are planning to support multiple pressed keys.
;    
;
;***************************************************************************
;
;    Some suggestions for 'improvements' or alternations
;
;    - Using the jumper 'disable-repeat' as a dedicated key for switching
;      to alternative keymapping.
;    - Enable repeat in alternative keymapping
;    - Clean up TX/RX code ( a bit messy )
;    - Using the led output ( or jumper input ) as an extra adress line
;      to the multiplexers ( in this case 2 pcs. 74HCT4051 ) we could have
;      4x16 keys instead. Would require some heavy modifications though
;      as there are not much ram/program space left. But if alternative
;        keymapping is discarded ( most likely if one has 64 keys ) each
;      key in the lookup table only needs to be 2 lines instead of 4.
;      That would 'only' require some modifications to preserv ram.
;    - Using the EERAM for 'macros' or similar ( not used at all now )
;
;***************************************************************************
;
;            LEDGEND
;
;    I tend to use the following when naming vars. etc. :
;    ( yes i DO like long names )
;    
;    For 'general' purpose pins:
;
;    An input pin is named I_xxx_Name where :
;
;        I_   - This is an input pin 😉
;        xxx_ - Optional what type of input, jmp=jumper etc.
;        Name - Self explanatory
;
;    An output pin is named O_xxx_Name where:
;    
;        O_   - This is an output pin 😉
;        xxx_ - Optional what type of output, led=LED etc.
;        Name - Self explanatory
;
;    Application(function) specific pins:
;
;    An application(function) specific pin is named xxName where:
;        
;        xx   - What/Where, for example pc=To/From pc
;        Name - Self explanatory ( what does it control etc )
;
;    An #define/constant/equ (no pin or ram ) uses all capital letters e.x. #define BREAK 0xF0
;
;    A bit variable will always start with '_'. For example '_IsLedStatus'
;
;    All other ( mostly ramvars. ) are named in various ways.
;
;
;***************************************************************************

    TITLE "PC Keyboard emulator - By Tony K&uuml;bek"

        Processor       16F628a
        Radix   DEC
        EXPAND

    

;***** HARDWARE DEFINITIONS ( processor type include file )

    INCLUDE <C:\PROGRAM\MPLAB\P16F84.INC>           ; this might need changing !

;***** CONFIGURATION BITS

    __CONFIG _WDT_ON&_HS_OSC&_CP_OFF&_PWRTE_ON        ; _WDT_OFF
    
    __IDLOCS 010Bh    ; version 1.0B

;***** CONSTANT DEFINITIONS

    CONSTANT    BREAK = 0xF0        ; the break key postfix ( when key is released )
    CONSTANT    EXTENDED = 0xE0     ; the extended key postfix

    ; As i dont really use the rate/delay I receive from the pc ( easy to change )
    ; this is the current rate/delay times i use:
    CONSTANT    DELAY_ENTER_ALTKEYMAP = 0x1E    ; x100 ms , approx 3 seconds ( 30 x 100 ms )
                            ; how long the 'enter altkeymap' key must
                            ; be in pressed state before the altkeymap is enabled
    CONSTANT    DELAY_EXIT_ALTKEYMAP = 0x0F    ; x0.5 sec , approx 7.5 sec
                            ; how long before we exit the alt keymap if no key is
                            ; pressed.
    CONSTANT     DELAY_REPEAT    = 0x08        ; x100 ms, approx 800 ms
                            ; how long before we START repeating a key
    CONSTANT    DELAY_RATE    = 0x02        ; x100 ms, approx 200 ms repeat rate
                            ; how fast we are repeating a key ( after the delay above )
    
;***** CONSTANT DEFINITIONS ( pins )

;    For connection with PC keyboard

#define pcCLOCK_in  PORTA,2   ; Input PC keyboard clock
#define pcCLOCK_out PORTA,3   ; Output PC keyboard clock
#define pcDATA_in   PORTA,1   ; Input PC keyboard data
#define pcDATA_out  PORTA,0   ; Output PC Keyboard data


;    For connection (input) with our own keyboard
; Note I actually dont use these (definitions!) in the program, but they might come in handy
; at one time or another 😉 ( I use the pins though.. )

#define kbROW_1   PORTB,7
#define kbROW_2   PORTB,6
#define kbROW_3   PORTB,5
#define kbROW_4   PORTB,4

;    Indications ( output )

#define O_led_KEYCOMM_ok    PORTA,4        ; communication seems ok led ( flashing )

;    Disable/enable key repeat input jumper

#define I_jmp_NoRepeat    PORTB,3    ; note: internal weak pullup enabled

;    For keybuffer ( using the indirect file selector FSR )

#define KeyBufferHead    FSR


;***** RAM ASSIGNMENT
      
;        RAM 0x0c
        CBLOCK    0x0C
            KeyBufferTail    ; where the last byte in buffer is..
            clkCount         ; used for clock timing
            Offset        ; used for table reads        
            Saved_Pclath       ; Saved registers during interrrupt
            Saved_Status       ; -----
            Saved_w            ; -----
            CurrKey        ; current key ( rx or tx )..
            KeyParity    ; key parity storage ( inc. for every '1' )
            Divisor_10ms    ; for the timer
            Divisor_100ms   ; ditto
            Divisor_500ms   ;
            Divisor_Repeat  ; timer for repeated key sends

            Flags        ; various flags
            RepeatFlags     ; flags for repeating a key
            bitCount    ; bitcounter for tx/rx
            Comm_Flags    ; flags used by both rx and tx routines
            Temp_Var    ; temp storage, can be used outside int loop
            TRX_Flags    ; flags used by both rx and tx routines
            CommandData     ; bit map when receving data bytes from pc
                    ; for example led status/ delay / etc
            KbLedStatus     ; to store status led bitmap for keyboard ( pc first sends 'ED' then this byte )
                        ;  bit 0=Scroll lock  ( 1=on )
                        ;  bit 1=Num lock
                        ;  bit 2=Caps lock
                        ;  bits 3-7 = unused
            KbRateDelay    ; to store repeat delay/rate ( pc first sends 'F3' then this byte )
                        ;  bit 0-4 (rate) = '00000' is 30x/sec '11111' is 2x/sec ( i.e. value * 33 ms )
                        ;  bit 5-7 (delay)= '00' is 250 ms, '11' is 1000 ms ( ie. value * 250 ms )   
                    ;  bit 7 = unused
            BufTemp        ; temp byte for storing scancode to put in buffer
            Temp        ; temp byte, used locally in buffer routine
            Temp2        ;
            LastKey        ; stores the last sent key
            KbBufferMin    ; where our keybuffer starts
            Kb1        ; used in keybuffer
            Kb2        ; used in keybuffer
            Kb3        ; used in keybuffer
            Kb4        ; used in keybuffer
            Kb5        ; used in keybuffer
            Kb6        ; used in keybuffer
            Kb7        ; used in keybuffer
            Kb8        ; used in keybuffer
            Kb9        ; used in keybuffer
            Kb10        ; used in keybuffer
            KbBufferMax    ; end of keybuffer
            TempOffset    ; temporary storage for key offset ( make/break )

            LastMakeOffset  ; storage of last pressed key ( offset in table )
            RepeatTimer    ; timer to determine how long a key has been pressed
            RepeatKey     ; the key to repeat
            repTemp        ; temporary storage in repeat key calc.
            repKeyMap    ; bit pattern for the column in which the repeat key is in
                    ; i.e. a copy of kbColumnXX_Old where 'XX' is the column
            LastKeyTime    ; counter when last key was pressed, used to get out of altkeymap
                    ; after a specific 'timeout'

            kbScan        ; scan code for pressed/released key
            kbTemp        ; temp storage for key states

            kbState        ; which keys that has changed in current columns
            kbBitCnt    ; bit counter for key check ( which key/bit )            
            
            kbColumnCnt    ; column counter ( loops from 8 to 0 )
                    ; Used as output to multiplexer/decoder and in debounce routines

            kbColumnVal    ; current value of the last 2 columns ( 2x4bits = 8 bits ) input pins ( keys )
                    ;
                    ; Note the kbColumnXX_New variables is not really needed
                    ; used it while making the program ( debugging 😉 ).
                    ; so if more free ram is needed change code using these to use
                    ; the current 'input' sample instead. ( kbColumnVal )
            kbColumn12_New  ; New debounced reading for column 1 & 2
            kbColumn12_Old  ; Latest known valid status of column 1 & 2
            kbColumn12Cnt    ; Debounce counter for column 1 & 2
            kbColumn12State ; State of debounce for column 1 & 2

            kbColumn34_New  ; New debounced reading for column 3 & 4
            kbColumn34_Old  ; Latest known valid status of column 3 & 4
            kbColumn34Cnt    ; Debounce counter for column 3 & 4
            kbColumn34State ; State of debounce for column 3 & 4

            kbColumn56_New  ; New debounced reading for column 5 & 6
            kbColumn56_Old  ; Latest known valid status of column 5 & 6
            kbColumn56Cnt    ; Debounce counter for column 5 & 6
            kbColumn56State ; State of debounce for column 5 & 6

            kbColumn78_New  ; New debounced reading for column 7 & 8
            kbColumn78_Old  ; Latest known valid status of column 7 & 8
            kbColumn78Cnt    ; Debounce counter for column 7 & 8
            kbColumn78State ; State of debounce for column 7 & 8

        ENDC

;******* END RAM

; *** flags used by both rx and tx routines
#define _isParity    Comm_Flags,0    ; bit in rx/tx is parity bit
#define _KeyError    Comm_Flags,1    ; set to '1' when an error is detected
#define _isStartBit    Comm_Flags,2    ; set to '1' when bit in rx/tx is startbit
#define _isStopBit    Comm_Flags,3    ; --- || --- but stopbit
#define _RxCanStart     Comm_Flags,4    ; for handshaking, when negotiating an rx seq.


; *** dito used for rx and tx
#define _KeyReceived    TRX_Flags,0    ; rx
#define _RX_Mode    TRX_Flags,1    ; rx is in progress ( started )
#define _doRXAck    TRX_Flags,2    ; do rx handshake
#define _RXAckDone    TRX_Flags,3    ; rx handshake is done
#define _RXEnd        TRX_Flags,4    ; rx seq is finished
#define _RXDone        TRX_Flags,5    ; rx exit bit
#define _KeySent    TRX_Flags,6    ; tx key has been succesfully sent
#define _TX_Mode    TRX_Flags,7    ; tx is in progress ( started )


; *** flags to determine the meaning of an incomming data is
; i.e. when then pc has sent a 'data follow' instruction
; these bits are set according to the command,
; i.e. the next incomming byte is.....
#define _IsLedStatus    CommandData,0    ; the next incoming byte contains kb led status
#define _IsRateDelay    CommandData,1    ; the next incoming byte contains kb rate/delay
#define _SkipByte    CommandData,2    ; other data not saved
 
; *** bit meaning in KbLedStatus ( the leds on the keyboard )
; i.e local ram copy of this byte
#define _LedScrollLock    KbLedStatus,0    ; '1' led is on
#define _LedNumLock     KbLedStatus,1    ;
#define _LedCapsLock    KbLedStatus,2    ;
#define _IsFirstLedStatus KbLedStatus,7    ; set this to '1' at startup, to know that our local
;                    ; copy (led status) is not yet syncronised. Used to
;                    ; 'force' sync. the first time we set the local numlock bit.

; *** flags used for various purposes
#define _Timer        Flags,0 ; used for waiting
;#define _WrongPar    Flags,1    ; ?? ( used during dev. to force wrong parity sends, removed )
#define _LastColumn    Flags,2    ; for kb scan code
#define _isBreak    Flags,3 ; if '1' the currently handled key is break ( released ), else make ( pressed )
;#define _isFree         Flags,4 ; Not used ! ( temporary during dev. )
#define _AltKeymap      Flags,5 ; is we use alternative keymap ( other scancodes ) see below !
                ; enabled ONLY when keyrepeat is disabled ( I_jmp_NoRepeat (RB3) is low )
#define _ExitAltKeymap  Flags,6 ; start checking if we should exit alternative keymap
                ; if all keys are 'free' ( not pressed ) we exit altkeymap
#define _InAltKeymap    Flags,7 ; if we are waiting for second press/release to get out of altkeymap


#define _doRepeat    RepeatFlags,0 ; if the last pressed key should be 'repeated'
#define _doSendKey    RepeatFlags,1 ; send the key in RepeatKey to pc     
#define _isExtended     RepeatFlags,2 ; temp flag if last key is extended
#define _RepeatIsExt    RepeatFlags,3 ; the key to repeat is extended key
#define _startRepeat    RepeatFlags,4 ; start repeat timer
#define _DoExitAltKeymap RepeatFlags,5     ; bit set when we are getting out of alternative keymap
#define _NumLock    RepeatFlags,6      ; 'mirror' of numlockstatus, by setting/clearing this bit
                           ; numlock status will be changed.
                    ; I.e. there is no need to 'manually' send break/make code for numlock
                    ; key, by setting this bit to '1' numlock status will by automaticlly
                    ; ( inside int handler ) set to 'on'( within 100 ms ). Se code for example.
#define _WaitNumLock    RepeatFlags,7    ; bit set when we have sent make/break numlock scancode
                    ; and waiting for numlock status byte reply.
                    ; ( to inhibit a new numlock scancode send )


;**************************************************************************    
;                Macros             
;**************************************************************************    
    

;+++++
;    BANK0/1 selects register bank 0/1.
;    Leave set to BANK0 normally.

BANK0    MACRO
    BCF    STATUS,RP0
    ENDM

BANK1    MACRO
    BSF    STATUS,RP0
    ENDM

;+++++
;    PUSH/PULL save and restore W, PCLATH and STATUS registers -
;    used on interrupt entry/exit

PUSH    MACRO
    MOVWF    Saved_w        ; Save W register on current bank
    SWAPF    STATUS,W    ; Swap status to be saved into W
    BANK0            ; Select BANK0
    MOVWF    Saved_Status    ; Save STATUS register on bank 0
    MOVFW    PCLATH
    MOVWF    Saved_Pclath    ; Save PCLATH on bank 0
    ENDM

PULL    MACRO
    BANK0            ; Select BANK0
    MOVFW    Saved_Pclath
    MOVWF    PCLATH        ; Restore PCLATH
    SWAPF    Saved_Status,W
    MOVWF    STATUS        ; Restore STATUS register - restores bank
    SWAPF    Saved_w,F
    SWAPF    Saved_w,W    ; Restore W register
    ENDM


;+++++        
;     We define a macro that will switch an output pin on or off depending
;     on its previous state. We must be on bank0 !!
;

TOGGLE_PIN    MACRO WHICH_PORT,WHICH_PIN
        LOCAL TOGGLE_PIN10, TOGGLE_END
        
        BTFSC    WHICH_PORT,WHICH_PIN    ; is the pin high ?
        GOTO     TOGGLE_PIN10        ; yes, clear it
        BSF    WHICH_PORT,WHICH_PIN    ; no, so set it                                        
            GOTO       TOGGLE_END
TOGGLE_PIN10:
        BCF    WHICH_PORT,WHICH_PIN    ; clear the pin            
TOGGLE_END:
        ENDM


;*************************************************************
; Credit for routine ( almost unchanged, expect it's now a macro 😉 )
; goes to Scott Dattalo http://www.interstice.com/~sdattalo/technical/software/software.html
; ( debouce routine at http://www.interstice.com/~sdattalo/technical/software/pic/debounce.html )
;
;     DEBOUNCE_BYTE - 'debounces' 8 bits, when a bit state
;    has been 'active' for 4 consecutive debounce loops
;    it's state is put into the 'debounced' sample ( i.e. output )
;
; The purpose of this routine is to debounce, i.e. digitally low pass filter
; inputs. The algorithm handles upto 8 bits at a time. An input is considered
; filtered if it has not changed states in the last 4 samples.
;
; 2-bit cyclic vertical counters count the 4 samples. As long as there is no
; change, the counters are held in the reset state of 00b. When a change is detected
; between the current sample and the filtered or debounced sample, the counters
; are incremented. The counting sequence is 00,01,10,11,00... When the counters
; roll over from 11b to 00b, the debounced state is updated. If the input changes
; back to the filtered state while the counters are counting, then the counters
; are re-initialized to the reset state and the filtered state is unaffected.
; In other words, a glitch or transient input has been filtered.
;
; Here's the C-psuedo code:
;
;static unsigned clock_A,clock_B,debounced_state
;
;debounce(unsigned new_sample)
;{
;  unsigned delta;
;
;  delta = new_sample ^ debounced_state;   //Find all of the changes;
;  clock_A ^= clock_B;                     //Increment the counters
;  clock_B  = ~clock_B;
;
;  clock_A &= delta;                       //Reset the counters if no changes
;  clock_B &= delta;                       //were detected.
;
;      //Preserve the state of those bits that are being filtered and simultaneously
;      //clear the states of those bits that are already filtered.
;  debounced_state &= (clock_A | clock_B);
;      //Re-write the bits that are already filtered.
;  debounced_state |= (~(clock_A | clock_B) & new_sample);
;}
;
; The 2-bit counters are arranged "vertically". In other words 8 counters
; are formed with 2 bytes such that the corresponding bits in the bytes are
; paired (e.g. MSBit of each byte is paired to form one counter).
; The counting sequence is 0,1,2,3,0,1,... And the state tables and Karnaugh
; maps are:
;
;State Table:     Karnaugh Maps:
;pres  next      B
; SS  SS         0   1
; AB  AB       +---+---+    +---+---+
;--------   A 0|   | 1 |    | 1 |   |
; 00  01       +---+---+    +---+---+
; 01  10      1| 1 |   |    | 1 |   |
; 10  11       +---+---+    +---+---+
; 11  00      A+ = A ^ B     B+ = ~B
;
; Here's the PIC code that implements the counter:
;       MOVF    SB,W    ;W = B
;       XORWF   SA,F    ;A+ = A ^ B
;       COMF    SB,F    ;B+ = ~B
;  14 instructions
;  15 cycles
; Inputs:
;   NewSample - The current sample
; Outputs
;   DebouncedSample - The current value (filtered version of NewSample)
;
; VARS used
;   DBCnt,
;   DBState - State variables for the 8 2-bit counters
;
    
DEBOUNCE_BYTE MACRO NewSample,DebouncedSample,DBCnt,DBState

        ;Increment the vertical counter        

    MOVF    DBState,W
        XORWF   DBCnt,F        
    COMF    DBState,F

        ;See if any changes occurred        

    MOVF    NewSample,W        
    XORWF   DebouncedSample,W

        ;Reset the counter if no change has occurred        

    ANDWF   DBState,F
        ANDWF   DBCnt,F        ;Determine the counter's state
        MOVF    DBState,W        
    IORWF   DBCnt,W

        ;Clear all bits that are filtered-or more accurately, save
        ;the state of those that are being filtered        

    ANDWF   DebouncedSample,F
        XORLW   0xff        ;Re-write the bits that haven't changed.
        ANDWF   NewSample,W        
    IORWF   DebouncedSample,F        
    
    ENDM

    

;**************************************************************************    
;                 Program Start
;**************************************************************************    


;    Reset Vector

    ORG    H'00'

    ; For the sole purpose of squeezing every last byte of the programming mem
    ; I actually use the 3 program positions before the interrupt vector
    ; before jumping to the main program. Take note though that
    ; ONLY 3 instructions are allowed before the jump to main loop !!

    BANK0

        CLRF    PCLATH
    CLRF    INTCON    

    GOTO    INIT

;**************************************************************************    
;                     Interrupt routine
; An humongously big int handler here 😉
; but the keyboard handling is FULLY in the background, without any intervention
; in the main loop whatsoever. I like the solution anyway.
;
;**************************************************************************


;    Interrupt vector

    ORG    H'04'

INT
    PUSH            ; Save registers and set to BANK 0
                
    
    BTFSS   INTCON,T0IF        ; check if TMR0 interrupt
        GOTO    INTX            ; whoops ! 'unknown' int, should be disabled...
                ; NOTE ! if an 'unknown' int triggers the int routine
                ; the program will loop here for ever 😉 ( as the calling flag is not cleared )

    

    ;+++
    ; Timer (TMR0) timeout either heart beat or tx/rx mode
    ; In 'heart beat mode' we monitor the clock and data lines
    ; at ( roughly )= 0.5 ms interval, we also check the send buffer
    ; if there are any keys to send to pc ( if clock/data levels allows us to )
    ; In tx/rx mode we are controlling the clock/data line = 27 us tick
    ; Note: This software works using both 8Mhz and 10Mhz resonators without modifications
    ; however the 'timing' will then of course be 'off'.

INT_CHECK    
    BCF    INTCON,T0IF     ; Clear the calling flag !
    
     BTFSC    _TX_Mode    ; check if we are in tx mode
    GOTO    INT_TX        ; yep, goto tx mode code..    
    BTFSC    _RX_Mode    ; are we in rx mode ?
    GOTO    INT_RX        ; yep goto rx mode code
    GOTO    INT_HEART_BEAT  ; nope just goto 'heart beat' mode

;*************** TX code start ***********************************

INT_TX
    MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
        MOVWF   TMR0
 
    BTFSS    clkCount,0    ; check if we should toggle clock
    GOTO    INT_DEC_CLOCK    ; bit low,decrement and check if we should toggle data instead
    
    DECFSZ    clkCount,F    ; decrement and check if we are at zero..
    GOTO    INT_CLOCK     ; not zero then toggle clock line
    
    GOTO    INT_EXIT_TX        

INT_CLOCK
    
    BTFSC    pcCLOCK_out    ; check if we are low
    GOTO    INT_CLOCK_HIGH  ; yep set to high
                
    BTFSS    pcCLOCK_in    ; check if pc is pulling the clock line low
                ; i.e. it wants to abort and send instead..
    GOTO    INT_TX_CHECK_ABORT    ; abort this transfer
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    GOTO    INTX

INT_CLOCK_HIGH
    BCF    pcCLOCK_out    ; set high ( release line )
                ;BCF    _ClockHigh    ;
    GOTO    INTX

INT_TX_CHECK_ABORT
    GOTO    INT_EXIT_TX

INT_DEC_CLOCK    
    DECF    clkCount,F    ; decrement clock counter ( so we toggle next time )
INT_DATA
    BTFSS    bitCount,0    ; check bit counter
    GOTO    INT_DATA_IDLE    ; no data toggle

    DECFSZ    bitCount,F    ; decrement bit counter
    GOTO    INT_DATA_NEXT    ; next bit..

INT_NO_BITS
    BSF    bitCount,0    ; just in case ( stupid code, not sure its needed, just
                ; to make it impossible to overdecrement )***
    BTFSC    _isParity    ; are we sending parity ?
    GOTO    INT_DATA_END    ; exit
    
    ; all bits sent
    ; delete the last key from the buffer
    CALL    INC_KEY_HEAD    ; remove the ( last ) key form the buffer as is was sent ok..

    ; all bits sent check parity
    
    BSF    _isParity    ; set flag data is parity
    
    BTFSS    KeyParity, 0    ; is the last parity bit high? ( odd num of bits )
                ; then parity should be high ( free )
    GOTO     INT_DATA_HIGH_PAR      ; yes
    BSF    pcDATA_out    ; no, parity should be 'low' ( pulled down )
    GOTO     INTX        ;

INT_DATA_HIGH_PAR:    
    BCF    pcDATA_out    ; set parity bit high ( release data line )
    GOTO    INTX        ; and exit..


INT_DATA_END    
    BTFSS    _isStopBit    ; is the stopbit sent ?
    GOTO    INT_DATA_STOPB    ; nope then set stopbit flag

    BCF    pcDATA_out    ; parity bit sent, always release data line ( stop bit )        
    GOTO    INTX

INT_DATA_STOPB
    BSF    _isStopBit    ; set the stopbit flag
    GOTO    INTX        ; and exit

INT_DATA_IDLE
    DECF    bitCount,F    ; decrement bit counter
    GOTO     INTX        ; no toggle of data line

INT_DATA_NEXT
    BTFSS     CurrKey,0    ; is the last bit of the key_buffer high?
    GOTO     INT_DATA_LOW    ; no, pull data low
    BCF      pcDATA_out    ; yes, release data line
    INCF    KeyParity,F    ; increment parity bit
    GOTO    INT_DATA_ROTATE    ; rotate data
    
INT_DATA_LOW    ; last bit is low
    BSF    pcDATA_out    ; set the bit

INT_DATA_ROTATE
    RRF    CurrKey,F    ; rotate right by 1 bit    
    GOTO    INTX

INT_EXIT_TX
    ; setup the timer so we accomplish an delay after an tx seq
    
    BCF    _TX_Mode    ; clear tx mode flag
    BCF    pcCLOCK_out    ; release clock line
    BCF    pcDATA_out    ; and data line
;
    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0

    GOTO    INTX


;************** TX code end *****************

;************** RX code start ***************

INT_RX
    MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
        MOVWF   TMR0


    BTFSS    clkCount,0    ; check if we should toggle clock
    GOTO    INT_RX_DEC_CLOCK    ; bit low,decrement and check if we should read data instead
    
    DECFSZ    clkCount,F    ; decrement and check if we are at zero..
    GOTO    INT_RX_CLOCK     ; not zero then toggle clock line
    
    BCF    pcCLOCK_out    ; release the clock line we are done..
    BCF    _RX_Mode    ; clear rx mode bit ( go over to heart beat mode )
    GOTO    INT_EXIT_RX            

INT_RX_CLOCK

    BTFSC    pcCLOCK_out    ; check if we are low
    GOTO    INT_RX_CLOCK_HIGH     ; yep set to high ( release line )
    
    BTFSC    _isStartBit    ; check if this is the first bit ( start )
    GOTO    INT_RX_START    ; clear start bit and continue
    
    BTFSC    _isParity    ; check if this is the parity bit ( or parity has been received )
    GOTO    INT_RX_PAR    ; yep check parity

    GOTO    INT_RX_BIT    ; ok just a 'normal' bit read it
    
    
INT_RX_PAR            ; check parity
    BTFSC    _doRXAck    ; check the handshake flag
    GOTO    INT_RX_HNDSHK    ; start handshake check

    BTFSS    pcDATA_in    ; is the input high ?
    GOTO    INT_RX_PAR_HIGH    ; yep
    BTFSC    KeyParity,0    ; is the parity '0' ( should be )
    GOTO    INT_RX_PAR_ERR    ; nope parity error
    GOTO    INT_RX_ACK    ; parity ok next should be ack ( we take data line low )

INT_RX_PAR_HIGH
    BTFSS    KeyParity,0    ; check that parity bit is '1'
    GOTO    INT_RX_PAR_ERR    ; nope parity error
    GOTO    INT_RX_ACK    ; parity ok, next is ack ( we take data line low )

INT_RX_PAR_ERR
    BSF    _KeyError    ; set error flag

INT_RX_ACK
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    BSF    _doRXAck    ; enable ack check
    GOTO    INTX

INT_RX_HNDSHK
    BTFSS    _RXEnd        ; if we are done dont take data low
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    
    BTFSC    _RXAckDone    ; chek if hand shake ( ack ) is done ?
    BSF    _RXEnd        ; ok we are now done just make one more clock pulse

    GOTO    INTX        ; exit    
        

INT_RX_CLOCK_HIGH

    BCF    pcCLOCK_out    ; set high ( release line )
    BTFSS    _RXAckDone    ; are we done.. ?
    GOTO    INTX
    BTFSS    _RXDone        ; finished ?
    GOTO    INTX

    BCF    _RX_Mode    ; and clear rx flag..
    GOTO    INT_EXIT_RX    ; bye bye baby

INT_RX_DEC_CLOCK    

    DECF    clkCount,F    ; decrement clock counter ( so we toggle next time )
    BTFSS    _doRXAck    ; check if we are waiting for handshake
    GOTO    INTX
    

     BTFSC    pcCLOCK_out     ; check if the clock is low ( pulled down )
    GOTO    INTX        ; nope we are pulling down then exit
                ; we only take over the data line if
                ; the clock is high ( idle )
                ; not sure about this though.. ???

     
    BTFSC    _RXEnd        ; are we done ?
    GOTO    INT_RX_END

    ; handshake check if data line is free ( high )
    BTFSS    pcDATA_in    ; is data line free ?
    GOTO    INTX        ; nope

    BSF    pcDATA_out    ; takeover data line
    BSF    _RXAckDone    ; we are done..at next switchover from low-high we exit
    GOTO    INTX        ;

INT_RX_END
    BCF    pcDATA_out    ; release data line
    BSF    _RXDone        ; we are now done
    GOTO    INTX

INT_RX_START
    BCF    _isStartBit    ; clear start bit flag
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    GOTO    INTX
INT_RX_BIT
    BCF    CurrKey,7
    BTFSS    pcDATA_in    ; is bit high
    GOTO    INT_RX_NEXT    ; nope , it's a '0'
    BSF    CurrKey,7    ; set highest bit to 1
    INCF    KeyParity,F    ; increase parity bit counter
    
INT_RX_NEXT
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )

    DECFSZ    bitCount,F    ; decrement data bit counter    
    GOTO    INT_RX_NEXT_OK

    BSF    bitCount,0    ; just in case ( so we cannot overdecrement )
    BSF    _isParity    ; next bit is parity
    GOTO    INTX

INT_RX_NEXT_OK
    CLRC            ; clear carry, so it doesnt affect receving byte
    RRF     CurrKey,F    ; rotate to make room for next bit
    GOTO    INTX        ; and exit

INT_EXIT_RX    

    ; handle the recevied key ( if not it is an 'data' byte )

    MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8 ( int handling code )= 200 us
                ;
        MOVWF   TMR0        ; this delay seems to be needed ( handshake ? )
    
    ; check if this is an data byte ( rate/delay led status etc )
    
    MOVF    CommandData,F    ; reload into itself ( affect zero flag )

    BTFSS    STATUS,Z    ; check zero flag
    GOTO    INT_STORE_DATA    ; byte contains data ( rate/delay etc )
    
    CALL    CHECK_RX_KEY    ; no data, handle recevied command
    GOTO    INTX

INT_STORE_DATA
    ; store data byte in 'currkey',
    ; first reply with 'ack'
    
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    BTFSS    _IsLedStatus    ; is it led status byte ?
    GOTO    INT_STORE_RATE  ; nope check next
    
INT_STORE_NUM
    ; byte in 'currkey' is led status byte, store it
    MOVF    CurrKey,W    ; get byte
    MOVWF    KbLedStatus    ; and store it
    BTFSC    _WaitNumLock    ; was this something we were waiting for ?
                ; i.e. we sent the make scancode for numlock.
    CALL    RELEASE_NUMLOCK    ; yep, then send release code for 'soft' numlock

    GOTO    INT_STORE_EXIT    ; store it in local ram copy and exit

INT_STORE_RATE

    BTFSS    _IsRateDelay    ; is it rate/delay byte ?
    GOTO    INT_STORE_EXIT  ; nope then send ack end exit
    ; byte in 'currkey' is rate/delay byte, store it
    MOVF    CurrKey,W    ; get byte
    MOVWF    KbRateDelay    ; and store it
    
INT_STORE_EXIT
    
    CLRF    CommandData    ; clear data byte flags
    GOTO    INTX


;******************* 'heart' beat code ( 0.5 ms tick ) *********
; Note: As I 'mess' with the int timer this 'heart beat' is by no means
; an accurate clock. However it can work as a rough estimate for local time.
;
INT_HEART_BEAT

    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0


INT_CHECK_CLKDTA:  

    ; CLOCK DATA   Action
    ;-----------+--------
    ;   L    L  |  wait ?
    ;   L    H  |  wait, buffer keys
    ;   H    L  |  start an rx sequence
    ;   H    H  |  keyboard can tx
        
    BTFSS    pcDATA_in    ; is the data line high ( free )..
    GOTO    INT_CHECK_RX    ; Nope it's pulled down, check if rx is requested
        
    BTFSC    pcCLOCK_in    ; Is the clk line low  ( pulled down ) ?        
    GOTO    INT_CHECK_BUFF  ; Nope, so check if we have any keys to send

    GOTO    INT_IDLE    ; clock is low , wait and buffer keys( i.e. no rx/tx )

        
                        
INT_CHECK_RX           ; pc ( probably ) wants to send something..
        
    BTFSS    pcCLOCK_in    ; wait until clock is released before we go into receving mode..
    GOTO    INT_RX_IDLE    ; nope still low

    ; clock now high test if we are set to start an rx seq.
    BTFSS    _RxCanStart     ; have we set the flag ?
    GOTO    INT_WAIT_RX    ; nope then set it      
            
    BTFSC    pcDATA_in    ; make sure that data still is low
    GOTO    INT_ABORT_RX    ; nope abort rx req, might been a 'glitch'

    ; initiate the rx seq.

    CLRF    Comm_Flags    ; used by both tx/rx routines ( and _RxCanStart bit !! )
    CLRF    TRX_Flags    ; clear tx/rx flags
    CLRF    KeyParity    ; clear parity counter

    BSF    _RX_Mode          ; set rx mode flag..
    BSF    _isStartBit    ; set that next sampling is start bit
    
    ; preset bit and clock counters

    MOVLW    H'2F'        ; = 47 dec, will toggle clock output every even number until zero
    MOVWF    clkCount    ; preset clock pulse counter

    MOVLW    H'08'        ; = 8 dec, number of bits to read
                ; then parity bit will be set instead

    MOVWF    bitCount    ; preset bit counter

    ; note as we are starting the clock here we allow a longer time before we start
    ; the actual 20 us tick, this the first time we wait about 200 us before the first clock 'tick'     

    MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8us ( int handling code )= 200 us
                ;
        MOVWF   TMR0
    
    GOTO    INTX        ; exit, the next int will start an rx seq

INT_WAIT_RX:
    BSF    _RxCanStart    ; set flag so we start rx next int ( 0.5 ms )
INT_RX_IDLE
    ; reload clock so we check more often
    MOVLW   H'F0'           ; start timer with 240 ( 256-16  = 240 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 16 + 8/10us ( int handling code )= (about) 60 us
        MOVWF   TMR0

    GOTO    INTX        ;

INT_ABORT_RX
    BCF    _RxCanStart    ; clear flag ( forces a 'new' rx start delay )
    GOTO    INT_IDLE    ;
                                               

INT_CHECK_BUFF:
    ; check if we have any keys to send to pc

        MOVF     KeyBufferTail,W ; get end of buffer to 'w'
        SUBWF     KeyBufferHead,W ; subtract start of buffer if head - tail = zero, no byte
        BTFSC   STATUS, Z       ; zero flag = no byte to send         
        GOTO    INT_IDLE    ; then do the 'idle' stuff
         
INT_SEND_KEY
    ;key in buffer, get it and initiate an tx seq...
    CALL    GET_KEY_BUFFER    ; get the key into CurrKey
    MOVF    CurrKey,W
    MOVWF    LastKey        ; store last sent key

    ; setup our tx/rx vars

    CLRF    Comm_Flags    ; used by both tx/rx routines ( and _RxCanStart bit !! )
    CLRF    TRX_Flags    ; clear tx/rx flags
    CLRF    KeyParity    ; clear parity counter

    BSF    _TX_Mode          ; set tx mode flag..
    
    ; preset bit and clock counters

    MOVLW    H'2B'        ; = 43 dec, will toggle clock out put every even number until zero
    MOVWF    clkCount    ; preset clock pulse counter

    MOVLW    H'12'        ; = 18 dec, will shift data out every even number until zero
                ; then parity bit will be set instead
    MOVWF    bitCount    ; preset bit counter

    ; data now set, initiate the clock to generate a 20 us tick ( rx/tx heart beat )

    BSF    pcDATA_out    ; start bit, always 'low' ( we pull down )
     
    MOVLW   H'FA'           ; start timer with 252 ( 256-6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8 ( int handling code )= 27 us
        MOVWF   TMR0
    
    GOTO    INTX        ; exit, the next int will start an tx

INT_IDLE:
    
    ; 'Idle' code i.e. no rx or tx possible AND/OR nothing to send

    DECF    Divisor_10ms,F  ; Count 0.5ms down to give 10 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .20
    MOVWF    Divisor_10ms     ; Preset the divide by 20

    ;+++
    ; 10 ms tick here
    


    ; Divide the 10 ms tick to give 100 ms second tick
INT_10MS
    DECF    Divisor_100ms,F    ; Count 10ms down to give 100 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .10
    MOVWF    Divisor_100ms    ; Preset the divide by 10

        ;+++
     ; 100 ms tick here

INT_100MS


    ; numlock check !! bit variable _Numlock does not actually set the numlock status directly.
    ; However, by setting this bit to '1' we make a test against the current numlock led status
    ; ( bit no 1 in variable KbLedStatus ), if that is different from this variable
    ; we send a 'numlock' press/release ( to toggle numlock status )
    ; so in essence, setting this bit to '1' will force numlock to be 'on' etc.

    BTFSC    _IsFirstLedStatus ;  if = 1 then we have not yet received numlock status
    GOTO    INT_REPEAT_CHECK    ; nope, then this is a consecutive byte, store as 'normal'

    BTFSS    _WaitNumLock    ; are we waiting for pc numlock reply ?
    GOTO    INT_NUMLOCK_CHECK ; yep then do repeat check instead
    
    DECFSZ    Temp_Var,F    ;
    GOTO    INT_REPEAT_CHECK

    CALL    RELEASE_NUMLOCK

INT_NUMLOCK_CHECK

    BTFSC    _LedNumLock    ; is the led on ?
    GOTO    INT_NUMLOCK_ON    ; yep, then test our 'local' numlock state ( wanted numlock state )

    ; nope numlock is off, is our wanted state also off ?
    BTFSS    _NumLock    ; is wanted state off ?
    GOTO    INT_REPEAT_CHECK ; yep continue   

    CALL    PRESS_NUMLOCK    ; nope then send numlock press/release code

    GOTO    INT_REPEAT_CHECK
            

INT_NUMLOCK_ON
    BTFSC    _NumLock    ; is wanted state also 'on' ?
    GOTO    INT_REPEAT_CHECK ; yep

    CALL    PRESS_NUMLOCK    ; nope then toggle numlock state

INT_REPEAT_CHECK

    ; check if a key should be 'repeated' ( when pressed longer than 500 ms )
    BTFSS    _startRepeat    ; start repeating a key ? ( delay !!! )
    GOTO    INT_CHECK_KEY    ; nope, then check if key should be repeated
    DECF    RepeatTimer,F    ;
    BNZ    INT_500MS    ; not zero yet, check timer instead

    BCF    _startRepeat    ; stop repeat timer ( delay is accomplished )
    BSF    _doRepeat    ; and enable 'key' is still down check
    MOVLW    .02        ; start repeat send timer
    MOVWF    Divisor_Repeat  ;

    GOTO    INT_500MS    ; do next timer check

INT_CHECK_KEY
    BTFSS    _doRepeat    ; key should be repeated ?
    GOTO    INT_500MS    ; nope
    
    ; ok key should be repeated, check if it still pressed ?
    CALL    CHECK_KEY_STATE    ; uses MakeKeyOffset to calculate which key that was
                ; the last pressed, and then check if it's still pressed
                ; if still pressed carry = '1',

    BTFSS    STATUS,C    ; check carry
    BCF    _doRepeat    ; clear repeat bit, stop repeating the key

    BTFSS    _doRepeat    ; still pressed ?
    GOTO    INT_500MS    ; nope

    DECF    Divisor_Repeat,F  ; should we send the key ?
    BNZ    INT_500MS    ; nope

    MOVLW    DELAY_RATE    ; reload timer with key rate delay
    ;MOVLW    .02        ; restart timer
    MOVWF    Divisor_Repeat  ;
    
    BSF    _doSendKey    ; set flag to send key, NOTE the actual sending ( putting into send buffer )
                ; is done inside mainloop.
     
    
INT_500MS
    
    DECF    Divisor_500ms,F    ; Count 100ms down to give 500 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .05
    MOVWF    Divisor_500ms    ; Preset the divide by 5

        ;+++
     ; 500 ms tick here


INT_500_NEXT
    
    TOGGLE_PIN O_led_KEYCOMM_ok    ; toggle the disco light 😉

    BTFSS    _DoExitAltKeymap    ; is the alt keymap toggle key pressed the second time ?
                    ; if so skip timeout test and exit
    BTFSS    _InAltKeymap    ; are we in altkeymap ?
    GOTO    INTX        ; nope
    
    ; we are in altkeymap, decrement the lastkeytime
    ; and check if we are at zero then we exit
    ; the altkeymap.

    DECF    LastKeyTime,F    ; decrease time
    BNZ    INTX        ; exit, timer has not expired
    ; timer expired, get out of altkey map
    BSF    _ExitAltKeymap    ;

; ***************** 'heart' beat code end ***************

INTX
    ;BCF    INTCON,T0IF     ; Clear the calling flag

    PULL            ; Restore registers
    RETFIE
    
; **************** end interrupt routine **************


;+++++        
;     Routines that will 'toggle' keyboard numlock status
;     by sending numlock make/break code
;

PRESS_NUMLOCK:     
        MOVLW    H'77'         ; numlock key scancode, make
        CALL    ADD_KEY
        MOVLW    H'06'         ; 6 x 100 ms = 600 ms ( release delay )
        MOVWF    Temp_Var    ;
        BSF    _WaitNumLock    ; we are waitin for numlock status reply from pc
        RETURN

RELEASE_NUMLOCK:
        MOVLW    BREAK        ; break prefix
        CALL    ADD_KEY    
        MOVLW    H'77'         ; numlock key scancode
        CALL    ADD_KEY
        BCF    _WaitNumLock
        RETURN
    
; ***********************************************************************
;
;  CHECK_RX_KEY - handles the received commands from pc
;
CHECK_RX_KEY    
    ; check the key in 'currkey' ( command from pc )

CHECK_ED
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'ED'        ; subtract value in W with 0xED
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_EE    ; the result of the subtraction was not zero check next
    ; ok 'ED'=set status leds ( in next byte ) received
    BSF    _IsLedStatus    ; set bit that next incoming byte is kb led staus
    GOTO    CHECK_SEND_ACK    ; send ack

CHECK_EE    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'EE'        ; subtract value in W with 0xEE
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F0    ; the result of the subtraction was not zero check next
    ; ok 'EE'= echo command received
    GOTO    CHECK_SEND_EE    ; send echo

CHECK_F0    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F0'        ; subtract value in W with 0xF0
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F2    ; the result of the subtraction was not zero check next
    ; ok 'F0'= scan code set ( in next commming byte ) received
    BSF    _SkipByte    ; skip next incomming byte ( or dont interpret )
    GOTO    CHECK_DONE    ; do not send ack !
CHECK_F2    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F2'        ; subtract value in W with 0xF0
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F3    ; the result of the subtraction was not zero check next
    ; ok 'F2'= Read ID command responds with 'AB' '83'
    GOTO    CHECK_SEND_ID    ; send id bytes

CHECK_F3    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F3'        ; subtract value in W with 0xF3
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_FE
;    GOTO    CHECK_F4    ; the result of the subtraction was not zero check next
    ; ok 'F3'= set repeat rate ( in next commming byte ) received
    BSF    _IsRateDelay    ; next incomming byte is rate/delay info
    GOTO    CHECK_SEND_ACK    ; send ack

; **** Note ! removed from test as I 'don't care' ********
; **** i.e. I dont disable or enable the keyboard at any time.

;CHECK_F4    
;    MOVF    CurrKey,W    ; move key buffer into W register
;    SUBLW    H'F4'        ; subtract value in W with 0xF4
;    BTFSS   STATUS, Z    ; check if the zero bit is set
;    GOTO    CHECK_F5    ; the result of the subtraction was not zero check next
    ; ok 'F4'= keyboard enable received
;    GOTO    CHECK_SEND_ACK    ; send ack
;CHECK_F5    
;    MOVF    CurrKey,W    ; move key buffer into W register
;    SUBLW    H'F5'        ; subtract value in W with 0xF5
;    BTFSS   STATUS, Z    ; check if the zero bit is set
;    GOTO    CHECK_FE    ; the result of the subtraction was not zero check next
    ; ok 'F5'= keyboard disable received
;    GOTO    CHECK_SEND_ACK    ; send ack
CHECK_FE    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'FE'        ; subtract value in W with 0xFE
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_FF    ; the result of the subtraction was not zero check next
    ; ok 'FE'= resend last sent byte
    MOVF    LastKey,W    ; get last key
    CALL    ADD_KEY        ; and put it on the que
    GOTO    CHECK_DONE

CHECK_FF
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'FF'        ; subtract value in W with 0xFF
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_ERROR    ; the result of the subtraction was not zero, unknown command
    ; ok 'FF'= reset keyboard received
    
    GOTO    CHECK_SEND_AA    ; send 'AA' power on self test passed

CHECK_ERROR            ; unknown command ( or command not interpreted )
    GOTO    CHECK_SEND_ACK

CHECK_SEND_ID
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    MOVLW    H'AB'        ; keyboard id first byte, always 0xAB
    CALL    ADD_KEY        ;

    MOVLW    H'83'        ; keyboard id second byte, always 0x83
    CALL    ADD_KEY        ;

    GOTO    CHECK_DONE

CHECK_SEND_ACK

    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;
    GOTO    CHECK_DONE

CHECK_SEND_AA
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    MOVLW    H'AA'        ; keyboard post passed
    CALL    ADD_KEY        ;
    GOTO    CHECK_DONE

CHECK_SEND_EE
    MOVLW    H'EE'        ; keyboard echo
    CALL    ADD_KEY        ;

CHECK_DONE
    RETLW    0        ; and we are done

; ***********************************************************************
;  Buffer code ( a bit modified ) from Stewe Lawther
; http://ourworld.compuserve.com/homepages/steve_lawther/ucindex.htm
;  And of course source of the exellent keyboard viewer. !! ( without which, this
;  project would have been close to impossible ) ( and of course my nifty
;  memory oscilloscope )
;
;  ADD_KEY_BUFFER - (outside int)add the key in CurrKey to our keybuffer que in the first
;            free position. If there is no more room the oldest byte is
;            'dumped'.
;  ADD_KEY      - Same but to be used inside int routine.( just skips int disable code )

ADD_KEY_BUFFER

ADD_STOP_INT            ; first stop all interrupts !!!!!!!
    BCF    INTCON,GIE    ; disable global interrupts..
    BTFSC    INTCON,GIE    ; check that is really was disabled
    GOTO    ADD_STOP_INT    ; nope try again
ADD_KEY                ; inside interuppt we call this instead ( as we dont need to disable int 🙂 )
    MOVWF    BufTemp        ; store key temporary
    MOVF    KeyBufferHead,W ; move buffer head out of FSR temporarily
        MOVWF   Temp            ; store in temp  
        MOVF    KeyBufferTail,W ; set FSR to buffer tail
        MOVWF   FSR        ; set indirect file pointer
        MOVF    BufTemp,W       ; set W to new scancode to send
        MOVWF   INDF            ; and put it in the buffer
        MOVF    Temp,W        ; get the head pointer back
        MOVWF   KeyBufferHead    ;

        INCF    KeyBufferTail,W ; get the end of the buffer
        SUBLW   KbBufferMax     ; check if at buffer max
        INCF    KeyBufferTail,W ; (reload value to w - doesn't affect C)
        BTFSS   STATUS, C       ; if so (negative result)
        MOVLW   KbBufferMin     ; set to buffer min ( wrap around )

        MOVWF   KeyBufferTail    
        SUBWF   KeyBufferHead,W ; see if we have any room ( head and tail have meet )
        BTFSC   STATUS, Z       ; if so (Z set)
        CALL    INC_KEY_HEAD    ; dump oldest byte

                ; finally turn on interrupts again
        MOVLW   b'10100000'     ; enable global & TMR0 interrupts
        MOVWF   INTCON

    RETURN


; ***********************************************************************
;
;  GET_KEY_BUFFER -     Gets a char from the buffer, and puts it into KeyBuffer
;            NOTE: Does not increase buffer pointers ( dump this key ).
;            A call to INC_KEY_HEAD will do this if the key is sent ok
GET_KEY_BUFFER    
          MOVF    INDF, W         ;put the byte to send into key buffer
                MOVWF   CurrKey
        RETURN            ; and go back, NOTE ! the key is not
                    ; removed from the buffer until a call
                    ; to INC_KEY_HEAD is done.
                    
; ***********************************************************************
;
;  INC_KEY_HEAD -     dump oldest byte in keybuffer, Do not call if byte
;            has not been fetched before ( GET_KEY_BUFFER )
;            
INC_KEY_HEAD    INCF    KeyBufferHead,W ; set to next byte in buffer
                SUBLW   KbBufferMax     ; check if at buffer max
                INCF    KeyBufferHead,W ; (reload value to w - doesn't affect C)
                BTFSS   STATUS, C       ; if so (negative result)
                MOVLW   KbBufferMin     ; set to buffer min ( wrap around )
                MOVWF   KeyBufferHead   ; and store ( in FSR )
                RETURN            ; go back
                
        

; ***********************************************************************
;
;  CHECK_KEY_STATE - Check if the last pressed key is still pressed
;             Returns with carry = '1' if still pressed
;             else carry = '0' ( or error )
;

CHECK_KEY_STATE:
    ; uses LastMakeOffset to calculate which key to test
    
    MOVF    LastMakeOffset,W    ; get offset
    ANDLW    H'18'        ; mask out column bits  
                ; lastmake offset has the following bits:
                ; '000yyxxx' where 'yy' is column no
                ; and 'xxx' is key num,
    BTFSC    STATUS,Z    ; zero = column 1 & 2
    GOTO    CHECK_COL_12    ; it is in column 1

    MOVWF    repTemp        ; save it temporary
    SUBLW    H'08'        ; subtract value in W with 0x08 ( columns 3 & 4 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_COL_34    ; it is in column 3 & 4

    MOVF    repTemp,W    ; get the column bits back
    SUBLW    H'10'        ; subtract value in W with 0x10 ( columns 5 & 6 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_COL_56    ; it is in column 5 & 6
    
CHECK_COL_78
    MOVF    kbColumn78_Old,W ; get bit map ( key status ) for keys in column 7 & 8
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_56
    MOVF    kbColumn56_Old,W ; get bit map ( key status ) for keys in column 5 & 6
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_34
    MOVF    kbColumn34_Old,W ; get bit map ( key status ) for keys in column 3 & 4
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_12
    MOVF    kbColumn12_Old,W ; get bit map ( key status ) for keys in column 1 & 2
    MOVWF    repKeyMap    ; and store it

;<-------Alt keymap code------->
    
    ;Checks ONLY column 1&2 bitmap ( keymap ) ( as it is now )
    ; this code has to be moved/changed if another column is choosen as the alt. keymap toggle key

    ; alternative keymap handling if key r3 c1 is pressed ( bit 2 in column 1&2 )
    ; then enable alternative keymap ( only if keyrepeat is disabled )
    
    ; check if this was the last key pressed

    ; check bit representing the alt. keymap key ( i've choosen key 2 )

    MOVF    LastMakeOffset,W ; get key offset again
    ANDLW    H'07'         ; mask out column bits
    SUBLW   H'02'        ; check if its bit num 2 ( the enter 'alt keymap' key )

    BTFSS   STATUS, Z    ; check if the zero bit is set    GOTO    CHECK_KEY    ; nope than another key was the last
                ; skip altkeymap enable

    ; the altkeymap key was the last pressed !
    ; is key repeat disabled ?

    BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
    GOTO    CHECK_KEY     ; yep, then skip altkeymap enable test

    ; enable altkeymap if key is still pressed
    
    BTFSC    repKeyMap,2    ; test bit 2 ( should be key 'F7' )
    GOTO    CHECK_ENABLE_ALT ; ok enable altkeymap ( if we are not already in altkeymap )
    GOTO    CHECK_KEY    ; nope another key in column 1&2 continue check

CHECK_ENABLE_ALT
    BTFSC    _AltKeymap    ; are we already in altkeymap ?
    GOTO    CHECK_KEY    ; yep then just continue

    ; We are just entering/enabling the alt. keymap

    BSF    _AltKeymap    ; enable alternative keymap

    ; Example of using an 'advanced' alt keymap handling    
    ; not enabled, to avoid intial confusion.

    ; I.E This snippet would only be called once when we
    ; are just entering(enabling) the alternative keymapping !

    ; This example code will 'soft' release the current altkeymap key ( it is in pressed state ! )
    ; ( i.e send release code for the enter alt. keymap key 'F7' )
    ; send the make scancode for left <alt> key instead.
    ; and force numlock to be off.
    
    ; Do not use if you dont understand the implifications !
        
    ; Also note that the scancodes are hardcoded here !
    ; ( i.e do not use the lookup table definition of the key/s )
    
    ; ***** start snippet
    
    ;MOVLW    BREAK    ; send break prefix
    ;CALL    ADD_KEY
    ;MOVLW    H'83'   ; and scancode for the enter alt keymap
    ;CALL    ADD_KEY
    ;MOVLW    H'11'     ; send make code for the left <alt> key
    ;CALL    ADD_KEY
    
    ; example of forcing the numlock status to a particular state
    ; the numlockstatus will change ( be checked ) inside the int routine
    ; See also at the end of KB_DEBOUNCE_12 where the numlock status
    ; will be restored when we release the key
    
    ;BCF    _NumLock    ; 'force' numlock to be off
    
    ; This bit MUST also be checked as we do not know if we have recevied
    ; first numlock status byte yet ( pc does not send numlock/led status
    ; after intial poweron, if not one of the numlock/capslock/scrolllock are pressed )
    ; i.e. if you connect this keyboard to a 'running' pc, the numlock status
    ; will be unknown.However if connected before poweron, it will be updated
    ; as numlock status is sent during pre-boot seq.
    
    ;BTFSC    _IsFirstLedStatus ; have we recevied numlock status yet ?
    ;CALL    PRESS_NUMLOCK
    
    ; ***** end snippet

CHECK_KEY
    ; 'normal' key down check
    ; column for pressed key is now in repKeyMap
    MOVF    LastMakeOffset,W ; get offset again
    ANDLW    H'07'        ; mask out key number ( lowest 3 bits )
    
     BTFSC    STATUS,Z    ; bit num zero ?
    GOTO    CHECK_KEY_DONE    ; yep lowest bit, check and return
    
       MOVWF    repTemp        ; and store it
CHECK_KEY_LOOP
    RRF    repKeyMap,F    ; rotate one step to right
    DECFSZ    repTemp,F        ; decrement bit counter
    GOTO    CHECK_KEY_LOOP    ; loop again

CHECK_KEY_DONE
    ; ok the key to test should now be the lowest bit in repKeyMap
    CLRC            ; clear carry
    BTFSC    repKeyMap,0    ; check bit 0
    BSF    STATUS,C    ; ok key is pressed set carry
    RETURN            ; and we are done..

; ***********************************************************************
;
;  DELAY_1ms -     Delay routine ! used when scanning our own keyboard
;         Delay is between output of adress to 4051 and reading of inputs
;         Increase to have a slower 'scan' rate or decrease to have a higher scan rate.
;

DELAY_1ms
        MOVLW    H'F0'        ; wait 255 cycles
        MOVWF    kbTemp        ; this var is 'safe' to be used in side mainloop
        MOVLW    H'03'
        MOVWF    kbState
DELAY_LOOP
        DECFSZ    kbTemp,F    ; decrement
        GOTO    $-1        ;
        
        MOVLW    H'F0'
        MOVWF    kbTemp
        DECFSZ    kbState,F
        GOTO    DELAY_LOOP

        RETURN


;---------------------------------------------------------------------------
;
;        Initialisation
;
;---------------------------------------------------------------------------
        
        
INIT:  

    ;+++
    ;    Set up the ports

        ; PORT A

    BANK1
        MOVLW    b'00000110'        ; Set port data directions RA1,RA2 inputs RA0,RA3,RA4 outputs
    MOVWF    TRISA            ; PC keyboard connections


        ; PORT B

        ; Used for our own 3x8 matrix keyboard
    
        BANK1
        MOVLW    b'11111000'        ; Set port data directions RB4-RB7 inputs rest outputs
    MOVWF    TRISB


    ;    Clear all registers on bank 0 ( memory )
    
    BANK0
    MOVLW    H'0C'
    MOVWF    FSR
    
INITMEM
    CLRF    0        ; Clear a register pointed to be FSR
    INCF    FSR,F
    CLRWDT            ; clear watchdog
    MOVLW    H'50'        ; Test if at top of memory
    SUBWF    FSR,W
    BNZ    INITMEM        ; Loop until all cleared

    ;+++     
    ;    Initiate the keybuffer pointers

INIT_BUFF:

    MOVLW   KbBufferMin    ; get adress of first buffer byte
        MOVWF    KeyBufferHead    ; store in FSR
        MOVWF    KeyBufferTail    ; and set last byte to the same ( no bytes in buffer )

    ;+++
    ;    Preset the timer dividers

    MOVLW    .20
    MOVWF    Divisor_10ms
    MOVLW    .10
    MOVWF    Divisor_100ms
    MOVLW    .05
    MOVWF    Divisor_500ms

    ;+++
    ;    Set up Timer 0.

    ;    Set up TMR0 to generate a 0.5ms tick
    ;    Pre scale of /8, post scale of /1
    
    BANK1
        
    MOVLW   b'00000010'     ; Initialisation of TMR0 prescale 8 '010'
                                ; weak pullup enabled by latch values.

        MOVWF   OPTION_REG      ; load option reg with prescale of 8
                
        BANK0

    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0
        

        
;---------------------------------------------------------------------------
;
;        the main 'program' loop ( starts really at MAIN_LOOP )
;
;---------------------------------------------------------------------------

MAIN:    
        BSF    pcDATA_in
        BSF    pcCLOCK_in
        BCF    pcDATA_out
        BCF    pcCLOCK_out
        CLRF    PORTB

        MOVLW    H'08'        ; preset the column counter
        MOVWF    kbColumnCnt    ;
        
         BSF    _NumLock    ; default state is numlock = on
        BSF    _IsFirstLedStatus ; we have not yet recevied led status byte.

            MOVLW   b'10100000'     ; enable global & TMR0 interrupts
            MOVWF   INTCON

        CLRWDT            ; clear watchdog
        BTFSS    O_led_KEYCOMM_ok
        GOTO    $-2        ; make an 0.5 second delay here
                    ; i.e. the led will come on when 0.5 seconds has passed
                    ; set inside the timer int.

        CLRWDT            ; clear watchdog
        BTFSC    O_led_KEYCOMM_ok
        GOTO    $-2        ; make an additional 0.5 second delay here
                    ; i.e. the led will be dark when 0.5 seconds has passed
                    ; set inside the timer int.


        MOVLW    H'AA'         ; post passed :-), always 0xAA
        CALL    ADD_KEY_BUFFER
        
        ; now go into infinite loop, the pc kb interface runs in the background ( as an int )
        ; where we continuously monitor the pcCLOCK/DATA_in lines

         
MAIN_LOOP:
        ; check whatever 🙂
        CLRWDT            ; clear watchdog

MAIN_CHECK_COL_1:
        ; scan our own keyboard, first four bits

        ; address and read column, read as complement so key pressed = '1'
        ; since we pull down when key is pressed ( weak pullup enabled )
        ; ( i.e. pressed key has level 0V ) to make it more 'logical' to work with

        CLRF    kbColumnVal
        
        ; get column counter / adress out
        MOVF    kbColumnCnt,W          
        
        MOVWF    PORTB        ; set the columns adress to the 74HCT4051
                    ; i.e. make column low
        IFNDEF    DEBUG
        CALL    DELAY_1ms    ; wait 1 ms let pins stabilize
        ENDIF

        COMF    PORTB,W        ; read back the pin values ( complement i.e. key pressed = '1' )
        ANDLW   b'11110000'    ; mask out unused pins
        
        MOVWF    kbColumnVal    ; store the pin values

        SWAPF    kbColumnVal,F    ; swap nibbles ( low<->high ) to make room for next column

        INCF    kbColumnCnt,F     ; inc column adress
MAIN_CHECK_COL_2:
        ; read next four bits
        ; put out adress and read next column, read as complement so key pressed = '1'
        ; this as we pull down when key is pressed ( weak pullup enabled )

        ; get column counter / adress out
        MOVF    kbColumnCnt,W          
        
        MOVWF    PORTB        ; set the columns adress to the 74HCT4051
                    ; i.e. make column low

        IFNDEF    DEBUG
        CALL    DELAY_1ms    ; wait 1 ms
        ENDIF

        COMF    PORTB,W        ; read back the pin values ( complement i.e. key pressed = '1' )
        ANDLW   b'11110000'    ; mask out unused pins
        
        ADDWF    kbColumnVal,F    ; and store pin values

        INCF    kbColumnCnt,F   

        ; reset column counter check
        ; i.e. we are 'only' using adress 0 - 7
        MOVF    kbColumnCnt,W
        SUBLW    H'08'        ; subtract value in W with 0x08
        BTFSS   STATUS, Z    ; check if the zero bit is set
        GOTO    MAIN_CHECK_DEBOUNCE ; nope continue

        CLRF    kbColumnCnt     ; reset counter/adress

MAIN_CHECK_DEBOUNCE:
        CALL    KB_DEBOUNCE    ; do debouncing on the current values and send make/break
                    ; for any key that has changed
                    ; NOTE uses the current column adress to determine which
                    ; columns to debounce!
MAIN_REPEAT:
        BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
        GOTO    MAIN_CHECK_REPEAT ; yep check key repeating
        
        ; keyrepeat disabled then do check on exit of altkeymap instead

        BTFSS    _ExitAltKeymap    ; we want to exit altkeymap ?
        GOTO    MAIN_LOOP    ; nope

        
        ; check that ALL keys are released
        ; before exiting the alt keymap
        MOVF    kbColumn78_Old,F ; reload column 78 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 78

        MOVF    kbColumn56_Old,F ; reload column 56 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 56

        MOVF    kbColumn34_Old,F ; reload column 34 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 34

        MOVF    kbColumn12_Old,F ; reload column 12 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 12
    
        ; all keys released !!
        BCF    _AltKeymap    ; exit altkeymap
        BCF    _ExitAltKeymap    ; exit release check
        BCF    _InAltKeymap    ; clear flag for second keypress check
        BCF    _DoExitAltKeymap ;
        GOTO    MAIN_LOOP    


MAIN_CHECK_REPEAT
        BTFSS    _doSendKey    ; if we should send a repeated key
        GOTO    MAIN_LOOP    ; nope continue

        ; send the key in RepeatedKey but first check if its an extended key
        BTFSS     _RepeatIsExt    ; is it extended ?
        GOTO    MAIN_SEND_REPEAT ; nope just send scan code
        
        ; last key pressed was extended send extended prefix
        MOVLW    EXTENDED    ; get extended code
        CALL    ADD_KEY_BUFFER    ; and put it into the buffer
        
MAIN_SEND_REPEAT:
        MOVF    RepeatKey,W    ; get key code for the last pressed key
        CALL    ADD_KEY_BUFFER    ; and put it into the buffer
        BCF    _doSendKey    ; and clear the flag, it will be set again
                    ; inside int handler if key still is pressed    
        
        GOTO    MAIN_LOOP    ; and return
    

; ***********************************************************************
;
;  KB_SEND_KEY - uses the Offset stored in var 'Offset' to fetch a key from our
;         key lookup table.
;           then checks the bit var _isBreak to see if make or break codes should be sent
;         It then puts the code/s into the key buffer ( for sending later, in int routine )

KB_SEND_KEY:

    MOVF    Offset,W    ; get current offset
    MOVWF    TempOffset    ; save it ( to be used in key repeat code, is its 'make' )
                ; temp offset has the following bits:
                ; '000yyxxx' where 'yy' is column offset
                ; and 'xxx' is key num,


    CLRC            ; clear carry so it dont affect byte rotation
    RLF    Offset,F    ; first rotate
    RLF    Offset,F    ; second rotate

                ; offset no have the following bits:
                ; '0yyxxx01' where 'yy' is column offset
                ; and 'xxx' is key num,
                ; as each key in table has 4 bytes of 'space'

    INCF    Offset,F    ; add one, for the 'movwf pcl' at the start of the table

    BCF    Offset,7    ; clear to bit, just in case so we dont
                ; 'overflow' the table, should not be needed !

    BCF    _isExtended     ; clear extended flag

    MOVLW   LOW LOOKUP_KEY    ; get low bit of table adress
    ADDWF    Offset,F    ; 8 bit add
    MOVLW    HIGH LOOKUP_KEY    ; get high 5 bits
    BTFSC    STATUS,C    ; is page boundary crossed ?
    ADDLW    1        ; yep, then inc high adress
    MOVWF    PCLATH        ; load high adress in latch
    MOVF    Offset,W    ; load computed offset in w
    CLRC                ; clear carry ( default= key is not extended )
                ; if key is extended then carry is set in jumptable lookup_key

    CALL    LOOKUP_KEY    ; get key scan code/s for this key
                ; key scan code/s are saved in
                ; W - scancode, should go into kbScan
                ; carry set - extend code
                ; carry clear - not extended code

    MOVWF    kbScan        ; store scancode
    ; if carry is set then key is extended so first send extended code
    ; before any make or break code

    BTFSS    STATUS,C    ; check carry flag
    GOTO    KB_CHK_BREAK    ; nope then check make/break status

    BSF    _isExtended    ; set extended flag
    MOVLW    EXTENDED    ;
    CALL    ADD_KEY_BUFFER    ; get extended code and put in in the buffer

KB_CHK_BREAK:
    
    ; check if it's make or break
    BTFSS    _isBreak    ; check if its pressed or released ?
    GOTO    KB_DO_MAKE_ONLY    ; send make code

    BCF    _isBreak    ; clear bit for next key

    ; break code, key is released
    MOVLW    BREAK        ; get break code
    CALL    ADD_KEY_BUFFER    ; and put into buffer
    GOTO    KB_DO_MAKE    ; and send key code also

    ; key is pressed !
KB_DO_MAKE_ONLY:
    BCF    _doSendKey    ; stop repeat sending
    BCF    _doRepeat    ; and bit for repeat key send
    BSF    _startRepeat    ; and set flag for start key repeat check
    BCF    _RepeatIsExt    ; clear repeat key extended flag ( just in case )

    BTFSC     _isExtended     ; is it extended ?
    BSF    _RepeatIsExt    ; set the flag
    
    ; save this key in 'last' pressed, to be used in key repeat code

    MOVF    TempOffset,W    ; get saved offset
    MOVWF    LastMakeOffset  ; and store it

    ; if keyrepat = enabled, alternative mapping = disabled
    BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
    GOTO    KB_REP_NOR    ; yep set normal delay ( 800 ms )
    
    ; else keyrepat = disabled, alternative mapping = enabled
    MOVLW    DELAY_ENTER_ALTKEYMAP ;reload delay before entering the altkeymap ( 3 sec )
                ; i.e how long the enter altkeymap key must be pressed before
                ; we enable altkey keymap codes.
                            
    GOTO    KB_REP_SET    ; and set it
KB_REP_NOR:    
    
    MOVLW    DELAY_REPEAT    ; reload 'normal' repeat delay ( 800 ms )
            
KB_REP_SET:
    MOVWF    RepeatTimer    ; and (re)start the timer for key repeat
    MOVF    kbScan,W    ; get key scan code    
    MOVWF    RepeatKey    ; and save it

KB_DO_MAKE:
    ; key pressed/released ( i.e. the scancode is sent both on make and break )
    MOVF    kbScan,W    ; get scan code into w
    CALL    ADD_KEY_BUFFER    ; and add to send buffer
    

    ; reset the 'get out of alt. keymap timer for each keypress
    ; note don't care if we are 'in' alt. keymap. Reset this timer anyway
    ; as the code for checking if we are currently in alt. key map
    ; would be as long as it takes to reset the timer.

    MOVLW    DELAY_EXIT_ALTKEYMAP ; reload the delay for exiting the altkeymap when no
                ; key is pressed ( 7.5 sec )
    MOVWF    LastKeyTime    ; (re)set lastkey timer ( used to get out of altkeymap )    
    RETURN

; ***********************************************************************
;
;  KB_DEBOUNCE - debounces two column readings from our keyboard
;         If a bit 'state' has been 'stable' for 4 consecutive debounces
;           the 'new' byte is updated with the new state
;         'normal' loop time ( no tx/rx/key press ) is about 2-3 ms
;         so from 'key' down until 'new' is updated it takes about 8-10 ms
;         ( as we are scanning columns two by two, the whole keyboard needs
;         4 loops to be fully updated, then 4 debounce samples for each 'pair' )    

KB_DEBOUNCE:
    ; debounce current column(s)
    MOVF    kbColumnCnt,F    ; reload value into itself ( affect zero flag )
    BTFSC    STATUS,Z    ; is it zero ?
    GOTO    KB_DEBOUNCE_78    ; debounce columns 7 & 8

    MOVF    kbColumnCnt,W    ; move column counter into W register
    SUBLW    H'04'        ; subtract value in W with 0x04 ( columns 5 & 6 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    KB_DEBOUNCE_34    ; debounce columns 3 & 4

    MOVF    kbColumnCnt,W    ; move column counter into W register
    SUBLW    H'06'        ; subtract value in W with 0x02 ( columns 3 & 4 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    KB_DEBOUNCE_56    ; ok column 1 & 2 debounce

    ; all above tests 'failed'
    ; columns to debouce are 1 & 2

KB_DEBOUNCE_12:    
    ; debounce columns 1 & 2
    DEBOUNCE_BYTE    kbColumnVal,kbColumn12_New,kbColumn12Cnt,kbColumn12State

    MOVF    kbColumn12_New,W    ; get debounced sample
    XORWF    kbColumn12_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn12_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_12    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_12_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
        
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_12_NEXT    

KB_LOOP_12_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_12_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_12_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_12    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_12

KB_12_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn12_New,W ; get new status
    MOVWF    kbColumn12_Old    ; and store it..

;<-------Alt keymap code------->

    ; ***** alternative keymap handling
    ; The alternative keymap is enabled by pressing key r4 c1 ( i.e. bit 3 in column 12 )
    ; Here, we enable a check to turn off alternative keymap if
    ; that key and all others are released ( bit is cleared ).
    ; ( else no (alternative)break codes would be sent for those keys that are still pressed )
    ; NOTE: _Altkeymap is set inside int routine when checking
    ; keyrepeat so there is a 'variable' delay before the altkeymap is active
    ;

    BTFSS    _AltKeymap        ; is altkeymap enabled ?
    RETURN                ; nope return


    BTFSC   _InAltKeymap        ; are we in altkeymap ?
    GOTO    KB_12_IN        ; yep alt keymap key has been released once
    
    ; nope still waiting for first release
    BTFSS   kbColumn12_Old,2    ; is key released ? ( first time )
    GOTO    KB_12_ALT        ; yep, reset timers and set bit variables

KB_12_IN
    BTFSC    _DoExitAltKeymap    ; are we waiting for release ?
    GOTO    KB_12_OUT        ; yes

    ; the key has been released once test for second press
    BTFSC   kbColumn12_Old,2    ; is it still pressed ?
    GOTO    KB_12_ALT2        ; yep

    BTFSS    _DoExitAltKeymap        ; are we now waiting for the last ( second ) release ?
    RETURN                ; nope

KB_12_OUT
    BTFSS    kbColumn12_Old,2    ; check if key still pressed ?
    BSF    _ExitAltKeymap        ; nope, then enable exit check that
                    ; will exit alt keymap as soon as all key are released
    
KB_12_ALT2
    BSF    _DoExitAltKeymap    ; check for second release
    RETURN
KB_12_ALT
    ; first release of the enter alt keymap key
    ; reset 'get out' timer and set bit variables to enable check
    ; for second press/release

    MOVLW    H'0F'        ; x0.5 sec = 7.5 sec
    MOVWF    LastKeyTime    ; (re)set lastkey timer ( used to get out of altkeymap automaticly)    

    BSF    _InAltKeymap    ; yep the first time, then set flag that we are now
                ; waiting for a second press/release to exit alt key map
                ; all keys are released before exiting altkeymap

    ;***** Example snippet(one line) to be paired with code in CHECK_KEY_STATE where I
    ; forced numlock status to be off while enetering the alt keymap
    ; but have not yet released the alt keymap toggle key.
    ; this code will be called at the first release of this key. Used
    ; to restore numlock status.
    ; As said before, do not use if implifications are not known !

    ;BSF    _NumLock    ; and also force numlock to be 'on'
                ; as it is set to 'off' when we enter altkeymap
                ; we must set it 'back'
    
    RETURN    
        
KB_DEBOUNCE_34:    
    ; debounce columns 3 & 4
    DEBOUNCE_BYTE    kbColumnVal,kbColumn34_New,kbColumn34Cnt,kbColumn34State

    MOVF    kbColumn34_New,W    ; get debounced sample
    XORWF    kbColumn34_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn34_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_34    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_34_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,3    ; set bit 3 for table read ( column 3 & 4 )

    ;BCF    _isBreak    ; clear break flag
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_34_NEXT

KB_LOOP_34_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_34_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_34_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_34    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_34

KB_34_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn34_New,W ; get new status
    MOVWF    kbColumn34_Old    ; and store it..
    RETURN    


KB_DEBOUNCE_56:    
    ; debounce columns 5 & 6
    DEBOUNCE_BYTE    kbColumnVal,kbColumn56_New,kbColumn56Cnt,kbColumn56State

    MOVF    kbColumn56_New,W    ; get debounced sample
    XORWF    kbColumn56_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only that 'a change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn56_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_56    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_56_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,4    ; set bit 4 for table read ( column 5 & 6 )
    
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_56_NEXT

KB_LOOP_56_SKIP
    RLF    kbTemp,F    ; rotate so we read next key

KB_LOOP_56_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_56_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_56    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_56

KB_56_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn56_New,W ; get new status
    MOVWF    kbColumn56_Old    ; and store it..
    RETURN    

KB_DEBOUNCE_78:    
    ; debounce columns 7 & 8
    DEBOUNCE_BYTE    kbColumnVal,kbColumn78_New,kbColumn78Cnt,kbColumn78State
    
    MOVF    kbColumn78_New,W    ; get debounced sample
    XORWF    kbColumn78_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits. ( 7-0 )
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn78_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_78    
    CLRF    Offset        ; clear offset counter ( for table read )
    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_78_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,4    ; set bit 3,4 for table read ( column 7 & 8 )
    BSF    Offset,3    ;
    
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released


    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars

    GOTO    KB_LOOP_78_NEXT
    
KB_LOOP_78_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_78_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_78_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_78    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_78

KB_78_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn78_New,W ; get new status
    MOVWF    kbColumn78_Old    ; and store it..
    RETURN    

    
; ***********************************************************************
;
;  LOOKUP_KEY -  lookup table for key scancodes.
;         Returns a scancode in w
;         Sets carry if key is extended
;         NOTE: If key R3 C1 has been pressed longer than keyrepeat delay
;         AND keyrepeat is disabled ( jumper on pin RB3 ) THEN the
;          bit _AltKeymap is set and we can return an alternative scancode in W
;        

LOOKUP_KEY    ; lookup table for the keys ( 32 keys x 4 lines + 1 = 129 lines )
        ; keys are labelled Rx - Cy where x = row number and y = column number
        ; handles a 4 row x 8 column keyboard = 32 keys
        
    MOVWF    PCL      ; add to program counter          
; R1 - C1 i.e. key 1
    NOP
    NOP
    NOP    
    RETLW    H'05'    ; scan code 'F1'
; R2 - C1 i.e. key 2
    NOP
    NOP
    NOP    
    RETLW    H'0C'    ; scan code 'F4'
; R3 - C1 i.e. key 3
    ; The famous alternative keymap toggle key !!! 😉
    ; It is adviced that this key does not use an alt scancode
    ; makes things cleaner and simplified.
    ; IF USED though, remember that a 'soft' release code must be sent
    ; for this key when entering the altkeymap !( + 'soft' make code for the alternative key )
    ; This as the key is pressed when entering altkeymap
    ; which makes the bit _Altkeymap be set, and hence when released
    ; the release code for this 'normal' key will never be sent
    ; instead the release code for the alternative key will be sent.
    ; To 'fix' this put the release code at the end of CHECK_KEY_STATE ( where the
    ; alt keymap bit is set ).i.e. break prefix+scancode for normal key.
    NOP
    NOP
    NOP
    RETLW    H'83'        ; scan code for F7 ( in 'normal' mode )
; R4 - C1 i.e. key 4
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'76'        ; send scancode for 'ESC'  instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'6B'        ; scan code 'arrow left' '<-'
; R1 - C2 i.e. key 5
    NOP
    NOP
    NOP    
    RETLW    H'06'    ;  scan code 'F2' hex06
; R2 - C2 i.e. key 6
    NOP
    NOP
    NOP    
    RETLW    H'03'    ; scan code 'F5'
; R3 - C2 i.e. key 7
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'0D'        ; send scancode for 'horizontaltab' HT instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'75'        ; scan code 'arrow up' '^'
; R4 - C2 i.e. key 8
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'14'        ; send scancode for 'left ctrl' instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'72'        ; scan code 'arrow down'
; R1 - C3 i.e. key 9
    NOP    
    NOP    
    NOP    
    RETLW    H'04'    ; scan code 'F3'
; R2 - C3 i.e. key 10
    NOP    
    NOP    
    NOP    
    RETLW    H'0B'    ; scan code 'F6'
; R3 - C3 i.e. key 11
    NOP    
    NOP    
    NOP    
    RETLW    H'0A'    ; scan code 'F8'
; R4 - C3 i.e. key 12
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'11'        ; send scancode for 'left alt' instead
    BSF    STATUS,C ; set carry ( i.e. extended code )
    RETLW    H'74'    ; scan code 'arrow right' '->'
; R1 - C4 i.e. key 13
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'6C'        ; send scancode for numeric '7' instead
    NOP    
    RETLW    H'3D'    ; scan code '7'
; R2 - C4 i.e. key 14
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'6B'        ; send scancode for numeric '4' instead
    NOP    
    RETLW    H'25'    ; scan code '4'
; R3 - C4 i.e. key 15
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'69'        ; send scancode for numeric '1' instead
    NOP    
    RETLW    H'16'    ; scan code '1'
; R4 - C4 i.e. key 16
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7B'        ; send scancode for numeric '-' instead
    NOP    
    RETLW    H'4A'    ; scan code '-' minus ( swe kbd )
; R1 - C5 i.e. key 17
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'75'        ; send scancode for numeric '8' instead
    NOP    
    RETLW    H'3E'    ; scan code '8'
; R2 - C5 i.e. key 18
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'73'        ; send scancode for numeric '5' instead
    NOP    
    RETLW    H'2E'    ; scan code '5'
; R3 - C5 i.e. key 19
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'72'        ; send scancode for numeric '2' instead
    NOP    
    RETLW    H'1E'    ; scan code '2'
; R4 - C5 i.e. key 20
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'45'        ; scan code '0' ( from keypad ) normal key
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'1F'        ; alt keycode ( windows start menu activate )
; R1 - C6 i.e. key 21
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7D'        ; send scancode for numeric '9' instead
    NOP    
    RETLW    H'46'    ; scan code '9'
; R2 - C6 i.e. key 22
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'74'        ; send scancode for numeric '6' instead
    NOP    
    RETLW    H'36'    ; scan code '6'
; R3 - C6 i.e. key 23
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7A'        ; send scancode for numeric '3' instead
    NOP    
    RETLW    H'26'    ; scan code '3'
; R4 - C6 i.e. key 24
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'49'        ; scan code '.' ( swe kbd ) normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'4A'    ; send scancode for numeric '/' instead
; R1 - C7 i.e. key 25
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'79'        ; send scancode for numeric '+' instead
    NOP    
    RETLW    H'4E'    ; scan code '+'
; R2 - C7 i.e. key 26
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'66'        ; scan code 'back space' BS, normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'71'    ; send scancode for 'delete' instead
; R3 - C7 i.e. key 27
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'5A'        ; scan code 'enter', normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'5A'    ; send scancode for numeric enter instead ( note ! extended )
; R4 - C7 i.e. key 28
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'5A'        ; scan code 'enter', normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'5A'    ; send scancode for numeric enter instead ( note ! extended )
; R1 - C8 i.e. key 29
    NOP    
    NOP    
    NOP    
    RETLW    H'2C'    ; scan code 't'
; R2 - C8 i.e. key 30
    NOP    
    NOP    
    NOP    
    RETLW    H'24'    ; scan code 'e'
; R3 - C8 i.e. key 31
    NOP    
    NOP    
    NOP    
    RETLW    H'1B'    ; scan code 's'
; R4 - C8 i.e. key 32
    NOP    
    NOP    
    NOP    
    RETLW    H'2C'    ; scan code 't'

    END

 

4.png

 

Quer um cafezinho também?

 

  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites

Isadora obrigado meu amor por ter me ajudado!....eu to melhorando aos poucos mais ainda dói tudo no meu corpo! essa tal de chiconcunha é terrivel.....eu sabia que você tinha um coração gentil é por isso que t amo! você agora mora no meu coração bj meu amor!!!! ...manda um abraço pro VTRX  obrigado...>>>>>Quer um cafezinho também? se você me desse esse cafezinho na cama eu adoraria porque ainda to um pouco mal mas vou melhorar em nome de jesus bjs☺️

Compartilhar este post


Link para o post
Compartilhar em outros sites
15 horas atrás, Isadora Ferraz disse:

Update...

Chance de sucesso ultrapassa 99%. Remapeei o endereço de acordo com d.s. do 628. A ram começa em 0x20 e não em 0x0c. Troque aí no fonte e reassemble.

  Mostrar conteúdo oculto

;
;               PC-Keyboard emulator using a PIC16F628A ...
;     Scan a 32 key keyboard ( with alternative mapping = 64 keys )
;            

;                 COPYRIGHT (c)1999 BY Tony K&uuml;bek && I.F.  CDH 2019
; This is kindly donated to the PIC community. It may be used freely,
;     and you are forbidden to deprive others from those rights.   
;    Please read the FSF (Free Software Foundation) GNU statement.
;
;*********************************************************************
;
;            E-MAIL    tony.kubek@flintab.se
;        
;        
; DATE            2000-01-23
; ITERATION        1.0B
; FILE SAVED AS        PiCBoard.ASM    
; FOR            PIC16F84-10/P        
; CLOCK            10.00 MHz RESONATOR ( OSC-HS )
; INSTRUCTION CLOCK    2.50 MHz T= 0.4 us
; SETTINGS        WDT-ON, PWT-ON, CP-OFF
; REVISION HISTORY    
;            0.1b -    First beta, mainly for testing
;            1.0b -     First public beta release
;
;
;
;
;***************************************************************************
;
;                PREFACE 😉
;
;    This is NOT an tutorial on pc keyboards in general, there are quite
;    a few sites/etc that have that already covered. However i DID find
;    some minor ambiguities regarding the actual protocol used but nothing
;    that warrants me to rewrite an complete pc keyboard FAQ.
;    So for 'general' pc keyboard info ( scancodes/protocol/pin config/etc )
;    here are some useful links:
;
;   http://www.senet.com.au/~cpeacock/     
;   http://ourworld.compuserve.com/homepages/steve_lawther/keybinfo.htm
;   http://204.210.50.240/techref/default.asp?url=io/keyboard.htm
;   http://www.arne.si/~mauricio/PIC.HTM
;    
;    PLEASE do not complain about code implementation, I know there are parts
;    in which is it 'a bit' messy and hard to follow, and other parts where
;    the code is not optimised. Take it as is. Futhermore I did hesitate
;    to include all of the functionality thats currently in, but decided to
;    keep most of it, this as the major complaint i had with the other available
;    pc-keyboard code was just that - 'is was incomplete'. Also do not
;    be discoraged by the size and complexity of it if you are an beginner,
;    as a matter of fact this is only my SECOND program ever using a pic.
;    I think I managed to give credit were credit was due ( 'borrowed code' ).
;    But the originators of these snippets has nothing to do with this project
;    and are probably totally unaware of this, so please do not contact them
;     if you have problems with 'my' implementation.
;
;    BTW ( shameless plug ) UltraEdit rules ! ( if you dont have it, get it ! )
;   http://www.ultraedit.com ( dont 'forget' the 'wordfile' for PIC, color indenting )
;    Without that I guess this file would be 'messy'.
;
;    Ok with that out of the way here we go:
;    
;
;***************************************************************************

;                DESCRIPTION ( short version 😉 )

;    A set of routines which forms a PC keyboard emulator.
;    Routines included are:
;    Interrupt controlled clock ( used for kb comm. and 'heart beat )
;    A 4x8 matrix keyboard scanner ( using an 74HCT4051 3 to 8 analogue multiplexer )
;    Communincation with PC keyboard controller, both send end recive.

;     PC keyboard routines are using 4 pins (2 inputs & 2 outputs) to control
;     keyboard's 2 bidirectional OC lines (CLK & DATA). The following
;     'drawing' conceptually shows how to connect the related pins/lines
;
;    ( ASCII art/info shamelessly 'borrowed' from http://www.arne.si/~mauricio/PIC.HTM )
;
;                 vcc    vcc
;                  |      |
;                  \     -+-
;                  / 2K2    /_\  1N4148
;                  \      |
; pcCLOCK_in -----------------o---o------o---------   kbd CLOCK
;                  |   |     |
;             2N2222   |50pF -+-
; pcCLOCK_out ____/\/\/\____|/     ===    /_\
;            2K2     |\>   |     |
;                  |   |     |
;                 /// ///    ///
;
;     An identical circuit is used for the DATA line.
;    Note: The 2N2222 transitors can be replaced with BC337 ( NPN ) which are cheaper
;    The keyboard matrix routines are using RB4-RB7 as inputs.
;    and RB0-RB2 as output to/from an 3 to 8 multiplexer
;    so that it can read up to 4x8 keys ( 32 ).
;
;    RA4/TOCK1 is an input from an jumper, if low then keyrepeat is disabled and
;    alt-key is enabled instead i.e. instead of repeating a key that has
;    been depressed a certain amount of time, a bit is set that can change the
;    scancode for a key ( of course, all keys can have an alternate scancode ).
;    To exit the alt. keymap press the 'enter alt. keymap' key once again or wait until
;    timeout. ( defined in the code )
;    NOTE !! The so called 'enter alt. keymap' key is hardcoded, i.e the key
;    i've choosen in this 'example' is Column 1 Row 2, if this is to be changed
;    code has to be changed/moved in the debounce and checkkeystate routines.
;    ( marked with <-------Alt keymap code-------> )
;     RB3 is currently used for a flashing diode. ( running )
;    Note my real keyboard ( KeyTronic ) uses a clock rate of 40 us, my implementation
;     uses about 50 us, easily to change.
;

;***************************************************************************

;            DESCRIPTION ( longer version 😉 )
;
;    Pin     Used for
;    ------------------------------------------------------------
;    RA0    Pc keyboard data out ( to pc )
;    RA1    Pc keyboard data in ( from pc )
;    RA2    Pc keyboard clock in
;    RA3    Pc keyboard clock out
;    RA4    Flashing disco led 0.5s duty cycle( could be used for some more meaningful purpose )
;
;    RB0    Least significant bit in column adress to 4051 multiplexer ( own keyboard )
;    RB1    Middle...
;    RB2    Most significant bit -- || --
;    RB3    Input. Disable keyrepeat & enable alternative keymapping ( if = '0',ground)
;        OR if '1' ( free,+V ) enable keyrepeat & disable alternative keymapping
;    RB4    Own keyboard input row 1
;    RB5    --- || --- row 2
;    RB6    --- || --- row 3
;    RB7    --- || --- row 4
;
;    'Basic program structure':
;
;    Init    -    Initialise ports , ram, int, vars
;    Start delay -     After init the timer int is enabled and the flashing led will
;            start to toggle ( flash ). Before I enter the mainloop
;            ( and send any keycodes ) I wait until the led has flashed
;            twice.    This is of course not really needed but I normally
;            like to have some kind of start delay ( I know 1 sec is a bit much 🙂 )
;    Time Int    -    The timer interrupt (BIG), runs in the background:
;            - 'Normal' rate 0.5 ms, when in rx/tx rate is 27 us
;             - Performs an rx/tx check, then jumps to rx, tx or 'heart beat' code.
;             - TX code sends a byte to pc, at a rate of 27us per int.
;               The int rate is actually double the bit rate, as
;               a bit is shifted out in the middle of the clock pulse,
;               I've seen different implementations of this and I think
;               that the bit is not sampled until clock goes low again BUT
;               when logging my keyboard ( Keytronic ) this is the way that it
;                  does it. When all bits are sent, stopbit/parity is sent.
;               And the key is removed from the buffer.
;               After stopbit/parity is sent, a delay is inserted ( 0.5 ms )
;               before next rx/tx check.
;             - RX code recevies a byte from the pc, PIC is controlling clock !!
;               Int rate ( 27 us ) is, again, double bit rate.
;               Toggles clock and samples the data pin to read a byte from pc.
;               When reception is finished an 'handshake' takes place.
;               When a byte has been recevied a routine is called to check
;               which command and/or data was received. If it was
;               keyboard rate/delay or led status byte, it is stored in local ram
;               variables. NOTE: The rate/delay is not actually used for
;               key repeat as the code is right now ( I use my 'own' fixed rate/delay )
;               however it is very easy to implement.
;               After handshake a delay is inserted ( 0.5 ms )
;               before next rx/tx check.
;            - 'Heart beat' ( idle code 0.5 ms tick ) performs:
;             - Check clock/data lines to see if pc wants to send something or
;               if tx is allowed.
;             - If tx is possible it checks the keybuffer for an available key and
;               if keys are in buffer then it initiates a tx seq.
;               and sets the int rate to 27 us.
;             - If the pc wants to send something, an rx seq. is initiated
;               ( there is some handshaking involved, during which
;               the int rate is set to 60 us ) after that, the int rate is
;               set to 27 us and an rx seq is started.
;             - Divides some clock counters to achive 10ms,100ms,500ms sections.
;             - In 100 ms section it performes a numlock status check and
;               keyrepeat check ( both rate and delay is local in 100 ms ticks,
;               thats why I dont use the 'real' rate delay )
;              - If numlock status is not the desired one code is called to
;                toggle the numlock status.
;              - If a key has been pressed long enough for repeat, an bit is set
;                so we can repeat the key ( send the scancode again ) in the main loop.
;            - In 500 ms section the led is toggled on each loop
;              - Some various alternative keymap checks to get out of
;                alternative keymap. ( i'll get to that in a bit )
;
;    Main loop    - Outputs an adress to the 4051 multiplexer waits 1 ms
;              reads the row inputs ( 4 bits/keys ), increments address
;              and outputs the new adress, waits 1 ms and reads the input
;               ( next 4 bits/keys ). Now using the address counter, calls a
;               debounce/send routine that first debounces the input,
;              ( four consecutive readings before current state is affected )
;              and when a key is changed a make/break code is sent ( put in buffer ).
;              In the next loop the next two columns are read etc. until all
;              4 column pairs are read.
;            - If keyrepeat is enabled ( see pin conf. above ) the
;              repeat flag is checked and if '1' the last pressed key scancode
;              is sent again ( put in buffer ).
;            - If keyrepeat is not enabled( alternative keymap is enabled instead )
;              then various checks to exit the alternative keymap are performed instead.
;
;    Scancodes for all key are located in a lookup table at the end of this file,
;    each key has four program rows, to make room for extended codes and alt. keymap codes.
;
;     Explanation of 'alternative' keymap:
;    
;    Using this program ( or an heavily modified version of it anyway 🙂 )
;     on a computer running Windows posed some small problems; namely:
;    -The keyboard ( mapping ) I used did not have any 'special' key such as
;     <alt>,<ctrl>,<tab> etc.
;    -In windows, things can go wrong, 🙂 if a dialog pops up or something similar
;     there were just no way one could dispose of this with the keymapping i used.
;    - 'Only' 28 keys were implemented ( hardware wise ).
;    In this particular case the keyrepeat was disabled ( due to the nature of the application )
;    Therefore i came up with the solution to use the keyrepeat related routines and vars.
;    To handle a so called 'alternative' keymapping.
;    This means that an key is dedicated to be the alt. keymap toggle key,
;    when pressing this longer than the programmed repeat delay, instead of
;    repeating the key a bit variable is set to use another set of scancodes for
;    the keyboard. This 'alternative' keymap is then enabled even though the
;    alt. keymap toggle key is released, but it also incorporates an timeout
;    that will return to normal keymap if no key is pressed within a certain time ( currently 7 sec )
;    Of course pressing the alt. keymap toggle key again ( while in alt. keymap )
;    will return the keyboard to the normal keymap.
;    NOTE !! the key choosen for the alt. keymap toggle is hardcoded, so if the scancode
;    for this is changed in the lookup table, changes have to be made in other routines
;    as well. ( namly : CHECK_KEY_STATE, and DEBOUNCE_COLUMN routines )
;    While in alt. keymap each key CAN have an alternative scancode ( see lookup table ).
;    Also by using the numlock bit, one can toggle numlock status 'on the fly' when entering
;    or exiting the alt keymap.
;
;    Some notes about the local keyboard interface ( matrix 😞
;    Although the hardware circuit and software allows virtually unlimited
;    simultaneosly pressed keys, the keyboard matrix itself normally poses
;    some limitations on this. If an keymatrix without any protective diodes
;    are used then one would have loops INSIDE the keymatrix itself when
;    multiple keys are pressed in different columns .
;    Look at the ( although horrible 😉 ) ASCII art below(internal weak pullup enabled):
;    0 - Key is free
;    1 - Key is pressed ( connection between hor/ver rows )
;    Three keys pressed adressing ( reading ) left column :
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    --------1-------1------ ( row 2 )
;                    |       |
;     To pic3    --------1-------0------ ( row 3 )
;                   |       |
;        Column(4051)    0V    -    ( Current column is set to 0V when adressing )
;
;    This works as intended, we can read a '0' on pic inputs 2,3 which
;    is what we expected. The current ( signal ) follows the route marked with '*':
;    ( only the 'signal path' is shown for clarity )
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    *********-------1------ ( row 2 )
;                    *       |
;    To pic3    *********-------0------ ( row 3 )
;                   *       |
;        Column(4051)    0V    -    ( Current column is set to 0V when adressing )
;
;    However, when we now read ( address ) the right column instead we
;    do not read what is expected ( same three keys still pressed 😞
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    *****************------ ( row 2 )
;                    *<-     *
;    To pic3    *********-------*------ ( row 3 )
;                   |       *
;        Column(4051)    -    0V    ( Current read column is set to 0V when adressing )
;
;    As you can see the 'signal' travels in the keymatrix itself ( where the '<-' is )and
;     will cause a 'ghost' signal to be read on the pic. So instead
;    of having an '0' on input 2 only we also can read an '0' on input 3.
;    This is because the two keys in column 1 are interconnected ( when they are pressed ).
;    Keep this in mind if you are planning to support multiple pressed keys.
;    
;
;***************************************************************************
;
;    Some suggestions for 'improvements' or alternations
;
;    - Using the jumper 'disable-repeat' as a dedicated key for switching
;      to alternative keymapping.
;    - Enable repeat in alternative keymapping
;    - Clean up TX/RX code ( a bit messy )
;    - Using the led output ( or jumper input ) as an extra adress line
;      to the multiplexers ( in this case 2 pcs. 74HCT4051 ) we could have
;      4x16 keys instead. Would require some heavy modifications though
;      as there are not much ram/program space left. But if alternative
;        keymapping is discarded ( most likely if one has 64 keys ) each
;      key in the lookup table only needs to be 2 lines instead of 4.
;      That would 'only' require some modifications to preserv ram.
;    - Using the EERAM for 'macros' or similar ( not used at all now )
;
;***************************************************************************
;
;            LEDGEND
;
;    I tend to use the following when naming vars. etc. :
;    ( yes i DO like long names )
;    
;    For 'general' purpose pins:
;
;    An input pin is named I_xxx_Name where :
;
;        I_   - This is an input pin 😉
;        xxx_ - Optional what type of input, jmp=jumper etc.
;        Name - Self explanatory
;
;    An output pin is named O_xxx_Name where:
;    
;        O_   - This is an output pin 😉
;        xxx_ - Optional what type of output, led=LED etc.
;        Name - Self explanatory
;
;    Application(function) specific pins:
;
;    An application(function) specific pin is named xxName where:
;        
;        xx   - What/Where, for example pc=To/From pc
;        Name - Self explanatory ( what does it control etc )
;
;    An #define/constant/equ (no pin or ram ) uses all capital letters e.x. #define BREAK 0xF0
;
;    A bit variable will always start with '_'. For example '_IsLedStatus'
;
;    All other ( mostly ramvars. ) are named in various ways.
;
;
;***************************************************************************

    TITLE "PC Keyboard emulator - By Tony K&uuml;bek"

;        Processor       16F628a
;        Radix   DEC
;        EXPAND

    

;***** HARDWARE DEFINITIONS ( processor type include file )

    INCLUDE <C:\Arquivos de programas\Microchip\MPASM Suite\p16f628a.inc>           ; this might need changing !

;***** CONFIGURATION BITS

    __CONFIG _WDT_ON&_HS_OSC&_CP_OFF&_PWRTE_ON        ; _WDT_OFF
    
    __IDLOCS 010Bh    ; version 1.0B

;***** CONSTANT DEFINITIONS

    CONSTANT    BREAK = 0xF0        ; the break key postfix ( when key is released )
    CONSTANT    EXTENDED = 0xE0     ; the extended key postfix

    ; As i dont really use the rate/delay I receive from the pc ( easy to change )
    ; this is the current rate/delay times i use:
    CONSTANT    DELAY_ENTER_ALTKEYMAP = 0x1E    ; x100 ms , approx 3 seconds ( 30 x 100 ms )
                            ; how long the 'enter altkeymap' key must
                            ; be in pressed state before the altkeymap is enabled
    CONSTANT    DELAY_EXIT_ALTKEYMAP = 0x0F    ; x0.5 sec , approx 7.5 sec
                            ; how long before we exit the alt keymap if no key is
                            ; pressed.
    CONSTANT     DELAY_REPEAT    = 0x08        ; x100 ms, approx 800 ms
                            ; how long before we START repeating a key
    CONSTANT    DELAY_RATE    = 0x02        ; x100 ms, approx 200 ms repeat rate
                            ; how fast we are repeating a key ( after the delay above )
    
;***** CONSTANT DEFINITIONS ( pins )

;    For connection with PC keyboard

#define pcCLOCK_in  PORTA,2   ; Input PC keyboard clock
#define pcCLOCK_out PORTA,3   ; Output PC keyboard clock
#define pcDATA_in   PORTA,1   ; Input PC keyboard data
#define pcDATA_out  PORTA,0   ; Output PC Keyboard data


;     For connection (input) with our own keyboard
; Note I actually dont use these (definitions!) in the program, but they might come in handy
; at one time or another 😉 ( I use the pins though.. )

#define kbROW_1   PORTB,7
#define kbROW_2   PORTB,6
#define kbROW_3   PORTB,5
#define kbROW_4   PORTB,4

;    Indications ( output )

#define O_led_KEYCOMM_ok    PORTA,4        ; communication seems ok led ( flashing )

;    Disable/enable key repeat input jumper

#define I_jmp_NoRepeat    PORTB,3    ; note: internal weak pullup enabled

;    For keybuffer ( using the indirect file selector FSR )

#define KeyBufferHead    FSR


;***** RAM ASSIGNMENT

        
        CBLOCK    0x20
            KeyBufferTail    ; where the last byte in buffer is..
            clkCount         ; used for clock timing
            Offset        ; used for table reads        
            Saved_Pclath       ; Saved registers during interrrupt
            Saved_Status       ; -----
            Saved_w            ; -----
            CurrKey        ; current key ( rx or tx )..
            KeyParity    ; key parity storage ( inc. for every '1' )
            Divisor_10ms    ; for the timer
            Divisor_100ms   ; ditto
            Divisor_500ms   ;
            Divisor_Repeat  ; timer for repeated key sends

            Flags        ; various flags
            RepeatFlags     ; flags for repeating a key
            bitCount    ; bitcounter for tx/rx
            Comm_Flags    ; flags used by both rx and tx routines
            Temp_Var    ; temp storage, can be used outside int loop
            TRX_Flags    ; flags used by both rx and tx routines
            CommandData     ; bit map when receving data bytes from pc
                    ; for example led status/ delay / etc
            KbLedStatus     ; to store status led bitmap for keyboard ( pc first sends 'ED' then this byte )
                        ;  bit 0=Scroll lock  ( 1=on )
                        ;  bit 1=Num lock
                        ;  bit 2=Caps lock
                        ;  bits 3-7 = unused
            KbRateDelay    ; to store repeat delay/rate ( pc first sends 'F3' then this byte )
                        ;  bit 0-4 (rate) = '00000' is 30x/sec '11111' is 2x/sec ( i.e. value * 33 ms )
                        ;  bit 5-7 (delay)= '00' is 250 ms, '11' is 1000 ms ( ie. value * 250 ms )   
                    ;  bit 7 = unused
            BufTemp        ; temp byte for storing scancode to put in buffer
            Temp        ; temp byte, used locally in buffer routine
            Temp2        ;
            LastKey        ; stores the last sent key
            KbBufferMin    ; where our keybuffer starts
            Kb1        ; used in keybuffer
            Kb2        ; used in keybuffer
            Kb3        ; used in keybuffer
            Kb4        ; used in keybuffer
            Kb5        ; used in keybuffer
            Kb6        ; used in keybuffer
            Kb7        ; used in keybuffer
            Kb8        ; used in keybuffer
            Kb9        ; used in keybuffer
            Kb10        ; used in keybuffer
            KbBufferMax    ; end of keybuffer
            TempOffset    ; temporary storage for key offset ( make/break )

            LastMakeOffset  ; storage of last pressed key ( offset in table )
            RepeatTimer    ; timer to determine how long a key has been pressed
            RepeatKey     ; the key to repeat
            repTemp        ; temporary storage in repeat key calc.
            repKeyMap    ; bit pattern for the column in which the repeat key is in
                    ; i.e. a copy of kbColumnXX_Old where 'XX' is the column
            LastKeyTime    ; counter when last key was pressed, used to get out of altkeymap
                    ; after a specific 'timeout'

            kbScan        ; scan code for pressed/released key
            kbTemp        ; temp storage for key states

            kbState        ; which keys that has changed in current columns
            kbBitCnt    ; bit counter for key check ( which key/bit )            
            
            kbColumnCnt    ; column counter ( loops from 8 to 0 )
                    ; Used as output to multiplexer/decoder and in debounce routines

            kbColumnVal    ; current value of the last 2 columns ( 2x4bits = 8 bits ) input pins ( keys )
                    ;
                    ; Note the kbColumnXX_New variables is not really needed
                    ; used it while making the program ( debugging 😉 ).
                    ; so if more free ram is needed change code using these to use
                    ; the current 'input' sample instead. ( kbColumnVal )
            kbColumn12_New  ; New debounced reading for column 1 & 2
            kbColumn12_Old  ; Latest known valid status of column 1 & 2
            kbColumn12Cnt    ; Debounce counter for column 1 & 2
            kbColumn12State ; State of debounce for column 1 & 2

            kbColumn34_New  ; New debounced reading for column 3 & 4
            kbColumn34_Old  ; Latest known valid status of column 3 & 4
            kbColumn34Cnt    ; Debounce counter for column 3 & 4
            kbColumn34State ; State of debounce for column 3 & 4

            kbColumn56_New  ; New debounced reading for column 5 & 6
            kbColumn56_Old  ; Latest known valid status of column 5 & 6
            kbColumn56Cnt    ; Debounce counter for column 5 & 6
            kbColumn56State ; State of debounce for column 5 & 6

            kbColumn78_New  ; New debounced reading for column 7 & 8
            kbColumn78_Old  ; Latest known valid status of column 7 & 8
            kbColumn78Cnt    ; Debounce counter for column 7 & 8
            kbColumn78State ; State of debounce for column 7 & 8

        ENDC

;******* END RAM

; *** flags used by both rx and tx routines
#define _isParity    Comm_Flags,0    ; bit in rx/tx is parity bit
#define _KeyError    Comm_Flags,1    ; set to '1' when an error is detected
#define _isStartBit    Comm_Flags,2    ; set to '1' when bit in rx/tx is startbit
#define _isStopBit    Comm_Flags,3    ; --- || --- but stopbit
#define _RxCanStart     Comm_Flags,4    ; for handshaking, when negotiating an rx seq.


; *** dito used for rx and tx
#define _KeyReceived    TRX_Flags,0    ; rx
#define _RX_Mode    TRX_Flags,1    ; rx is in progress ( started )
#define _doRXAck    TRX_Flags,2    ; do rx handshake
#define _RXAckDone    TRX_Flags,3    ; rx handshake is done
#define _RXEnd        TRX_Flags,4    ; rx seq is finished
#define _RXDone        TRX_Flags,5    ; rx exit bit
#define _KeySent    TRX_Flags,6    ; tx key has been succesfully sent
#define _TX_Mode    TRX_Flags,7    ; tx is in progress ( started )


; *** flags to determine the meaning of an incomming data is
; i.e. when then pc has sent a 'data follow' instruction
; these bits are set according to the command,
; i.e. the next incomming byte is.....
#define _IsLedStatus    CommandData,0    ; the next incoming byte contains kb led status
#define _IsRateDelay    CommandData,1    ; the next incoming byte contains kb rate/delay
#define _SkipByte    CommandData,2    ; other data not saved
 
; *** bit meaning in KbLedStatus ( the leds on the keyboard )
; i.e local ram copy of this byte
#define _LedScrollLock    KbLedStatus,0    ; '1' led is on
#define _LedNumLock     KbLedStatus,1    ;
#define _LedCapsLock    KbLedStatus,2    ;
#define _IsFirstLedStatus KbLedStatus,7    ; set this to '1' at startup, to know that our local
;                    ; copy (led status) is not yet syncronised. Used to
;                    ; 'force' sync. the first time we set the local numlock bit.

; *** flags used for various purposes
#define _Timer        Flags,0 ; used for waiting
;#define _WrongPar    Flags,1    ; ?? ( used during dev. to force wrong parity sends, removed )
#define _LastColumn    Flags,2    ; for kb scan code
#define _isBreak    Flags,3 ; if '1' the currently handled key is break ( released ), else make ( pressed )
;#define _isFree         Flags,4 ; Not used ! ( temporary during dev. )
#define _AltKeymap      Flags,5 ; is we use alternative keymap ( other scancodes ) see below !
                ; enabled ONLY when keyrepeat is disabled ( I_jmp_NoRepeat (RB3) is low )
#define _ExitAltKeymap  Flags,6 ; start checking if we should exit alternative keymap
                ; if all keys are 'free' ( not pressed ) we exit altkeymap
#define _InAltKeymap    Flags,7 ; if we are waiting for second press/release to get out of altkeymap


#define _doRepeat    RepeatFlags,0 ; if the last pressed key should be 'repeated'
#define _doSendKey    RepeatFlags,1 ; send the key in RepeatKey to pc     
#define _isExtended     RepeatFlags,2 ; temp flag if last key is extended
#define _RepeatIsExt    RepeatFlags,3 ; the key to repeat is extended key
#define _startRepeat    RepeatFlags,4 ; start repeat timer
#define _DoExitAltKeymap RepeatFlags,5     ; bit set when we are getting out of alternative keymap
#define _NumLock    RepeatFlags,6      ; 'mirror' of numlockstatus, by setting/clearing this bit
                           ; numlock status will be changed.
                    ; I.e. there is no need to 'manually' send break/make code for numlock
                    ; key, by setting this bit to '1' numlock status will by automaticlly
                    ; ( inside int handler ) set to 'on'( within 100 ms ). Se code for example.
#define _WaitNumLock    RepeatFlags,7    ; bit set when we have sent make/break numlock scancode
                    ; and waiting for numlock status byte reply.
                    ; ( to inhibit a new numlock scancode send )


;**************************************************************************    
;                Macros             
;**************************************************************************    
    

;+++++
;    BANK0/1 selects register bank 0/1.
;    Leave set to BANK0 normally.

BANK0    MACRO
    BCF    STATUS,RP0
    ENDM

BANK1    MACRO
    BSF    STATUS,RP0
    ENDM

;+++++
;    PUSH/PULL save and restore W, PCLATH and STATUS registers -
;    used on interrupt entry/exit

PUSH    MACRO
    MOVWF    Saved_w        ; Save W register on current bank
    SWAPF    STATUS,W    ; Swap status to be saved into W
    BANK0            ; Select BANK0
    MOVWF    Saved_Status    ; Save STATUS register on bank 0
    MOVFW    PCLATH
    MOVWF    Saved_Pclath    ; Save PCLATH on bank 0
    ENDM

PULL    MACRO
    BANK0            ; Select BANK0
    MOVFW    Saved_Pclath
    MOVWF    PCLATH        ; Restore PCLATH
    SWAPF    Saved_Status,W
    MOVWF    STATUS        ; Restore STATUS register - restores bank
    SWAPF    Saved_w,F
    SWAPF    Saved_w,W    ; Restore W register
    ENDM


;+++++        
;     We define a macro that will switch an output pin on or off depending
;     on its previous state. We must be on bank0 !!
;

TOGGLE_PIN    MACRO WHICH_PORT,WHICH_PIN
        LOCAL TOGGLE_PIN10, TOGGLE_END
        
        BTFSC    WHICH_PORT,WHICH_PIN    ; is the pin high ?
        GOTO     TOGGLE_PIN10        ; yes, clear it
        BSF    WHICH_PORT,WHICH_PIN    ; no, so set it                                        
            GOTO       TOGGLE_END
TOGGLE_PIN10:
        BCF    WHICH_PORT,WHICH_PIN    ; clear the pin            
TOGGLE_END:
        ENDM


;*************************************************************
; Credit for routine ( almost unchanged, expect it's now a macro 😉 )
; goes to Scott Dattalo http://www.interstice.com/~sdattalo/technical/software/software.html
; ( debouce routine at http://www.interstice.com/~sdattalo/technical/software/pic/debounce.html )
;
;     DEBOUNCE_BYTE - 'debounces' 8 bits, when a bit state
;    has been 'active' for 4 consecutive debounce loops
;    it's state is put into the 'debounced' sample ( i.e. output )
;
; The purpose of this routine is to debounce, i.e. digitally low pass filter
; inputs. The algorithm handles upto 8 bits at a time. An input is considered
; filtered if it has not changed states in the last 4 samples.
;
; 2-bit cyclic vertical counters count the 4 samples. As long as there is no
; change, the counters are held in the reset state of 00b. When a change is detected
; between the current sample and the filtered or debounced sample, the counters
; are incremented. The counting sequence is 00,01,10,11,00... When the counters
; roll over from 11b to 00b, the debounced state is updated. If the input changes
; back to the filtered state while the counters are counting, then the counters
; are re-initialized to the reset state and the filtered state is unaffected.
; In other words, a glitch or transient input has been filtered.
;
; Here's the C-psuedo code:
;
;static unsigned clock_A,clock_B,debounced_state
;
;debounce(unsigned new_sample)
;{
;  unsigned delta;
;
;  delta = new_sample ^ debounced_state;   //Find all of the changes;
;  clock_A ^= clock_B;                     //Increment the counters
;  clock_B  = ~clock_B;
;
;  clock_A &= delta;                       //Reset the counters if no changes
;  clock_B &= delta;                       //were detected.
;
;      //Preserve the state of those bits that are being filtered and simultaneously
;      //clear the states of those bits that are already filtered.
;  debounced_state &= (clock_A | clock_B);
;      //Re-write the bits that are already filtered.
;  debounced_state |= (~(clock_A | clock_B) & new_sample);
;}
;
; The 2-bit counters are arranged "vertically". In other words 8 counters
; are formed with 2 bytes such that the corresponding bits in the bytes are
; paired (e.g. MSBit of each byte is paired to form one counter).
; The counting sequence is 0,1,2,3,0,1,... And the state tables and Karnaugh
; maps are:
;
;State Table:     Karnaugh Maps:
;pres  next      B
; SS  SS         0   1
; AB  AB       +---+---+    +---+---+
;--------   A 0|   | 1 |    | 1 |   |
; 00  01       +---+---+    +---+---+
; 01  10      1| 1 |   |    | 1 |   |
; 10  11       +---+---+    +---+---+
; 11  00      A+ = A ^ B     B+ = ~B
;
; Here's the PIC code that implements the counter:
;       MOVF    SB,W    ;W = B
;       XORWF   SA,F    ;A+ = A ^ B
;       COMF    SB,F    ;B+ = ~B
;  14 instructions
;  15 cycles
; Inputs:
;   NewSample - The current sample
; Outputs
;   DebouncedSample - The current value (filtered version of NewSample)
;
; VARS used
;   DBCnt,
;   DBState - State variables for the 8 2-bit counters
;
    
DEBOUNCE_BYTE MACRO NewSample,DebouncedSample,DBCnt,DBState

        ;Increment the vertical counter        

    MOVF    DBState,W
        XORWF   DBCnt,F        
    COMF    DBState,F

        ;See if any changes occurred        

    MOVF    NewSample,W        
    XORWF   DebouncedSample,W

        ;Reset the counter if no change has occurred        

    ANDWF   DBState,F
        ANDWF   DBCnt,F        ;Determine the counter's state
        MOVF    DBState,W        
    IORWF   DBCnt,W

        ;Clear all bits that are filtered-or more accurately, save
        ;the state of those that are being filtered        

    ANDWF   DebouncedSample,F
        XORLW   0xff        ;Re-write the bits that haven't changed.
        ANDWF   NewSample,W        
    IORWF   DebouncedSample,F        
    
    ENDM

    

;**************************************************************************    
;                 Program Start
;**************************************************************************    


;    Reset Vector

    ORG    H'00'

    ; For the sole purpose of squeezing every last byte of the programming mem
    ; I actually use the 3 program positions before the interrupt vector
    ; before jumping to the main program. Take note though that
    ; ONLY 3 instructions are allowed before the jump to main loop !!

    BANK0

        CLRF    PCLATH
    CLRF    INTCON    

    GOTO    INIT

;**************************************************************************    
;                     Interrupt routine
; An humongously big int handler here 😉
; but the keyboard handling is FULLY in the background, without any intervention
; in the main loop whatsoever. I like the solution anyway.
;
;**************************************************************************


;    Interrupt vector

    ORG    H'04'

INT
    PUSH            ; Save registers and set to BANK 0
                
    
    BTFSS   INTCON,T0IF        ; check if TMR0 interrupt
        GOTO    INTX            ; whoops ! 'unknown' int, should be disabled...
                ; NOTE ! if an 'unknown' int triggers the int routine
                ; the program will loop here for ever 😉 ( as the calling flag is not cleared )

    

    ;+++
    ; Timer (TMR0) timeout either heart beat or tx/rx mode
    ; In 'heart beat mode' we monitor the clock and data lines
    ; at ( roughly )= 0.5 ms interval, we also check the send buffer
    ; if there are any keys to send to pc ( if clock/data levels allows us to )
    ; In tx/rx mode we are controlling the clock/data line = 27 us tick
    ; Note: This software works using both 8Mhz and 10Mhz resonators without modifications
    ; however the 'timing' will then of course be 'off'.

INT_CHECK    
    BCF    INTCON,T0IF     ; Clear the calling flag !
    
     BTFSC    _TX_Mode    ; check if we are in tx mode
    GOTO    INT_TX        ; yep, goto tx mode code..    
    BTFSC    _RX_Mode    ; are we in rx mode ?
    GOTO    INT_RX        ; yep goto rx mode code
    GOTO    INT_HEART_BEAT  ; nope just goto 'heart beat' mode

;*************** TX code start ***********************************

INT_TX
    MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
        MOVWF   TMR0
 
    BTFSS    clkCount,0    ; check if we should toggle clock
    GOTO    INT_DEC_CLOCK    ; bit low,decrement and check if we should toggle data instead
    
    DECFSZ    clkCount,F    ; decrement and check if we are at zero..
    GOTO    INT_CLOCK     ; not zero then toggle clock line
    
    GOTO    INT_EXIT_TX        

INT_CLOCK
    
    BTFSC    pcCLOCK_out    ; check if we are low
    GOTO    INT_CLOCK_HIGH  ; yep set to high
                
    BTFSS    pcCLOCK_in    ; check if pc is pulling the clock line low
                ; i.e. it wants to abort and send instead..
    GOTO    INT_TX_CHECK_ABORT    ; abort this transfer
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    GOTO    INTX

INT_CLOCK_HIGH
    BCF    pcCLOCK_out    ; set high ( release line )
                ;BCF    _ClockHigh    ;
    GOTO    INTX

INT_TX_CHECK_ABORT
    GOTO    INT_EXIT_TX

INT_DEC_CLOCK    
    DECF    clkCount,F    ; decrement clock counter ( so we toggle next time )
INT_DATA
    BTFSS    bitCount,0    ; check bit counter
    GOTO    INT_DATA_IDLE    ; no data toggle

    DECFSZ    bitCount,F    ; decrement bit counter
    GOTO    INT_DATA_NEXT    ; next bit..

INT_NO_BITS
    BSF    bitCount,0    ; just in case ( stupid code, not sure its needed, just
                ; to make it impossible to overdecrement )***
    BTFSC    _isParity    ; are we sending parity ?
    GOTO    INT_DATA_END    ; exit
    
    ; all bits sent
    ; delete the last key from the buffer
    CALL    INC_KEY_HEAD    ; remove the ( last ) key form the buffer as is was sent ok..

    ; all bits sent check parity
    
    BSF    _isParity    ; set flag data is parity
    
    BTFSS    KeyParity, 0    ; is the last parity bit high? ( odd num of bits )
                ; then parity should be high ( free )
    GOTO     INT_DATA_HIGH_PAR      ; yes
    BSF    pcDATA_out    ; no, parity should be 'low' ( pulled down )
    GOTO     INTX        ;

INT_DATA_HIGH_PAR:    
    BCF    pcDATA_out    ; set parity bit high ( release data line )
    GOTO    INTX        ; and exit..


INT_DATA_END    
    BTFSS    _isStopBit    ; is the stopbit sent ?
    GOTO    INT_DATA_STOPB    ; nope then set stopbit flag

    BCF    pcDATA_out    ; parity bit sent, always release data line ( stop bit )        
    GOTO    INTX

INT_DATA_STOPB
    BSF    _isStopBit    ; set the stopbit flag
    GOTO    INTX        ; and exit

INT_DATA_IDLE
    DECF    bitCount,F    ; decrement bit counter
    GOTO     INTX        ; no toggle of data line

INT_DATA_NEXT
    BTFSS     CurrKey,0    ; is the last bit of the key_buffer high?
    GOTO     INT_DATA_LOW    ; no, pull data low
    BCF      pcDATA_out    ; yes, release data line
    INCF    KeyParity,F    ; increment parity bit
    GOTO    INT_DATA_ROTATE    ; rotate data
    
INT_DATA_LOW    ; last bit is low
    BSF    pcDATA_out    ; set the bit

INT_DATA_ROTATE
    RRF    CurrKey,F    ; rotate right by 1 bit    
    GOTO    INTX

INT_EXIT_TX
    ; setup the timer so we accomplish an delay after an tx seq
    
    BCF    _TX_Mode    ; clear tx mode flag
    BCF    pcCLOCK_out    ; release clock line
    BCF    pcDATA_out    ; and data line
;
    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0

    GOTO    INTX


;************** TX code end *****************

;************** RX code start ***************

INT_RX
    MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
        MOVWF   TMR0


    BTFSS    clkCount,0    ; check if we should toggle clock
    GOTO    INT_RX_DEC_CLOCK    ; bit low,decrement and check if we should read data instead
    
    DECFSZ    clkCount,F    ; decrement and check if we are at zero..
    GOTO    INT_RX_CLOCK     ; not zero then toggle clock line
    
    BCF    pcCLOCK_out    ; release the clock line we are done..
    BCF    _RX_Mode    ; clear rx mode bit ( go over to heart beat mode )
    GOTO    INT_EXIT_RX            

INT_RX_CLOCK

    BTFSC    pcCLOCK_out    ; check if we are low
    GOTO    INT_RX_CLOCK_HIGH     ; yep set to high ( release line )
    
    BTFSC    _isStartBit    ; check if this is the first bit ( start )
    GOTO    INT_RX_START    ; clear start bit and continue
    
    BTFSC    _isParity    ; check if this is the parity bit ( or parity has been received )
    GOTO    INT_RX_PAR    ; yep check parity

    GOTO    INT_RX_BIT    ; ok just a 'normal' bit read it
    
    
INT_RX_PAR            ; check parity
    BTFSC    _doRXAck    ; check the handshake flag
    GOTO    INT_RX_HNDSHK    ; start handshake check

    BTFSS    pcDATA_in    ; is the input high ?
    GOTO    INT_RX_PAR_HIGH    ; yep
    BTFSC    KeyParity,0    ; is the parity '0' ( should be )
    GOTO    INT_RX_PAR_ERR    ; nope parity error
    GOTO    INT_RX_ACK    ; parity ok next should be ack ( we take data line low )

INT_RX_PAR_HIGH
    BTFSS    KeyParity,0    ; check that parity bit is '1'
    GOTO    INT_RX_PAR_ERR    ; nope parity error
    GOTO    INT_RX_ACK    ; parity ok, next is ack ( we take data line low )

INT_RX_PAR_ERR
    BSF    _KeyError    ; set error flag

INT_RX_ACK
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    BSF    _doRXAck    ; enable ack check
    GOTO    INTX

INT_RX_HNDSHK
    BTFSS    _RXEnd        ; if we are done dont take data low
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    
    BTFSC    _RXAckDone    ; chek if hand shake ( ack ) is done ?
    BSF    _RXEnd        ; ok we are now done just make one more clock pulse

    GOTO    INTX        ; exit    
        

INT_RX_CLOCK_HIGH

    BCF    pcCLOCK_out    ; set high ( release line )
    BTFSS    _RXAckDone    ; are we done.. ?
    GOTO    INTX
    BTFSS    _RXDone        ; finished ?
    GOTO    INTX

    BCF    _RX_Mode    ; and clear rx flag..
    GOTO    INT_EXIT_RX    ; bye bye baby

INT_RX_DEC_CLOCK    

    DECF    clkCount,F    ; decrement clock counter ( so we toggle next time )
    BTFSS    _doRXAck    ; check if we are waiting for handshake
    GOTO    INTX
    

     BTFSC    pcCLOCK_out     ; check if the clock is low ( pulled down )
    GOTO    INTX        ; nope we are pulling down then exit
                ; we only take over the data line if
                ; the clock is high ( idle )
                ; not sure about this though.. ???

     
    BTFSC    _RXEnd        ; are we done ?
    GOTO    INT_RX_END

    ; handshake check if data line is free ( high )
    BTFSS    pcDATA_in    ; is data line free ?
    GOTO    INTX        ; nope

    BSF    pcDATA_out    ; takeover data line
    BSF    _RXAckDone    ; we are done..at next switchover from low-high we exit
    GOTO    INTX        ;

INT_RX_END
    BCF    pcDATA_out    ; release data line
    BSF    _RXDone        ; we are now done
    GOTO    INTX

INT_RX_START
    BCF    _isStartBit    ; clear start bit flag
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    GOTO    INTX
INT_RX_BIT
    BCF    CurrKey,7
    BTFSS    pcDATA_in    ; is bit high
    GOTO    INT_RX_NEXT    ; nope , it's a '0'
    BSF    CurrKey,7    ; set highest bit to 1
    INCF    KeyParity,F    ; increase parity bit counter
    
INT_RX_NEXT
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )

    DECFSZ    bitCount,F    ; decrement data bit counter    
    GOTO    INT_RX_NEXT_OK

    BSF    bitCount,0    ; just in case ( so we cannot overdecrement )
    BSF    _isParity    ; next bit is parity
    GOTO    INTX

INT_RX_NEXT_OK
    CLRC            ; clear carry, so it doesnt affect receving byte
    RRF     CurrKey,F    ; rotate to make room for next bit
    GOTO    INTX        ; and exit

INT_EXIT_RX    

    ; handle the recevied key ( if not it is an 'data' byte )

    MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8 ( int handling code )= 200 us
                ;
        MOVWF   TMR0        ; this delay seems to be needed ( handshake ? )
    
    ; check if this is an data byte ( rate/delay led status etc )
    
    MOVF    CommandData,F    ; reload into itself ( affect zero flag )

    BTFSS    STATUS,Z    ; check zero flag
    GOTO    INT_STORE_DATA    ; byte contains data ( rate/delay etc )
    
    CALL    CHECK_RX_KEY    ; no data, handle recevied command
    GOTO    INTX

INT_STORE_DATA
    ; store data byte in 'currkey',
    ; first reply with 'ack'
    
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    BTFSS    _IsLedStatus    ; is it led status byte ?
    GOTO    INT_STORE_RATE  ; nope check next
    
INT_STORE_NUM
    ; byte in 'currkey' is led status byte, store it
    MOVF    CurrKey,W    ; get byte
    MOVWF    KbLedStatus    ; and store it
    BTFSC    _WaitNumLock    ; was this something we were waiting for ?
                ; i.e. we sent the make scancode for numlock.
    CALL    RELEASE_NUMLOCK    ; yep, then send release code for 'soft' numlock

    GOTO    INT_STORE_EXIT    ; store it in local ram copy and exit

INT_STORE_RATE

    BTFSS    _IsRateDelay    ; is it rate/delay byte ?
    GOTO    INT_STORE_EXIT  ; nope then send ack end exit
    ; byte in 'currkey' is rate/delay byte, store it
    MOVF    CurrKey,W    ; get byte
    MOVWF    KbRateDelay    ; and store it
    
INT_STORE_EXIT
    
    CLRF    CommandData    ; clear data byte flags
    GOTO    INTX


;******************* 'heart' beat code ( 0.5 ms tick ) *********
; Note: As I 'mess' with the int timer this 'heart beat' is by no means
; an accurate clock. However it can work as a rough estimate for local time.
;
INT_HEART_BEAT

    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0


INT_CHECK_CLKDTA:  

    ; CLOCK DATA   Action
    ;-----------+--------
    ;   L    L  |  wait ?
    ;   L    H  |  wait, buffer keys
    ;   H    L  |  start an rx sequence
    ;   H    H  |  keyboard can tx
        
    BTFSS    pcDATA_in    ; is the data line high ( free )..
    GOTO    INT_CHECK_RX    ; Nope it's pulled down, check if rx is requested
        
    BTFSC    pcCLOCK_in    ; Is the clk line low  ( pulled down ) ?        
    GOTO    INT_CHECK_BUFF  ; Nope, so check if we have any keys to send

    GOTO    INT_IDLE    ; clock is low , wait and buffer keys( i.e. no rx/tx )

        
                        
INT_CHECK_RX           ; pc ( probably ) wants to send something..
        
    BTFSS    pcCLOCK_in    ; wait until clock is released before we go into receving mode..
    GOTO    INT_RX_IDLE    ; nope still low

    ; clock now high test if we are set to start an rx seq.
    BTFSS    _RxCanStart     ; have we set the flag ?
    GOTO    INT_WAIT_RX    ; nope then set it      
            
    BTFSC    pcDATA_in    ; make sure that data still is low
    GOTO    INT_ABORT_RX    ; nope abort rx req, might been a 'glitch'

    ; initiate the rx seq.

    CLRF    Comm_Flags    ; used by both tx/rx routines ( and _RxCanStart bit !! )
    CLRF    TRX_Flags    ; clear tx/rx flags
    CLRF    KeyParity    ; clear parity counter

    BSF    _RX_Mode          ; set rx mode flag..
    BSF    _isStartBit    ; set that next sampling is start bit
    
    ; preset bit and clock counters

    MOVLW    H'2F'        ; = 47 dec, will toggle clock output every even number until zero
    MOVWF    clkCount    ; preset clock pulse counter

    MOVLW    H'08'        ; = 8 dec, number of bits to read
                ; then parity bit will be set instead

    MOVWF    bitCount    ; preset bit counter

    ; note as we are starting the clock here we allow a longer time before we start
    ; the actual 20 us tick, this the first time we wait about 200 us before the first clock 'tick'     

    MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8us ( int handling code )= 200 us
                ;
        MOVWF   TMR0
    
    GOTO    INTX        ; exit, the next int will start an rx seq

INT_WAIT_RX:
    BSF    _RxCanStart    ; set flag so we start rx next int ( 0.5 ms )
INT_RX_IDLE
    ; reload clock so we check more often
    MOVLW   H'F0'           ; start timer with 240 ( 256-16  = 240 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 16 + 8/10us ( int handling code )= (about) 60 us
        MOVWF   TMR0

    GOTO    INTX        ;

INT_ABORT_RX
    BCF    _RxCanStart    ; clear flag ( forces a 'new' rx start delay )
    GOTO    INT_IDLE    ;
                                               

INT_CHECK_BUFF:
    ; check if we have any keys to send to pc

        MOVF     KeyBufferTail,W ; get end of buffer to 'w'
        SUBWF     KeyBufferHead,W ; subtract start of buffer if head - tail = zero, no byte
        BTFSC   STATUS, Z       ; zero flag = no byte to send         
        GOTO    INT_IDLE    ; then do the 'idle' stuff
        
INT_SEND_KEY
    ;key in buffer, get it and initiate an tx seq...
    CALL    GET_KEY_BUFFER    ; get the key into CurrKey
    MOVF    CurrKey,W
    MOVWF    LastKey        ; store last sent key

    ; setup our tx/rx vars

    CLRF    Comm_Flags    ; used by both tx/rx routines ( and _RxCanStart bit !! )
    CLRF    TRX_Flags    ; clear tx/rx flags
    CLRF    KeyParity    ; clear parity counter

    BSF    _TX_Mode          ; set tx mode flag..
    
    ; preset bit and clock counters

    MOVLW    H'2B'        ; = 43 dec, will toggle clock out put every even number until zero
    MOVWF    clkCount    ; preset clock pulse counter

    MOVLW    H'12'        ; = 18 dec, will shift data out every even number until zero
                ; then parity bit will be set instead
    MOVWF    bitCount    ; preset bit counter

    ; data now set, initiate the clock to generate a 20 us tick ( rx/tx heart beat )

    BSF    pcDATA_out    ; start bit, always 'low' ( we pull down )
     
    MOVLW   H'FA'           ; start timer with 252 ( 256-6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8 ( int handling code )= 27 us
        MOVWF   TMR0
    
    GOTO    INTX        ; exit, the next int will start an tx

INT_IDLE:
    
    ; 'Idle' code i.e. no rx or tx possible AND/OR nothing to send

    DECF    Divisor_10ms,F  ; Count 0.5ms down to give 10 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .20
    MOVWF    Divisor_10ms     ; Preset the divide by 20

    ;+++
    ; 10 ms tick here
    


    ; Divide the 10 ms tick to give 100 ms second tick
INT_10MS
    DECF    Divisor_100ms,F    ; Count 10ms down to give 100 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .10
    MOVWF    Divisor_100ms    ; Preset the divide by 10

        ;+++
     ; 100 ms tick here

INT_100MS


    ; numlock check !! bit variable _Numlock does not actually set the numlock status directly.
    ; However, by setting this bit to '1' we make a test against the current numlock led status
    ; ( bit no 1 in variable KbLedStatus ), if that is different from this variable
    ; we send a 'numlock' press/release ( to toggle numlock status )
    ; so in essence, setting this bit to '1' will force numlock to be 'on' etc.

    BTFSC    _IsFirstLedStatus ;  if = 1 then we have not yet received numlock status
    GOTO    INT_REPEAT_CHECK    ; nope, then this is a consecutive byte, store as 'normal'

    BTFSS    _WaitNumLock    ; are we waiting for pc numlock reply ?
    GOTO    INT_NUMLOCK_CHECK ; yep then do repeat check instead
    
    DECFSZ    Temp_Var,F    ;
    GOTO    INT_REPEAT_CHECK

    CALL    RELEASE_NUMLOCK

INT_NUMLOCK_CHECK

    BTFSC    _LedNumLock    ; is the led on ?
    GOTO    INT_NUMLOCK_ON    ; yep, then test our 'local' numlock state ( wanted numlock state )

    ; nope numlock is off, is our wanted state also off ?
    BTFSS    _NumLock    ; is wanted state off ?
    GOTO    INT_REPEAT_CHECK ; yep continue   

    CALL    PRESS_NUMLOCK    ; nope then send numlock press/release code

    GOTO    INT_REPEAT_CHECK
            

INT_NUMLOCK_ON
    BTFSC    _NumLock    ; is wanted state also 'on' ?
    GOTO    INT_REPEAT_CHECK ; yep

    CALL    PRESS_NUMLOCK    ; nope then toggle numlock state

INT_REPEAT_CHECK

    ; check if a key should be 'repeated' ( when pressed longer than 500 ms )
    BTFSS    _startRepeat    ; start repeating a key ? ( delay !!! )
    GOTO    INT_CHECK_KEY    ; nope, then check if key should be repeated
    DECF    RepeatTimer,F    ;
    BNZ    INT_500MS    ; not zero yet, check timer instead

    BCF    _startRepeat    ; stop repeat timer ( delay is accomplished )
    BSF    _doRepeat    ; and enable 'key' is still down check
    MOVLW    .02        ; start repeat send timer
    MOVWF    Divisor_Repeat  ;

    GOTO    INT_500MS    ; do next timer check

INT_CHECK_KEY
    BTFSS    _doRepeat    ; key should be repeated ?
    GOTO    INT_500MS    ; nope
    
    ; ok key should be repeated, check if it still pressed ?
    CALL    CHECK_KEY_STATE    ; uses MakeKeyOffset to calculate which key that was
                ; the last pressed, and then check if it's still pressed
                ; if still pressed carry = '1',

    BTFSS    STATUS,C    ; check carry
    BCF    _doRepeat    ; clear repeat bit, stop repeating the key

    BTFSS    _doRepeat    ; still pressed ?
    GOTO    INT_500MS    ; nope

    DECF    Divisor_Repeat,F  ; should we send the key ?
    BNZ    INT_500MS    ; nope

    MOVLW    DELAY_RATE    ; reload timer with key rate delay
    ;MOVLW    .02        ; restart timer
    MOVWF    Divisor_Repeat  ;
    
    BSF    _doSendKey    ; set flag to send key, NOTE the actual sending ( putting into send buffer )
                ; is done inside mainloop.
     
    
INT_500MS
    
    DECF    Divisor_500ms,F    ; Count 100ms down to give 500 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .05
    MOVWF    Divisor_500ms    ; Preset the divide by 5

        ;+++
     ; 500 ms tick here


INT_500_NEXT
    
    TOGGLE_PIN O_led_KEYCOMM_ok    ; toggle the disco light 😉

    BTFSS    _DoExitAltKeymap    ; is the alt keymap toggle key pressed the second time ?
                    ; if so skip timeout test and exit
    BTFSS    _InAltKeymap    ; are we in altkeymap ?
    GOTO    INTX        ; nope
    
    ; we are in altkeymap, decrement the lastkeytime
    ; and check if we are at zero then we exit
    ; the altkeymap.

    DECF    LastKeyTime,F    ; decrease time
    BNZ    INTX        ; exit, timer has not expired
    ; timer expired, get out of altkey map
    BSF    _ExitAltKeymap    ;

; ***************** 'heart' beat code end ***************

INTX
    ;BCF    INTCON,T0IF     ; Clear the calling flag

    PULL            ; Restore registers
    RETFIE
    
; **************** end interrupt routine **************


;+++++        
;     Routines that will 'toggle' keyboard numlock status
;     by sending numlock make/break code
;

PRESS_NUMLOCK:    
        MOVLW    H'77'         ; numlock key scancode, make
        CALL    ADD_KEY
        MOVLW    H'06'         ; 6 x 100 ms = 600 ms ( release delay )
        MOVWF    Temp_Var    ;
        BSF    _WaitNumLock    ; we are waitin for numlock status reply from pc
        RETURN

RELEASE_NUMLOCK:
        MOVLW    BREAK        ; break prefix
        CALL    ADD_KEY    
        MOVLW    H'77'         ; numlock key scancode
        CALL    ADD_KEY
        BCF    _WaitNumLock
        RETURN
    
; ***********************************************************************
;
;  CHECK_RX_KEY - handles the received commands from pc
;
CHECK_RX_KEY    
    ; check the key in 'currkey' ( command from pc )

CHECK_ED
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'ED'        ; subtract value in W with 0xED
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_EE    ; the result of the subtraction was not zero check next
    ; ok 'ED'=set status leds ( in next byte ) received
    BSF    _IsLedStatus    ; set bit that next incoming byte is kb led staus
    GOTO    CHECK_SEND_ACK    ; send ack

CHECK_EE    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'EE'        ; subtract value in W with 0xEE
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F0    ; the result of the subtraction was not zero check next
    ; ok 'EE'= echo command received
    GOTO    CHECK_SEND_EE    ; send echo

CHECK_F0    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F0'        ; subtract value in W with 0xF0
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F2    ; the result of the subtraction was not zero check next
    ; ok 'F0'= scan code set ( in next commming byte ) received
    BSF    _SkipByte    ; skip next incomming byte ( or dont interpret )
    GOTO    CHECK_DONE    ; do not send ack !
CHECK_F2    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F2'        ; subtract value in W with 0xF0
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F3    ; the result of the subtraction was not zero check next
    ; ok 'F2'= Read ID command responds with 'AB' '83'
    GOTO    CHECK_SEND_ID    ; send id bytes

CHECK_F3    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F3'        ; subtract value in W with 0xF3
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_FE
;    GOTO    CHECK_F4    ; the result of the subtraction was not zero check next
    ; ok 'F3'= set repeat rate ( in next commming byte ) received
    BSF    _IsRateDelay    ; next incomming byte is rate/delay info
    GOTO    CHECK_SEND_ACK    ; send ack

; **** Note ! removed from test as I 'don't care' ********
; **** i.e. I dont disable or enable the keyboard at any time.

;CHECK_F4    
;    MOVF    CurrKey,W    ; move key buffer into W register
;    SUBLW    H'F4'        ; subtract value in W with 0xF4
;    BTFSS   STATUS, Z    ; check if the zero bit is set
;    GOTO    CHECK_F5    ; the result of the subtraction was not zero check next
    ; ok 'F4'= keyboard enable received
;    GOTO    CHECK_SEND_ACK    ; send ack
;CHECK_F5    
;    MOVF    CurrKey,W    ; move key buffer into W register
;    SUBLW    H'F5'        ; subtract value in W with 0xF5
;    BTFSS   STATUS, Z    ; check if the zero bit is set
;    GOTO    CHECK_FE    ; the result of the subtraction was not zero check next
    ; ok 'F5'= keyboard disable received
;    GOTO    CHECK_SEND_ACK    ; send ack
CHECK_FE    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'FE'        ; subtract value in W with 0xFE
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_FF    ; the result of the subtraction was not zero check next
    ; ok 'FE'= resend last sent byte
    MOVF    LastKey,W    ; get last key
    CALL    ADD_KEY        ; and put it on the que
    GOTO    CHECK_DONE

CHECK_FF
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'FF'        ; subtract value in W with 0xFF
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_ERROR    ; the result of the subtraction was not zero, unknown command
    ; ok 'FF'= reset keyboard received
    
    GOTO    CHECK_SEND_AA    ; send 'AA' power on self test passed

CHECK_ERROR            ; unknown command ( or command not interpreted )
    GOTO    CHECK_SEND_ACK

CHECK_SEND_ID
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    MOVLW    H'AB'        ; keyboard id first byte, always 0xAB
    CALL    ADD_KEY        ;

    MOVLW    H'83'        ; keyboard id second byte, always 0x83
    CALL    ADD_KEY        ;

    GOTO    CHECK_DONE

CHECK_SEND_ACK

    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;
    GOTO    CHECK_DONE

CHECK_SEND_AA
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    MOVLW    H'AA'        ; keyboard post passed
    CALL    ADD_KEY        ;
    GOTO    CHECK_DONE

CHECK_SEND_EE
    MOVLW    H'EE'        ; keyboard echo
    CALL    ADD_KEY        ;

CHECK_DONE
    RETLW    0        ; and we are done

; ***********************************************************************
;  Buffer code ( a bit modified ) from Stewe Lawther
http://ourworld.compuserve.com/homepages/steve_lawther/ucindex.htm
;  And of course source of the exellent keyboard viewer. !! ( without which, this
;  project would have been close to impossible ) ( and of course my nifty
;  memory oscilloscope )
;
;  ADD_KEY_BUFFER - (outside int)add the key in CurrKey to our keybuffer que in the first
;            free position. If there is no more room the oldest byte is
;            'dumped'.
;  ADD_KEY      - Same but to be used inside int routine.( just skips int disable code )

ADD_KEY_BUFFER

ADD_STOP_INT            ; first stop all interrupts !!!!!!!
    BCF    INTCON,GIE    ; disable global interrupts..
    BTFSC    INTCON,GIE    ; check that is really was disabled
    GOTO    ADD_STOP_INT    ; nope try again
ADD_KEY                ; inside interuppt we call this instead ( as we dont need to disable int 🙂 )
    MOVWF    BufTemp        ; store key temporary
    MOVF    KeyBufferHead,W ; move buffer head out of FSR temporarily
        MOVWF   Temp            ; store in temp  
        MOVF    KeyBufferTail,W ; set FSR to buffer tail
        MOVWF   FSR        ; set indirect file pointer
        MOVF    BufTemp,W       ; set W to new scancode to send
        MOVWF   INDF            ; and put it in the buffer
        MOVF    Temp,W        ; get the head pointer back
        MOVWF   KeyBufferHead    ;

        INCF    KeyBufferTail,W ; get the end of the buffer
        SUBLW   KbBufferMax     ; check if at buffer max
        INCF    KeyBufferTail,W ; (reload value to w - doesn't affect C)
        BTFSS   STATUS, C       ; if so (negative result)
        MOVLW   KbBufferMin     ; set to buffer min ( wrap around )

        MOVWF   KeyBufferTail    
        SUBWF   KeyBufferHead,W ; see if we have any room ( head and tail have meet )
        BTFSC   STATUS, Z       ; if so (Z set)
        CALL    INC_KEY_HEAD    ; dump oldest byte

                ; finally turn on interrupts again
        MOVLW   b'10100000'     ; enable global & TMR0 interrupts
        MOVWF   INTCON

    RETURN


; ***********************************************************************
;
;  GET_KEY_BUFFER -     Gets a char from the buffer, and puts it into KeyBuffer
;            NOTE: Does not increase buffer pointers ( dump this key ).
;            A call to INC_KEY_HEAD will do this if the key is sent ok
GET_KEY_BUFFER    
          MOVF    INDF, W         ;put the byte to send into key buffer
                MOVWF   CurrKey
        RETURN            ; and go back, NOTE ! the key is not
                    ; removed from the buffer until a call
                    ; to INC_KEY_HEAD is done.
                    
; ***********************************************************************
;
;  INC_KEY_HEAD -     dump oldest byte in keybuffer, Do not call if byte
;            has not been fetched before ( GET_KEY_BUFFER )
;            
INC_KEY_HEAD    INCF    KeyBufferHead,W ; set to next byte in buffer
                SUBLW   KbBufferMax     ; check if at buffer max
                INCF    KeyBufferHead,W ; (reload value to w - doesn't affect C)
                BTFSS   STATUS, C       ; if so (negative result)
                MOVLW   KbBufferMin     ; set to buffer min ( wrap around )
                MOVWF   KeyBufferHead   ; and store ( in FSR )
                RETURN            ; go back
                
        

; ***********************************************************************
;
;  CHECK_KEY_STATE - Check if the last pressed key is still pressed
;             Returns with carry = '1' if still pressed
;             else carry = '0' ( or error )
;

CHECK_KEY_STATE:
    ; uses LastMakeOffset to calculate which key to test
    
    MOVF    LastMakeOffset,W    ; get offset
    ANDLW    H'18'        ; mask out column bits  
                ; lastmake offset has the following bits:
                ; '000yyxxx' where 'yy' is column no
                ; and 'xxx' is key num,
    BTFSC    STATUS,Z    ; zero = column 1 & 2
    GOTO    CHECK_COL_12    ; it is in column 1

    MOVWF    repTemp        ; save it temporary
    SUBLW    H'08'        ; subtract value in W with 0x08 ( columns 3 & 4 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_COL_34    ; it is in column 3 & 4

    MOVF    repTemp,W    ; get the column bits back
    SUBLW    H'10'        ; subtract value in W with 0x10 ( columns 5 & 6 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_COL_56    ; it is in column 5 & 6
    
CHECK_COL_78
    MOVF    kbColumn78_Old,W ; get bit map ( key status ) for keys in column 7 & 8
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_56
    MOVF    kbColumn56_Old,W ; get bit map ( key status ) for keys in column 5 & 6
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_34
    MOVF    kbColumn34_Old,W ; get bit map ( key status ) for keys in column 3 & 4
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_12
    MOVF    kbColumn12_Old,W ; get bit map ( key status ) for keys in column 1 & 2
    MOVWF    repKeyMap    ; and store it

;<-------Alt keymap code------->
    
    ;Checks ONLY column 1&2 bitmap ( keymap ) ( as it is now )
    ; this code has to be moved/changed if another column is choosen as the alt. keymap toggle key

    ; alternative keymap handling if key r3 c1 is pressed ( bit 2 in column 1&2 )
    ; then enable alternative keymap ( only if keyrepeat is disabled )
    
    ; check if this was the last key pressed

    ; check bit representing the alt. keymap key ( i've choosen key 2 )

    MOVF    LastMakeOffset,W ; get key offset again
    ANDLW    H'07'         ; mask out column bits
    SUBLW   H'02'        ; check if its bit num 2 ( the enter 'alt keymap' key )

    BTFSS   STATUS, Z    ; check if the zero bit is set    GOTO    CHECK_KEY    ; nope than another key was the last
                ; skip altkeymap enable

    ; the altkeymap key was the last pressed !
    ; is key repeat disabled ?

    BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
    GOTO    CHECK_KEY     ; yep, then skip altkeymap enable test

    ; enable altkeymap if key is still pressed
    
    BTFSC    repKeyMap,2    ; test bit 2 ( should be key 'F7' )
    GOTO    CHECK_ENABLE_ALT ; ok enable altkeymap ( if we are not already in altkeymap )
    GOTO    CHECK_KEY    ; nope another key in column 1&2 continue check

CHECK_ENABLE_ALT
    BTFSC    _AltKeymap    ; are we already in altkeymap ?
    GOTO    CHECK_KEY    ; yep then just continue

    ; We are just entering/enabling the alt. keymap

    BSF    _AltKeymap    ; enable alternative keymap

    ; Example of using an 'advanced' alt keymap handling    
    ; not enabled, to avoid intial confusion.

    ; I.E This snippet would only be called once when we
    ; are just entering(enabling) the alternative keymapping !

    ; This example code will 'soft' release the current altkeymap key ( it is in pressed state ! )
    ; ( i.e send release code for the enter alt. keymap key 'F7' )
    ; send the make scancode for left <alt> key instead.
    ; and force numlock to be off.
    
    ; Do not use if you dont understand the implifications !
        
    ; Also note that the scancodes are hardcoded here !
    ; ( i.e do not use the lookup table definition of the key/s )
    
    ; ***** start snippet
    
    ;MOVLW    BREAK    ; send break prefix
    ;CALL    ADD_KEY
    ;MOVLW    H'83'   ; and scancode for the enter alt keymap
    ;CALL    ADD_KEY
    ;MOVLW    H'11'     ; send make code for the left <alt> key
    ;CALL    ADD_KEY
    
    ; example of forcing the numlock status to a particular state
    ; the numlockstatus will change ( be checked ) inside the int routine
    ; See also at the end of KB_DEBOUNCE_12 where the numlock status
    ; will be restored when we release the key
    
    ;BCF    _NumLock    ; 'force' numlock to be off
    
    ; This bit MUST also be checked as we do not know if we have recevied
    ; first numlock status byte yet ( pc does not send numlock/led status
    ; after intial poweron, if not one of the numlock/capslock/scrolllock are pressed )
    ; i.e. if you connect this keyboard to a 'running' pc, the numlock status
    ; will be unknown.However if connected before poweron, it will be updated
    ; as numlock status is sent during pre-boot seq.
    
    ;BTFSC    _IsFirstLedStatus ; have we recevied numlock status yet ?
    ;CALL    PRESS_NUMLOCK
    
    ; ***** end snippet

CHECK_KEY
    ; 'normal' key down check
    ; column for pressed key is now in repKeyMap
    MOVF    LastMakeOffset,W ; get offset again
    ANDLW    H'07'        ; mask out key number ( lowest 3 bits )
    
     BTFSC    STATUS,Z    ; bit num zero ?
    GOTO    CHECK_KEY_DONE    ; yep lowest bit, check and return
    
       MOVWF    repTemp        ; and store it
CHECK_KEY_LOOP
    RRF    repKeyMap,F    ; rotate one step to right
    DECFSZ    repTemp,F        ; decrement bit counter
    GOTO    CHECK_KEY_LOOP    ; loop again

CHECK_KEY_DONE
    ; ok the key to test should now be the lowest bit in repKeyMap
    CLRC            ; clear carry
    BTFSC    repKeyMap,0    ; check bit 0
    BSF    STATUS,C    ; ok key is pressed set carry
    RETURN            ; and we are done..

; ***********************************************************************
;
;  DELAY_1ms -     Delay routine ! used when scanning our own keyboard
;         Delay is between output of adress to 4051 and reading of inputs
;         Increase to have a slower 'scan' rate or decrease to have a higher scan rate.
;

DELAY_1ms
        MOVLW    H'F0'        ; wait 255 cycles
        MOVWF    kbTemp        ; this var is 'safe' to be used in side mainloop
        MOVLW    H'03'
        MOVWF    kbState
DELAY_LOOP
        DECFSZ    kbTemp,F    ; decrement
        GOTO    $-1        ;
        
        MOVLW    H'F0'
        MOVWF    kbTemp
        DECFSZ    kbState,F
        GOTO    DELAY_LOOP

        RETURN


;---------------------------------------------------------------------------
;
;        Initialisation
;
;---------------------------------------------------------------------------
        
        
INIT:  

    ;+++
    ;    Set up the ports

        ; PORT A

    BANK1 ;
    MOVLW    b'00000110'        ; Set port data directions RA1,RA2 inputs RA0,RA3,RA4 outputs
    MOVWF    TRISA            ; PC keyboard connections


        ; PORT B

        ; Used for our own 3x8 matrix keyboard
    
    BANK1;
    MOVLW    b'11111000'        ; Set port data directions RB4-RB7 inputs rest outputs
    MOVWF    TRISB


    ;    Clear all registers on bank 0 ( memory )
    
    BANK0
    MOVLW    H'0C'
    MOVWF    FSR
    
INITMEM
    CLRF    0        ; Clear a register pointed to be FSR
    INCF    FSR,F
    CLRWDT            ; clear watchdog
    MOVLW    H'50'        ; Test if at top of memory
    SUBWF    FSR,W
    BNZ    INITMEM        ; Loop until all cleared

    ;+++     
    ;    Initiate the keybuffer pointers

INIT_BUFF:

    MOVLW   KbBufferMin    ; get adress of first buffer byte
    MOVWF    KeyBufferHead    ; store in FSR
    MOVWF    KeyBufferTail    ; and set last byte to the same ( no bytes in buffer )

    ;+++
    ;    Preset the timer dividers

    MOVLW    .20
    MOVWF    Divisor_10ms
    MOVLW    .10
    MOVWF    Divisor_100ms
    MOVLW    .05
    MOVWF    Divisor_500ms

    ;+++
    ;    Set up Timer 0.

    ;    Set up TMR0 to generate a 0.5ms tick
    ;    Pre scale of /8, post scale of /1
    
    BANK1
        
    MOVLW   b'00000010'     ; Initialisation of TMR0 prescale 8 '010'
                                ; weak pullup enabled by latch values.

    MOVWF   OPTION_REG      ; load option reg with prescale of 8
                
    BANK0

    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
    MOVWF   TMR0
        

        
;---------------------------------------------------------------------------
;
;        the main 'program' loop ( starts really at MAIN_LOOP )
;
;---------------------------------------------------------------------------

MAIN:    
        BSF    pcDATA_in
        BSF    pcCLOCK_in
        BCF    pcDATA_out
        BCF    pcCLOCK_out
        CLRF    PORTB

        MOVLW    H'08'        ; preset the column counter
        MOVWF    kbColumnCnt    ;
        
         BSF    _NumLock    ; default state is numlock = on
        BSF    _IsFirstLedStatus ; we have not yet recevied led status byte.

            MOVLW   b'10100000'     ; enable global & TMR0 interrupts
            MOVWF   INTCON

        CLRWDT            ; clear watchdog
        BTFSS    O_led_KEYCOMM_ok
        GOTO    $-2        ; make an 0.5 second delay here
                    ; i.e. the led will come on when 0.5 seconds has passed
                    ; set inside the timer int.

        CLRWDT            ; clear watchdog
        BTFSC    O_led_KEYCOMM_ok
        GOTO    $-2        ; make an additional 0.5 second delay here
                    ; i.e. the led will be dark when 0.5 seconds has passed
                    ; set inside the timer int.


        MOVLW    H'AA'         ; post passed :-), always 0xAA
        CALL    ADD_KEY_BUFFER
        
        ; now go into infinite loop, the pc kb interface runs in the background ( as an int )
        ; where we continuously monitor the pcCLOCK/DATA_in lines

         
MAIN_LOOP:
        ; check whatever 🙂
        CLRWDT            ; clear watchdog

MAIN_CHECK_COL_1:
        ; scan our own keyboard, first four bits

        ; address and read column, read as complement so key pressed = '1'
        ; since we pull down when key is pressed ( weak pullup enabled )
        ; ( i.e. pressed key has level 0V ) to make it more 'logical' to work with

        CLRF    kbColumnVal
        
        ; get column counter / adress out
        MOVF    kbColumnCnt,W          
        
        MOVWF    PORTB        ; set the columns adress to the 74HCT4051
                    ; i.e. make column low
        IFNDEF    DEBUG
        CALL    DELAY_1ms    ; wait 1 ms let pins stabilize
        ENDIF

        COMF    PORTB,W        ; read back the pin values ( complement i.e. key pressed = '1' )
        ANDLW   b'11110000'    ; mask out unused pins
        
        MOVWF    kbColumnVal    ; store the pin values

        SWAPF    kbColumnVal,F    ; swap nibbles ( low<->high ) to make room for next column

        INCF    kbColumnCnt,F     ; inc column adress
MAIN_CHECK_COL_2:
        ; read next four bits
        ; put out adress and read next column, read as complement so key pressed = '1'
        ; this as we pull down when key is pressed ( weak pullup enabled )

        ; get column counter / adress out
        MOVF    kbColumnCnt,W          
        
        MOVWF    PORTB        ; set the columns adress to the 74HCT4051
                    ; i.e. make column low

        IFNDEF    DEBUG
        CALL    DELAY_1ms    ; wait 1 ms
        ENDIF

        COMF    PORTB,W        ; read back the pin values ( complement i.e. key pressed = '1' )
        ANDLW   b'11110000'    ; mask out unused pins
        
        ADDWF    kbColumnVal,F    ; and store pin values

        INCF    kbColumnCnt,F   

        ; reset column counter check
        ; i.e. we are 'only' using adress 0 - 7
        MOVF    kbColumnCnt,W
        SUBLW    H'08'        ; subtract value in W with 0x08
        BTFSS   STATUS, Z    ; check if the zero bit is set
        GOTO    MAIN_CHECK_DEBOUNCE ; nope continue

        CLRF    kbColumnCnt     ; reset counter/adress

MAIN_CHECK_DEBOUNCE:
        CALL    KB_DEBOUNCE    ; do debouncing on the current values and send make/break
                    ; for any key that has changed
                    ; NOTE uses the current column adress to determine which
                    ; columns to debounce!
MAIN_REPEAT:
        BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
        GOTO    MAIN_CHECK_REPEAT ; yep check key repeating
        
        ; keyrepeat disabled then do check on exit of altkeymap instead

        BTFSS    _ExitAltKeymap    ; we want to exit altkeymap ?
        GOTO    MAIN_LOOP    ; nope

        
        ; check that ALL keys are released
        ; before exiting the alt keymap
        MOVF    kbColumn78_Old,F ; reload column 78 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 78

        MOVF    kbColumn56_Old,F ; reload column 56 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 56

        MOVF    kbColumn34_Old,F ; reload column 34 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 34

        MOVF    kbColumn12_Old,F ; reload column 12 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 12
    
        ; all keys released !!
        BCF    _AltKeymap    ; exit altkeymap
        BCF    _ExitAltKeymap    ; exit release check
        BCF    _InAltKeymap    ; clear flag for second keypress check
        BCF    _DoExitAltKeymap ;
        GOTO    MAIN_LOOP    


MAIN_CHECK_REPEAT
        BTFSS    _doSendKey    ; if we should send a repeated key
        GOTO    MAIN_LOOP    ; nope continue

        ; send the key in RepeatedKey but first check if its an extended key
        BTFSS     _RepeatIsExt    ; is it extended ?
        GOTO    MAIN_SEND_REPEAT ; nope just send scan code
        
        ; last key pressed was extended send extended prefix
        MOVLW    EXTENDED    ; get extended code
        CALL    ADD_KEY_BUFFER    ; and put it into the buffer
        
MAIN_SEND_REPEAT:
        MOVF    RepeatKey,W    ; get key code for the last pressed key
        CALL    ADD_KEY_BUFFER    ; and put it into the buffer
        BCF    _doSendKey    ; and clear the flag, it will be set again
                    ; inside int handler if key still is pressed    
        
        GOTO    MAIN_LOOP    ; and return
    

; ***********************************************************************
;
;  KB_SEND_KEY - uses the Offset stored in var 'Offset' to fetch a key from our
;         key lookup table.
;           then checks the bit var _isBreak to see if make or break codes should be sent
;         It then puts the code/s into the key buffer ( for sending later, in int routine )

KB_SEND_KEY:

    MOVF    Offset,W    ; get current offset
    MOVWF    TempOffset    ; save it ( to be used in key repeat code, is its 'make' )
                ; temp offset has the following bits:
                ; '000yyxxx' where 'yy' is column offset
                ; and 'xxx' is key num,


    CLRC            ; clear carry so it dont affect byte rotation
    RLF    Offset,F    ; first rotate
    RLF    Offset,F    ; second rotate

                ; offset no have the following bits:
                ; '0yyxxx01' where 'yy' is column offset
                ; and 'xxx' is key num,
                ; as each key in table has 4 bytes of 'space'

    INCF    Offset,F    ; add one, for the 'movwf pcl' at the start of the table

    BCF    Offset,7    ; clear to bit, just in case so we dont
                ; 'overflow' the table, should not be needed !

    BCF    _isExtended     ; clear extended flag

    MOVLW   LOW LOOKUP_KEY    ; get low bit of table adress
    ADDWF    Offset,F    ; 8 bit add
    MOVLW    HIGH LOOKUP_KEY    ; get high 5 bits
    BTFSC    STATUS,C    ; is page boundary crossed ?
    ADDLW    1        ; yep, then inc high adress
    MOVWF    PCLATH        ; load high adress in latch
    MOVF    Offset,W    ; load computed offset in w
    CLRC                ; clear carry ( default= key is not extended )
                ; if key is extended then carry is set in jumptable lookup_key

    CALL    LOOKUP_KEY    ; get key scan code/s for this key
                ; key scan code/s are saved in
                ; W - scancode, should go into kbScan
                ; carry set - extend code
                ; carry clear - not extended code

    MOVWF    kbScan        ; store scancode
    ; if carry is set then key is extended so first send extended code
    ; before any make or break code

    BTFSS    STATUS,C    ; check carry flag
    GOTO    KB_CHK_BREAK    ; nope then check make/break status

    BSF    _isExtended    ; set extended flag
    MOVLW    EXTENDED    ;
    CALL    ADD_KEY_BUFFER    ; get extended code and put in in the buffer

KB_CHK_BREAK:
    
    ; check if it's make or break
    BTFSS    _isBreak    ; check if its pressed or released ?
    GOTO    KB_DO_MAKE_ONLY    ; send make code

    BCF    _isBreak    ; clear bit for next key

    ; break code, key is released
    MOVLW    BREAK        ; get break code
    CALL    ADD_KEY_BUFFER    ; and put into buffer
    GOTO    KB_DO_MAKE    ; and send key code also

    ; key is pressed !
KB_DO_MAKE_ONLY:
    BCF    _doSendKey    ; stop repeat sending
    BCF    _doRepeat    ; and bit for repeat key send
    BSF    _startRepeat    ; and set flag for start key repeat check
    BCF    _RepeatIsExt    ; clear repeat key extended flag ( just in case )

    BTFSC     _isExtended     ; is it extended ?
    BSF    _RepeatIsExt    ; set the flag
    
    ; save this key in 'last' pressed, to be used in key repeat code

    MOVF    TempOffset,W    ; get saved offset
    MOVWF    LastMakeOffset  ; and store it

    ; if keyrepat = enabled, alternative mapping = disabled
    BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
    GOTO    KB_REP_NOR    ; yep set normal delay ( 800 ms )
    
    ; else keyrepat = disabled, alternative mapping = enabled
    MOVLW    DELAY_ENTER_ALTKEYMAP ;reload delay before entering the altkeymap ( 3 sec )
                ; i.e how long the enter altkeymap key must be pressed before
                ; we enable altkey keymap codes.
                            
    GOTO    KB_REP_SET    ; and set it
KB_REP_NOR:    
    
    MOVLW    DELAY_REPEAT    ; reload 'normal' repeat delay ( 800 ms )
            
KB_REP_SET:
    MOVWF    RepeatTimer    ; and (re)start the timer for key repeat
    MOVF    kbScan,W    ; get key scan code    
    MOVWF    RepeatKey    ; and save it

KB_DO_MAKE:
    ; key pressed/released ( i.e. the scancode is sent both on make and break )
    MOVF    kbScan,W    ; get scan code into w
    CALL    ADD_KEY_BUFFER    ; and add to send buffer
    

    ; reset the 'get out of alt. keymap timer for each keypress
    ; note don't care if we are 'in' alt. keymap. Reset this timer anyway
    ; as the code for checking if we are currently in alt. key map
    ; would be as long as it takes to reset the timer.

    MOVLW    DELAY_EXIT_ALTKEYMAP ; reload the delay for exiting the altkeymap when no
                ; key is pressed ( 7.5 sec )
    MOVWF    LastKeyTime    ; (re)set lastkey timer ( used to get out of altkeymap )    
    RETURN

; ***********************************************************************
;
;  KB_DEBOUNCE - debounces two column readings from our keyboard
;         If a bit 'state' has been 'stable' for 4 consecutive debounces
;           the 'new' byte is updated with the new state
;         'normal' loop time ( no tx/rx/key press ) is about 2-3 ms
;         so from 'key' down until 'new' is updated it takes about 8-10 ms
;         ( as we are scanning columns two by two, the whole keyboard needs
;         4 loops to be fully updated, then 4 debounce samples for each 'pair' )    

KB_DEBOUNCE:
    ; debounce current column(s)
    MOVF    kbColumnCnt,F    ; reload value into itself ( affect zero flag )
    BTFSC    STATUS,Z    ; is it zero ?
    GOTO    KB_DEBOUNCE_78    ; debounce columns 7 & 8

    MOVF    kbColumnCnt,W    ; move column counter into W register
    SUBLW    H'04'        ; subtract value in W with 0x04 ( columns 5 & 6 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    KB_DEBOUNCE_34    ; debounce columns 3 & 4

    MOVF    kbColumnCnt,W    ; move column counter into W register
    SUBLW    H'06'        ; subtract value in W with 0x02 ( columns 3 & 4 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    KB_DEBOUNCE_56    ; ok column 1 & 2 debounce

    ; all above tests 'failed'
    ; columns to debouce are 1 & 2

KB_DEBOUNCE_12:    
    ; debounce columns 1 & 2
    DEBOUNCE_BYTE    kbColumnVal,kbColumn12_New,kbColumn12Cnt,kbColumn12State

    MOVF    kbColumn12_New,W    ; get debounced sample
    XORWF    kbColumn12_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn12_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_12    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_12_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
        
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_12_NEXT    

KB_LOOP_12_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_12_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_12_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_12    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_12

KB_12_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn12_New,W ; get new status
    MOVWF    kbColumn12_Old    ; and store it..

;<-------Alt keymap code------->

    ; ***** alternative keymap handling
    ; The alternative keymap is enabled by pressing key r4 c1 ( i.e. bit 3 in column 12 )
    ; Here, we enable a check to turn off alternative keymap if
    ; that key and all others are released ( bit is cleared ).
    ; ( else no (alternative)break codes would be sent for those keys that are still pressed )
    ; NOTE: _Altkeymap is set inside int routine when checking
    ; keyrepeat so there is a 'variable' delay before the altkeymap is active
    ;

    BTFSS    _AltKeymap        ; is altkeymap enabled ?
    RETURN                ; nope return


    BTFSC   _InAltKeymap        ; are we in altkeymap ?
    GOTO    KB_12_IN        ; yep alt keymap key has been released once
    
    ; nope still waiting for first release
    BTFSS   kbColumn12_Old,2    ; is key released ? ( first time )
    GOTO    KB_12_ALT        ; yep, reset timers and set bit variables

KB_12_IN
    BTFSC    _DoExitAltKeymap    ; are we waiting for release ?
    GOTO    KB_12_OUT        ; yes

    ; the key has been released once test for second press
    BTFSC   kbColumn12_Old,2    ; is it still pressed ?
    GOTO    KB_12_ALT2        ; yep

    BTFSS    _DoExitAltKeymap        ; are we now waiting for the last ( second ) release ?
    RETURN                ; nope

KB_12_OUT
    BTFSS    kbColumn12_Old,2    ; check if key still pressed ?
    BSF    _ExitAltKeymap        ; nope, then enable exit check that
                    ; will exit alt keymap as soon as all key are released
    
KB_12_ALT2
    BSF    _DoExitAltKeymap    ; check for second release
    RETURN
KB_12_ALT
    ; first release of the enter alt keymap key
    ; reset 'get out' timer and set bit variables to enable check
    ; for second press/release

    MOVLW    H'0F'        ; x0.5 sec = 7.5 sec
    MOVWF    LastKeyTime    ; (re)set lastkey timer ( used to get out of altkeymap automaticly)    

    BSF    _InAltKeymap    ; yep the first time, then set flag that we are now
                ; waiting for a second press/release to exit alt key map
                ; all keys are released before exiting altkeymap

    ;***** Example snippet(one line) to be paired with code in CHECK_KEY_STATE where I
    ; forced numlock status to be off while enetering the alt keymap
    ; but have not yet released the alt keymap toggle key.
    ; this code will be called at the first release of this key. Used
    ; to restore numlock status.
    ; As said before, do not use if implifications are not known !

    ;BSF    _NumLock    ; and also force numlock to be 'on'
                ; as it is set to 'off' when we enter altkeymap
                ; we must set it 'back'
    
    RETURN    
        
KB_DEBOUNCE_34:    
    ; debounce columns 3 & 4
    DEBOUNCE_BYTE    kbColumnVal,kbColumn34_New,kbColumn34Cnt,kbColumn34State

    MOVF    kbColumn34_New,W    ; get debounced sample
    XORWF    kbColumn34_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn34_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_34    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_34_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,3    ; set bit 3 for table read ( column 3 & 4 )

    ;BCF    _isBreak    ; clear break flag
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_34_NEXT

KB_LOOP_34_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_34_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_34_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_34    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_34

KB_34_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn34_New,W ; get new status
    MOVWF    kbColumn34_Old    ; and store it..
    RETURN    


KB_DEBOUNCE_56:    
    ; debounce columns 5 & 6
    DEBOUNCE_BYTE    kbColumnVal,kbColumn56_New,kbColumn56Cnt,kbColumn56State

    MOVF    kbColumn56_New,W    ; get debounced sample
    XORWF    kbColumn56_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only that 'a change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn56_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_56    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_56_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,4    ; set bit 4 for table read ( column 5 & 6 )
    
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_56_NEXT

KB_LOOP_56_SKIP
    RLF    kbTemp,F    ; rotate so we read next key

KB_LOOP_56_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_56_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_56    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_56

KB_56_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn56_New,W ; get new status
    MOVWF    kbColumn56_Old    ; and store it..
    RETURN    

KB_DEBOUNCE_78:    
    ; debounce columns 7 & 8
    DEBOUNCE_BYTE    kbColumnVal,kbColumn78_New,kbColumn78Cnt,kbColumn78State
    
    MOVF    kbColumn78_New,W    ; get debounced sample
    XORWF    kbColumn78_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits. ( 7-0 )
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn78_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_78    
    CLRF    Offset        ; clear offset counter ( for table read )
    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_78_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,4    ; set bit 3,4 for table read ( column 7 & 8 )
    BSF    Offset,3    ;
    
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released


    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars

    GOTO    KB_LOOP_78_NEXT
    
KB_LOOP_78_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_78_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_78_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_78    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_78

KB_78_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn78_New,W ; get new status
    MOVWF    kbColumn78_Old    ; and store it..
    RETURN    

    
; ***********************************************************************
;
;  LOOKUP_KEY -  lookup table for key scancodes.
;         Returns a scancode in w
;         Sets carry if key is extended
;         NOTE: If key R3 C1 has been pressed longer than keyrepeat delay
;         AND keyrepeat is disabled ( jumper on pin RB3 ) THEN the
;          bit _AltKeymap is set and we can return an alternative scancode in W
;        

LOOKUP_KEY    ; lookup table for the keys ( 32 keys x 4 lines + 1 = 129 lines )
        ; keys are labelled Rx - Cy where x = row number and y = column number
        ; handles a 4 row x 8 column keyboard = 32 keys
        
    MOVWF    PCL      ; add to program counter          
; R1 - C1 i.e. key 1
    NOP
    NOP
    NOP    
    RETLW    H'05'    ; scan code 'F1'
; R2 - C1 i.e. key 2
    NOP
    NOP
    NOP    
    RETLW    H'0C'    ; scan code 'F4'
; R3 - C1 i.e. key 3
    ; The famous alternative keymap toggle key !!!  😉
    ; It is adviced that this key does not use an alt scancode
    ; makes things cleaner and simplified.
    ; IF USED though, remember that a 'soft' release code must be sent
    ; for this key when entering the altkeymap !( + 'soft' make code for the alternative key )
    ; This as the key is pressed when entering altkeymap
    ; which makes the bit _Altkeymap be set, and hence when released
    ; the release code for this 'normal' key will never be sent
    ; instead the release code for the alternative key will be sent.
    ; To 'fix' this put the release code at the end of CHECK_KEY_STATE ( where the
    ; alt keymap bit is set ).i.e. break prefix+scancode for normal key.
    NOP
    NOP
    NOP
    RETLW    H'83'        ; scan code for F7 ( in 'normal' mode )
; R4 - C1 i.e. key 4
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'76'        ; send scancode for 'ESC'  instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'6B'        ; scan code 'arrow left' '<-'
; R1 - C2 i.e. key 5
    NOP
    NOP
    NOP    
    RETLW    H'06'    ;  scan code 'F2' hex06
; R2 - C2 i.e. key 6
    NOP
    NOP
    NOP    
    RETLW    H'03'    ; scan code 'F5'
; R3 - C2 i.e. key 7
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'0D'        ; send scancode for 'horizontaltab' HT instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'75'        ; scan code 'arrow up' '^'
; R4 - C2 i.e. key 8
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'14'        ; send scancode for 'left ctrl' instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'72'        ; scan code 'arrow down'
; R1 - C3 i.e. key 9
    NOP    
    NOP    
    NOP    
    RETLW    H'04'    ; scan code 'F3'
; R2 - C3 i.e. key 10
    NOP    
    NOP    
    NOP    
    RETLW    H'0B'    ; scan code 'F6'
; R3 - C3 i.e. key 11
    NOP    
    NOP    
    NOP    
    RETLW    H'0A'    ; scan code 'F8'
; R4 - C3 i.e. key 12
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'11'        ; send scancode for 'left alt' instead
    BSF    STATUS,C ; set carry ( i.e. extended code )
    RETLW    H'74'    ; scan code 'arrow right' '->'
; R1 - C4 i.e. key 13
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'6C'        ; send scancode for numeric '7' instead
    NOP    
    RETLW    H'3D'    ; scan code '7'
; R2 - C4 i.e. key 14
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'6B'        ; send scancode for numeric '4' instead
    NOP    
    RETLW    H'25'    ; scan code '4'
; R3 - C4 i.e. key 15
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'69'        ; send scancode for numeric '1' instead
    NOP    
    RETLW    H'16'    ; scan code '1'
; R4 - C4 i.e. key 16
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7B'        ; send scancode for numeric '-' instead
    NOP    
    RETLW    H'4A'    ; scan code '-' minus ( swe kbd )
; R1 - C5 i.e. key 17
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'75'        ; send scancode for numeric '8' instead
    NOP    
    RETLW    H'3E'    ; scan code '8'
; R2 - C5 i.e. key 18
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'73'        ; send scancode for numeric '5' instead
    NOP    
    RETLW    H'2E'    ; scan code '5'
; R3 - C5 i.e. key 19
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'72'        ; send scancode for numeric '2' instead
    NOP    
    RETLW    H'1E'    ; scan code '2'
; R4 - C5 i.e. key 20
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'45'        ; scan code '0' ( from keypad ) normal key
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'1F'        ; alt keycode ( windows start menu activate )
; R1 - C6 i.e. key 21
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7D'        ; send scancode for numeric '9' instead
    NOP    
    RETLW    H'46'    ; scan code '9'
; R2 - C6 i.e. key 22
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'74'        ; send scancode for numeric '6' instead
    NOP    
    RETLW    H'36'    ; scan code '6'
; R3 - C6 i.e. key 23
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7A'        ; send scancode for numeric '3' instead
    NOP    
    RETLW    H'26'    ; scan code '3'
; R4 - C6 i.e. key 24
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'49'        ; scan code '.' ( swe kbd ) normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'4A'    ; send scancode for numeric '/' instead
; R1 - C7 i.e. key 25
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'79'        ; send scancode for numeric '+' instead
    NOP    
    RETLW    H'4E'    ; scan code '+'
; R2 - C7 i.e. key 26
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'66'        ; scan code 'back space' BS, normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'71'    ; send scancode for 'delete' instead
; R3 - C7 i.e. key 27
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'5A'        ; scan code 'enter', normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'5A'    ; send scancode for numeric enter instead ( note ! extended )
; R4 - C7 i.e. key 28
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'5A'        ; scan code 'enter', normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'5A'    ; send scancode for numeric enter instead ( note ! extended )
; R1 - C8 i.e. key 29
    NOP    
    NOP    
    NOP    
    RETLW    H'2C'    ; scan code 't'
; R2 - C8 i.e. key 30
    NOP    
    NOP    
    NOP    
    RETLW    H'24'    ; scan code 'e'
; R3 - C8 i.e. key 31
    NOP    
    NOP    
    NOP    
    RETLW    H'1B'    ; scan code 's'
; R4 - C8 i.e. key 32
    NOP    
    NOP    
    NOP    
    RETLW    H'2C'    ; scan code 't'

    END

Será que algum programador pode me ajudar? minha duvida é a seguinte ...eu encontrei um projeto na internet ae eu copiei o codigo mas quando eu coloco o codigo no mplab eu não consigo fazer o make ele da erro!! build falhou!! será que alguem pode me ajudar?? será que esse codigo ta errado!! se algum programador poder me ajudar eu agradeço!!!

 

;
;               PC-Keyboard emulator using a PIC16F84
;     Scan a 32 key keyboard ( with alternative mapping = 64 keys )
;            

;                 COPYRIGHT (c)1999 BY Tony K&uuml;bek
; This is kindly donated to the PIC community. It may be used freely,
;     and you are forbidden to deprive others from those rights.   
;    Please read the FSF (Free Software Foundation) GNU statement.
;
;*********************************************************************
;
;            E-MAIL    tony.kubek@flintab.se
;        
;        
; DATE            2000-01-23
; ITERATION        1.0B
; FILE SAVED AS        PiCBoard.ASM    
; FOR            PIC16F84-10/P        
; CLOCK            10.00 MHz RESONATOR ( OSC-HS )
; INSTRUCTION CLOCK    2.50 MHz T= 0.4 us
; SETTINGS        WDT-ON, PWT-ON, CP-OFF
; REVISION HISTORY    
;            0.1b -    First beta, mainly for testing
;            1.0b -     First public beta release
;
;
;
;
;***************************************************************************
;
;                PREFACE 😉
;
;    This is NOT an tutorial on pc keyboards in general, there are quite
;    a few sites/etc that have that already covered. However i DID find
;    some minor ambiguities regarding the actual protocol used but nothing
;    that warrants me to rewrite an complete pc keyboard FAQ.
;    So for 'general' pc keyboard info ( scancodes/protocol/pin config/etc )
;    here are some useful links:
;
;   http://www.senet.com.au/~cpeacock/  
;   http://ourworld.compuserve.com/homepages/steve_lawther/keybinfo.htm
;   http://204.210.50.240/techref/default.asp?url=io/keyboard.htm
;   http://www.arne.si/~mauricio/PIC.HTM
;    
;    PLEASE do not complain about code implementation, I know there are parts
;    in which is it 'a bit' messy and hard to follow, and other parts where
;    the code is not optimised. Take it as is. Futhermore I did hesitate
;    to include all of the functionality thats currently in, but decided to
;    keep most of it, this as the major complaint i had with the other available
;    pc-keyboard code was just that - 'is was incomplete'. Also do not
;    be discoraged by the size and complexity of it if you are an beginner,
;    as a matter of fact this is only my SECOND program ever using a pic.
;    I think I managed to give credit were credit was due ( 'borrowed code' ).
;    But the originators of these snippets has nothing to do with this project
;    and are probably totally unaware of this, so please do not contact them
;    if you have problems with 'my' implementation.
;
;    BTW ( shameless plug ) UltraEdit rules ! ( if you dont have it, get it ! )
;   http://www.ultraedit.com ( dont 'forget' the 'wordfile' for PIC, color indenting )
;    Without that I guess this file would be 'messy'.
;
;    Ok with that out of the way here we go:
;    
;
;***************************************************************************

;                DESCRIPTION ( short version 😉 )

;    A set of routines which forms a PC keyboard emulator.
;    Routines included are:
;    Interrupt controlled clock ( used for kb comm. and 'heart beat )
;    A 4x8 matrix keyboard scanner ( using an 74HCT4051 3 to 8 analogue multiplexer )
;    Communincation with PC keyboard controller, both send end recive.

;     PC keyboard routines are using 4 pins (2 inputs & 2 outputs) to control
;     keyboard's 2 bidirectional OC lines (CLK & DATA). The following
;     'drawing' conceptually shows how to connect the related pins/lines
;
;    ( ASCII art/info shamelessly 'borrowed' from http://www.arne.si/~mauricio/PIC.HTM )
;
;                 vcc    vcc
;                  |      |
;                  \     -+-
;                  / 2K2    /_\  1N4148
;                  \      |
; pcCLOCK_in -----------------o---o------o---------   kbd CLOCK
;                  |   |     |
;             2N2222   |50pF -+-
; pcCLOCK_out ____/\/\/\____|/     ===    /_\
;            2K2     |\>   |     |
;                  |   |     |
;                 /// ///    ///
;
;     An identical circuit is used for the DATA line.
;    Note: The 2N2222 transitors can be replaced with BC337 ( NPN ) which are cheaper
;    The keyboard matrix routines are using RB4-RB7 as inputs.
;    and RB0-RB2 as output to/from an 3 to 8 multiplexer
;    so that it can read up to 4x8 keys ( 32 ).
;
;    RA4/TOCK1 is an input from an jumper, if low then keyrepeat is disabled and
;    alt-key is enabled instead i.e. instead of repeating a key that has
;    been depressed a certain amount of time, a bit is set that can change the
;    scancode for a key ( of course, all keys can have an alternate scancode ).
;    To exit the alt. keymap press the 'enter alt. keymap' key once again or wait until
;    timeout. ( defined in the code )
;    NOTE !! The so called 'enter alt. keymap' key is hardcoded, i.e the key
;    i've choosen in this 'example' is Column 1 Row 2, if this is to be changed
;    code has to be changed/moved in the debounce and checkkeystate routines.
;    ( marked with <-------Alt keymap code-------> )
;     RB3 is currently used for a flashing diode. ( running )
;    Note my real keyboard ( KeyTronic ) uses a clock rate of 40 us, my implementation
;    uses about 50 us, easily to change.
;

;***************************************************************************

;            DESCRIPTION ( longer version 😉 )
;
;    Pin     Used for
;    ------------------------------------------------------------
;    RA0    Pc keyboard data out ( to pc )
;    RA1    Pc keyboard data in ( from pc )
;    RA2    Pc keyboard clock in
;    RA3    Pc keyboard clock out
;    RA4    Flashing disco led 0.5s duty cycle( could be used for some more meaningful purpose )
;
;    RB0    Least significant bit in column adress to 4051 multiplexer ( own keyboard )
;    RB1    Middle...
;    RB2    Most significant bit -- || --
;    RB3    Input. Disable keyrepeat & enable alternative keymapping ( if = '0',ground)
;        OR if '1' ( free,+V ) enable keyrepeat & disable alternative keymapping
;    RB4    Own keyboard input row 1
;    RB5    --- || --- row 2
;    RB6    --- || --- row 3
;    RB7    --- || --- row 4
;
;    'Basic program structure':
;
;    Init    -    Initialise ports , ram, int, vars
;    Start delay -     After init the timer int is enabled and the flashing led will
;            start to toggle ( flash ). Before I enter the mainloop
;            ( and send any keycodes ) I wait until the led has flashed
;            twice.    This is of course not really needed but I normally
;            like to have some kind of start delay ( I know 1 sec is a bit much 🙂 )
;    Time Int    -    The timer interrupt (BIG), runs in the background:
;            - 'Normal' rate 0.5 ms, when in rx/tx rate is 27 us
;             - Performs an rx/tx check, then jumps to rx, tx or 'heart beat' code.
;             - TX code sends a byte to pc, at a rate of 27us per int.
;               The int rate is actually double the bit rate, as
;               a bit is shifted out in the middle of the clock pulse,
;               I've seen different implementations of this and I think
;               that the bit is not sampled until clock goes low again BUT
;               when logging my keyboard ( Keytronic ) this is the way that it
;                  does it. When all bits are sent, stopbit/parity is sent.
;               And the key is removed from the buffer.
;               After stopbit/parity is sent, a delay is inserted ( 0.5 ms )
;               before next rx/tx check.
;             - RX code recevies a byte from the pc, PIC is controlling clock !!
;               Int rate ( 27 us ) is, again, double bit rate.
;               Toggles clock and samples the data pin to read a byte from pc.
;               When reception is finished an 'handshake' takes place.
;               When a byte has been recevied a routine is called to check
;               which command and/or data was received. If it was
;               keyboard rate/delay or led status byte, it is stored in local ram
;               variables. NOTE: The rate/delay is not actually used for
;               key repeat as the code is right now ( I use my 'own' fixed rate/delay )
;               however it is very easy to implement.
;               After handshake a delay is inserted ( 0.5 ms )
;               before next rx/tx check.
;            - 'Heart beat' ( idle code 0.5 ms tick ) performs:
;             - Check clock/data lines to see if pc wants to send something or
;               if tx is allowed.
;             - If tx is possible it checks the keybuffer for an available key and
;               if keys are in buffer then it initiates a tx seq.
;               and sets the int rate to 27 us.
;             - If the pc wants to send something, an rx seq. is initiated
;               ( there is some handshaking involved, during which
;               the int rate is set to 60 us ) after that, the int rate is
;               set to 27 us and an rx seq is started.
;             - Divides some clock counters to achive 10ms,100ms,500ms sections.
;             - In 100 ms section it performes a numlock status check and
;               keyrepeat check ( both rate and delay is local in 100 ms ticks,
;               thats why I dont use the 'real' rate delay )
;              - If numlock status is not the desired one code is called to
;                 toggle the numlock status.
;              - If a key has been pressed long enough for repeat, an bit is set
;                so we can repeat the key ( send the scancode again ) in the main loop.
;            - In 500 ms section the led is toggled on each loop
;              - Some various alternative keymap checks to get out of
;                alternative keymap. ( i'll get to that in a bit )
;
;    Main loop    - Outputs an adress to the 4051 multiplexer waits 1 ms
;              reads the row inputs ( 4 bits/keys ), increments address
;              and outputs the new adress, waits 1 ms and reads the input
;               ( next 4 bits/keys ). Now using the address counter, calls a
;              debounce/send routine that first debounces the input,
;              ( four consecutive readings before current state is affected )
;              and when a key is changed a make/break code is sent ( put in buffer ).
;              In the next loop the next two columns are read etc. until all
;              4 column pairs are read.
;            - If keyrepeat is enabled ( see pin conf. above ) the
;              repeat flag is checked and if '1' the last pressed key scancode
;              is sent again ( put in buffer ).
;            - If keyrepeat is not enabled( alternative keymap is enabled instead )
;              then various checks to exit the alternative keymap are performed instead.
;
;    Scancodes for all key are located in a lookup table at the end of this file,
;    each key has four program rows, to make room for extended codes and alt. keymap codes.
;
;     Explanation of 'alternative' keymap:
;    
;    Using this program ( or an heavily modified version of it anyway 🙂 )
;     on a computer running Windows posed some small problems; namely:
;    -The keyboard ( mapping ) I used did not have any 'special' key such as
;     <alt>,<ctrl>,<tab> etc.
;    -In windows, things can go wrong, 🙂 if a dialog pops up or something similar
;     there were just no way one could dispose of this with the keymapping i used.
;    - 'Only' 28 keys were implemented ( hardware wise ).
;    In this particular case the keyrepeat was disabled ( due to the nature of the application )
;    Therefore i came up with the solution to use the keyrepeat related routines and vars.
;    To handle a so called 'alternative' keymapping.
;    This means that an key is dedicated to be the alt. keymap toggle key,
;    when pressing this longer than the programmed repeat delay, instead of
;    repeating the key a bit variable is set to use another set of scancodes for
;    the keyboard. This 'alternative' keymap is then enabled even though the
;    alt. keymap toggle key is released, but it also incorporates an timeout
;    that will return to normal keymap if no key is pressed within a certain time ( currently 7 sec )
;    Of course pressing the alt. keymap toggle key again ( while in alt. keymap )
;    will return the keyboard to the normal keymap.
;    NOTE !! the key choosen for the alt. keymap toggle is hardcoded, so if the scancode
;    for this is changed in the lookup table, changes have to be made in other routines
;    as well. ( namly : CHECK_KEY_STATE, and DEBOUNCE_COLUMN routines )
;    While in alt. keymap each key CAN have an alternative scancode ( see lookup table ).
;    Also by using the numlock bit, one can toggle numlock status 'on the fly' when entering
;    or exiting the alt keymap.
;
;    Some notes about the local keyboard interface ( matrix 😞
;    Although the hardware circuit and software allows virtually unlimited
;    simultaneosly pressed keys, the keyboard matrix itself normally poses
;    some limitations on this. If an keymatrix without any protective diodes
;    are used then one would have loops INSIDE the keymatrix itself when
;    multiple keys are pressed in different columns .
;    Look at the ( although horrible 😉 ) ASCII art below(internal weak pullup enabled):
;    0 - Key is free
;    1 - Key is pressed ( connection between hor/ver rows )
;    Three keys pressed adressing ( reading ) left column :
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    --------1-------1------ ( row 2 )
;                    |       |
;    To pic3    --------1-------0------ ( row 3 )
;                   |       |
;        Column(4051)    0V    -    ( Current column is set to 0V when adressing )
;
;    This works as intended, we can read a '0' on pic inputs 2,3 which
;    is what we expected. The current ( signal ) follows the route marked with '*':
;    ( only the 'signal path' is shown for clarity )
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    *********-------1------ ( row 2 )
;                    *       |
;    To pic3    *********-------0------ ( row 3 )
;                   *       |
;        Column(4051)    0V    -    ( Current column is set to 0V when adressing )
;
;    However, when we now read ( address ) the right column instead we
;    do not read what is expected ( same three keys still pressed 😞
;
;    To pic1    --------0-------0------ ( row 1 )
;                |       |
;    To pic2    *****************------ ( row 2 )
;                    *<-     *
;    To pic3    *********-------*------ ( row 3 )
;                   |       *
;        Column(4051)    -    0V    ( Current read column is set to 0V when adressing )
;
;    As you can see the 'signal' travels in the keymatrix itself ( where the '<-' is )and
;    will cause a 'ghost' signal to be read on the pic. So instead
;    of having an '0' on input 2 only we also can read an '0' on input 3.
;    This is because the two keys in column 1 are interconnected ( when they are pressed ).
;    Keep this in mind if you are planning to support multiple pressed keys.
;    
;
;***************************************************************************
;
;    Some suggestions for 'improvements' or alternations
;
;    - Using the jumper 'disable-repeat' as a dedicated key for switching
;      to alternative keymapping.
;    - Enable repeat in alternative keymapping
;    - Clean up TX/RX code ( a bit messy )
;    - Using the led output ( or jumper input ) as an extra adress line
;      to the multiplexers ( in this case 2 pcs. 74HCT4051 ) we could have
;      4x16 keys instead. Would require some heavy modifications though
;      as there are not much ram/program space left. But if alternative
;        keymapping is discarded ( most likely if one has 64 keys ) each
;      key in the lookup table only needs to be 2 lines instead of 4.
;      That would 'only' require some modifications to preserv ram.
;    - Using the EERAM for 'macros' or similar ( not used at all now )
;
;***************************************************************************
;
;            LEDGEND
;
;    I tend to use the following when naming vars. etc. :
;    ( yes i DO like long names )
;    
;    For 'general' purpose pins:
;
;    An input pin is named I_xxx_Name where :
;
;        I_   - This is an input pin 😉
;        xxx_ - Optional what type of input, jmp=jumper etc.
;        Name - Self explanatory
;
;    An output pin is named O_xxx_Name where:
;    
;        O_   - This is an output pin 😉
;        xxx_ - Optional what type of output, led=LED etc.
;        Name - Self explanatory
;
;    Application(function) specific pins:
;
;    An application(function) specific pin is named xxName where:
;        
;        xx   - What/Where, for example pc=To/From pc
;        Name - Self explanatory ( what does it control etc )
;
;    An #define/constant/equ (no pin or ram ) uses all capital letters e.x. #define BREAK 0xF0
;
;    A bit variable will always start with '_'. For example '_IsLedStatus'
;
;    All other ( mostly ramvars. ) are named in various ways.
;
;
;***************************************************************************

    TITLE "PC Keyboard emulator - By Tony K&uuml;bek"

        Processor       16F628a
        Radix   DEC
        EXPAND

    

;***** HARDWARE DEFINITIONS ( processor type include file )

    INCLUDE <C:\PROGRAM\MPLAB\P16F84.INC>           ; this might need changing !

;***** CONFIGURATION BITS

    __CONFIG _WDT_ON&_HS_OSC&_CP_OFF&_PWRTE_ON        ; _WDT_OFF
    
    __IDLOCS 010Bh    ; version 1.0B

;***** CONSTANT DEFINITIONS

    CONSTANT    BREAK = 0xF0        ; the break key postfix ( when key is released )
    CONSTANT    EXTENDED = 0xE0     ; the extended key postfix

    ; As i dont really use the rate/delay I receive from the pc ( easy to change )
    ; this is the current rate/delay times i use:
    CONSTANT    DELAY_ENTER_ALTKEYMAP = 0x1E    ; x100 ms , approx 3 seconds ( 30 x 100 ms )
                            ; how long the 'enter altkeymap' key must
                            ; be in pressed state before the altkeymap is enabled
    CONSTANT    DELAY_EXIT_ALTKEYMAP = 0x0F    ; x0.5 sec , approx 7.5 sec
                            ; how long before we exit the alt keymap if no key is
                            ; pressed.
    CONSTANT     DELAY_REPEAT    = 0x08        ; x100 ms, approx 800 ms
                            ; how long before we START repeating a key
    CONSTANT    DELAY_RATE    = 0x02        ; x100 ms, approx 200 ms repeat rate
                            ; how fast we are repeating a key ( after the delay above )
    
;***** CONSTANT DEFINITIONS ( pins )

;    For connection with PC keyboard

#define pcCLOCK_in  PORTA,2   ; Input PC keyboard clock
#define pcCLOCK_out PORTA,3   ; Output PC keyboard clock
#define pcDATA_in   PORTA,1   ; Input PC keyboard data
#define pcDATA_out  PORTA,0   ; Output PC Keyboard data


;    For connection (input) with our own keyboard
; Note I actually dont use these (definitions!) in the program, but they might come in handy
; at one time or another 😉 ( I use the pins though.. )

#define kbROW_1   PORTB,7
#define kbROW_2   PORTB,6
#define kbROW_3   PORTB,5
#define kbROW_4   PORTB,4

;    Indications ( output )

#define O_led_KEYCOMM_ok    PORTA,4        ; communication seems ok led ( flashing )

;    Disable/enable key repeat input jumper

#define I_jmp_NoRepeat    PORTB,3    ; note: internal weak pullup enabled

;    For keybuffer ( using the indirect file selector FSR )

#define KeyBufferHead    FSR


;***** RAM ASSIGNMENT
      
;        RAM 0x0c
        CBLOCK    0x0C
            KeyBufferTail    ; where the last byte in buffer is..
            clkCount         ; used for clock timing
            Offset        ; used for table reads        
            Saved_Pclath       ; Saved registers during interrrupt
            Saved_Status       ; -----
            Saved_w            ; -----
            CurrKey        ; current key ( rx or tx )..
            KeyParity    ; key parity storage ( inc. for every '1' )
            Divisor_10ms    ; for the timer
            Divisor_100ms   ; ditto
            Divisor_500ms   ;
            Divisor_Repeat  ; timer for repeated key sends

            Flags        ; various flags
            RepeatFlags     ; flags for repeating a key
            bitCount    ; bitcounter for tx/rx
            Comm_Flags    ; flags used by both rx and tx routines
            Temp_Var    ; temp storage, can be used outside int loop
            TRX_Flags    ; flags used by both rx and tx routines
            CommandData     ; bit map when receving data bytes from pc
                    ; for example led status/ delay / etc
            KbLedStatus     ; to store status led bitmap for keyboard ( pc first sends 'ED' then this byte )
                        ;  bit 0=Scroll lock  ( 1=on )
                        ;  bit 1=Num lock
                        ;  bit 2=Caps lock
                        ;  bits 3-7 = unused
            KbRateDelay    ; to store repeat delay/rate ( pc first sends 'F3' then this byte )
                        ;  bit 0-4 (rate) = '00000' is 30x/sec '11111' is 2x/sec ( i.e. value * 33 ms )
                        ;  bit 5-7 (delay)= '00' is 250 ms, '11' is 1000 ms ( ie. value * 250 ms )   
                    ;  bit 7 = unused
            BufTemp        ; temp byte for storing scancode to put in buffer
            Temp        ; temp byte, used locally in buffer routine
            Temp2        ;
            LastKey        ; stores the last sent key
            KbBufferMin    ; where our keybuffer starts
            Kb1        ; used in keybuffer
            Kb2        ; used in keybuffer
            Kb3        ; used in keybuffer
            Kb4        ; used in keybuffer
            Kb5        ; used in keybuffer
            Kb6        ; used in keybuffer
            Kb7        ; used in keybuffer
            Kb8        ; used in keybuffer
            Kb9        ; used in keybuffer
            Kb10        ; used in keybuffer
            KbBufferMax    ; end of keybuffer
            TempOffset    ; temporary storage for key offset ( make/break )

            LastMakeOffset  ; storage of last pressed key ( offset in table )
            RepeatTimer    ; timer to determine how long a key has been pressed
            RepeatKey     ; the key to repeat
            repTemp        ; temporary storage in repeat key calc.
            repKeyMap    ; bit pattern for the column in which the repeat key is in
                    ; i.e. a copy of kbColumnXX_Old where 'XX' is the column
            LastKeyTime    ; counter when last key was pressed, used to get out of altkeymap
                    ; after a specific 'timeout'

            kbScan        ; scan code for pressed/released key
            kbTemp        ; temp storage for key states

            kbState        ; which keys that has changed in current columns
            kbBitCnt    ; bit counter for key check ( which key/bit )            
            
            kbColumnCnt    ; column counter ( loops from 8 to 0 )
                    ; Used as output to multiplexer/decoder and in debounce routines

            kbColumnVal    ; current value of the last 2 columns ( 2x4bits = 8 bits ) input pins ( keys )
                    ;
                    ; Note the kbColumnXX_New variables is not really needed
                    ; used it while making the program ( debugging 😉 ).
                    ; so if more free ram is needed change code using these to use
                    ; the current 'input' sample instead. ( kbColumnVal )
            kbColumn12_New  ; New debounced reading for column 1 & 2
            kbColumn12_Old  ; Latest known valid status of column 1 & 2
            kbColumn12Cnt    ; Debounce counter for column 1 & 2
            kbColumn12State ; State of debounce for column 1 & 2

            kbColumn34_New  ; New debounced reading for column 3 & 4
            kbColumn34_Old  ; Latest known valid status of column 3 & 4
            kbColumn34Cnt    ; Debounce counter for column 3 & 4
            kbColumn34State ; State of debounce for column 3 & 4

            kbColumn56_New  ; New debounced reading for column 5 & 6
            kbColumn56_Old  ; Latest known valid status of column 5 & 6
            kbColumn56Cnt    ; Debounce counter for column 5 & 6
            kbColumn56State ; State of debounce for column 5 & 6

            kbColumn78_New  ; New debounced reading for column 7 & 8
            kbColumn78_Old  ; Latest known valid status of column 7 & 8
            kbColumn78Cnt    ; Debounce counter for column 7 & 8
            kbColumn78State ; State of debounce for column 7 & 8

        ENDC

;******* END RAM

; *** flags used by both rx and tx routines
#define _isParity    Comm_Flags,0    ; bit in rx/tx is parity bit
#define _KeyError    Comm_Flags,1    ; set to '1' when an error is detected
#define _isStartBit    Comm_Flags,2    ; set to '1' when bit in rx/tx is startbit
#define _isStopBit    Comm_Flags,3    ; --- || --- but stopbit
#define _RxCanStart     Comm_Flags,4    ; for handshaking, when negotiating an rx seq.


; *** dito used for rx and tx
#define _KeyReceived    TRX_Flags,0    ; rx
#define _RX_Mode    TRX_Flags,1    ; rx is in progress ( started )
#define _doRXAck    TRX_Flags,2    ; do rx handshake
#define _RXAckDone    TRX_Flags,3    ; rx handshake is done
#define _RXEnd        TRX_Flags,4    ; rx seq is finished
#define _RXDone        TRX_Flags,5    ; rx exit bit
#define _KeySent    TRX_Flags,6    ; tx key has been succesfully sent
#define _TX_Mode    TRX_Flags,7    ; tx is in progress ( started )


; *** flags to determine the meaning of an incomming data is
; i.e. when then pc has sent a 'data follow' instruction
; these bits are set according to the command,
; i.e. the next incomming byte is.....
#define _IsLedStatus    CommandData,0    ; the next incoming byte contains kb led status
#define _IsRateDelay    CommandData,1    ; the next incoming byte contains kb rate/delay
#define _SkipByte    CommandData,2    ; other data not saved
 
; *** bit meaning in KbLedStatus ( the leds on the keyboard )
; i.e local ram copy of this byte
#define _LedScrollLock    KbLedStatus,0    ; '1' led is on
#define _LedNumLock     KbLedStatus,1    ;
#define _LedCapsLock    KbLedStatus,2    ;
#define _IsFirstLedStatus KbLedStatus,7    ; set this to '1' at startup, to know that our local
;                    ; copy (led status) is not yet syncronised. Used to
;                    ; 'force' sync. the first time we set the local numlock bit.

; *** flags used for various purposes
#define _Timer        Flags,0 ; used for waiting
;#define _WrongPar    Flags,1    ; ?? ( used during dev. to force wrong parity sends, removed )
#define _LastColumn    Flags,2    ; for kb scan code
#define _isBreak    Flags,3 ; if '1' the currently handled key is break ( released ), else make ( pressed )
;#define _isFree         Flags,4 ; Not used ! ( temporary during dev. )
#define _AltKeymap      Flags,5 ; is we use alternative keymap ( other scancodes ) see below !
                ; enabled ONLY when keyrepeat is disabled ( I_jmp_NoRepeat (RB3) is low )
#define _ExitAltKeymap  Flags,6 ; start checking if we should exit alternative keymap
                ; if all keys are 'free' ( not pressed ) we exit altkeymap
#define _InAltKeymap    Flags,7 ; if we are waiting for second press/release to get out of altkeymap


#define _doRepeat    RepeatFlags,0 ; if the last pressed key should be 'repeated'
#define _doSendKey    RepeatFlags,1 ; send the key in RepeatKey to pc     
#define _isExtended     RepeatFlags,2 ; temp flag if last key is extended
#define _RepeatIsExt    RepeatFlags,3 ; the key to repeat is extended key
#define _startRepeat    RepeatFlags,4 ; start repeat timer
#define _DoExitAltKeymap RepeatFlags,5     ; bit set when we are getting out of alternative keymap
#define _NumLock    RepeatFlags,6      ; 'mirror' of numlockstatus, by setting/clearing this bit
                           ; numlock status will be changed.
                    ; I.e. there is no need to 'manually' send break/make code for numlock
                    ; key, by setting this bit to '1' numlock status will by automaticlly
                    ; ( inside int handler ) set to 'on'( within 100 ms ). Se code for example.
#define _WaitNumLock    RepeatFlags,7    ; bit set when we have sent make/break numlock scancode
                    ; and waiting for numlock status byte reply.
                    ; ( to inhibit a new numlock scancode send )


;**************************************************************************    
;                Macros             
;**************************************************************************    
    

;+++++
;    BANK0/1 selects register bank 0/1.
;    Leave set to BANK0 normally.

BANK0    MACRO
    BCF    STATUS,RP0
    ENDM

BANK1    MACRO
    BSF    STATUS,RP0
    ENDM

;+++++
;    PUSH/PULL save and restore W, PCLATH and STATUS registers -
;    used on interrupt entry/exit

PUSH    MACRO
    MOVWF    Saved_w        ; Save W register on current bank
    SWAPF    STATUS,W    ; Swap status to be saved into W
    BANK0            ; Select BANK0
    MOVWF    Saved_Status    ; Save STATUS register on bank 0
    MOVFW    PCLATH
    MOVWF    Saved_Pclath    ; Save PCLATH on bank 0
    ENDM

PULL    MACRO
    BANK0            ; Select BANK0
    MOVFW    Saved_Pclath
    MOVWF    PCLATH        ; Restore PCLATH
    SWAPF    Saved_Status,W
    MOVWF    STATUS        ; Restore STATUS register - restores bank
    SWAPF    Saved_w,F
    SWAPF    Saved_w,W    ; Restore W register
    ENDM


;+++++        
;     We define a macro that will switch an output pin on or off depending
;     on its previous state. We must be on bank0 !!
;

TOGGLE_PIN    MACRO WHICH_PORT,WHICH_PIN
        LOCAL TOGGLE_PIN10, TOGGLE_END
        
        BTFSC    WHICH_PORT,WHICH_PIN    ; is the pin high ?
        GOTO     TOGGLE_PIN10        ; yes, clear it
        BSF    WHICH_PORT,WHICH_PIN    ; no, so set it                                        
            GOTO       TOGGLE_END
TOGGLE_PIN10:
        BCF    WHICH_PORT,WHICH_PIN    ; clear the pin            
TOGGLE_END:
        ENDM


;*************************************************************
; Credit for routine ( almost unchanged, expect it's now a macro 😉 )
; goes to Scott Dattalo http://www.interstice.com/~sdattalo/technical/software/software.html
; ( debouce routine at http://www.interstice.com/~sdattalo/technical/software/pic/debounce.html )
;
;     DEBOUNCE_BYTE - 'debounces' 8 bits, when a bit state
;    has been 'active' for 4 consecutive debounce loops
;    it's state is put into the 'debounced' sample ( i.e. output )
;
; The purpose of this routine is to debounce, i.e. digitally low pass filter
; inputs. The algorithm handles upto 8 bits at a time. An input is considered
; filtered if it has not changed states in the last 4 samples.
;
; 2-bit cyclic vertical counters count the 4 samples. As long as there is no
; change, the counters are held in the reset state of 00b. When a change is detected
; between the current sample and the filtered or debounced sample, the counters
; are incremented. The counting sequence is 00,01,10,11,00... When the counters
; roll over from 11b to 00b, the debounced state is updated. If the input changes
; back to the filtered state while the counters are counting, then the counters
; are re-initialized to the reset state and the filtered state is unaffected.
; In other words, a glitch or transient input has been filtered.
;
; Here's the C-psuedo code:
;
;static unsigned clock_A,clock_B,debounced_state
;
;debounce(unsigned new_sample)
;{
;  unsigned delta;
;
;  delta = new_sample ^ debounced_state;   //Find all of the changes;
;  clock_A ^= clock_B;                     //Increment the counters
;  clock_B  = ~clock_B;
;
;  clock_A &= delta;                       //Reset the counters if no changes
;  clock_B &= delta;                       //were detected.
;
;      //Preserve the state of those bits that are being filtered and simultaneously
;      //clear the states of those bits that are already filtered.
;  debounced_state &= (clock_A | clock_B);
;      //Re-write the bits that are already filtered.
;  debounced_state |= (~(clock_A | clock_B) & new_sample);
;}
;
; The 2-bit counters are arranged "vertically". In other words 8 counters
; are formed with 2 bytes such that the corresponding bits in the bytes are
; paired (e.g. MSBit of each byte is paired to form one counter).
; The counting sequence is 0,1,2,3,0,1,... And the state tables and Karnaugh
; maps are:
;
;State Table:     Karnaugh Maps:
;pres  next      B
; SS  SS         0   1
; AB  AB       +---+---+    +---+---+
;--------   A 0|   | 1 |    | 1 |   |
; 00  01       +---+---+    +---+---+
; 01  10      1| 1 |   |    | 1 |   |
; 10  11       +---+---+    +---+---+
; 11  00      A+ = A ^ B     B+ = ~B
;
; Here's the PIC code that implements the counter:
;       MOVF    SB,W    ;W = B
;       XORWF   SA,F    ;A+ = A ^ B
;       COMF    SB,F    ;B+ = ~B
;  14 instructions
;  15 cycles
; Inputs:
;   NewSample - The current sample
; Outputs
;   DebouncedSample - The current value (filtered version of NewSample)
;
; VARS used
;   DBCnt,
;   DBState - State variables for the 8 2-bit counters
;
    
DEBOUNCE_BYTE MACRO NewSample,DebouncedSample,DBCnt,DBState

        ;Increment the vertical counter        

    MOVF    DBState,W
        XORWF   DBCnt,F        
    COMF    DBState,F

        ;See if any changes occurred        

    MOVF    NewSample,W        
    XORWF   DebouncedSample,W

        ;Reset the counter if no change has occurred        

    ANDWF   DBState,F
        ANDWF   DBCnt,F        ;Determine the counter's state
        MOVF    DBState,W        
    IORWF   DBCnt,W

        ;Clear all bits that are filtered-or more accurately, save
        ;the state of those that are being filtered        

    ANDWF   DebouncedSample,F
        XORLW   0xff        ;Re-write the bits that haven't changed.
        ANDWF   NewSample,W        
    IORWF   DebouncedSample,F        
    
    ENDM

    

;**************************************************************************    
;                 Program Start
;**************************************************************************    


;    Reset Vector

    ORG    H'00'

    ; For the sole purpose of squeezing every last byte of the programming mem
    ; I actually use the 3 program positions before the interrupt vector
    ; before jumping to the main program. Take note though that
    ; ONLY 3 instructions are allowed before the jump to main loop !!

    BANK0

        CLRF    PCLATH
    CLRF    INTCON    

    GOTO    INIT

;**************************************************************************    
;                     Interrupt routine
; An humongously big int handler here 😉
; but the keyboard handling is FULLY in the background, without any intervention
; in the main loop whatsoever. I like the solution anyway.
;
;**************************************************************************


;    Interrupt vector

    ORG    H'04'

INT
    PUSH            ; Save registers and set to BANK 0
                
    
    BTFSS   INTCON,T0IF        ; check if TMR0 interrupt
        GOTO    INTX            ; whoops ! 'unknown' int, should be disabled...
                ; NOTE ! if an 'unknown' int triggers the int routine
                ; the program will loop here for ever 😉 ( as the calling flag is not cleared )

    

    ;+++
    ; Timer (TMR0) timeout either heart beat or tx/rx mode
    ; In 'heart beat mode' we monitor the clock and data lines
    ; at ( roughly )= 0.5 ms interval, we also check the send buffer
    ; if there are any keys to send to pc ( if clock/data levels allows us to )
    ; In tx/rx mode we are controlling the clock/data line = 27 us tick
    ; Note: This software works using both 8Mhz and 10Mhz resonators without modifications
    ; however the 'timing' will then of course be 'off'.

INT_CHECK    
    BCF    INTCON,T0IF     ; Clear the calling flag !
    
     BTFSC    _TX_Mode    ; check if we are in tx mode
    GOTO    INT_TX        ; yep, goto tx mode code..    
    BTFSC    _RX_Mode    ; are we in rx mode ?
    GOTO    INT_RX        ; yep goto rx mode code
    GOTO    INT_HEART_BEAT  ; nope just goto 'heart beat' mode

;*************** TX code start ***********************************

INT_TX
    MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
        MOVWF   TMR0
 
    BTFSS    clkCount,0    ; check if we should toggle clock
    GOTO    INT_DEC_CLOCK    ; bit low,decrement and check if we should toggle data instead
    
    DECFSZ    clkCount,F    ; decrement and check if we are at zero..
    GOTO    INT_CLOCK     ; not zero then toggle clock line
    
    GOTO    INT_EXIT_TX        

INT_CLOCK
    
    BTFSC    pcCLOCK_out    ; check if we are low
    GOTO    INT_CLOCK_HIGH  ; yep set to high
                
    BTFSS    pcCLOCK_in    ; check if pc is pulling the clock line low
                ; i.e. it wants to abort and send instead..
    GOTO    INT_TX_CHECK_ABORT    ; abort this transfer
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    GOTO    INTX

INT_CLOCK_HIGH
    BCF    pcCLOCK_out    ; set high ( release line )
                ;BCF    _ClockHigh    ;
    GOTO    INTX

INT_TX_CHECK_ABORT
    GOTO    INT_EXIT_TX

INT_DEC_CLOCK    
    DECF    clkCount,F    ; decrement clock counter ( so we toggle next time )
INT_DATA
    BTFSS    bitCount,0    ; check bit counter
    GOTO    INT_DATA_IDLE    ; no data toggle

    DECFSZ    bitCount,F    ; decrement bit counter
    GOTO    INT_DATA_NEXT    ; next bit..

INT_NO_BITS
    BSF    bitCount,0    ; just in case ( stupid code, not sure its needed, just
                ; to make it impossible to overdecrement )***
    BTFSC    _isParity    ; are we sending parity ?
    GOTO    INT_DATA_END    ; exit
    
    ; all bits sent
    ; delete the last key from the buffer
    CALL    INC_KEY_HEAD    ; remove the ( last ) key form the buffer as is was sent ok..

    ; all bits sent check parity
    
    BSF    _isParity    ; set flag data is parity
    
    BTFSS    KeyParity, 0    ; is the last parity bit high? ( odd num of bits )
                ; then parity should be high ( free )
    GOTO     INT_DATA_HIGH_PAR      ; yes
    BSF    pcDATA_out    ; no, parity should be 'low' ( pulled down )
    GOTO     INTX        ;

INT_DATA_HIGH_PAR:    
    BCF    pcDATA_out    ; set parity bit high ( release data line )
    GOTO    INTX        ; and exit..


INT_DATA_END    
    BTFSS    _isStopBit    ; is the stopbit sent ?
    GOTO    INT_DATA_STOPB    ; nope then set stopbit flag

    BCF    pcDATA_out    ; parity bit sent, always release data line ( stop bit )        
    GOTO    INTX

INT_DATA_STOPB
    BSF    _isStopBit    ; set the stopbit flag
    GOTO    INTX        ; and exit

INT_DATA_IDLE
    DECF    bitCount,F    ; decrement bit counter
    GOTO     INTX        ; no toggle of data line

INT_DATA_NEXT
    BTFSS     CurrKey,0    ; is the last bit of the key_buffer high?
    GOTO     INT_DATA_LOW    ; no, pull data low
    BCF      pcDATA_out    ; yes, release data line
    INCF    KeyParity,F    ; increment parity bit
    GOTO    INT_DATA_ROTATE    ; rotate data
    
INT_DATA_LOW    ; last bit is low
    BSF    pcDATA_out    ; set the bit

INT_DATA_ROTATE
    RRF    CurrKey,F    ; rotate right by 1 bit    
    GOTO    INTX

INT_EXIT_TX
    ; setup the timer so we accomplish an delay after an tx seq
    
    BCF    _TX_Mode    ; clear tx mode flag
    BCF    pcCLOCK_out    ; release clock line
    BCF    pcDATA_out    ; and data line
;
    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0

    GOTO    INTX


;************** TX code end *****************

;************** RX code start ***************

INT_RX
    MOVLW   H'FA'           ; preset timer with 252 ( 256 - 6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8us ( int handling code )= 27 us
        MOVWF   TMR0


    BTFSS    clkCount,0    ; check if we should toggle clock
    GOTO    INT_RX_DEC_CLOCK    ; bit low,decrement and check if we should read data instead
    
    DECFSZ    clkCount,F    ; decrement and check if we are at zero..
    GOTO    INT_RX_CLOCK     ; not zero then toggle clock line
    
    BCF    pcCLOCK_out    ; release the clock line we are done..
    BCF    _RX_Mode    ; clear rx mode bit ( go over to heart beat mode )
    GOTO    INT_EXIT_RX            

INT_RX_CLOCK

    BTFSC    pcCLOCK_out    ; check if we are low
    GOTO    INT_RX_CLOCK_HIGH     ; yep set to high ( release line )
    
    BTFSC    _isStartBit    ; check if this is the first bit ( start )
    GOTO    INT_RX_START    ; clear start bit and continue
    
    BTFSC    _isParity    ; check if this is the parity bit ( or parity has been received )
    GOTO    INT_RX_PAR    ; yep check parity

    GOTO    INT_RX_BIT    ; ok just a 'normal' bit read it
    
    
INT_RX_PAR            ; check parity
    BTFSC    _doRXAck    ; check the handshake flag
    GOTO    INT_RX_HNDSHK    ; start handshake check

    BTFSS    pcDATA_in    ; is the input high ?
    GOTO    INT_RX_PAR_HIGH    ; yep
    BTFSC    KeyParity,0    ; is the parity '0' ( should be )
    GOTO    INT_RX_PAR_ERR    ; nope parity error
    GOTO    INT_RX_ACK    ; parity ok next should be ack ( we take data line low )

INT_RX_PAR_HIGH
    BTFSS    KeyParity,0    ; check that parity bit is '1'
    GOTO    INT_RX_PAR_ERR    ; nope parity error
    GOTO    INT_RX_ACK    ; parity ok, next is ack ( we take data line low )

INT_RX_PAR_ERR
    BSF    _KeyError    ; set error flag

INT_RX_ACK
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    BSF    _doRXAck    ; enable ack check
    GOTO    INTX

INT_RX_HNDSHK
    BTFSS    _RXEnd        ; if we are done dont take data low
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    
    BTFSC    _RXAckDone    ; chek if hand shake ( ack ) is done ?
    BSF    _RXEnd        ; ok we are now done just make one more clock pulse

    GOTO    INTX        ; exit    
        

INT_RX_CLOCK_HIGH

    BCF    pcCLOCK_out    ; set high ( release line )
    BTFSS    _RXAckDone    ; are we done.. ?
    GOTO    INTX
    BTFSS    _RXDone        ; finished ?
    GOTO    INTX

    BCF    _RX_Mode    ; and clear rx flag..
    GOTO    INT_EXIT_RX    ; bye bye baby

INT_RX_DEC_CLOCK    

    DECF    clkCount,F    ; decrement clock counter ( so we toggle next time )
    BTFSS    _doRXAck    ; check if we are waiting for handshake
    GOTO    INTX
    

     BTFSC    pcCLOCK_out     ; check if the clock is low ( pulled down )
    GOTO    INTX        ; nope we are pulling down then exit
                ; we only take over the data line if
                ; the clock is high ( idle )
                ; not sure about this though.. ???

     
    BTFSC    _RXEnd        ; are we done ?
    GOTO    INT_RX_END

    ; handshake check if data line is free ( high )
    BTFSS    pcDATA_in    ; is data line free ?
    GOTO    INTX        ; nope

    BSF    pcDATA_out    ; takeover data line
    BSF    _RXAckDone    ; we are done..at next switchover from low-high we exit
    GOTO    INTX        ;

INT_RX_END
    BCF    pcDATA_out    ; release data line
    BSF    _RXDone        ; we are now done
    GOTO    INTX

INT_RX_START
    BCF    _isStartBit    ; clear start bit flag
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )
    GOTO    INTX
INT_RX_BIT
    BCF    CurrKey,7
    BTFSS    pcDATA_in    ; is bit high
    GOTO    INT_RX_NEXT    ; nope , it's a '0'
    BSF    CurrKey,7    ; set highest bit to 1
    INCF    KeyParity,F    ; increase parity bit counter
    
INT_RX_NEXT
    BSF    pcCLOCK_out    ; ok to set clock low ( pull down )

    DECFSZ    bitCount,F    ; decrement data bit counter    
    GOTO    INT_RX_NEXT_OK

    BSF    bitCount,0    ; just in case ( so we cannot overdecrement )
    BSF    _isParity    ; next bit is parity
    GOTO    INTX

INT_RX_NEXT_OK
    CLRC            ; clear carry, so it doesnt affect receving byte
    RRF     CurrKey,F    ; rotate to make room for next bit
    GOTO    INTX        ; and exit

INT_EXIT_RX    

    ; handle the recevied key ( if not it is an 'data' byte )

    MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8 ( int handling code )= 200 us
                ;
        MOVWF   TMR0        ; this delay seems to be needed ( handshake ? )
    
    ; check if this is an data byte ( rate/delay led status etc )
    
    MOVF    CommandData,F    ; reload into itself ( affect zero flag )

    BTFSS    STATUS,Z    ; check zero flag
    GOTO    INT_STORE_DATA    ; byte contains data ( rate/delay etc )
    
    CALL    CHECK_RX_KEY    ; no data, handle recevied command
    GOTO    INTX

INT_STORE_DATA
    ; store data byte in 'currkey',
    ; first reply with 'ack'
    
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    BTFSS    _IsLedStatus    ; is it led status byte ?
    GOTO    INT_STORE_RATE  ; nope check next
    
INT_STORE_NUM
    ; byte in 'currkey' is led status byte, store it
    MOVF    CurrKey,W    ; get byte
    MOVWF    KbLedStatus    ; and store it
    BTFSC    _WaitNumLock    ; was this something we were waiting for ?
                ; i.e. we sent the make scancode for numlock.
    CALL    RELEASE_NUMLOCK    ; yep, then send release code for 'soft' numlock

    GOTO    INT_STORE_EXIT    ; store it in local ram copy and exit

INT_STORE_RATE

    BTFSS    _IsRateDelay    ; is it rate/delay byte ?
    GOTO    INT_STORE_EXIT  ; nope then send ack end exit
    ; byte in 'currkey' is rate/delay byte, store it
    MOVF    CurrKey,W    ; get byte
    MOVWF    KbRateDelay    ; and store it
    
INT_STORE_EXIT
    
    CLRF    CommandData    ; clear data byte flags
    GOTO    INTX


;******************* 'heart' beat code ( 0.5 ms tick ) *********
; Note: As I 'mess' with the int timer this 'heart beat' is by no means
; an accurate clock. However it can work as a rough estimate for local time.
;
INT_HEART_BEAT

    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0


INT_CHECK_CLKDTA:  

    ; CLOCK DATA   Action
    ;-----------+--------
    ;   L    L  |  wait ?
    ;   L    H  |  wait, buffer keys
    ;   H    L  |  start an rx sequence
    ;   H    H  |  keyboard can tx
        
    BTFSS    pcDATA_in    ; is the data line high ( free )..
    GOTO    INT_CHECK_RX    ; Nope it's pulled down, check if rx is requested
        
    BTFSC    pcCLOCK_in    ; Is the clk line low  ( pulled down ) ?        
    GOTO    INT_CHECK_BUFF  ; Nope, so check if we have any keys to send

    GOTO    INT_IDLE    ; clock is low , wait and buffer keys( i.e. no rx/tx )

        
                        
INT_CHECK_RX           ; pc ( probably ) wants to send something..
        
    BTFSS    pcCLOCK_in    ; wait until clock is released before we go into receving mode..
    GOTO    INT_RX_IDLE    ; nope still low

    ; clock now high test if we are set to start an rx seq.
    BTFSS    _RxCanStart     ; have we set the flag ?
    GOTO    INT_WAIT_RX    ; nope then set it      
            
    BTFSC    pcDATA_in    ; make sure that data still is low
    GOTO    INT_ABORT_RX    ; nope abort rx req, might been a 'glitch'

    ; initiate the rx seq.

    CLRF    Comm_Flags    ; used by both tx/rx routines ( and _RxCanStart bit !! )
    CLRF    TRX_Flags    ; clear tx/rx flags
    CLRF    KeyParity    ; clear parity counter

    BSF    _RX_Mode          ; set rx mode flag..
    BSF    _isStartBit    ; set that next sampling is start bit
    
    ; preset bit and clock counters

    MOVLW    H'2F'        ; = 47 dec, will toggle clock output every even number until zero
    MOVWF    clkCount    ; preset clock pulse counter

    MOVLW    H'08'        ; = 8 dec, number of bits to read
                ; then parity bit will be set instead

    MOVWF    bitCount    ; preset bit counter

    ; note as we are starting the clock here we allow a longer time before we start
    ; the actual 20 us tick, this the first time we wait about 200 us before the first clock 'tick'     

    MOVLW   H'C4'           ; preset timer with 196 ( 256 - 60  = 196 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 60  + 8us ( int handling code )= 200 us
                ;
        MOVWF   TMR0
    
    GOTO    INTX        ; exit, the next int will start an rx seq

INT_WAIT_RX:
    BSF    _RxCanStart    ; set flag so we start rx next int ( 0.5 ms )
INT_RX_IDLE
    ; reload clock so we check more often
    MOVLW   H'F0'           ; start timer with 240 ( 256-16  = 240 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 16 + 8/10us ( int handling code )= (about) 60 us
        MOVWF   TMR0

    GOTO    INTX        ;

INT_ABORT_RX
    BCF    _RxCanStart    ; clear flag ( forces a 'new' rx start delay )
    GOTO    INT_IDLE    ;
                                               

INT_CHECK_BUFF:
    ; check if we have any keys to send to pc

        MOVF     KeyBufferTail,W ; get end of buffer to 'w'
        SUBWF     KeyBufferHead,W ; subtract start of buffer if head - tail = zero, no byte
        BTFSC   STATUS, Z       ; zero flag = no byte to send         
        GOTO    INT_IDLE    ; then do the 'idle' stuff
         
INT_SEND_KEY
    ;key in buffer, get it and initiate an tx seq...
    CALL    GET_KEY_BUFFER    ; get the key into CurrKey
    MOVF    CurrKey,W
    MOVWF    LastKey        ; store last sent key

    ; setup our tx/rx vars

    CLRF    Comm_Flags    ; used by both tx/rx routines ( and _RxCanStart bit !! )
    CLRF    TRX_Flags    ; clear tx/rx flags
    CLRF    KeyParity    ; clear parity counter

    BSF    _TX_Mode          ; set tx mode flag..
    
    ; preset bit and clock counters

    MOVLW    H'2B'        ; = 43 dec, will toggle clock out put every even number until zero
    MOVWF    clkCount    ; preset clock pulse counter

    MOVLW    H'12'        ; = 18 dec, will shift data out every even number until zero
                ; then parity bit will be set instead
    MOVWF    bitCount    ; preset bit counter

    ; data now set, initiate the clock to generate a 20 us tick ( rx/tx heart beat )

    BSF    pcDATA_out    ; start bit, always 'low' ( we pull down )
     
    MOVLW   H'FA'           ; start timer with 252 ( 256-6  = 250 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 6 + 8 ( int handling code )= 27 us
        MOVWF   TMR0
    
    GOTO    INTX        ; exit, the next int will start an tx

INT_IDLE:
    
    ; 'Idle' code i.e. no rx or tx possible AND/OR nothing to send

    DECF    Divisor_10ms,F  ; Count 0.5ms down to give 10 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .20
    MOVWF    Divisor_10ms     ; Preset the divide by 20

    ;+++
    ; 10 ms tick here
    


    ; Divide the 10 ms tick to give 100 ms second tick
INT_10MS
    DECF    Divisor_100ms,F    ; Count 10ms down to give 100 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .10
    MOVWF    Divisor_100ms    ; Preset the divide by 10

        ;+++
     ; 100 ms tick here

INT_100MS


    ; numlock check !! bit variable _Numlock does not actually set the numlock status directly.
    ; However, by setting this bit to '1' we make a test against the current numlock led status
    ; ( bit no 1 in variable KbLedStatus ), if that is different from this variable
    ; we send a 'numlock' press/release ( to toggle numlock status )
    ; so in essence, setting this bit to '1' will force numlock to be 'on' etc.

    BTFSC    _IsFirstLedStatus ;  if = 1 then we have not yet received numlock status
    GOTO    INT_REPEAT_CHECK    ; nope, then this is a consecutive byte, store as 'normal'

    BTFSS    _WaitNumLock    ; are we waiting for pc numlock reply ?
    GOTO    INT_NUMLOCK_CHECK ; yep then do repeat check instead
    
    DECFSZ    Temp_Var,F    ;
    GOTO    INT_REPEAT_CHECK

    CALL    RELEASE_NUMLOCK

INT_NUMLOCK_CHECK

    BTFSC    _LedNumLock    ; is the led on ?
    GOTO    INT_NUMLOCK_ON    ; yep, then test our 'local' numlock state ( wanted numlock state )

    ; nope numlock is off, is our wanted state also off ?
    BTFSS    _NumLock    ; is wanted state off ?
    GOTO    INT_REPEAT_CHECK ; yep continue   

    CALL    PRESS_NUMLOCK    ; nope then send numlock press/release code

    GOTO    INT_REPEAT_CHECK
            

INT_NUMLOCK_ON
    BTFSC    _NumLock    ; is wanted state also 'on' ?
    GOTO    INT_REPEAT_CHECK ; yep

    CALL    PRESS_NUMLOCK    ; nope then toggle numlock state

INT_REPEAT_CHECK

    ; check if a key should be 'repeated' ( when pressed longer than 500 ms )
    BTFSS    _startRepeat    ; start repeating a key ? ( delay !!! )
    GOTO    INT_CHECK_KEY    ; nope, then check if key should be repeated
    DECF    RepeatTimer,F    ;
    BNZ    INT_500MS    ; not zero yet, check timer instead

    BCF    _startRepeat    ; stop repeat timer ( delay is accomplished )
    BSF    _doRepeat    ; and enable 'key' is still down check
    MOVLW    .02        ; start repeat send timer
    MOVWF    Divisor_Repeat  ;

    GOTO    INT_500MS    ; do next timer check

INT_CHECK_KEY
    BTFSS    _doRepeat    ; key should be repeated ?
    GOTO    INT_500MS    ; nope
    
    ; ok key should be repeated, check if it still pressed ?
    CALL    CHECK_KEY_STATE    ; uses MakeKeyOffset to calculate which key that was
                ; the last pressed, and then check if it's still pressed
                ; if still pressed carry = '1',

    BTFSS    STATUS,C    ; check carry
    BCF    _doRepeat    ; clear repeat bit, stop repeating the key

    BTFSS    _doRepeat    ; still pressed ?
    GOTO    INT_500MS    ; nope

    DECF    Divisor_Repeat,F  ; should we send the key ?
    BNZ    INT_500MS    ; nope

    MOVLW    DELAY_RATE    ; reload timer with key rate delay
    ;MOVLW    .02        ; restart timer
    MOVWF    Divisor_Repeat  ;
    
    BSF    _doSendKey    ; set flag to send key, NOTE the actual sending ( putting into send buffer )
                ; is done inside mainloop.
     
    
INT_500MS
    
    DECF    Divisor_500ms,F    ; Count 100ms down to give 500 milli second tick
    BNZ    INTX        ; Exit if divider not zeroed
    MOVLW    .05
    MOVWF    Divisor_500ms    ; Preset the divide by 5

        ;+++
     ; 500 ms tick here


INT_500_NEXT
    
    TOGGLE_PIN O_led_KEYCOMM_ok    ; toggle the disco light 😉

    BTFSS    _DoExitAltKeymap    ; is the alt keymap toggle key pressed the second time ?
                    ; if so skip timeout test and exit
    BTFSS    _InAltKeymap    ; are we in altkeymap ?
    GOTO    INTX        ; nope
    
    ; we are in altkeymap, decrement the lastkeytime
    ; and check if we are at zero then we exit
    ; the altkeymap.

    DECF    LastKeyTime,F    ; decrease time
    BNZ    INTX        ; exit, timer has not expired
    ; timer expired, get out of altkey map
    BSF    _ExitAltKeymap    ;

; ***************** 'heart' beat code end ***************

INTX
    ;BCF    INTCON,T0IF     ; Clear the calling flag

    PULL            ; Restore registers
    RETFIE
    
; **************** end interrupt routine **************


;+++++        
;     Routines that will 'toggle' keyboard numlock status
;     by sending numlock make/break code
;

PRESS_NUMLOCK:     
        MOVLW    H'77'         ; numlock key scancode, make
        CALL    ADD_KEY
        MOVLW    H'06'         ; 6 x 100 ms = 600 ms ( release delay )
        MOVWF    Temp_Var    ;
        BSF    _WaitNumLock    ; we are waitin for numlock status reply from pc
        RETURN

RELEASE_NUMLOCK:
        MOVLW    BREAK        ; break prefix
        CALL    ADD_KEY    
        MOVLW    H'77'         ; numlock key scancode
        CALL    ADD_KEY
        BCF    _WaitNumLock
        RETURN
    
; ***********************************************************************
;
;  CHECK_RX_KEY - handles the received commands from pc
;
CHECK_RX_KEY    
    ; check the key in 'currkey' ( command from pc )

CHECK_ED
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'ED'        ; subtract value in W with 0xED
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_EE    ; the result of the subtraction was not zero check next
    ; ok 'ED'=set status leds ( in next byte ) received
    BSF    _IsLedStatus    ; set bit that next incoming byte is kb led staus
    GOTO    CHECK_SEND_ACK    ; send ack

CHECK_EE    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'EE'        ; subtract value in W with 0xEE
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F0    ; the result of the subtraction was not zero check next
    ; ok 'EE'= echo command received
    GOTO    CHECK_SEND_EE    ; send echo

CHECK_F0    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F0'        ; subtract value in W with 0xF0
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F2    ; the result of the subtraction was not zero check next
    ; ok 'F0'= scan code set ( in next commming byte ) received
    BSF    _SkipByte    ; skip next incomming byte ( or dont interpret )
    GOTO    CHECK_DONE    ; do not send ack !
CHECK_F2    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F2'        ; subtract value in W with 0xF0
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_F3    ; the result of the subtraction was not zero check next
    ; ok 'F2'= Read ID command responds with 'AB' '83'
    GOTO    CHECK_SEND_ID    ; send id bytes

CHECK_F3    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'F3'        ; subtract value in W with 0xF3
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_FE
;    GOTO    CHECK_F4    ; the result of the subtraction was not zero check next
    ; ok 'F3'= set repeat rate ( in next commming byte ) received
    BSF    _IsRateDelay    ; next incomming byte is rate/delay info
    GOTO    CHECK_SEND_ACK    ; send ack

; **** Note ! removed from test as I 'don't care' ********
; **** i.e. I dont disable or enable the keyboard at any time.

;CHECK_F4    
;    MOVF    CurrKey,W    ; move key buffer into W register
;    SUBLW    H'F4'        ; subtract value in W with 0xF4
;    BTFSS   STATUS, Z    ; check if the zero bit is set
;    GOTO    CHECK_F5    ; the result of the subtraction was not zero check next
    ; ok 'F4'= keyboard enable received
;    GOTO    CHECK_SEND_ACK    ; send ack
;CHECK_F5    
;    MOVF    CurrKey,W    ; move key buffer into W register
;    SUBLW    H'F5'        ; subtract value in W with 0xF5
;    BTFSS   STATUS, Z    ; check if the zero bit is set
;    GOTO    CHECK_FE    ; the result of the subtraction was not zero check next
    ; ok 'F5'= keyboard disable received
;    GOTO    CHECK_SEND_ACK    ; send ack
CHECK_FE    
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'FE'        ; subtract value in W with 0xFE
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_FF    ; the result of the subtraction was not zero check next
    ; ok 'FE'= resend last sent byte
    MOVF    LastKey,W    ; get last key
    CALL    ADD_KEY        ; and put it on the que
    GOTO    CHECK_DONE

CHECK_FF
    MOVF    CurrKey,W    ; move key buffer into W register
    SUBLW    H'FF'        ; subtract value in W with 0xFF
    BTFSS   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_ERROR    ; the result of the subtraction was not zero, unknown command
    ; ok 'FF'= reset keyboard received
    
    GOTO    CHECK_SEND_AA    ; send 'AA' power on self test passed

CHECK_ERROR            ; unknown command ( or command not interpreted )
    GOTO    CHECK_SEND_ACK

CHECK_SEND_ID
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    MOVLW    H'AB'        ; keyboard id first byte, always 0xAB
    CALL    ADD_KEY        ;

    MOVLW    H'83'        ; keyboard id second byte, always 0x83
    CALL    ADD_KEY        ;

    GOTO    CHECK_DONE

CHECK_SEND_ACK

    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;
    GOTO    CHECK_DONE

CHECK_SEND_AA
    MOVLW    H'FA'        ; keyboard ack
    CALL    ADD_KEY        ;

    MOVLW    H'AA'        ; keyboard post passed
    CALL    ADD_KEY        ;
    GOTO    CHECK_DONE

CHECK_SEND_EE
    MOVLW    H'EE'        ; keyboard echo
    CALL    ADD_KEY        ;

CHECK_DONE
    RETLW    0        ; and we are done

; ***********************************************************************
;  Buffer code ( a bit modified ) from Stewe Lawther
; http://ourworld.compuserve.com/homepages/steve_lawther/ucindex.htm
;  And of course source of the exellent keyboard viewer. !! ( without which, this
;  project would have been close to impossible ) ( and of course my nifty
;  memory oscilloscope )
;
;  ADD_KEY_BUFFER - (outside int)add the key in CurrKey to our keybuffer que in the first
;            free position. If there is no more room the oldest byte is
;            'dumped'.
;  ADD_KEY      - Same but to be used inside int routine.( just skips int disable code )

ADD_KEY_BUFFER

ADD_STOP_INT            ; first stop all interrupts !!!!!!!
    BCF    INTCON,GIE    ; disable global interrupts..
    BTFSC    INTCON,GIE    ; check that is really was disabled
    GOTO    ADD_STOP_INT    ; nope try again
ADD_KEY                ; inside interuppt we call this instead ( as we dont need to disable int 🙂 )
    MOVWF    BufTemp        ; store key temporary
    MOVF    KeyBufferHead,W ; move buffer head out of FSR temporarily
        MOVWF   Temp            ; store in temp  
        MOVF    KeyBufferTail,W ; set FSR to buffer tail
        MOVWF   FSR        ; set indirect file pointer
        MOVF    BufTemp,W       ; set W to new scancode to send
        MOVWF   INDF            ; and put it in the buffer
        MOVF    Temp,W        ; get the head pointer back
        MOVWF   KeyBufferHead    ;

        INCF    KeyBufferTail,W ; get the end of the buffer
        SUBLW   KbBufferMax     ; check if at buffer max
        INCF    KeyBufferTail,W ; (reload value to w - doesn't affect C)
        BTFSS   STATUS, C       ; if so (negative result)
        MOVLW   KbBufferMin     ; set to buffer min ( wrap around )

        MOVWF   KeyBufferTail    
        SUBWF   KeyBufferHead,W ; see if we have any room ( head and tail have meet )
        BTFSC   STATUS, Z       ; if so (Z set)
        CALL    INC_KEY_HEAD    ; dump oldest byte

                ; finally turn on interrupts again
        MOVLW   b'10100000'     ; enable global & TMR0 interrupts
        MOVWF   INTCON

    RETURN


; ***********************************************************************
;
;  GET_KEY_BUFFER -     Gets a char from the buffer, and puts it into KeyBuffer
;            NOTE: Does not increase buffer pointers ( dump this key ).
;            A call to INC_KEY_HEAD will do this if the key is sent ok
GET_KEY_BUFFER    
          MOVF    INDF, W         ;put the byte to send into key buffer
                MOVWF   CurrKey
        RETURN            ; and go back, NOTE ! the key is not
                    ; removed from the buffer until a call
                    ; to INC_KEY_HEAD is done.
                    
; ***********************************************************************
;
;  INC_KEY_HEAD -     dump oldest byte in keybuffer, Do not call if byte
;            has not been fetched before ( GET_KEY_BUFFER )
;            
INC_KEY_HEAD    INCF    KeyBufferHead,W ; set to next byte in buffer
                SUBLW   KbBufferMax     ; check if at buffer max
                INCF    KeyBufferHead,W ; (reload value to w - doesn't affect C)
                BTFSS   STATUS, C       ; if so (negative result)
                MOVLW   KbBufferMin     ; set to buffer min ( wrap around )
                MOVWF   KeyBufferHead   ; and store ( in FSR )
                RETURN            ; go back
                
        

; ***********************************************************************
;
;  CHECK_KEY_STATE - Check if the last pressed key is still pressed
;             Returns with carry = '1' if still pressed
;             else carry = '0' ( or error )
;

CHECK_KEY_STATE:
    ; uses LastMakeOffset to calculate which key to test
    
    MOVF    LastMakeOffset,W    ; get offset
    ANDLW    H'18'        ; mask out column bits  
                ; lastmake offset has the following bits:
                ; '000yyxxx' where 'yy' is column no
                ; and 'xxx' is key num,
    BTFSC    STATUS,Z    ; zero = column 1 & 2
    GOTO    CHECK_COL_12    ; it is in column 1

    MOVWF    repTemp        ; save it temporary
    SUBLW    H'08'        ; subtract value in W with 0x08 ( columns 3 & 4 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_COL_34    ; it is in column 3 & 4

    MOVF    repTemp,W    ; get the column bits back
    SUBLW    H'10'        ; subtract value in W with 0x10 ( columns 5 & 6 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    CHECK_COL_56    ; it is in column 5 & 6
    
CHECK_COL_78
    MOVF    kbColumn78_Old,W ; get bit map ( key status ) for keys in column 7 & 8
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_56
    MOVF    kbColumn56_Old,W ; get bit map ( key status ) for keys in column 5 & 6
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_34
    MOVF    kbColumn34_Old,W ; get bit map ( key status ) for keys in column 3 & 4
    MOVWF    repKeyMap    ; and store it
    GOTO    CHECK_KEY    ; and continue to check bit

CHECK_COL_12
    MOVF    kbColumn12_Old,W ; get bit map ( key status ) for keys in column 1 & 2
    MOVWF    repKeyMap    ; and store it

;<-------Alt keymap code------->
    
    ;Checks ONLY column 1&2 bitmap ( keymap ) ( as it is now )
    ; this code has to be moved/changed if another column is choosen as the alt. keymap toggle key

    ; alternative keymap handling if key r3 c1 is pressed ( bit 2 in column 1&2 )
    ; then enable alternative keymap ( only if keyrepeat is disabled )
    
    ; check if this was the last key pressed

    ; check bit representing the alt. keymap key ( i've choosen key 2 )

    MOVF    LastMakeOffset,W ; get key offset again
    ANDLW    H'07'         ; mask out column bits
    SUBLW   H'02'        ; check if its bit num 2 ( the enter 'alt keymap' key )

    BTFSS   STATUS, Z    ; check if the zero bit is set    GOTO    CHECK_KEY    ; nope than another key was the last
                ; skip altkeymap enable

    ; the altkeymap key was the last pressed !
    ; is key repeat disabled ?

    BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
    GOTO    CHECK_KEY     ; yep, then skip altkeymap enable test

    ; enable altkeymap if key is still pressed
    
    BTFSC    repKeyMap,2    ; test bit 2 ( should be key 'F7' )
    GOTO    CHECK_ENABLE_ALT ; ok enable altkeymap ( if we are not already in altkeymap )
    GOTO    CHECK_KEY    ; nope another key in column 1&2 continue check

CHECK_ENABLE_ALT
    BTFSC    _AltKeymap    ; are we already in altkeymap ?
    GOTO    CHECK_KEY    ; yep then just continue

    ; We are just entering/enabling the alt. keymap

    BSF    _AltKeymap    ; enable alternative keymap

    ; Example of using an 'advanced' alt keymap handling    
    ; not enabled, to avoid intial confusion.

    ; I.E This snippet would only be called once when we
    ; are just entering(enabling) the alternative keymapping !

    ; This example code will 'soft' release the current altkeymap key ( it is in pressed state ! )
    ; ( i.e send release code for the enter alt. keymap key 'F7' )
    ; send the make scancode for left <alt> key instead.
    ; and force numlock to be off.
    
    ; Do not use if you dont understand the implifications !
        
    ; Also note that the scancodes are hardcoded here !
    ; ( i.e do not use the lookup table definition of the key/s )
    
    ; ***** start snippet
    
    ;MOVLW    BREAK    ; send break prefix
    ;CALL    ADD_KEY
    ;MOVLW    H'83'   ; and scancode for the enter alt keymap
    ;CALL    ADD_KEY
    ;MOVLW    H'11'     ; send make code for the left <alt> key
    ;CALL    ADD_KEY
    
    ; example of forcing the numlock status to a particular state
    ; the numlockstatus will change ( be checked ) inside the int routine
    ; See also at the end of KB_DEBOUNCE_12 where the numlock status
    ; will be restored when we release the key
    
    ;BCF    _NumLock    ; 'force' numlock to be off
    
    ; This bit MUST also be checked as we do not know if we have recevied
    ; first numlock status byte yet ( pc does not send numlock/led status
    ; after intial poweron, if not one of the numlock/capslock/scrolllock are pressed )
    ; i.e. if you connect this keyboard to a 'running' pc, the numlock status
    ; will be unknown.However if connected before poweron, it will be updated
    ; as numlock status is sent during pre-boot seq.
    
    ;BTFSC    _IsFirstLedStatus ; have we recevied numlock status yet ?
    ;CALL    PRESS_NUMLOCK
    
    ; ***** end snippet

CHECK_KEY
    ; 'normal' key down check
    ; column for pressed key is now in repKeyMap
    MOVF    LastMakeOffset,W ; get offset again
    ANDLW    H'07'        ; mask out key number ( lowest 3 bits )
    
     BTFSC    STATUS,Z    ; bit num zero ?
    GOTO    CHECK_KEY_DONE    ; yep lowest bit, check and return
    
       MOVWF    repTemp        ; and store it
CHECK_KEY_LOOP
    RRF    repKeyMap,F    ; rotate one step to right
    DECFSZ    repTemp,F        ; decrement bit counter
    GOTO    CHECK_KEY_LOOP    ; loop again

CHECK_KEY_DONE
    ; ok the key to test should now be the lowest bit in repKeyMap
    CLRC            ; clear carry
    BTFSC    repKeyMap,0    ; check bit 0
    BSF    STATUS,C    ; ok key is pressed set carry
    RETURN            ; and we are done..

; ***********************************************************************
;
;  DELAY_1ms -     Delay routine ! used when scanning our own keyboard
;         Delay is between output of adress to 4051 and reading of inputs
;         Increase to have a slower 'scan' rate or decrease to have a higher scan rate.
;

DELAY_1ms
        MOVLW    H'F0'        ; wait 255 cycles
        MOVWF    kbTemp        ; this var is 'safe' to be used in side mainloop
        MOVLW    H'03'
        MOVWF    kbState
DELAY_LOOP
        DECFSZ    kbTemp,F    ; decrement
        GOTO    $-1        ;
        
        MOVLW    H'F0'
        MOVWF    kbTemp
        DECFSZ    kbState,F
        GOTO    DELAY_LOOP

        RETURN


;---------------------------------------------------------------------------
;
;        Initialisation
;
;---------------------------------------------------------------------------
        
        
INIT:  

    ;+++
    ;    Set up the ports

        ; PORT A

    BANK1
        MOVLW    b'00000110'        ; Set port data directions RA1,RA2 inputs RA0,RA3,RA4 outputs
    MOVWF    TRISA            ; PC keyboard connections


        ; PORT B

        ; Used for our own 3x8 matrix keyboard
    
        BANK1
        MOVLW    b'11111000'        ; Set port data directions RB4-RB7 inputs rest outputs
    MOVWF    TRISB


    ;    Clear all registers on bank 0 ( memory )
    
    BANK0
    MOVLW    H'0C'
    MOVWF    FSR
    
INITMEM
    CLRF    0        ; Clear a register pointed to be FSR
    INCF    FSR,F
    CLRWDT            ; clear watchdog
    MOVLW    H'50'        ; Test if at top of memory
    SUBWF    FSR,W
    BNZ    INITMEM        ; Loop until all cleared

    ;+++     
    ;    Initiate the keybuffer pointers

INIT_BUFF:

    MOVLW   KbBufferMin    ; get adress of first buffer byte
        MOVWF    KeyBufferHead    ; store in FSR
        MOVWF    KeyBufferTail    ; and set last byte to the same ( no bytes in buffer )

    ;+++
    ;    Preset the timer dividers

    MOVLW    .20
    MOVWF    Divisor_10ms
    MOVLW    .10
    MOVWF    Divisor_100ms
    MOVLW    .05
    MOVWF    Divisor_500ms

    ;+++
    ;    Set up Timer 0.

    ;    Set up TMR0 to generate a 0.5ms tick
    ;    Pre scale of /8, post scale of /1
    
    BANK1
        
    MOVLW   b'00000010'     ; Initialisation of TMR0 prescale 8 '010'
                                ; weak pullup enabled by latch values.

        MOVWF   OPTION_REG      ; load option reg with prescale of 8
                
        BANK0

    MOVLW   H'64'           ; start timer with 100 ( 256-100 = 156 )
                                ; timetick = 0.4uS x 8 ( prescale ) x 156 = (roufly) 0.5 mS
                                
        MOVWF   TMR0
        

        
;---------------------------------------------------------------------------
;
;        the main 'program' loop ( starts really at MAIN_LOOP )
;
;---------------------------------------------------------------------------

MAIN:    
        BSF    pcDATA_in
        BSF    pcCLOCK_in
        BCF    pcDATA_out
        BCF    pcCLOCK_out
        CLRF    PORTB

        MOVLW    H'08'        ; preset the column counter
        MOVWF    kbColumnCnt    ;
        
         BSF    _NumLock    ; default state is numlock = on
        BSF    _IsFirstLedStatus ; we have not yet recevied led status byte.

            MOVLW   b'10100000'     ; enable global & TMR0 interrupts
            MOVWF   INTCON

        CLRWDT            ; clear watchdog
        BTFSS    O_led_KEYCOMM_ok
        GOTO    $-2        ; make an 0.5 second delay here
                    ; i.e. the led will come on when 0.5 seconds has passed
                    ; set inside the timer int.

        CLRWDT            ; clear watchdog
        BTFSC    O_led_KEYCOMM_ok
        GOTO    $-2        ; make an additional 0.5 second delay here
                    ; i.e. the led will be dark when 0.5 seconds has passed
                    ; set inside the timer int.


        MOVLW    H'AA'         ; post passed :-), always 0xAA
        CALL    ADD_KEY_BUFFER
        
        ; now go into infinite loop, the pc kb interface runs in the background ( as an int )
        ; where we continuously monitor the pcCLOCK/DATA_in lines

         
MAIN_LOOP:
        ; check whatever 🙂
        CLRWDT            ; clear watchdog

MAIN_CHECK_COL_1:
        ; scan our own keyboard, first four bits

        ; address and read column, read as complement so key pressed = '1'
        ; since we pull down when key is pressed ( weak pullup enabled )
        ; ( i.e. pressed key has level 0V ) to make it more 'logical' to work with

        CLRF    kbColumnVal
        
        ; get column counter / adress out
        MOVF    kbColumnCnt,W          
        
        MOVWF    PORTB        ; set the columns adress to the 74HCT4051
                    ; i.e. make column low
        IFNDEF    DEBUG
        CALL    DELAY_1ms    ; wait 1 ms let pins stabilize
        ENDIF

        COMF    PORTB,W        ; read back the pin values ( complement i.e. key pressed = '1' )
        ANDLW   b'11110000'    ; mask out unused pins
        
        MOVWF    kbColumnVal    ; store the pin values

        SWAPF    kbColumnVal,F    ; swap nibbles ( low<->high ) to make room for next column

        INCF    kbColumnCnt,F     ; inc column adress
MAIN_CHECK_COL_2:
        ; read next four bits
        ; put out adress and read next column, read as complement so key pressed = '1'
        ; this as we pull down when key is pressed ( weak pullup enabled )

        ; get column counter / adress out
        MOVF    kbColumnCnt,W          
        
        MOVWF    PORTB        ; set the columns adress to the 74HCT4051
                    ; i.e. make column low

        IFNDEF    DEBUG
        CALL    DELAY_1ms    ; wait 1 ms
        ENDIF

        COMF    PORTB,W        ; read back the pin values ( complement i.e. key pressed = '1' )
        ANDLW   b'11110000'    ; mask out unused pins
        
        ADDWF    kbColumnVal,F    ; and store pin values

        INCF    kbColumnCnt,F   

        ; reset column counter check
        ; i.e. we are 'only' using adress 0 - 7
        MOVF    kbColumnCnt,W
        SUBLW    H'08'        ; subtract value in W with 0x08
        BTFSS   STATUS, Z    ; check if the zero bit is set
        GOTO    MAIN_CHECK_DEBOUNCE ; nope continue

        CLRF    kbColumnCnt     ; reset counter/adress

MAIN_CHECK_DEBOUNCE:
        CALL    KB_DEBOUNCE    ; do debouncing on the current values and send make/break
                    ; for any key that has changed
                    ; NOTE uses the current column adress to determine which
                    ; columns to debounce!
MAIN_REPEAT:
        BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
        GOTO    MAIN_CHECK_REPEAT ; yep check key repeating
        
        ; keyrepeat disabled then do check on exit of altkeymap instead

        BTFSS    _ExitAltKeymap    ; we want to exit altkeymap ?
        GOTO    MAIN_LOOP    ; nope

        
        ; check that ALL keys are released
        ; before exiting the alt keymap
        MOVF    kbColumn78_Old,F ; reload column 78 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 78

        MOVF    kbColumn56_Old,F ; reload column 56 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 56

        MOVF    kbColumn34_Old,F ; reload column 34 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 34

        MOVF    kbColumn12_Old,F ; reload column 12 to itself ( affect zero flag )
        BTFSS    STATUS,Z    ; check if zero ?
        GOTO    MAIN_LOOP    ; key/s still down in column 12
    
        ; all keys released !!
        BCF    _AltKeymap    ; exit altkeymap
        BCF    _ExitAltKeymap    ; exit release check
        BCF    _InAltKeymap    ; clear flag for second keypress check
        BCF    _DoExitAltKeymap ;
        GOTO    MAIN_LOOP    


MAIN_CHECK_REPEAT
        BTFSS    _doSendKey    ; if we should send a repeated key
        GOTO    MAIN_LOOP    ; nope continue

        ; send the key in RepeatedKey but first check if its an extended key
        BTFSS     _RepeatIsExt    ; is it extended ?
        GOTO    MAIN_SEND_REPEAT ; nope just send scan code
        
        ; last key pressed was extended send extended prefix
        MOVLW    EXTENDED    ; get extended code
        CALL    ADD_KEY_BUFFER    ; and put it into the buffer
        
MAIN_SEND_REPEAT:
        MOVF    RepeatKey,W    ; get key code for the last pressed key
        CALL    ADD_KEY_BUFFER    ; and put it into the buffer
        BCF    _doSendKey    ; and clear the flag, it will be set again
                    ; inside int handler if key still is pressed    
        
        GOTO    MAIN_LOOP    ; and return
    

; ***********************************************************************
;
;  KB_SEND_KEY - uses the Offset stored in var 'Offset' to fetch a key from our
;         key lookup table.
;           then checks the bit var _isBreak to see if make or break codes should be sent
;         It then puts the code/s into the key buffer ( for sending later, in int routine )

KB_SEND_KEY:

    MOVF    Offset,W    ; get current offset
    MOVWF    TempOffset    ; save it ( to be used in key repeat code, is its 'make' )
                ; temp offset has the following bits:
                ; '000yyxxx' where 'yy' is column offset
                ; and 'xxx' is key num,


    CLRC            ; clear carry so it dont affect byte rotation
    RLF    Offset,F    ; first rotate
    RLF    Offset,F    ; second rotate

                ; offset no have the following bits:
                ; '0yyxxx01' where 'yy' is column offset
                ; and 'xxx' is key num,
                ; as each key in table has 4 bytes of 'space'

    INCF    Offset,F    ; add one, for the 'movwf pcl' at the start of the table

    BCF    Offset,7    ; clear to bit, just in case so we dont
                ; 'overflow' the table, should not be needed !

    BCF    _isExtended     ; clear extended flag

    MOVLW   LOW LOOKUP_KEY    ; get low bit of table adress
    ADDWF    Offset,F    ; 8 bit add
    MOVLW    HIGH LOOKUP_KEY    ; get high 5 bits
    BTFSC    STATUS,C    ; is page boundary crossed ?
    ADDLW    1        ; yep, then inc high adress
    MOVWF    PCLATH        ; load high adress in latch
    MOVF    Offset,W    ; load computed offset in w
    CLRC                ; clear carry ( default= key is not extended )
                ; if key is extended then carry is set in jumptable lookup_key

    CALL    LOOKUP_KEY    ; get key scan code/s for this key
                ; key scan code/s are saved in
                ; W - scancode, should go into kbScan
                ; carry set - extend code
                ; carry clear - not extended code

    MOVWF    kbScan        ; store scancode
    ; if carry is set then key is extended so first send extended code
    ; before any make or break code

    BTFSS    STATUS,C    ; check carry flag
    GOTO    KB_CHK_BREAK    ; nope then check make/break status

    BSF    _isExtended    ; set extended flag
    MOVLW    EXTENDED    ;
    CALL    ADD_KEY_BUFFER    ; get extended code and put in in the buffer

KB_CHK_BREAK:
    
    ; check if it's make or break
    BTFSS    _isBreak    ; check if its pressed or released ?
    GOTO    KB_DO_MAKE_ONLY    ; send make code

    BCF    _isBreak    ; clear bit for next key

    ; break code, key is released
    MOVLW    BREAK        ; get break code
    CALL    ADD_KEY_BUFFER    ; and put into buffer
    GOTO    KB_DO_MAKE    ; and send key code also

    ; key is pressed !
KB_DO_MAKE_ONLY:
    BCF    _doSendKey    ; stop repeat sending
    BCF    _doRepeat    ; and bit for repeat key send
    BSF    _startRepeat    ; and set flag for start key repeat check
    BCF    _RepeatIsExt    ; clear repeat key extended flag ( just in case )

    BTFSC     _isExtended     ; is it extended ?
    BSF    _RepeatIsExt    ; set the flag
    
    ; save this key in 'last' pressed, to be used in key repeat code

    MOVF    TempOffset,W    ; get saved offset
    MOVWF    LastMakeOffset  ; and store it

    ; if keyrepat = enabled, alternative mapping = disabled
    BTFSS    I_jmp_NoRepeat    ; check if repeat code is enabled ?
    GOTO    KB_REP_NOR    ; yep set normal delay ( 800 ms )
    
    ; else keyrepat = disabled, alternative mapping = enabled
    MOVLW    DELAY_ENTER_ALTKEYMAP ;reload delay before entering the altkeymap ( 3 sec )
                ; i.e how long the enter altkeymap key must be pressed before
                ; we enable altkey keymap codes.
                            
    GOTO    KB_REP_SET    ; and set it
KB_REP_NOR:    
    
    MOVLW    DELAY_REPEAT    ; reload 'normal' repeat delay ( 800 ms )
            
KB_REP_SET:
    MOVWF    RepeatTimer    ; and (re)start the timer for key repeat
    MOVF    kbScan,W    ; get key scan code    
    MOVWF    RepeatKey    ; and save it

KB_DO_MAKE:
    ; key pressed/released ( i.e. the scancode is sent both on make and break )
    MOVF    kbScan,W    ; get scan code into w
    CALL    ADD_KEY_BUFFER    ; and add to send buffer
    

    ; reset the 'get out of alt. keymap timer for each keypress
    ; note don't care if we are 'in' alt. keymap. Reset this timer anyway
    ; as the code for checking if we are currently in alt. key map
    ; would be as long as it takes to reset the timer.

    MOVLW    DELAY_EXIT_ALTKEYMAP ; reload the delay for exiting the altkeymap when no
                ; key is pressed ( 7.5 sec )
    MOVWF    LastKeyTime    ; (re)set lastkey timer ( used to get out of altkeymap )    
    RETURN

; ***********************************************************************
;
;  KB_DEBOUNCE - debounces two column readings from our keyboard
;         If a bit 'state' has been 'stable' for 4 consecutive debounces
;           the 'new' byte is updated with the new state
;         'normal' loop time ( no tx/rx/key press ) is about 2-3 ms
;         so from 'key' down until 'new' is updated it takes about 8-10 ms
;         ( as we are scanning columns two by two, the whole keyboard needs
;         4 loops to be fully updated, then 4 debounce samples for each 'pair' )    

KB_DEBOUNCE:
    ; debounce current column(s)
    MOVF    kbColumnCnt,F    ; reload value into itself ( affect zero flag )
    BTFSC    STATUS,Z    ; is it zero ?
    GOTO    KB_DEBOUNCE_78    ; debounce columns 7 & 8

    MOVF    kbColumnCnt,W    ; move column counter into W register
    SUBLW    H'04'        ; subtract value in W with 0x04 ( columns 5 & 6 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    KB_DEBOUNCE_34    ; debounce columns 3 & 4

    MOVF    kbColumnCnt,W    ; move column counter into W register
    SUBLW    H'06'        ; subtract value in W with 0x02 ( columns 3 & 4 )
    BTFSC   STATUS, Z    ; check if the zero bit is set
    GOTO    KB_DEBOUNCE_56    ; ok column 1 & 2 debounce

    ; all above tests 'failed'
    ; columns to debouce are 1 & 2

KB_DEBOUNCE_12:    
    ; debounce columns 1 & 2
    DEBOUNCE_BYTE    kbColumnVal,kbColumn12_New,kbColumn12Cnt,kbColumn12State

    MOVF    kbColumn12_New,W    ; get debounced sample
    XORWF    kbColumn12_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn12_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_12    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_12_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
        
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_12_NEXT    

KB_LOOP_12_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_12_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_12_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_12    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_12

KB_12_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn12_New,W ; get new status
    MOVWF    kbColumn12_Old    ; and store it..

;<-------Alt keymap code------->

    ; ***** alternative keymap handling
    ; The alternative keymap is enabled by pressing key r4 c1 ( i.e. bit 3 in column 12 )
    ; Here, we enable a check to turn off alternative keymap if
    ; that key and all others are released ( bit is cleared ).
    ; ( else no (alternative)break codes would be sent for those keys that are still pressed )
    ; NOTE: _Altkeymap is set inside int routine when checking
    ; keyrepeat so there is a 'variable' delay before the altkeymap is active
    ;

    BTFSS    _AltKeymap        ; is altkeymap enabled ?
    RETURN                ; nope return


    BTFSC   _InAltKeymap        ; are we in altkeymap ?
    GOTO    KB_12_IN        ; yep alt keymap key has been released once
    
    ; nope still waiting for first release
    BTFSS   kbColumn12_Old,2    ; is key released ? ( first time )
    GOTO    KB_12_ALT        ; yep, reset timers and set bit variables

KB_12_IN
    BTFSC    _DoExitAltKeymap    ; are we waiting for release ?
    GOTO    KB_12_OUT        ; yes

    ; the key has been released once test for second press
    BTFSC   kbColumn12_Old,2    ; is it still pressed ?
    GOTO    KB_12_ALT2        ; yep

    BTFSS    _DoExitAltKeymap        ; are we now waiting for the last ( second ) release ?
    RETURN                ; nope

KB_12_OUT
    BTFSS    kbColumn12_Old,2    ; check if key still pressed ?
    BSF    _ExitAltKeymap        ; nope, then enable exit check that
                    ; will exit alt keymap as soon as all key are released
    
KB_12_ALT2
    BSF    _DoExitAltKeymap    ; check for second release
    RETURN
KB_12_ALT
    ; first release of the enter alt keymap key
    ; reset 'get out' timer and set bit variables to enable check
    ; for second press/release

    MOVLW    H'0F'        ; x0.5 sec = 7.5 sec
    MOVWF    LastKeyTime    ; (re)set lastkey timer ( used to get out of altkeymap automaticly)    

    BSF    _InAltKeymap    ; yep the first time, then set flag that we are now
                ; waiting for a second press/release to exit alt key map
                ; all keys are released before exiting altkeymap

    ;***** Example snippet(one line) to be paired with code in CHECK_KEY_STATE where I
    ; forced numlock status to be off while enetering the alt keymap
    ; but have not yet released the alt keymap toggle key.
    ; this code will be called at the first release of this key. Used
    ; to restore numlock status.
    ; As said before, do not use if implifications are not known !

    ;BSF    _NumLock    ; and also force numlock to be 'on'
                ; as it is set to 'off' when we enter altkeymap
                ; we must set it 'back'
    
    RETURN    
        
KB_DEBOUNCE_34:    
    ; debounce columns 3 & 4
    DEBOUNCE_BYTE    kbColumnVal,kbColumn34_New,kbColumn34Cnt,kbColumn34State

    MOVF    kbColumn34_New,W    ; get debounced sample
    XORWF    kbColumn34_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn34_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_34    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_34_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,3    ; set bit 3 for table read ( column 3 & 4 )

    ;BCF    _isBreak    ; clear break flag
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_34_NEXT

KB_LOOP_34_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_34_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_34_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_34    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_34

KB_34_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn34_New,W ; get new status
    MOVWF    kbColumn34_Old    ; and store it..
    RETURN    


KB_DEBOUNCE_56:    
    ; debounce columns 5 & 6
    DEBOUNCE_BYTE    kbColumnVal,kbColumn56_New,kbColumn56Cnt,kbColumn56State

    MOVF    kbColumn56_New,W    ; get debounced sample
    XORWF    kbColumn56_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only that 'a change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits.
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn56_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_56    
    CLRF    Offset        ; clear offset counter ( for table read )

    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_56_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,4    ; set bit 4 for table read ( column 5 & 6 )
    
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released

    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars


    GOTO    KB_LOOP_56_NEXT

KB_LOOP_56_SKIP
    RLF    kbTemp,F    ; rotate so we read next key

KB_LOOP_56_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_56_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_56    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_56

KB_56_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn56_New,W ; get new status
    MOVWF    kbColumn56_Old    ; and store it..
    RETURN    

KB_DEBOUNCE_78:    
    ; debounce columns 7 & 8
    DEBOUNCE_BYTE    kbColumnVal,kbColumn78_New,kbColumn78Cnt,kbColumn78State
    
    MOVF    kbColumn78_New,W    ; get debounced sample
    XORWF    kbColumn78_Old,W    ; get changed bits
    
    BTFSC    STATUS,Z    ; check if zero = no change
    RETURN            ; no change. return
    
    ; key/s has been changed, w contains which key/s that has been changed in column 7 & 8
    ; Note ! Not the actual state of the key, only 'change has occured' = '1'

    MOVWF    kbState        ; save change bit/s
    MOVLW    H'07'           ; preset bit counter
    MOVWF    kbBitCnt    ; loop though all eight bits. ( 7-0 )
    BCF    _LastColumn    ; clear end seq bit ( set when are done with last bit )

    MOVF    kbColumn78_New,W ; get new sample
    MOVWF    kbTemp        ; and store it

KB_LOOP_78    
    CLRF    Offset        ; clear offset counter ( for table read )
    CLRC            ; clear carry
    RLF    kbState,F    ; rotate left, and store back
    BTFSS    STATUS,C     ; check carry '1' = bit was high = change has occured
    GOTO    KB_LOOP_78_SKIP    ; nope, no change check next bit ( or exit )
        
    ; bit changed
    MOVF    kbBitCnt,W    ; get bit counter ( for offset calc. )
    MOVWF    Offset        ; store bit num ( for offset )
    
    BSF    Offset,4    ; set bit 3,4 for table read ( column 7 & 8 )
    BSF    Offset,3    ;
    
    CLRC            ; clear carry
    RLF    kbTemp,F    ; rotate left ( next bit )
    BTFSS    STATUS,C     ; check carry '1' = key is down ( i.e. make )
    BSF     _isBreak     ; c = '0' = send break code, i.e. key is released


    CALL    KB_SEND_KEY    ; send key code/s make/break uses
                ; Offset, and _isBreak vars

    GOTO    KB_LOOP_78_NEXT
    
KB_LOOP_78_SKIP
    RLF    kbTemp,F    ; rotate so we read next key
    
KB_LOOP_78_NEXT
    BTFSC    _LastColumn    ; are we done ?
    GOTO    KB_78_DONE    ; yep, save new key bit map and exit

    DECFSZ    kbBitCnt,F    ; decrement bit counter
    GOTO    KB_LOOP_78    ; bits left
    BSF    _LastColumn    ; set bit so we break out after next run
    GOTO    KB_LOOP_78

KB_78_DONE:
    ; and update our 'last known' status for the columns
    MOVF    kbColumn78_New,W ; get new status
    MOVWF    kbColumn78_Old    ; and store it..
    RETURN    

    
; ***********************************************************************
;
;  LOOKUP_KEY -  lookup table for key scancodes.
;         Returns a scancode in w
;         Sets carry if key is extended
;         NOTE: If key R3 C1 has been pressed longer than keyrepeat delay
;         AND keyrepeat is disabled ( jumper on pin RB3 ) THEN the
;          bit _AltKeymap is set and we can return an alternative scancode in W
;        

LOOKUP_KEY    ; lookup table for the keys ( 32 keys x 4 lines + 1 = 129 lines )
        ; keys are labelled Rx - Cy where x = row number and y = column number
        ; handles a 4 row x 8 column keyboard = 32 keys
        
    MOVWF    PCL      ; add to program counter          
; R1 - C1 i.e. key 1
    NOP
    NOP
    NOP    
    RETLW    H'05'    ; scan code 'F1'
; R2 - C1 i.e. key 2
    NOP
    NOP
    NOP    
    RETLW    H'0C'    ; scan code 'F4'
; R3 - C1 i.e. key 3
    ; The famous alternative keymap toggle key !!! 😉
    ; It is adviced that this key does not use an alt scancode
    ; makes things cleaner and simplified.
    ; IF USED though, remember that a 'soft' release code must be sent
    ; for this key when entering the altkeymap !( + 'soft' make code for the alternative key )
    ; This as the key is pressed when entering altkeymap
    ; which makes the bit _Altkeymap be set, and hence when released
    ; the release code for this 'normal' key will never be sent
    ; instead the release code for the alternative key will be sent.
    ; To 'fix' this put the release code at the end of CHECK_KEY_STATE ( where the
    ; alt keymap bit is set ).i.e. break prefix+scancode for normal key.
    NOP
    NOP
    NOP
    RETLW    H'83'        ; scan code for F7 ( in 'normal' mode )
; R4 - C1 i.e. key 4
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'76'        ; send scancode for 'ESC'  instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'6B'        ; scan code 'arrow left' '<-'
; R1 - C2 i.e. key 5
    NOP
    NOP
    NOP    
    RETLW    H'06'    ;  scan code 'F2' hex06
; R2 - C2 i.e. key 6
    NOP
    NOP
    NOP    
    RETLW    H'03'    ; scan code 'F5'
; R3 - C2 i.e. key 7
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'0D'        ; send scancode for 'horizontaltab' HT instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'75'        ; scan code 'arrow up' '^'
; R4 - C2 i.e. key 8
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'14'        ; send scancode for 'left ctrl' instead
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'72'        ; scan code 'arrow down'
; R1 - C3 i.e. key 9
    NOP    
    NOP    
    NOP    
    RETLW    H'04'    ; scan code 'F3'
; R2 - C3 i.e. key 10
    NOP    
    NOP    
    NOP    
    RETLW    H'0B'    ; scan code 'F6'
; R3 - C3 i.e. key 11
    NOP    
    NOP    
    NOP    
    RETLW    H'0A'    ; scan code 'F8'
; R4 - C3 i.e. key 12
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'11'        ; send scancode for 'left alt' instead
    BSF    STATUS,C ; set carry ( i.e. extended code )
    RETLW    H'74'    ; scan code 'arrow right' '->'
; R1 - C4 i.e. key 13
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'6C'        ; send scancode for numeric '7' instead
    NOP    
    RETLW    H'3D'    ; scan code '7'
; R2 - C4 i.e. key 14
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'6B'        ; send scancode for numeric '4' instead
    NOP    
    RETLW    H'25'    ; scan code '4'
; R3 - C4 i.e. key 15
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'69'        ; send scancode for numeric '1' instead
    NOP    
    RETLW    H'16'    ; scan code '1'
; R4 - C4 i.e. key 16
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7B'        ; send scancode for numeric '-' instead
    NOP    
    RETLW    H'4A'    ; scan code '-' minus ( swe kbd )
; R1 - C5 i.e. key 17
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'75'        ; send scancode for numeric '8' instead
    NOP    
    RETLW    H'3E'    ; scan code '8'
; R2 - C5 i.e. key 18
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'73'        ; send scancode for numeric '5' instead
    NOP    
    RETLW    H'2E'    ; scan code '5'
; R3 - C5 i.e. key 19
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'72'        ; send scancode for numeric '2' instead
    NOP    
    RETLW    H'1E'    ; scan code '2'
; R4 - C5 i.e. key 20
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'45'        ; scan code '0' ( from keypad ) normal key
    BSF    STATUS,C     ; set carry ( i.e. extended code )
    RETLW    H'1F'        ; alt keycode ( windows start menu activate )
; R1 - C6 i.e. key 21
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7D'        ; send scancode for numeric '9' instead
    NOP    
    RETLW    H'46'    ; scan code '9'
; R2 - C6 i.e. key 22
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'74'        ; send scancode for numeric '6' instead
    NOP    
    RETLW    H'36'    ; scan code '6'
; R3 - C6 i.e. key 23
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'7A'        ; send scancode for numeric '3' instead
    NOP    
    RETLW    H'26'    ; scan code '3'
; R4 - C6 i.e. key 24
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'49'        ; scan code '.' ( swe kbd ) normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'4A'    ; send scancode for numeric '/' instead
; R1 - C7 i.e. key 25
    BTFSC    _AltKeymap    ; check for alternative keymap
    RETLW    H'79'        ; send scancode for numeric '+' instead
    NOP    
    RETLW    H'4E'    ; scan code '+'
; R2 - C7 i.e. key 26
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'66'        ; scan code 'back space' BS, normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'71'    ; send scancode for 'delete' instead
; R3 - C7 i.e. key 27
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'5A'        ; scan code 'enter', normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'5A'    ; send scancode for numeric enter instead ( note ! extended )
; R4 - C7 i.e. key 28
    BTFSS    _AltKeymap    ; check for alternative keymap
    RETLW    H'5A'        ; scan code 'enter', normal key
                ; use alternative keymap
    BSF    STATUS,C    ; set carry ( i.e. extended code )
    RETLW    H'5A'    ; send scancode for numeric enter instead ( note ! extended )
; R1 - C8 i.e. key 29
    NOP    
    NOP    
    NOP    
    RETLW    H'2C'    ; scan code 't'
; R2 - C8 i.e. key 30
    NOP    
    NOP    
    NOP    
    RETLW    H'24'    ; scan code 'e'
; R3 - C8 i.e. key 31
    NOP    
    NOP    
    NOP    
    RETLW    H'1B'    ; scan code 's'
; R4 - C8 i.e. key 32
    NOP    
    NOP    
    NOP    
    RETLW    H'2C'    ; scan code 't'

    END

 

4.png

 

Quer um cafezinho também?

 

Deu um show hein!

Os avisos que aparecem é que o código foi programado para o 16F84 em modo 'antigo',por isso não faço mais códigos para o 16F84,migrei tudo para o 16F628,deve ter alguma outra referencia no código ao 16F84.

A mensagem que o registrador não esta no banco 0 eu não achei o porque,comparando aos códigos que usam o 16F628...

Estou super agarrado a um projeto aqui e não tentei analisar o código,mas acho que funciona...

  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites
8 horas atrás, vtrx disse:

não tentei analisar o código

Se você fizesse isso, você era o chuck norris. De fato nem eu. Apenas foquei nas msgs de erro e consultei algumas décadas de calo.

Aquele warning significa que o assemblador não tem certeza que o registro TRIS está no banco 1. Segundo ele, poderia estar no banco 0. Mas 2º o d.s. está sim no 1. Portanto penso que pode ser ignorado.

 

edit...

Paranóia...

Coloque "errorlevel -302" logo abaixo do "#include c:\...\p16f628a.inc" que cancela o warning do bank1. Mas mantém o warning  da supressão do warning ... voltou a nóia!...😡

 

 

15 horas atrás, marcoxr365 disse:

essa tal de chiconcunha é terrivel.

Que 'zica' hein? Vá na Fé e tome um copo de água com ela. Pronto! Sua cura começou! É sério! Você não precisa compreender, apenas aceitar mas é algo como: as energias de sua fé alteram algumas características químicas da água. Afinal sua característica básica é essencialmente a 'essência da vida'.

abç

Compartilhar este post


Link para o post
Compartilhar em outros sites

Isadora eu queria agradecer a você pela sua ajuda com codigo ele funcionou perfeitamente no projeto mas eu queria t pedir mas uma ajuda ...... porque esse codigo ele ta funcionando com 12 teclas mas o teclado tem 16 botões ficou faltando 4 botões.... eu consegui mudar as letras de cada tecla mas ficou faltando 4 botões será que tem como agregar mas 4 botões nesse codigo Isadora? Só você pode me ajudar isadora! Por favor meu amor só mas uma ajuda!! graças a Deus eu melhorei da chicongunha e agora rs...eu tentei colocar ele no proteus mas não funcionou.... mas na pratica ele funcionou perfeito.. gravei o codigo no pic e funcionou certinho só que ta faltando 4 botões por favor isadora só mas essa ajuda!!!

adicionado 2 minutos depois

Que 'zica' hein? Vá na Fé e tome um copo de água com ela. Pronto! Sua cura começou! É sério! Você não precisa compreender, apenas aceitar mas é algo como: as energias de sua fé alteram algumas características químicas da água. Afinal sua característica básica é essencialmente a 'essência da vida'.

abç  

 

     Vtrx eu fiquei muito mal mas graças a Deus eu to melhor obrigado por suas palavras positivas!!!

Compartilhar este post


Link para o post
Compartilhar em outros sites

Nosso amigo @vtrx é phd em assembly pros pics.. Será que ele não pode te orientar melhor?

 

Mas antes, você deve publicar o esquema elétrico, desenhos, fotos, videos, etc de seu trabalho. Ou no mínimo mostrar de onde veio 'azidéia'. Isso facilita as análises bem como é o preço que o forum cobra de nós entende? É que seu trabalho e dúvidas resolvidas podem ser úteis pra membros do futuro,...  quiçá do presente. Mostra aí pra nós. Desculpinha como não sei tirar fotos, meu celular não presta, não sei publicar no forum e afins não colam ok?

abç

Compartilhar este post


Link para o post
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisar ser um membro para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar agora