You are on page 1of 12

Electronic door codelock with PIC

version 2, 2001/10/22
designed by Jap
NOTE for beginners: PICs are general purpose microcontrollers which have to be programmed
before you can use them in the actual circuit! Check out this link to learn more.

description

This is my electronic codelock to use with an outdoor gate. The lock itself is implemented in
software. It operates a relay (for example to open a door) for a few seconds if someone enters
the valid code. This relay can operate a power-to-open type electric strike with a shorting
contact or a power-to-hold type electromagnetic lock with a breaking contact (these usually
work with AC, not DC). The secret code can be changed any time after entering the current
code. Note that in this application it's not necessary to make an exact clock frequency with
crystal (as seen on the schematic diagram), you can also use an RC type oscillator or other
crystals.
I have the codelock design on my webpage for long and it is really poular. But it had some
weaknesses like source code complexity, it needed an external 74LS138 and diodes because it
was built on a general protoboard. Therefore I totally rewrote and redesigned it with simplicity
in mind. The old design is still available here.

software

The version 2 codelock software has cool new features:


 low power consumption due to keyboard wake-up
 stores the code in the internal DATA EEPROM
 the length of the code is user definable (actual is 4 numbers)
 the running frequency is adjustable (actual is 10MHz)
 output pulse width is adjustable (actual is 3 seconds)
 code can be changed any time

The source code is freely downloadable. This is written for the PIC16F84, but you can easily
adapt it to newer controllers like the 16F628, OTP versions with or without internal EEPROM.
The first few lines contain the definitions of changeable parameters. If you are lazy, here is a
compiled HEX file of the source intended to use with 10MHz crystal and P16F84(A). The
codelock will work fine regardless of the actual frequency (the crystal you use) of PIC running,
but the timing loop is calibrated to the frequency defined in the code (10MHz). More details on
the source code can be found here.

operation

The keyboard, which is organized into a matrix looks like this:


COL0 COL1 COL2 COL3
ROW0 1 2 3 A
ROW1 4 5 6 B
ROW2 7 8 9 C
ROW3 * 0 # D
You can use a 3x4 keyboard as well, because column 3 is not necessary in this application. # is
used as 'enter' after you type in the valid code. The initial code is 1234 after programming the
original source. You could activate the output with typing in:
1234#
* is used to change the code. Type in the actual code then press *. If you didn't miss the
actual code, the code change indicator LED will light up. Then type in the new code twice. Eg:
1234*1998#1998#
will change the code to 1998. The code changes immediately and permanently after typing the
new code twice. If you miss entering the new code twice, the original code is kept.
If you don't want anyone to be able to change the code, you can replace the '*' key in the
keytable to '#'. Or you can place the 3rd column inside your lock and use a key in this column
coded '*' to change the code. If you loose the actual code, you will need to access the DATA
EEPROM of the controller with a programmer. The code can be read out or rewritten by the
programmer.

FAQ

Q: Will the source code work on the PIC16F84A?


A: yes, without modifications.
Q: Will the source code work on the PIC16F628?
A: no, but you can make it work from the source. The EEPROM handling code needs to be
rewritten, the original source won't work. If you send me the modified code, I'll be glad to
publicate it here
Q: Is there a way to make this design work as an on/off switch instead of a momentary
switch? In detail, enter code to turn a switch on, enter the code again to turn the switch off
A: yes. Please ask for the experimental code in E-mail
Q: The codelock seems to work (it beeps once for every keypress), but it doesn't operate the
relay!
A1: disconnect resistor from pin#8 (RB2) and touch it to the VCC (pin#14). If it does not
operate the relay, check your transistor and the diode polarity. Measure the output of pin#8
(RB2) when you entered the correct code. It should change to +5VDC for the preset time
interval then back to 0VDC
A2: you possibly misconnected some row/column lines so incorrect scan codes are generated.
Eg, if you swap col0 and col1 wires, when you press the keys 1 2 3 4, it gets interpreted as 2 1
35

troubleshooting

please also check the FAQ for your problem.


 before putting the PIC into the working circuit, test that connecting the resistor on pin#8 to VCC operates the
relay
 while the relay is on (see previous instruction), check that VCC is +5VDC
 putting in the programmed PIC, you should hear a beep at power-up. If you hear nothing, the program is not
