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
| file: /Techref/microchip/4x4keypad-tm.htm, 45KB, , updated: 2005/2/11 13:07, local time: 2025/10/26 07:07,
216.73.216.180,10-8-63-169:LOG IN
|
| ©2025 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions? <A HREF="http://techref.massmind.org/techref/microchip/4x4keypad-tm.htm"> PIC Microcontroler based Keyboards </A> |
| Did you find what you needed? |
Welcome to massmind.org! |
|
The Backwoods Guide to Computer Lingo |
.