; lock - garage door opener
; (C) 2002 by Chris Cole, August 6, 2002
; 16F84 running at 12MHz - 9600 baud, 1bit=104uS => 312 cycles
; pins: 17=Rx, 18=Tx, 4=10kohm->+5V, 5=gnd, 14=+5V, 15&16=10/12MHz xtal, 13=to set/run switch,
;       1=relay that opens door

	TITLE	"lock"
	LIST	P=16F84a
	errorlevel -302

	__CONFIG _CP_OFF & _WDT_ON & _HS_OSC;

#include "C:\progra~1\mplab\p16f84a.inc" ;

#define relay_pin	PORTA,2	; (pin 1)
#define serial_rx_pin	PORTA,0	; (pin 17)
#define serial_tx_pin	PORTA,1	; (pin 18)
#define run_switch	PORTB,7	; (pin 13)
				;
#define MAX_INPUT	10	; maximum number of keystrokes on the input fifo
#define KEY_TIMEOUT	3*46	; amount of time until keyboard queue is cleared out automatically
				; there are 46 ticks/sec, so a value of 46 means a 1 second timeout
				;
;#define SERIAL_SUPPORT		; serial port support for input/output debug messages
;#define DEBUG			; produce output debug messages
				;
RAMbase	EQU		0x0c	;
				;
; DNOP - Double NOP. delay of 2 cycles, takes only one instruction;
DNOP	MACRO			;
	LOCAL	Label		;
Label	GOTO	Label+1		;
	ENDM			;
				;
; delay3w - delay 3 * w cycles, three instructions;
delay3w	MACRO			;
	LOCAL	label		;
	MOVWF	delay		;
label	DECFSZ	delay,f		;
	GOTO	label		;
	ENDM			;
				;
; variable eclarations		;
vars	org	0x0c		;
index		res	1	;
delay		res	1	;
bits_to_go	res	1	;
serial_byte	res	1	;
run_switch_last	res	1	; BIT0: 1=run mode, 0=set mode
				; BIT1: 0=internal code has not been cleared, 1=code has been cleared
temp		res	1	;
keypad_temp	res	1	;
entered_code	res	MAX_INPUT;
key_debounce	res	4	;
key_down	res	1	; BIT0: status of matrix keypress
w_temp		res	1	; isr context save register for w register
status_temp	res	1	; isr context save register for STATUS register
pclath_temp	res	1	; isr context save register for PCLATH register
fsr_temp	res	1	; isr context save register for FSR register
timer_count	res	1	;
timer_flags	res	1	; BIT0: 1=request that key input queue be cleared
;----------------------------------------------------------------------------------------------------
	org	0x00		; processor reset vector
	goto	main		;
;----------------------------------------------------------------------------------------------------
	org	0x04		; interrupt vector location
isr				; isr context save
	bcf	INTCON,GIE	; disable all interrupts
	btfsc	INTCON,GIE	; assure interrupts are disabled
	goto	isr		;
	movwf	w_temp		; save w
	swapf	STATUS,w	; save STATUS
	movwf	status_temp	; context save
	clrf	STATUS		; bank 0, regardless of current bank
	movfw	PCLATH		; save PCLATH
	movwf	pclath_temp	; context save
	clrf	PCLATH		; page zero, regardless of current page
	bcf	STATUS,IRP	; return to bank 0
	movfw	FSR		; save FSR
	movwf	fsr_temp	; do context save
	bcf	INTCON,T0IF	; ack interrupt
	clrw			;
	xorwf	timer_count,w	;
	btfsc	STATUS,Z	;
	  goto	isr_end		;
#ifdef	DEBUG			;
	bsf	PORTA,3		;
	bcf	PORTA,3		;
#endif				;
	decfsz	timer_count,f	; at 21.8mS per tick, 46 will occur per second
	  goto	isr_end		;
	bsf	timer_flags,0	; key timeout has occurred, request that input fifo be cleared
