PIC Microcontroler based Keyboards

The hardware consists of a PIC 16C84 or PIC 16F84, a 16 key xy matrix keypad, a 4.00 Mhz resonator with built-in caps, a red and a green LED, 2 330 ohm resistors, 1 4.7K resistor, 1 10K resistor, a printed circuit board, 10 pin header, one general purpose NPN transistor such as MPSA05, and one 5 V PCB DPDT Relay.

keypad.dwg - AutoCad schematic

keypad.dxf - dxf format of AutoCad drawing

;			KEYPAD.ASM
;                       written by
;                    Fr. Thomas McGahee
;             Don Bosco Technical High School
;                      202 Union Ave.
;                   Paterson, NJ  07502
;                tom_mcgahee@sigmais.com
;
;                       March, 1997


;                Microchip MPASM format
;           Specifically designed for PIC16C84
;              but will also run on 16F84
;                   with minor changes
;
; note: written in all lower case so case sensitivity doesn't matter.
; however: set assembler to case-insensitive, except within strings using /c- option
;


;
; directives
;
; search for ### to locate items that need to be changed if using the 16f84
; instead of the 16c84




		list		p=16c84		;this directive must come first
						;### p=16f84 if using 16f84




; instead of using the [ include <16c84.inc> ] directive, we have placed the 
; contents of the microchip-supplied include file below for documentation purposes.
;
; this section defines configurations, registers, and other useful bits of
; information for the pic16c84 microcontroller.  these names are taken to match 
; the latest data sheets as closely as possible.  


; note that the processor must be selected before this file is 
; included.  the processor may be selected the following ways:


;       1. command line switch:
;               c:\ mpasm myfile.asm /pic16c84
;       2. list directive in the source file	;(this is the method chosen here)
;               list   p=pic16c84		;### 16c84.
;       3. processor type entry in the mpasm full-screen interface


;==========================================================================
;
;       revision history
;
;==========================================================================


;rev:   date:    reason:


;1.00   10/31/95 initial release


;==========================================================================
;
;       verify processor
;
;==========================================================================


        ifndef __16c84	;### 16c84. change to ifndef __16f84 if using 16f84
           messg "processor-header file mismatch.  verify selected processor."
        endif


;==========================================================================
;
;       register definitions
;
;==========================================================================


w                            equ     h'0000'
f                            equ     h'0001'


;----- register files------------------------------------------------------


indf                         equ     h'0000'
tmr0                         equ     h'0001'
pcl                          equ     h'0002'
status                       equ     h'0003'
fsr                          equ     h'0004'
porta                        equ     h'0005'
portb                        equ     h'0006'
eedata                       equ     h'0008'
eeadr                        equ     h'0009'
pclath                       equ     h'000a'
intcon                       equ     h'000b'


option_reg                   equ     h'0081'
trisa                        equ     h'0085'
trisb                        equ     h'0086'
eecon1                       equ     h'0088'
eecon2                       equ     h'0089'


;----- status bits --------------------------------------------------------


irp                          equ     h'0007'
rp1                          equ     h'0006'
rp0                          equ     h'0005'
not_to                       equ     h'0004'
not_pd                       equ     h'0003'
z                            equ     h'0002'
dc                           equ     h'0001'
c                            equ     h'0000'


;----- intcon bits --------------------------------------------------------


gie                          equ     h'0007'
eeie                         equ     h'0006'
t0ie                         equ     h'0005'
inte                         equ     h'0004'
rbie                         equ     h'0003'
t0if                         equ     h'0002'
intf                         equ     h'0001'
rbif                         equ     h'0000'


;----- option bits --------------------------------------------------------


not_rbpu                     equ     h'0007'
intedg                       equ     h'0006'
t0cs                         equ     h'0005'
t0se                         equ     h'0004'
psa                          equ     h'0003'
ps2                          equ     h'0002'
ps1                          equ     h'0001'
ps0                          equ     h'0000'


;----- eecon1 bits --------------------------------------------------------


eeif                         equ     h'0004'
wrerr                        equ     h'0003'
wren                         equ     h'0002'
wr                           equ     h'0001'
rd                           equ     h'0000'


;==========================================================================
;
;       ram definition
;
;==========================================================================


        __maxram h'af'
        __badram h'07', h'30'-h'7f', h'87' ;### 16c84.
;(__badram h'07', h'50'-h'7f', h'87' ;use *this* one if using 16f84)


;==========================================================================
;
;       configuration bits
;
;==========================================================================


_cp_on                       equ     h'3fef'
_cp_off                      equ     h'3fff'
_pwrte_on                    equ     h'3fff'
_pwrte_off                   equ     h'3ff7'
_wdt_on                      equ     h'3fff'
_wdt_off                     equ     h'3ffb'
_lp_osc                      equ     h'3ffc'
_xt_osc                      equ     h'3ffd'
_hs_osc                      equ     h'3ffe'
_rc_osc                      equ     h'3fff'


;end of <include> file stuff




;==============================================================================
; set configuration bits
;==============================================================================


;we have to set the configuration bits
;	__config a & b & c 
;       _rc_osc, _xt_osc, _hs_osc, _lp_osc oscillator type
;       _wdt_on, _wdt_off watchdog timer
;       _cp_on, _cp_off code protect
;       _pwrte_on, _pwrte_off power up timer enable


		__config 	_xt_osc & _wdt_off & _pwrte_on & _cp_off




;==============================================================================
; some constant equates
;==============================================================================


; note use of 4.00 mhz xtal. this will affect timing loops


xtal_freq	=		d'4000000'	;crystal frequency
clock		=		xtal_freq/4	;base operating frequency






;==============================================================================
; pic16c84 pinouts and hardware details
;==============================================================================