running in the PIC. Check the crystal and verify the programming of the PIC.
 pressing keys on the keyboard should generate one beep per press. If you hear nothing, check the contacts by
an ohmmeter: put the leads to one row and a column. Pressing the key in the crossing should give a low (or 0)
resistance on the meter. Untouched, every row-column combination should give no contact
 pressing keys on the keyboard should generate one beep per press. If you hear more than one beeps, you need
to adjust the running frequency of the program. Try decreasing the mhz definition or add more delays in the
read loop

schematic diagram
CODIGO FUENTE
;**********************************************************************
; *
; Filename: cl2.asm *
; Date: *
; File Version: Codelock rewritten *
; *
; Author: el@jap.hu *
; http://jap.hu/electronic/ *
;**********************************************************************
;NOTES
;
; the rewritten codelock has the following changes:
;
; - no multiplexer is needed (the old version used a generic devboard)
; - no row diodes needed (rows are never driven HIGH)
; - low power consumption due to keyboard wake-up
; - stores the code in the internal EEPROM
; - user defined codelength and pulse output
; - user adjustable running frequency
; - improved code changing function with a "change" indicator LED
;
; PIC ports used:
;
; PA0-3 outputs: row select pulldown outputs (tristate or driven LOW)
; PB1 output: code change indicator LED
; PB2 output: output pulse to control a relay
; PB3 output: piezo beeper output
; PB4-PB7 inputs: column inputs with internal pullup
;
;**********************************************************************
;HISTORY
;
; 020-20010929 rewrite started
; 021-20011022 udelay calibrated to 100 usec (4, 10 MHz)
; 022-20011022 scan, input and compare functions work
; 023-20011022 code change function works
;
;**********************************************************************
list p=16f84a
__CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC

#include <p16F84a.inc>

mhz EQU D'10' ; processor frequency in MHz


pulsewidth EQU D'150'; delay in 20ms steps (150=3 sec)
clen EQU 4 ; length of code

; EEPROM contents
ORG 0x2100
de "123456" ; default code (clen chars are used)
; which is stored in EEPROM

; RAM registers
ram_start EQU 0x0c
dcnt0 EQU ram_start+1 ; delay counter 0
dcnt1 EQU ram_start+2 ; delay counter 1
dcnt2 EQU ram_start+3 ; delay counter 2
beepcnt EQU ram_start+4 ; beep cycle counter
keycode EQU ram_start+5
rowcnt EQU ram_start+6
colcnt EQU ram_start+7
colstatus EQU ram_start+8
cod EQU ram_start+9 ; actual code
cod_end EQU cod+clen

readlen EQU cod_end


readbuf EQU cod_end+1
readbuf_end EQU readbuf+clen

tmptr EQU readbuf_end ; pointer for comparing and copying readbuf


tmbyte EQU readbuf_end+1; temp storage for comparing and copying

vectors ORG 0
goto main
nop
nop
nop
retfie

keytable ;determine pressed key's real code from scancode


movf keycode, W
addwf PCL, F
dt 0x60
dt "123a"
dt "456b"
dt "789c"
dt "*0#d"

eep_read ; read EEPROM contents to RAM from cod to cod_end-1


movlw cod
movwf FSR
clrf EEADR

eep_0 ;bcf INTCON, GIE


bsf STATUS, RP0
bsf EECON1, RD
bcf STATUS, RP0
;bsf INTCON, GIE
movf EEDATA, W
movwf INDF

incf FSR, F
incf EEADR, F

movlw cod_end
subwf FSR, W
bnz eep_0
return

eep_write ; save RAM contents to EEPROM from cod to cod_end-1


movlw cod
movwf FSR
clrf EEADR

eep_1 movf INDF, W


movwf EEDATA
;bcf INTCON, GIE
bsf STATUS, RP0
bcf EECON1, EEIF
bsf EECON1, WREN
movlw 0x55
movwf EECON2
movlw 0xaa
movwf EECON2
bsf EECON1, WR

; wait for write completition


eep_2 bcf STATUS, RP0
;bsf INTCON, GIE
nop
nop
;bcf INTCON, GIE
bsf STATUS, RP0
btfss EECON1, EEIF
goto eep_2
bcf STATUS, RP0
;bsf INTCON, GIE

incf FSR, F
incf EEADR, F