isr_end	movfw	fsr_temp	; context restore
	movwf	FSR		;
	movfw	pclath_temp	;
	movwf	PCLATH		;
	swapf	status_temp,w	;
	movwf	STATUS		;
	swapf	w_temp,f	;
	swapf	w_temp,w	;
	retfie			; enable global interrupt (INTCON,GIE)
;----------------------------------------------------------------------------------------------------
#ifdef DEBUG			;
string_data			;
	addwf	PCL,f		;
string_data_start		;
string_data_save		;
	dt	's','a','v','e',0;
string_data_clear		;
	dt	'c','l','e','a','r',0;
string_data_reset		;
	dt	'r','e','s','e','t','\r','\n',0;
string_data_run			;
	dt	'r','u','n','\r','\n',0;
string_data_set			;
	dt	's','e','t','\r','\n',0;
string_data_access		;
	dt	'a','c','c','e','s','s','\r','\n',0;
string_data_crlf		;
	dt	'\r','\n',0	;
#endif				;
;----------------------------------------------------------------------------------------------------
main:				;
	bsf	STATUS, RP0	; bank1
#ifdef	DEBUG			;
	movlw	b'10001'	; RA0=ser_rx(I), RA1=ser_tx(O), RA2=relay(O), RA3=DEBUG(O), RA4=unused(I)
#else				;
	movlw	b'11001'	; RA0=ser_rx(I), RA1=ser_tx(O), RA2=relay(O), RA3,RA4=unused(I)
#endif				;
	movwf	TRISA		;
	movlw	b'11111111'	; RBx=all inputs
	movwf	TRISB		;
	bcf	0x81,7		; enable weak pullups on PORTB
	bcf	STATUS, RP0	; bank0
init:				;
	bcf	INTCON,GIE	; disable interrupts
	clrwdt			; clear wdt and prescaler
	bsf	STATUS,RP0	; bank1
	bsf	OPTION_REG,PS0	; set prescale value
	bsf	OPTION_REG,PS1	; set prescale value
	bsf	OPTION_REG,PS2	; set prescale value
	bcf	OPTION_REG,PSA	; assign prescale to timer0
	bcf	OPTION_REG,T0CS	; enable timer mode on timer0
	bcf	STATUS,RP0	; bank0
	bcf	INTCON,T0IF	; clear any pending timer0 interrupt
	bsf	INTCON,T0IE	; enable interrupt on timer0 rollover
	bsf	INTCON,GIE	; enable interrupts
	bsf	serial_tx_pin	; set up initial states
#ifdef DEBUG			;
	movlw	string_data_reset-string_data_start;
	call	print_str	;
#endif				;
	bcf	relay_pin	; Turn off relay initially
	bcf	key_down,0	; matrix keypad defaults to no key pressed
	bsf	run_switch_last,0; assume starting in run mode
	bcf	run_switch_last,1; internal code has not been cleared
	call	clear_entered_code; clear input fifo
	clrf	timer_count	;
main_loop:			; wait here for incoming serial Cmd.
	clrwdt			;
	btfsc	timer_flags,0	; should the input fifo be cleared?
	  call	clear_entered_code;
	call	check_switch	; check the status of the set/run switch
	call	get_keypad	; read the matrix scan keypad
	call	filter_keypad	; filter and validate the keypad input
#ifdef SERIAL_SUPPORT		;
	call	getch		; check for data coming in on the rs232 serial port
	btfsc	STATUS,Z	; did getch return a valid char?
	  call	shell		; yes
#endif				;
	goto	main_loop	;
;----------------------------------------------------------------------------------------------------
eeprom_save_code		; save code into onboard eeprom
#ifdef DEBUG			;
	movlw	string_data_save-string_data_start;
	call	print_str	;
#endif				;
	clrf	index		;
	movlw	entered_code	;
	movwf	FSR		;
eeprom_save_code_loop:		;
#ifdef DEBUG			;
	movlw	'.'		;
	call	putch		;