;               pic16c84 pinouts
;
;       ra2     <1>     <18>    ra1
;       ra3     <2>     <17>    ra0
;  (oc) ra4/tmr0<3>     <16>    osc1/clkin	
;       !mclr!  <4>     <15>    osc2/clkout
;       gnd     <5>     <14>    +2 to +6 volts
;       rb0/int <6>     <13>    rb7
;       rb1     <7>     <12>    rb6
;       rb2     <8>     <11>    rb5
;       rb3     <9>     <10>    rb4
;
;osc1 & osc2 using 4.00 mhz ceramic resonator or xtal with 20 pf caps on each lead.
;
;!mclr! tied high via 10K. use a switch to force it low for manual reset.


;porta assignments


;ra4 and ra3 are not used in this design.


;ra0 is connected to cathode of green led with 330 ohm to +5.
;ra1 is connected to cathode of red led with 330 ohm to +5.
;ra2 is connected to 1K to base of npn. npn drives 5v relay.
;coil of relay is shunted with diode. cathode to v+, anode to npn collector.
;common and normally open contacts of relay are used to control door opener.


;==============================================================================
; porta *bit* assignments
;==============================================================================


n_red		equ	0	;low on ra0 turns red led on
n_green		equ	1	;low on ra1 turns green led on
relay		equ	2	;high on ra2 turns relay on
ra3		equ	3	;**not used**
ra4		equ	4	;**not used**


;==============================================================================
; portb *bit* assignments
;==============================================================================


;rb7-rb0 are ttl. weak pullups are programmed using option_reg<7>=0
;rb7-rb4 assigned as *inputs*
;rb7-rb4 will generate an interrupt. set intcon<3> rbie=1. intcon<0> rbif is flag. software reset.
;
;rb3-rb0 are assigned as *outputs*. low is output to "scan" the inputs of the keypad.


;==============================================================================
; keypad details
;==============================================================================


;keypad layout (grayhill 8622 with additional marking 8637. 8 pins)
;pin numbers of keypad shown in <> brackets
;
;		<5>	<6>	<7>	<8>
;	<1>	7	8	9	(up)
;	<2>	4	5	6	(down)
;	<3>	1	2	3	(run)
;	<4>	(load)	0	(clr)	(ent)


; *top view* of keypad pins: 1,2,3,4,5,6,7,8
; pin 1 is marked with the letter M on my units.
; interconnect was done using a 20 pin idc cable, with the #1
; pin of the connector going to keypad #1 and pcb header #1.
; being cheap, i used a ten pin header strip on the pcb.
; pcb header pins 9 and 10 are on pc board, but not used.


; inputs will be rb4,5,6,7  outputs will be rb0,1,2,3
; so we can take advantage of interrupt on rb 4-7 going low.
; this leads us to the following coding/decoding scheme
; based on keeping the interface wiring straight forward.
; *keypad pin* 1=rb0 2=rb1 3=rb2 4=rb3 5=rb4 6=rb5 7=rb6 8=rb7
; x refers to the original code, and y to the decoded value,
; which has been chosen to be a *character* type e.g. '1' instead of 1.


; note that the method used to allow a variable length code
; requires a dummy filler code to pad out unused digits.
; h'00' has been chosen as the filler code, as it is very easy to test for.




;==============================================================================
; decoding of keypad *scancodes* to stored *character* codes:
;==============================================================================


xload	=	b'11100111'
yload	=	'l'		;load character 'l' (el), *not* a <'1'>
x0	=	b'11010111'
y0	=	'0'		;0
xclr	=	b'10110111'
yclr	=	'c'		;clear
xent	=	b'01110111'
yent	=	'e'		;enter
x1	=	b'11101011'
y1	=	'1'		;1 (one)
x2	=	b'11011011'
y2	=	'2'		;2
x3	=	b'10111011'
y3	=	'3'		;3
xrun	=	b'01111011'
yrun	=	'r'		;run
x4	=	b'11101101'
y4	=	'4'		;4
x5	=	b'11011101'
y5	=	'5'		;5
x6	=	b'10111101'
y6	=	'6'		;6
xdown	=	b'01111101'
ydown	=	'd'		;down
x7	=	b'11101110'
y7	=	'7'		;7
x8	=	b'11011110'
y8	=	'8'		;8
x9	=	b'10111110'
y9	=	'9'		;9
xup	=	b'01111110'
yup	=	'u'		;up


xx	=	b'00000000'	;filler code. allows simple z test for end.


;==============================================================================
; eeprom data area    64x8 bytes of eeprom starting at h'2100'
;==============================================================================


	org	h'2100'		;set data eeprom origin


;user codes can be from 1 to 8 digits in length
;and may also contain <load> <up> and <down>.
;the special codes <clr> <ent> and <run> may not appear in a user code


;since codes are stored decoded, use y0-y9 and yup, ydown, and yload to specify code set.
;xx indicates blank filler code


usercode0       de  xx, xx, xx, xx, xx, xx, xx, xx     	;user 1
usercode1       de  xx, xx, xx, xx, xx, xx, xx, xx     	;user 2
usercode2       de  xx, xx, xx, xx, xx, xx, xx, xx     	;user 3
usercode3       de  xx, xx, xx, xx, xx, xx, xx, xx     	;user 4
usercode4       de  xx, xx, xx, xx, xx, xx, xx, xx     	;user 5
usercode5       de  xx, xx, xx, xx, xx, xx, xx, xx	;user 6
umastercode	de  y1, y2, y3, y4, y5, y6, y7, y8     	;user mastercode.
							; 1-8 digits
							; *can* be changed.
mastercode      de  y1, y0, y2, y8, y1, y9, y4, y6      ;factory mastercode.
							; 8 digits
							; can *not* be changed.


;==============================================================================
; explanation of how data entry occurs
;==============================================================================


;normal codes: hit <clr>, enter code, hit <ent>. relay will close for 10 sec.
;you may terminate relay closure early by hitting *any* key.


;hitting <clr> will always re-synchronize system.