movlw cod_end
subwf FSR, W
bnz eep_1
return

udelay ; delay W * 100 usec


movwf dcnt0

udelay0 movlw 8 * mhz


movwf dcnt1

udelay1 decfsz dcnt1, F


goto udelay1

decfsz dcnt0, F
goto udelay0

return

beep movwf beepcnt


beep0 bsf PORTB, 3 ; beepctl bit
movlw 3
call udelay
bcf PORTB, 3 ; beepctl bit
movlw 3
call udelay
decfsz beepcnt, F
goto beep0
return

keyscan ; scan the keyboard


clrf keycode
movlw 4
movwf rowcnt

movlw 0xfe
tris PORTA ; select row 0

rowscan movlw 0xa0


call udelay
swapf PORTB, W
movwf colstatus

movlw 4
movwf colcnt

colscan incf keycode, F


rrf colstatus, F
btfss STATUS, C
goto keytable ; a key was found

decfsz colcnt, F
goto colscan

bsf STATUS, C

bsf STATUS, RP0


rlf TRISA, F ; select next row
bcf STATUS, RP0

decfsz rowcnt, F
goto rowscan
retlw 0 ; no key was found

main ; program starts here


clrf PORTA
clrw
tris PORTA ; porta all output
clrf PORTB
movlw 0xf0 ; pb4-7 inputs
tris PORTB
bsf STATUS, RP0 ; bank 1
bcf OPTION_REG, NOT_RBPU ;internal pullups on port B enabled
bcf STATUS, RP0 ;bank 0

warm movlw 0xf0


call beep
call eep_read ; read code from eeprom to ram at cod

loop clrf PORTB ; clear output

call read ; read code from keyboard into readbuf


movlw cod
call compbuf ; compare code in readbuf with code at cod
bnz loop ; the code is different