#endif				;
	movf	index,w		;
	movwf	EEADR		;
	movf	INDF,w		;
	movwf	EEDATA		;
	bsf	STATUS,RP0	; bank1
	bcf	INTCON,GIE	; disable interrupts
	bsf	EECON1,WREN	; write enable
	movlw	0x55		;
	movwf	EECON2		;
	movlw	0xaa		;
	movwf	EECON2		;
	bsf	EECON1,WR	; begin write
eeprom_save_busy		;
	clrwdt			;
	btfsc	EECON1,WR	; check to see if the write process has completed
	  goto	eeprom_save_busy;
	bsf	INTCON,GIE	; enable interrupts
	bcf	STATUS,RP0	; bank0
	incf	FSR,f		;
	incf	index,f		;
	movlw	10		;
	xorwf	index,w		;
	btfss	STATUS,Z	;
	  goto	eeprom_save_code_loop;
#ifdef DEBUG			;
	movlw	string_data_crlf-string_data_start;
	call	print_str	;
#endif				;
	return			;
;----------------------------------------------------------------------------------------------------
eeprom_read_code		; read code from onboard eeprom
	clrf	index		;
eeprom_read_code_loop:		;
	movf	index,w		;
	movwf	EEADR		;
	bsf	STATUS,RP0	; bank1
	bsf	EECON1,RD	; read data
	bcf	STATUS,RP0	; bank0
	movf	EEDATA,w	;
#ifdef DEBUG			;
	call	putch		;
#endif				;
	incf	index,f		;
	movlw	10		;
	xorwf	index,w		;
	btfss	STATUS,Z	;
	  goto	eeprom_read_code_loop;
	return			;
;----------------------------------------------------------------------------------------------------
compare_codes:			; does the entered code match the internal code?
	clrf	index		;
	movlw	entered_code	;
	movwf	FSR		;
compare_codes_loop:		;
	movf	index,w		;
	movwf	EEADR		;
	bsf	STATUS,RP0	; bank1
	bsf	EECON1,RD	; read data
	bcf	STATUS,RP0	; bank0
	movf	EEDATA,w	; read internal code character
	movwf	temp		;
	clrw			;
	xorwf	temp,w		; check to see if we reached the end of the internal code string
	btfsc	STATUS,Z	;
	  goto	access		; end of internal code has been reached and matches the input
	movf	INDF,w		; get entered code character
	xorwf	temp,w		;
	btfss	STATUS,Z	;
	  return		;
	incf	FSR,f		;
	incf	index,f		;
	movlw	MAX_INPUT	;
	xorwf	index,w		;
	btfss	STATUS,Z	;
	  goto	compare_codes_loop;
access:				; access is granted
#ifdef DEBUG			;
	movlw	string_data_access-string_data_start;
	call	print_str	;
#endif				;
	bsf	relay_pin	; turn on the relay
	movlw	46		; hold garage door switch closed for this amount of time
	movwf	timer_count	;
access_hold:			; delay here to allow garage door to get the signal to open
	clrwdt			;
	clrw			;
	xorwf	timer_count,w	;
	btfss	STATUS,Z	; has the time elapsed for the switch to be held closed?
	  goto	access_hold	; hold garage door switch in the switch-closed state
	bcf	relay_pin	; turn off the relay
	return			;
;----------------------------------------------------------------------------------------------------
clear_entered_code:		;
#ifdef DEBUG			;
	movlw	string_data_clear-string_data_start;
	call	print_str	;
#endif				;
	movlw	10		;
	movwf	index		;
	movlw	entered_code	;
	movwf	FSR		;
clear_entered_code_loop:	;
#ifdef DEBUG			;
	movlw	'.'		;
	call	putch		;
#endif				;
	clrf	INDF		;
	incf	FSR,f		;
	decfsz	index,f		;
	  goto	clear_entered_code_loop;
#ifdef DEBUG			;
	movlw	string_data_crlf-string_data_start;
	call	print_str	;