;in addition to allowing regular relay closure, the mastercodes can be used
;to do special things. the factory master code is never changed, but the user
;may enter their own easy-to-remember personal mastercode, and up to 6
;regular user codes.


;hit <clr>, enter one of the mastercodes, hit <run>, hit set # <0-6>, hit <load>,
;enter 1-8 digits, and terminate with <ent>. if no digits are entered, then set is
;cleared. This allows you to remove any of the existing codes, except the factory
;supplied mastercode.


;user set #0 is actually the user-selectable mastercode. It may be used just
;like the regular factory-set mastercode. it is suggested that this be an
;8 digit code.


;remember that <load>, <up>, and <down> are all valid keys and can be used
;the same as if they were digits. <clr>, <ent>, and <run> may *not* be used
;as part of the user code, since they are used to direct program flow and select
;what type of entry is in progress.




;==============================================================================
; file register ram useage h'oc' to h'2f'. 36 bytes available
;==============================================================================


;(### if using 16f84 sram begins at h'0c' and ends at h'4f'. room for 68 bytes.)


;note that the assembler can *not* use db or similar codes to load the contents.


;the use of the cblock/endc directives allow us to assign ram storage
;without having to use equate statements. this allows the programmer
;to add/remove/move variable assignments without having to worry about fixing
;up the actual assigned *value*. makes life simpler for the programmer!


		cblock	h'0c'


keycode		;storage for undecoded/decoded keycode
savew		;for inthandler
savestatus	;for inthandler
savefsr		;for inthandler
eeadr1		;storage for eeadr 
xmillisec	;outer loop counter for milliseconds
ymillisec	;inner loop counter for milliseconds
keycounter1	;outer loop counter
keycounter2	;inner loop counter
ok		;non-zero means ok
blinks		;number of blinks to perform (green)
exitkey		;holds value of key used to terminate entry
usercounter1	;multiple uses involving user key entry
user0		;user input is stored here.
user1		; user0-user7 hold the code entered
user2		; via the keypad both for normal entry,          	
user3		; master code entry, and the entry of         	               
user4		; *new* user code         	
user5
user6
user7


		endc


;==============================================================================
; program space  1kx14 (h'400') can only be changed via programmer.
;==============================================================================


;because we stay within a 1K bank, no need to worry about bank selection.
		
	org	h'0000'	;set code origin within rom space


start
	goto	setup	;must get past interrupt vector at h'0004'


;==============================================================================
; interrupt handler:	handles all keypad input and decoding.
;			*decoded* value is placed in <keycode>.
;
;			main program looks for non-zero value while in loop.
;			after being used, main program must clrf keycode!
;==============================================================================


;               there is a single interrupt location at 004
;		in our case there is only 1 source of interrupt,
;		so we don't have to worry about that.


;		see <setup> section for interrupt assignment stuff.


	org	h'0004'	;interrupt vector at h'0004'


;interrupt occurs when any key is hit.
;this routine handles debounce and returns the key code in keycode.
;on any error it returns 0 in keycode. debounce is executed at
;beginning and end of keypress. Only the first key detected will
;be captured if there are multiple simultaneous keys pressed.
;keypress can be of any duration.


inthandler
				;global interrupts automatically
				;disabled on entry!
	movwf	savew		;save w register!
	swapf	status,w	;(twisted)
				; swapf used so as not to disturb
				; the contents of the status register
	movwf	savestatus	;save status register!
	movf	fsr,w		;save fsr!
	movwf	savefsr
						
;instead of doing a regular delay on entry for debounce, we 'waste' the time
;doing something useful: we search for the scancode. We do this 65K times
;before assuming there was no key hit.


	clrf	keycode		;clear keycode.
	clrf	keycounter1	;initialize outer counter


loop256x256
	decfsz	keycounter1,f	;256 outer loops...
	goto	more256x256
	goto	nogood		;65K tries & it was no good!


more256x256
	clrf	keycounter2	;initialize inner counter


loopinner256
	decfsz	keycounter2,f	;256 inner loops per outer loop...
	goto	innertest
	goto	loop256x256


innertest
	comf	portb,w
	andlw	b'11110000'	;test for *any* key hit first...
	btfsc	status,z
	goto	loopinner256
				;if we got here, we might have a hit.
		
scankey0
	movlw	b'11111110'	;load bitmask
	movwf	portb		;twiddle one bit at a time
	nop			;give signal a chance to settle down
	movf	portb,w		;read current value into keycode
	movwf	keycode		;now we can play with it.
	comf	keycode,w	;get complemented form
	andlw	b'11110000'	;look only at high bits
	btfss	status,z	;no hit sets z flag
	goto	decode		;if hit, decode & debounce...
					;if no hit, check next key set
scankey1
	movlw	b'11111101'	;load bitmask
	movwf	portb		;twiddle one bit at a time
	nop			;give signal a chance to settle down
	movf	portb,w		;read current value into keycode
	movwf	keycode		;now we can play with it.
	comf	keycode,w	;get complemented form
	andlw	b'11110000'	;look only at high bits
	btfss	status,z	;no hit sets z flag
	goto	decode		;if hit, decode & debounce...
					;if no hit, check next key set
scankey2
	movlw	b'11111011'	;load bitmask
	movwf	portb		;twiddle one bit at a time
	nop			;give signal a chance to settle down
	movf	portb,w		;read current value into keycode
	movwf	keycode		;now we can play with it.
	comf	keycode,w	;get complemented form
	andlw	b'11110000'	;look only at high bits
	btfss	status,z	;no hit sets z flag
	goto	decode		;if hit, decode & debounce...
				;if no hit, check next key set
scankey3
	movlw	b'11110111'	;load bitmask
	movwf	portb		;twiddle one bit at a time
	nop			;give signal a chance to settle down
	movf	portb,w		;read current value into keycode
	movwf	keycode		;now we can play with it.
	comf	keycode,w	;get complemented form
	andlw	b'11110000'	;look only at high bits
	btfss	status,z	;no hit sets z flag
	goto	decode		;if hit, decode & debounce...
	goto	loopinner256	;if there were no hits at all!


nogood
	clrf	keycode		;reset keycode to 0 (indicates no hit)


;the following decodes the scan code value into a ycode character.
;due to the nature of the scancodes, we can not use a decode table efficiently,
;so we will use a skip method instead.


decode
	movf	keycode,w	;copy to w and set flags
	btfsc	status,z
	goto	fillercode	;if keycode=0 convert to filler


decode0
	movf	keycode,w	;reload keycode to convert		
	sublw	x0		;compare with xcode definition
	btfss	status,z
	goto	decode1		;if no match, check next
	movlw	y0		;if match, load ycode into w
	goto	savecode	;save converted code.


decode1
	movf	keycode,w	;reload keycode to convert		
	sublw	x1		;compare with xcode definition
	btfss	status,z
	goto	decode2		;if no match, check next
	movlw	y1		;if match, load ycode into w
	goto	savecode	;save converted code.


decode2
	movf	keycode,w	;reload keycode to convert		
	sublw	x2		;compare with xcode definition
	btfss	status,z
	goto	decode3		;if no match, check next
	movlw	y2		;if match, load ycode into w
	goto	savecode	;save converted code.


decode3
	movf	keycode,w	;reload keycode to convert		
	sublw	x3		;compare with xcode definition
	btfss	status,z
	goto	decode4		;if no match, check next
	movlw	y3		;if match, load ycode into w
	goto	savecode	;save converted code.


decode4
	movf	keycode,w	;reload keycode to convert		
	sublw	x4		;compare with xcode definition
	btfss	status,z
	goto	decode5		;if no match, check next
	movlw	y4		;if match, load ycode into w
	goto	savecode	;save converted code.


decode5
	movf	keycode,w	;reload keycode to convert		
	sublw	x5		;compare with xcode definition
	btfss	status,z
	goto	decode6		;if no match, check next
	movlw	y5		;if match, load ycode into w
	goto	savecode	;save converted code.


decode6
	movf	keycode,w	;reload keycode to convert		
	sublw	x6		;compare with xcode definition
	btfss	status,z
	goto	decode7		;if no match, check next
	movlw	y6		;if match, load ycode into w
	goto	savecode	;save converted code.


decode7
	movf	keycode,w	;reload keycode to convert		
	sublw	x7		;compare with xcode definition
	btfss	status,z
	goto	decode8		;if no match, check next
	movlw	y7		;if match, load ycode into w
	goto	savecode	;save converted code.


decode8
	movf	keycode,w	;reload keycode to convert		
	sublw	x8		;compare with xcode definition
	btfss	status,z
	goto	decode9		;if no match, check next
	movlw	y8		;if match, load ycode into w
	goto	savecode	;save converted code.


decode9	
	movf	keycode,w	;reload keycode to convert		
	sublw	x9		;compare with xcode definition
	btfss	status,z
	goto	decodeload	;if no match, check next
	movlw	y9		;if match, load ycode into w
	goto	savecode	;save converted code.


decodeload
	movf	keycode,w	;reload keycode to convert		
	sublw	xload		;compare with xcode definition
	btfss	status,z
	goto	decodeclr	;if no match, check next
	movlw	yload		;if match, load ycode into w
	goto	savecode	;save converted code.


decodeclr
	movf	keycode,w	;reload keycode to convert		
	sublw	xclr		;compare with xcode definition
	btfss	status,z
	goto	decodeent	;if no match, check next
	movlw	yclr		;if match, load ycode into w
	goto	savecode	;save converted code.


decodeent
	movf	keycode,w	;reload keycode to convert		
	sublw	xent		;compare with xcode definition
	btfss	status,z
	goto	decoderun	;if no match, check next
	movlw	yent		;if match, load ycode into w
	goto	savecode	;save converted code.


decoderun
	movf	keycode,w	;reload keycode to convert		
	sublw	xrun		;compare with xcode definition
	btfss	status,z
	goto	decodedown	;if no match, check next
	movlw	yrun		;if match, load ycode into w
	goto	savecode	;save converted code.


decodedown
	movf	keycode,w	;reload keycode to convert		
	sublw	xdown		;compare with xcode definition
	btfss	status,z
	goto	decodeup	;if no match, check next
	movlw	ydown		;if match, load ycode into w
	goto	savecode	;save converted code.


decodeup
	movf	keycode,w	;reload keycode to convert		
	sublw	xup		;compare with xcode definition
	btfss	status,z
	goto	fillercode	;if no match, use fillercode
	movlw	yup		;if match, load ycode into w
	goto	savecode	;save converted code.
		
fillercode
	clrf	keycode		;set up filler code
	goto	intreturn	;skip delay since it is empty
		
savecode
	movwf	keycode		;store converted keycode in keycode!
	bsf	porta,n_red 	;turn off red led until key is
				;released. used as a visual
				;indicator that we got it.
	movlw	b'11110000'	;allow any key hit to be detected
	movwf	portb		;make it so
	nop			;wait to stabilize.
		
release
	comf	portb,w		;wait for all keys to be released...
	andlw	b'11110000'	;only have to check upper 4 bits.
	btfss	status,z
	goto	release		;if any key is down, wait...
	movlw	d'25'		;set debounce delay to 25 ms.
	call	wmillisec	;wait after last key is released.
		
				;clean up and get ready to return.
					
	comf	portb,w		;check for *really* bad bounce!
	andlw	b'11110000'	;only have to check upper 4 bits.
	btfss	status,z
	goto	release		;if key is still down, wait...
	movlw	d'25'		;set debounce delay to 25 ms
	call	wmillisec	;wait after last key is released
		
				;clean up and get ready to return.
		
intreturn
	movlw	b'11110000'	;set up default keyscan (all)
	movwf	portb		;so new key presses can be detected.
	bcf	porta,n_red	;turn red back on after keypress
		
	bcf     intcon,rbif	;clear appropriate interrupt flag!
				;(in this example we used rbif)
				
				;restore stuff we saved.
				
	movf	savefsr,w	;restore fsr!
	movwf	fsr
	swapf	savestatus,w	;untwist twisted saved status
	movwf	status		;restore normalized status!
	swapf	savew,f		;restore w! first twist nibbles
	swapf	savew,w		;then twist again and place 
				;result in w.
				;we had to use swapf to ensure that
				;status register was not clobbered
				;while recovering w.


	retfie			;return from interrupt!
		
	;gie is auto-re-enabled.
	;keycode contains character code if OK
	;else keycode is 00000000.


;==============================================================================
; setup is the initialization code for ports and registers
;==============================================================================


;page 1 stuff includes option_reg, trisa, trisb, eecon1, eecon2


setup
	bsf     status,rp0	;allow access to page 1 stuff!


	movlw	b'00000000'	;set porta direction for i/o pins
	movwf	trisa		;0=output  1=input


	movlw	b'11110000'	;set portb direction for i/o pins
	movwf	trisb		;0=output  1=input
		
	;although we could use a simple movlw/movwf option_reg
	;instruction to setup all option_reg bits at once, it is 
	;preferable to use individual bit twiddling during 
	;development, so changes can be easily made and commented.


	bcf     option_reg,not_rbpu ;!rbpu! rb_pullup 
				; 0=enabled 1=disabled
				; enables only portb inputs.
				;(enable weak pullups on portb inputs)
						
	bcf     option_reg,intedg ;intedg 
				; 0=int on falling 1=int on rising
				; note: intedg and t0se use
				; opposite definitions!
				;(interrupt on falling edge of portb)
				
	bcf     option_reg,t0cs	;t0cs timer0clocksource 
				; 0=internal clkout 1=ra4/int 
				;(enable internal clkout)


	bcf     option_reg,t0se	;t0se timer0signaledge 
				; 0=inc on rising 1=inc on falling
				; note: intedg and t0se use 
				; opposite definition!
				;(inc on rising edge of clock)


	bcf     option_reg,psa	;psa pre scaler assignment
				; 0=tmr0 1=wdt
				;ps2-ps0 determine prescalerrate, 
				; which is dependent also on whether 
				; tmr0 or wdt is selected:
				;wdt from 0-7 is 
				; div by 1 2 4 8 16 32 64 128
				;tmr0 from 0-7 is 
				; div by 2 4 8 16 32 64 128 256


				;we have chosen tmr0 prescaler of 2.
				;we will not be using tmr0 or the wdt,
				;but we will state and set
				;bits as if we might use tmr0
				;since we do *not* want to use wdt.
				
	bcf     option_reg,ps2	;ps2 of psa
	bcf     option_reg,ps1	;ps1 of psa
	bcf     option_reg,ps0	;ps0 of psa


	bcf     status,rp0	;allow access to page 0 stuff again.
				; back to normal!


;==============================================================================
; enable interrupt related stuff
;==============================================================================


;intcon register: 


;enables... 1=enable 0=disable
;<7>=gie=global_int_enable
;<6>=eeie=eeprom_int_enable
;<5>=t0ie=tmr0_int_enable (enables <2> t0if)
;<4>=inte=int_enable (rb0/int) (enables <1> intf)
;<3>=rbie=rb_int_enable (enables <0> rbif)


;flags. software reset. 0=reset 1=flagged
;<2>=t0if=tmr0_int_flag
;<1>=intf=int_flag (rb0/int)
;<0>=rbif=rb_int_flag (rb7-rb4)


;upon power up and !mclr!, intcon will contain 0000 000x
;this means that initially all interrupts are disabled.


;note: option_reg register is used to program use of tmr0 and wdt




	clrf	tmr0		;reset tmr0 (not used)
	clrf	intcon          ;clear any pending interrupt requests
	bsf     intcon,gie	;set global interrupt enable
	bsf     intcon,rbie	;enable interrupt on portb change
				;pins 7,6,5,4


	clrf	keycode		;clear any existing keycode.
	bcf	porta,relay	;turn relay off.
		


;==============================================================================
; main program
;==============================================================================


;main program is state-driven.
;entry begins with the user hitting the clr key.
;this is indicated with a quick blink of the green led.
;the red led is usually on. it goes off while any key is depressed.
;key input is handled by an interrupt driven routine based on 
;int on portb<7:4>.


;extensive debouncing ensures accurate entry of keys.
;keys are decoded from row/column form into simple ascii character form
;by interrupt handler. 
;ascii character code representations used are:
; '0'-'9' and 'c' clr, 'e' ent, 'r' run, 'u' up, 'd' down, 'l' load.


;the clr key will always cause synchronization of the system, and
;*all* valid entry begins with a clr. 


;green led is blinked on clr release.
;digits are then entered. valid codes can be from 1 to 8 digits long.
;while any digit is being entered, the red led will go off.
;this provides a visual feedback of proper data entry.


;normal entry mode: if a set of key entries ends with ent, 
;then all 8 code sets (including the master codes) are compared 
;with the user entries. entry of more than 8 digits will be detected 
;as a 'no-match'.


;if entry was 1-8 digits, these are compared and if any set matches,
;then the green and red leds will alternate on and off for ten
;seconds while the relay is closed for ten seconds. at the end
;of ten seconds the relay will re-open and the system will be waiting
;for user input. user may terminate the relay on condition
;by hitting any key.


;run mode: if the run key is used to terminate a set of digits, then
;the system goes into a special mode that allows the user to change
;any of 7 sets of user entry codes. the ** master code ** that must be
;entered (followed by run) *must* be 8 digits. since there must
;be some means to indicate which set is to be changed, the following
;sequence is followed:
		
;1) clr followed by 8 digit master code followed by run
;2) flashing red led comes on to indicate you have entered run mode.
;   green led comes on steady
;3) next entry *must* be a digit from 0-6 to indicate which *set*.
;   (0 indicates entry of new user-mastercode)
;   as soon as this digit has been entered the red led goes steady,
;   and the green led will stay on to indicate run normal mode.
;4) the next entry *must* be the load key, or input is terminated.
;5) up to 8 digits may now be entered.
;   a) if *no* digits are entered, set is invalid (never matches).
;      this is useful in *removing* a code without entering a new one.
;   b) if more than 8 digits are entered, then entry is ignored.
;6) entry of valid set is complete when terminating ent key is hit.
;7) the set # entered is now available.