; the code matches, check which enter (#*) was pressed

movlw '*'
subwf keycode, W ; * changes code
bz codechange

pulseout ; # operates output


movlw 0x04 ; RB2 is output
movwf PORTB

movlw pulsewidth
movwf dcnt2

out0 movlw d'200'


call udelay
decfsz dcnt2, F
goto out0

goto loop

codechange movlw 2 ; * changes code


movwf PORTB ; indicate changing the code
call read ; read new code into readbuf

movlw cod
call copybuf ; copy new code into cod

call read ; read new code twice


movlw cod ; and check if the new code is confirmed
call compbuf ; wrong code entry, restart with the original code
bnz warm

; new code is comfirmed twice, store into eeprom


call eep_write
goto loop

read clrf readlen

readloop ; wait until no key is pressed


clrw
tris PORTA ; porta all LOW
movf PORTB, W
andlw 0xf0 ; keymask
xorlw 0xf0
btfss STATUS, Z
goto readloop

movlw 0xf0 ; wait 24 ms


call udelay ; (debounce)

; no key pressed, go to sleep


movf PORTB, W
movlw 1<<RBIE ; enable RB port change wake-up
movwf INTCON
sleep

key_pressed
call keyscan
andlw 0xff
movwf keycode
bz readloop

movlw 0xf0 ; wait 24 ms


call udelay ; (debounce)

; check if the buffer is full


movlw clen
subwf readlen, W
bnz read_notfull

; buffer is full, can return if an enter key (*#) is pressed

; check for ENTER


call read_chkenter
bnz read_notenter

; enter is pressed, return


movlw 0x40
call beep
movf keycode, W
return

read_notenter
; buffer is full, but more characters entered
; shift the buffer
movlw readbuf+1
movwf FSR

read_shift movf INDF, W


decf FSR, F
movwf INDF
incf FSR, F
incf FSR, F
movlw readbuf_end
subwf FSR, W
bnz read_shift

decf readlen, F

read_notfull call read_chkenter ; if the buffer is not full and an


bz read ; enter key (*#) is pressed, clear buffer

movlw 0x40
call beep

movlw readbuf
addwf readlen, W
movwf FSR
movf keycode, W
movwf INDF
incf readlen, F
goto readloop

read_chkenter ; check if a * or # is pressed which indicates


; the end of entry
movlw '#'
subwf keycode, W
btfsc STATUS, Z
return ; Z=1, enter
movlw '*'
subwf keycode, W
return

compbuf ; compare read buffer to a code in RAM at W

movwf tmptr ; compare pointer


clrf readlen ; compare index starts from 0

comp0 movlw readbuf


addwf readlen, W
movwf FSR
movf INDF, W
movwf tmbyte ; the read byte which is compared

movf tmptr, W
addwf readlen, W
movwf FSR
movf INDF, W ; the byte readbuf is compared to
subwf tmbyte, W
btfss STATUS, Z
return ; Z=0: the code is different

incf readlen, F
movlw clen
subwf readlen, W
bnz comp0 ; compare next character
; Z=1: the code is the same

return

copybuf ; copy readbuf to RAM at W


movwf tmptr ; copy pointer
clrf readlen ; copy index starts from 0
copy0 movlw readbuf
addwf readlen, W
movwf FSR
movf INDF, W
movwf tmbyte ; the read byte which is copied

movf tmptr, W
addwf readlen, W
movwf FSR
movf tmbyte, W ; the byte from readbuf
movwf INDF

incf readlen, F
movlw clen
subwf readlen, W
bnz copy0 ; copy next character

return

end
keytable: subroutine 82-89
input = scancode of key ( = row * 4 + column + 1) in keycode
output = ASCII code of key

eep_read: subroutine 91-110


read the code from EEPROM to RAM
the actual code is read into the RAM @ address cod

eep_write: subroutine 112-147


write the code into EEPROM from RAM
the actual code is written into the EEPROM from RAM address cod

udelay: subroutine 149-161


input = delay length in 100 usecs

delay main program execution for the given time


delay constant is calculated from the defined mhz variable
for the actual frequency

beep: subroutine 163-172


input = length of the beep
generate a beep on the PB3 output pin

keyscan: subroutine 174-206


output = ASCII code of a pressed key or 0 if no key is found

scan the keyboard for a pressed key

keycode = 0, rowcnt = 4
select row 0 (pull PA0 pin low, float PA1, PA2, PA3)
rowscan: scan a selected keyboard row for a pressed key
delay 100 ms (debounce delay & charge row)
read PB4-PB7 pins as column inputs from the row into colstatus
colcnt = 4
colscan: keycode = keycode + 1 (keycode is the scancode of the actual key checked)
rotate right colstatus bits
if the lowest bit was 0, a pressed key is found: exit with sub keytable
otherwise colcnt = colcnt - 1
if colcnt > 0, goto label colscan
otherwise select next row (pull the according PA# pin low, float others)
rowcnt = rowcnt - 1
if rowcnt > 0, goto label rowscan
return with 0: no key found pressed

main: 208-217
program execution starts here
setup PORTA, PORTB pin states

PORTA pins are outputs, PORTB0-3 are outputs


PORTB4-7 are inputs with internal pullup resistors

warm: 219-221
generate a beep, indicating that the program is running
call subroutine eep_read to read the actual code from EEPROM

loop: 223-
program main loop
clear all PORTB pins
call subroutine read to read a code from the keyboard
call subroutine compbuf to compare the code read in with the actual code
if combuf returned with * as last character entered, goto codechange
otherwise
pulseout: send out a pulse on PB2 pin. Use the defined pulsewidth variable as
delay in 20 ms steps. Go back to loop when finished

codechange: setup PB1 pin to indicate code change on the LED


call subroutine read to read the new code from the keyboard
copy read in code into the RAM storage area of the actual code
call subroutine read to read the new code again from the keyboard
call subroutine compuf to compare the code read in with the previous one
if the new code was not correctly entered twice, goto label warm
(this will reset the actual code in RAM from the EEPROM and beep to indicate
the code change was not successful)
otherwise call subroutine eep_write to write the new code into EEPROM
and go back to loop

read: subroutine 266-351


read a sequence of keys with length clen into the RAM buffer @ readbuf
only the last clen characters are stored, previous characters are lost.
go sleep when the keyboard is inactive. The * or # key is used to terminate
the sequence. If pressed and at least clen characters were entered, the
subroutine returns. Otherwise, all entered characters are cleared and a new
read starts.

compbuf: subroutine 353-378


input = RAM address of buffer to compare with
output = Z, status of match

compare the read buffer with the given buffer, set flag Z

copybuf: subroutine 380-401


input = RAM address of buffer to copy to

copy the read buffer to the given buffer

You might also like