#endif				;
	bcf	timer_flags,0	; fifo has been cleared
	return			;
;----------------------------------------------------------------------------------------------------
#ifdef SERIAL_SUPPORT		;
shell:				;
#ifdef DEBUG			;
	movwf	temp		;
	movf	temp,w		;
	movlw	'i'		; dump input queue
	xorwf	temp,w		; compare
	btfsc	STATUS,Z	; is this a match?
	  goto	dump_entered_code;
	movf	temp,w		;
	movlw	'e'		; view contents of eeprom
	xorwf	temp,w		; compare
	btfsc	STATUS,Z	; is this a match?
	  goto	eeprom_read_code;
	movf	temp,w		;
#endif				;
	call	process_input	;
	return			;
#endif				;
;----------------------------------------------------------------------------------------------------
#ifdef DEBUG			;
dump_entered_code:		;
	movlw	string_data_crlf-string_data_start;
	call	print_str	;
	movlw	MAX_INPUT-1	;
	movwf	index		;
	movlw	entered_code	;
	movwf	FSR		;
dump_entered_code_loop:		;
	movf	INDF,w		;
	movwf	temp		;
	clrw			;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  movlw	'-'		; null
	btfss	STATUS,Z	;
	  movf	temp,w		;
	call	putch		;
	movlw	':'		;
	call	putch		;
	incf	FSR,f		;
	decf	index,f		;
	movf	index,w		;
	xorlw	0xff		;
	btfss	STATUS,Z	;
	  goto	dump_entered_code_loop;
	movlw	string_data_crlf-string_data_start;
	call	print_str	;
	return			;
#endif				;
;----------------------------------------------------------------------------------------------------
get_keypad:			; read key on matrix-scan keypad, return in w
	bsf	STATUS, RP0	;
	movlw	b'11100011'	;
	movwf	TRISB		;
	bcf	STATUS, RP0	;
	clrf	PORTB		; ground 2,3,4 - leave 0,1,5,6,7 pulled up
	movlw	0x0a		;
	delay3w			; allow for weak pullups to bring up the open connections
	movf	PORTB,w		; read contents of keypad
	movwf	keypad_temp	;
	bsf	STATUS, RP0	;
	movlw	b'10011100'	;
	movwf	TRISB		;
	bcf	STATUS, RP0	;
	clrf	PORTB		; ground 0,1,5,6 - leave 2,3,4,7 pulled up
	movlw	0x0a		;
	delay3w			; allow for weak pullups to bring up the open connections
	movf	PORTB,w		; read contents of keypad
	iorwf	keypad_temp,f	;
	comf	keypad_temp,w	;
	andlw	0x7f		;
	call	translate_keypad;
	return			;
;----------------------------------------------------------------------------------------------------
check_switch:			;
	btfss	run_switch	;
	  goto	check_switch_set;
check_switch_run:		; check to see if last time we were in set mode
	btfss	run_switch_last,0; bit 0 is set when in run mode
	  call	switch_changed_to_run;
	bsf	run_switch_last,0; bit 0 is set when in run mode
	return			;
check_switch_set:		; check to see if last time we were in run mode
	btfsc	run_switch_last,0; bit 0 is clear when in set mode
	  call	switch_changed_to_set;
	bcf	run_switch_last,0; bit 0 is clear when in set mode
	return			;
;----------------------------------------------------------------------------------------------------
switch_changed_to_run:		;
	btfsc	run_switch_last,1; has the code been cleared yet?
	  call	eeprom_save_code;
	bcf	run_switch_last,1; internal code has not been cleared
	call	clear_entered_code; clear input fifo
#ifdef DEBUG			;
	movlw	string_data_run-string_data_start;
	call	print_str	;
#endif				;
	return			;
;----------------------------------------------------------------------------------------------------
switch_changed_to_set:		;
	bcf	run_switch_last,1; internal code has not been cleared