mainprog
	movlw	b'11110000'	;scan all 4 lines at once.
	movwf	portb		;initialize keyboard scanning
	bcf	porta,relay	;turn off relay
	bcf	porta,n_red	;turn red led on while waiting for keypress.
				;interrupt handler will turn red led off
				;during keypress and back on when 
				;key is released.
	bsf	porta,n_green	;turn green led off.
	
	movf	keycode,w	;check for yclr (may have occurred in subroutine)
	sublw	yclr
	btfsc	status,z
	goto	gotyclr		;yclr is handled special.
	
	bsf	porta,n_green	;turn green led off. when green is on
				;it indicates special master code mode.	
	clrf	keycode		;clear keycode to allow new keycode.


mainloop
	movf	keycode,w	;see if keycode is present
	btfsc	status,z
	goto	mainloop	;loop while empty...
				;eventually inthandler loads keycode
	sublw	yclr		;was it yclr?
	btfsc	status,z
	goto	gotyclr		;if so,continue...
	goto	mainloop	;else keep looking for yclr




gotyclr
	bcf	porta,n_green 	;blink green led *once* to indicate yclr
	movlw	d'20'
	call	wmillisec
	bsf	porta,n_green 	;turn green led off.
	clrf	keycode		;clear keycode & wait for next key
	
gotyclr1
	movf	keycode,w	;check keycode
	btfsc	status,z
	goto	gotyclr1	;loop while empty...
				;eventually inthandler loads keycode
	movf	keycode,w	;it might be another yclr!
	sublw	yclr
	btfsc	status,z
	goto	gotyclr		;if so, handle it properly.


	call	getuser		;get a set of inputs. (have 1 already)


	movf	exitkey,w	;check for exitmode.
	sublw	0		;was it error?
	btfsc	status,z
	goto	mainprog	;on error or yclr start all over.
	movf	exitkey,w
	sublw	yent		;check for yent
	btfsc	status,z
	goto	wasent		;handle elsewhere if yent.
	movf	exitkey,w
	sublw	yrun		;check for yrun
	btfsc	status,z
	goto	wasrun		;handle elsewhere if yrun.
	goto	mainprog	;anything else was an error.
				;(yclr is handled by mainprog)
		
wasent
				;compare user set with all eight
				;code sets. If ANY set matches,
				;then open door. wait, then goto mainprog
		
	movlw	0*8		;8 checks are very similar...
	movwf	eeadr		;set up eeadr for proper set.
	call	compare8	;do generic compare of user/eeprom sets
	movf	ok,w		;if ok is not zero then we have a match.
	btfss	status,z	;0-5 are 6 user sets, and 
	goto	entok		;6 and 7 are mastercode sets.


	movlw	1*8		;if there was no match, then program
	movwf	eeadr		;checks *next* set of codes in eeprom
	call	compare8
	movf	ok,w
	btfss	status,z
	goto	entok
		
	movlw	2*8		;etc.
	movwf	eeadr
	call	compare8
	movf	ok,w
	btfss	status,z
	goto	entok
		
	movlw	3*8		;etc.
	movwf	eeadr
	call	compare8
	movf	ok,w
	btfss	status,z
	goto	entok
		
	movlw	4*8		;etc.
	movwf	eeadr
	call	compare8
	movf	ok,w
	btfss	status,z
	goto	entok
		
		
	movlw	5*8		;etc
	movwf	eeadr
	call	compare8
	movf	ok,w
	btfss	status,z
	goto	entok


	movlw	6*8		;include user mastercode check.
	movwf	eeadr
	call	compare8
	movf	ok,w
	btfss	status,z
	goto	entok
		
	movlw	7*8		;include factory mastercode check.
	movwf	eeadr
	call	compare8
	movf	ok,w
	btfss	status,z
	goto	entok
	goto	mainprog	;if no sets matched
				;then incorrect codes were entered.