#ifdef DEBUG			;
	movlw	string_data_set-string_data_start;
	call	print_str	;
#endif				;
	return			;
;----------------------------------------------------------------------------------------------------
filter_keypad:			; filter matrix keypad input (debounce)
	movwf	temp		;
	movf	key_debounce+2,w;
	movwf	key_debounce+3	;
	movf	key_debounce+1,w;
	movwf	key_debounce+2	;
	movf	key_debounce,w	;
	movwf	key_debounce+1	;
	movf	temp,w		;
	movwf	key_debounce	;
				; now check for a good 'key hit' edge
	movf	key_debounce+3,w;
	xorwf	key_debounce+2,w; A -> A -> a -> a ?
	btfss	STATUS,Z	;
	  return		; no
				; yes - maybe we have a stable key transition
	movf	key_debounce+2,w;
	xorwf	key_debounce+1,w; a -> A -> A -> a ?
	btfss	STATUS,Z	;
	  return		; no
				; yes - maybe we have a stable key transition
	movf	key_debounce+1,w;
	xorwf	key_debounce,w	; a -> a -> A -> A ?
	btfss	STATUS,Z	;
	  return		; no
				; yes - we DO have a stable key transition
	clrw			;
	xorwf	temp,w		;
	btfss	STATUS,Z	;
	  goto	filter_keypad_1	;
	bcf	key_down,0	; key press is gone, don't show a key up (0x00)
	return			;
filter_keypad_1:		;
	btfsc	key_down,0	;
	  return		; key that was already detected is still being held down
	bsf	key_down,0	; flag that a valid keypress is active
	movf	key_debounce,w	;
	call	process_input	;
	return			;
;----------------------------------------------------------------------------------------------------
process_input:			; shift matrix keyboard input from w.  destroys w.
	movwf	temp		; save code character just entered
	movlw	KEY_TIMEOUT	;
	movwf	timer_count	; set key timeout
	movlw	MAX_INPUT-1	;
	movwf	index		;
	movlw	entered_code	;
	addlw	MAX_INPUT	;
	addlw	-2		;
	movwf	FSR		;
shift_key_fifo_loop:		;
	movf	INDF,w		;
	incf	FSR,f		;
	movwf	INDF		;
	decf	FSR,f		;
shift_key_fifo_loop_cont:	;
	decf	FSR,f		;
	decfsz	index,f		;
	  goto	shift_key_fifo_loop;
	movf	temp,w		;
	movwf	entered_code	;
#ifdef DEBUG			;
	call	putch		; echo the key that was entered
#endif				;
	btfss	run_switch	; check to see if we are in set state
	  goto	filter_keypad_set;
filter_keypad_run:		; key processing while in run mode
	call	compare_codes	;
	return			;
filter_keypad_set:		; key processing while in set mode
	btfss	run_switch_last,1; has the code been cleared yet?
	  bsf	run_switch_last,1;
	return			;
;----------------------------------------------------------------------------------------------------
#ifdef SERIAL_SUPPORT		;
getch:				; returns serial value in w register
	bcf	STATUS,Z	;
	btfsc	serial_rx_pin	;
	  return		;
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
gotch:				; starting to rx a serial char
	bcf	INTCON,GIE	; disable all interrupts
	btfsc	INTCON,GIE	; assure interrupts are disabled
	goto	gotch		;
	movlw	8		;
	movwf	bits_to_go	;
	movlw	0x96		;
	delay3w			; wait for startbit to pass and get into middle of first bit
get_next_rx_bit:		;
	bcf	STATUS,C	; [1] zero out CARRY to begin with
	btfsc	serial_rx_pin	; [1]
	  bsf	STATUS,C	; [1]
	rrf	serial_byte,f	; [1] rotate right, bring in carry bit on left side
	movlw	0x65		; [1]
	delay3w			; [303]
	decfsz	bits_to_go,f	; [1]
	  goto	get_next_rx_bit	; [2]
	movf	serial_byte,w	;
	bsf	STATUS,Z	;
	bsf	INTCON,GIE	; enable interrupts
	return			;