;if we got this far, then it was a complete match!


entok
	bsf	porta,relay	;turn relay on
	movlw	d'100'		;for 100*100 msec=1 sec
	movwf	usercounter1
	clrf	keycode		;clear keycode to allow new key
ldelay	
	movf	keycode,w	;any key hit will terminate.
	btfss	status,z
	goto	mainprog
	
	bcf	porta,n_red	;turn red led on.
	bsf	porta,n_green	;and green led off.
	movlw	d'50'
	call	wmillisec	;delay 50 msec
	
	bsf	porta,n_red	;turn red led off.
	bcf	porta,n_green	;and green led on.
	movlw	d'50'
	call	wmillisec	;delay 50 msec
	
	decfsz	usercounter1,f	;update counter
	goto	ldelay
	goto	mainprog


;run mode: if the run key is used to terminate a set of digits, then
;the system goes into a special mode that allows the user to change
;any of 7 sets of user entry codes. the *** master code *** that must be
;entered (followed by run) *must* be 8 digits. since there must
;be some means to indicate which set is to be changed, the following
;sequence is followed:
		
;1) clr followed by 8 digit master code followed by run
;2) flashing red led comes on to indicate you have entered run set mode.
;   green led comes on steady
;3) next entry *must* be a digit from 0-6 to indicate which *set*.
;   (0 indicates entry of new user-mastercode)
;   as soon as this digit has been entered the red led goes steady,
;   and the green led will stay on to indicate run normal mode.
;4) the next entry *must* be the load key, or input is terminated.
;5) up to 8 digits may now be entered.
;   a) if *no* digits are entered, then set is made invalid (never matches)
;      this is useful in *removing* a code without entering a new one.
;   b) if more than 8 digits are entered, then entry is ignored.
;6) entry of valid set is complete when terminating ent key is hit.
;7) the set # entered is now available.