#endif				;
;----------------------------------------------------------------------------------------------------
#ifdef DEBUG			;
putch:				;
	bcf	INTCON,GIE	; disable all interrupts
	btfsc	INTCON,GIE	; assure interrupts are disabled
	goto	putch		;
	movwf	serial_byte	; store char to send out
	movlw	8		;
	movwf	bits_to_go	; # bits to send out
	bcf	serial_tx_pin	; send out start bit
	movlw	0x67		; [1] set length for start bit
	delay3w			; [309]
putch_loop:			;
	rrf	serial_byte,f	; [1]
	btfsc	STATUS,C	; [1] is it a 1?
	  goto	putch_1		; [2] yes - send a '1'
	bcf	serial_tx_pin	; [1]
	nop			; [1]
	movlw	0x65		; [1]
	delay3w			; [303]
	decfsz	bits_to_go,f	; [1]
	  goto	putch_loop	; [2]
	goto	putch_done	;
putch_1:			;
	bsf	serial_tx_pin	; [1]
	movlw	0x65		; [1]
	delay3w			; [303]
	decfsz	bits_to_go,f	; [1]
	  goto	putch_loop	; [2]
putch_done:			;
	bsf	serial_tx_pin	; [1] send out stop bit
	movlw	0x65		; [1]
	delay3w			; [303]
	bsf	serial_tx_pin	; idle state for TXpin
	bsf	INTCON,GIE	; enable interrupts
	return			;
#endif				;
;----------------------------------------------------------------------------------------------------
#ifdef DEBUG			;
put_hex:			; print out hex value of w
	movwf	temp		;
	swapf	temp,w		;
	andlw	0x0f		;
	addlw	-10		;
	btfsc	STATUS,C	;
	  goto	put_hex_1	;
	swapf	temp,w		;
	andlw	0x0f		;
	addlw	'0'		;
	goto	put_hex_2	;
put_hex_1:			;
	swapf	temp,w		;
	andlw	0x0f		;
	addlw	'A'-10		;
put_hex_2:			;
	call	putch		;
	movf	temp,w		;
	andlw	0x0f		;
	addlw	-10		;
	btfsc	STATUS,C	;
	  goto	put_hex_3	;
	movf	temp,w		;
	andlw	0x0f		;
	addlw	'0'		;
	goto	put_hex_4	;
put_hex_3:			;
	movf	temp,w		;
	andlw	0x0f		;
	addlw	'A'-10		;
put_hex_4:			;
	call	putch		;
	return			;
#endif				;
;----------------------------------------------------------------------------------------------------
#ifdef DEBUG			;
print_str:			; prints string pointed to by w
	movwf	index		;
print_str_loop:			;
	clrwdt			;
	movf	index,w		;
	call	string_data	;
	movwf	temp		;
	xorlw	0		;
	btfsc	STATUS,Z	;
	  return		;
	movf	temp,w		;
	call	putch		;
	incf	index,f		;
	goto	print_str_loop	;
#endif				;
;----------------------------------------------------------------------------------------------------
translate_keypad:		; translates matrix key scancodes into ascii codes, returned in w
	movwf	temp		;
	movlw	0x24		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'0'		;
	movlw	0x50		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'1'		;
	movlw	0x44		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'2'		;
	movlw	0x48		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'3'		;
	movlw	0x11		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'4'		;
	movlw	0x05		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'5'		;
	movlw	0x09		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'6'		;
	movlw	0x12		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'7'		;
	movlw	0x06		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'8'		;
	movlw	0x0A		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'9'		;
	movlw	0x30		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'*'		;
	movlw	0x28		;
	xorwf	temp,w		;
	btfsc	STATUS,Z	;
	  retlw	'#'		;
	retlw	0		; no match
;----------------------------------------------------------------------------------------------------
	END			;