wasrun			;compare user set with mastercode.
			;and also with user-mastercode!
			;if ok, then allow new entry set
			;turn on green led.
			;1st key is pointer, next 8 max
			;get entered at pointer in eeprom
			;then goto mainprog
		
	movlw	6*8		;compare user with user-mastercode
	movwf	eeadr
	call	compare8
	movf	ok,w
	btfss	status,z
	goto	runok
		
	movlw	7*8		;compare user with factory-mastercode
	movwf	eeadr
	call	compare8
	movf	ok,w
	btfss	status,z
	goto	runok
	goto	mainprog 	;if no match, begin again.
		
runok	
	clrf	keycode		;clear out any old key data.
	bcf	porta,n_green 	;turn green led on.
	
runloop1
	bcf	porta,n_red 	;turn red led on.
	movlw	d'35'
	call	wmillisec	;delay 35 msec
	
	bsf	porta,n_red	;then turn red led off.
	movlw	d'35'
	call	wmillisec	;delay 35 msec
	
	movf	keycode,w	;interrupt will load it!
	btfsc	status,z
	goto	runloop1	;wait for key.
	
				;once a key is found
	bcf	porta,n_red	;turn red led on.
	bcf	porta,n_green	;turn green led on.
	
	sublw	yclr
	btfsc	status,z
	goto	mainprog	;handle yclr special.
	
	movf	keycode,w	;look at code again
	sublw	'6'		;allow only stuff <= '6'
	btfss	status,c	;if w<='6', c is set
	goto	mainprog	;if w>'6', c is cleared, so ignore.
	
	movf	keycode,w	;look at code again
	andlw	b'00000111'	;convert from ascii to binary
				; 0-6 max!
	movwf	eeadr1		;save it
	decf	eeadr1,f	;reduce address by 1
	btfss	eeadr1,7	;0-1 would give us 11111111
	goto	rollem		;if msb=0 handle normal.
	
	movlw	b'0000110'	;convert 0 to 6
	movwf	eeadr1		;so it is now user mastercode
				;then save it in eeadr1.
	
rollem	
	bcf	status,c	;initial carry in must be 0!
	rlf	eeadr1,f	;*2
	rlf	eeadr1,f	;*4
	rlf	eeadr1,f	;*8
		
	;we now have proper ee address set in eeadr1
	
eeadrok	
	clrf	keycode		;get ready for next key.
	
loadloop
	movf	keycode,w	;interrupt will load it!
	btfsc	status,z
	goto	loadloop	;wait for key.
	sublw	yload		;it *must* be the load key
	btfss	status,z
	goto	mainprog	;if not, forget it!
	clrf	keycode
	call	getuser		;get user input(s)


	movf	exitkey,w	;check for exitmode.
	sublw	yent		;was it ent?
	btfss	status,z
	goto	mainprog	;on error or yclr start all over.
		
ent2stuff
	movlw	d'8'		;eight digits to be moved
	movwf	usercounter1	;use this as a counter
	movf	eeadr1,w	;recover starting address
	movwf	eeadr		;set up ee address
	movlw	user0		;point to user set in ram.
	movwf	fsr		;make it indirect pointer.
	
nextent2
	movf	indf,w		;use indirect pointer
	movwf	eedata		;copy ram to eedata
			
	call	writeeedata	;use subroutine to do that.


	decfsz	usercounter1,f	;update loop
	goto	next1ent2
	goto	mainprog	;when done, start all over.
	
next1ent2
	incf	eeadr,f		;update ee address
	incf	fsr,f		;update indirect pointer
	goto	nextent2	;do all 8 digits...
		


;==============================================================================
; getuser: (subroutine) : gets up to 8 digits and a terminator
; returns exitkey with value of terminator, or 0 if error
; such as too many digits entered or clr hit during entry.
;==============================================================================
		
getuser
	clrf	exitkey		;get ready for next key...
	movf	keycode,w	;always check for yclr!
	sublw	yclr		;was it yclr?
	btfss	status,z
	goto	get8keys	;if not, do usual
	
handleyclr
	movlw	h'00'
	movwf	exitkey		;tell 'em via exitkey
	goto	checkret	;handle yclr special


get8keys
	clrf	user0		;clear all key holders
	clrf	user1
	clrf	user2
	clrf	user3
	clrf	user4
	clrf	user5
	clrf	user6
	clrf	user7
		
	movlw	user0
	movwf	fsr		;allow indirect addressing.
	clrf	usercounter1	;we handle 8 keys, 1-8
	
get8wait
	movf	keycode,w	;updated via interrupt!
	btfsc	status,z
	goto	get8wait	;if empty, keep looking...
	sublw	yclr		;always check for yclr
	btfsc	status,z
	goto	handleyclr	;yclear always synchronizes things!
		
				;now check for yent and yrun *early*.
					
ent
	movf	keycode,w	;get current key input into w
	sublw	yent		;yent means terminate entry
	btfsc	status,z
	goto	checkent	;handle yent elsewhere...
	movf	keycode,w	;check for yrun
	sublw	yrun
	btfsc	status,z
	goto	checkrun	;if yrun, handle special...
	
	;if you got here, then last key was not special
		
	movf	keycode,w
	movwf	indf		;indirect keycode to user elements
	clrf	keycode		;reset keycode to allow next key entry.
	incf	fsr,f		;update indirect pointer
	incf	usercounter1,f	;update counter
	movf	usercounter1,w
	sublw	9		;only 8 digits allowed max (1-8)
	btfss	status,z
	goto	get8wait	;if not '8' yet, get next entry...


	;if you got here, then 9th key was not special
	;which means it *must* be an error!
	
	goto	checkret	;return with exitkey=0
				;to indicate error.
		
checkent
	movlw	yent
	movwf	exitkey
	goto	checkret	;return with yent in exitkey.
		
checkrun
	movlw	yrun
	movwf	exitkey
	
checkret
	return			;return with yrun in exitkey.
					
compare8
	clrf	ok		;assume not ok at first.
	movf	user0,w		;look at first key entry
	btfsc	status,z
	goto	compareret	;return with error set if null set
				;(this prevents null set from being ok)


				;compare user set with eeadr set.
				;returns with ok non-zero if matched.
				
	movlw	1		;set ok for now.
	movwf	ok
	movlw	8
	movwf	usercounter1	;initialize counter to 8
	movlw	user0
	movwf	fsr		;allow indirect addressing
	
compareloop
	movf	indf,w		;recover user data
	bsf     status,rp0	;we do this stuff on page 1
	bsf	eecon1,rd	;set up read
	bcf     status,rp0	;we do usual stuff on page 0
	subwf	eedata,w	;compare with eeprom data
	btfss	status,z
	goto	compareerror	;if different, error!
	incf	eeadr,f		;get ready for next... direct
	incf	fsr,f		;and indirect
	decfsz	usercounter1,f	;update counter
	goto	compareloop	;if not all 8 done, do more.
	goto	compareret	;if all 8 matched, all done!
	
compareerror
	clrf	ok		;on error, say so.
	
compareret
	return


;==============================================================================
; eeprom stuff
;==============================================================================


;eecon1 register:
;<7,6,5> unused. rest are r/w.
;<4> eeif. eeprom_int_flag. see intcon eeie. can be software polled / reset.
;<3> wrerr. write_error flag (mclr or wdt). software reset.
;<2> wren. write_enable. 1=enable. software set/reset.
;<1> wr. write. set=1 to initiate write. cleared automatically by hardware.
;<0> rd. read. set=1 to initiate read. cleared automatically by hardware.
;
;reading/writing eeprom data area on-the-fly:
;
;accessed via eedata and eeadr. eedata is accessed for r/w data, and
;eeadr is used to hold the address of the desired data.
;eecon1 and eecon2 are eeprom_control registers to make sure that there
;can never be an accidental write to the eeprom.
;
;to read: 
;load eeadr with the desired address.
;set eecon1<0> rd=1. (it is automatically cleared by hardware)
;read eedata. data remains in eedata until next read/write.
;
;to write: follow example below exactly!




;==============================================================================
; writeeedata: (subroutine) : enter with eedata and eeadr loaded
;==============================================================================




writeeedata
	bsf     status,rp0	;we do this stuff on page 1
	bcf     eecon1,eeif	;we have to clear this sucker!
	bsf	eecon1,wren	;enable writes!
	bcf	intcon,gie	;disable interrupts!


	movlw	h'55'		; note: code sequence must be
	movwf	eecon2		;*exactly* as shown.
	movlw	h'aa'
	movwf	eecon2
	bsf	eecon1,wr


eewait  
        btfss	eecon1,eeif	;poll eeif.
	goto	eewait
	bcf	eecon1,eeif	;we have to clear this sucker!
	bcf	eecon1,wren	;disable further writes!
	bsf	intcon,gie	;re-enable interrupts!
	bcf	status,rp0	;back to page 0
	return


;==============================================================================
; wblinks: (subroutine) : enter with number of blinks in w. blinks green led.
;==============================================================================
	
wblinks
	movwf	blinks		;save # of blinks
	incf	blinks,f	;adjust for initial decrement
	
wblinksloop
	decfsz	blinks,f	;update
	goto	blinkit		;on/off once
	
	return			;return when all done.
	
blinkit	
	bcf	porta,n_green	;on
	call	xmillisecs	;delay
	bsf	porta,n_green	;off
	call	xmillisecs	;delay
	goto	wblinksloop	;do more blink sets...
		


;==============================================================================
; xmillisecs: (subroutine) : 200 msec delay. flows into wmillisec.
;==============================================================================


xmillisecs
	movlw	d'200'		;delay...


;==============================================================================
; wmillisecs: (subroutine) : delay for w millisecs. tuned for 4.00 mhz xtal.
;==============================================================================


;called subroutine set to generate delays.
;enter with # of milliseconds to delay in W


wmillisec
	movwf	xmillisec	;save # of millisecs in w to delay.


	incf	xmillisec,f	;adjust to account for initial decrement.
				;first (outer) loop.
	
wmloop1
	decfsz	xmillisec,f	;update first (outer) loop
	goto	wmloopa		;if more to do, do it.
	
	return			;done when xmillisec=0.
	
wmloopa	
	clrf	ymillisec	;second (inner) loop
				;256 loops (256+1 becomes 0)
	
wmloopb	
	decfsz	ymillisec,f	;update 256 part of inner loop.
	goto	wmloopb		;3 usec per loop
	
	movlw	d'75'+1
	movwf	ymillisec	;75 loops for third loop
	
wmloopc	
	decfsz	ymillisec,f	;update 75 part of inner loop.
	goto	wmloopc		;3 usec per loop
	
	goto	wmloop1		;continue with next outer loop
				
				;total for inner loop sets is 
				;3*(256+75)=993 usec.
				;this together with the overhead
				;makes the total as close to 1000 usec
				;as we can get it.
				;1000 usec = 1 millisecond


;==============================================================================
; end of program
;==============================================================================


		end