;  ------  DALLAS DS1990A based lock ------
;  ------  Supports 9 keys + 1 master-key -----
;  ------  wubblick@yahoo.com  -------

;TARGET	= AT89C2051
DEBUG	= 0		;0-off, 1-rdfn ports, 2-on
#INCLUDE "LIBR51.ASM"	;8051 SFR set

CLK_KHZ	= 10000		;OSC frequency, KHZ
RTC_MS    = 20                ;system clock, MS

RTCV	= -(CLK_KHZ*RTC_MS)/12

;Macros:

#DEFINE LO(XXX) XXX & 0FFH
#DEFINE HI(XXX) (XXX >> 8) & 0FFH

#IF (DEBUG>1)
#INCLUDE "LIBDEF.ASM"
#ENDIF

;  ------ Constantes ------

MAXK	.EQU 9		;max number of keys
I2C_ADDR	.EQU 0A0H		;I2C address for 24C02 (A0,A1,A2=0)
TMATMV	.EQU 25		;x20mS TM access delay time
AENTMV	.EQU 2		;x20mS TM access enable delay
OPNTMV	.EQU 150		;x20mS open pulse duration
RETTMV	.EQU 250		;x20mS return to normal mode delay

;  ------ Ports ------

DDATA	.EQU P1		;display data port
KEYOP	.EQU INT0		;key OPEN
OWP	.EQU INT1		;1-Wire port
SDA	.EQU T0		;I2C SDA line
SCL	.EQU T1		;I2C SCL line
SOLEN	.EQU RXD		;solenoid control line
WD	.EQU TXD		;watchdog line
KEYSL	.EQU P3.7		;key SELECT

;  ------ Variables ------

;Bit addressing memory:

RTPC	.EQU 020H		;Real time program counter
T040M	.EQU O20H.0	;40mS  period bit
T080M	.EQU O20H.1	;80mS  period bit
T160M	.EQU O20H.2	;160mS period bit
T320M	.EQU O20H.3	;320mS period bit
T640M	.EQU O20H.4	;640mS period bit
T1S28	.EQU O20H.5	;1.28S period bit
T2S56	.EQU O20H.6	;2.56S period bit
T5S12	.EQU O20H.7	;5.12S period bit

RTPCS	.EQU 021H		;Real time program counter (100mS part)

FLAGS1	.EQU 022H
PROG	.EQU O22H.0	;program mode flag
SNUM	.EQU O22H.1	;set number flag
BLINK	.EQU O22H.2	;display blink bit
SLKPR	.EQU O22H.3	;SELECT press flag
OPNPR	.EQU O22H.4	;OPEN   press flag

;Internal Data Memory:

	.ORG 0030H	;data memory segment

ROMD	.DS 8		;touch memory ROM data
TEMP	.DS 1		;temporary byte (used in ACCESS90)

TMATM	.DS 1		;touch memory access timer
AENTM	.DS 1		;TM access enable timer
OPNTM	.DS 1		;open timer
RETTM	.DS 1		;return timer

NUM	.DS 1		;current PRG number (1..9)

;Debugger variables:

#IF (DEBUG>1)
DBGVA	.DS 1		;debugger variable address in internal memory
DBGVV	.DS 1		;debugger variable value   in internal memory
DBGA	.EQU 0FFFFH	;debugger address          in external memory
#ENDIF

STACK:			;stack location

;  ------ Vectors Area ------

	.ORG 0000H	;reset vector
	LJMP INIT
	.ORG 000BH	;INT TIMER 0 vector
	LJMP RTC

;  ------ Main Program ------

INIT:	MOV SP,#STACK	;stack init
#IF (DEBUG>1)
	DEBUGINIT		;debug init
#ENDIF

;Variables init:

	CLR A
	MOV FLAGS1,A	;clear flags 1
	MOV TMATM,A	;touch memory access timer clear
	MOV AENTM,A	;touch memory access delay timer clear
	MOV OPNTM,A	;open timer clear
	MOV RETTM,A	;return timer clear

;Periferal setup:

	CLR TR0		;timer 0 stop
	CLR TR1		;timer 1 stop
	MOV TMOD,#11H    	;timer 0 and timer 1 init
	MOV TL0,#LO(RTCV)  	;timer 0 load
	MOV TH0,#HI(RTCV)
	SETB TR0		;timer 0 start
	CLR PT0          	;int. timer 0 low priority
	SETB ET0		;int. timer 0 enable
	SETB EA		;interrupts enable

;  ------ Main Loop ------

;Read touch memory:

MAIN:	LCALL ACCESS90	;read touch memory
	JNC NOTCH		;no touch
	JB PROG,PRG	;jump to process PROG mode
	LCALL CHKMAS	;check for master code
	JC OPN
	LCALL CHKMEM	;check for member code
	JC OPN
	SJMP NOTCH
PRG:	LCALL PROGT	;process PROG mode when touch
	SJMP NOTCH
OPN:	LCALL OPEN	;open door

;Check open key:

NOTCH:	LCALL CHKOPK	;check open key
	JNC NOOPN
	LCALL OPEN	;open door

;Check select key:

NOOPN:	LCALL CHKSLK	;check select key
	JNC NOSEL
	LCALL PROGS	;process PROG mode when select

;Open timer check:

NOSEL:	MOV A,OPNTM
	JNZ NOCLS
	LCALL CLOSE	;solenoid off

;Return timer check:

NOCLS:	MOV A,RETTM
	JNZ NORET
	LCALL RETMD	;return to normal mode

;Display, watchdog wakeup:

NORET:	LCALL DISP	;display
	LCALL WAKEUP	;watchdog wakeup
	LJMP MAIN

;  ------ Subroutines Area ------

;Process PROG mode when touch:

PROGT:    JB SNUM,PRGT1	;SNUM = 1 ?
	LCALL CHKMAS	;check for master
	JC PRGT3
	RET

PRGT3:	SETB SNUM		;set "set number" flag
	CLR BLINK		;clear blink flag
	MOV NUM,#1	;clear number
	MOV RETTM,#RETTMV	;load return delay
	RET

PRGT1:	JB BLINK,PRGT2	;PROG = 1, BLINK = 0 ?
	SETB BLINK	;set blink flag
	MOV RETTM,#RETTMV	;load return delay
	RET

PRGT2:	LCALL SAVE	;NVM[NUM] <- new key
	CLR BLINK		;clear blink flag
	MOV RETTM,#RETTMV	;load return delay
	RET

;Process PROG mode when select:

PROGS:    JB PROG,PRGS1	;PROG = 1 ?
	SETB PROG		;set program mode flag
	CLR  SNUM		;clear "set number" flag
	CLR BLINK		;clear blink flag
	MOV RETTM,#RETTMV	;load return delay
	RET

PRGS1:    JNB SNUM,PRGS2	;SNUM = 0 ?
	CLR BLINK		;clear blink flag
	INC NUM		;NUM + 1
	MOV A,NUM
	CJNE A,#MAXK+1,PRGS2
	MOV NUM,#1
PRGS2:	MOV RETTM,#RETTMV	;load return delay
	RET

;Return to normal mode:

RETMD:	CLR BLINK		;clear blink flag
	CLR PROG		;clear program mode flag
	CLR SNUM		;clear "set number" flag
	RET

;Open door:

OPEN:	CLR SOLEN
	MOV OPNTM,#OPNTMV	;load open pulse duration
	RET

;Solenoid off:

CLOSE:	SETB SOLEN
	RET

;Check open key:
;Returns C=1 if pressed

CHKOPK:   JNB KEYOP,OP1	;jump if key OPEN pressed
	CLR OPNPR		;clear OPEN pressed flag
OP3:	CLR C
	RET

OP1:	JB OPNPR,OP2
	LCALL DEL15	;delay 15 mS
	JB KEYOP,OP3
	SETB OPNPR	;set OPEN pressed flag
OP2:	SETB C		;C <- 1 if key OPEN pressed
	RET

;Check select key:
;Returns C=1 if pressed

CHKSLK:   JNB KEYSL,SL1	;jump if key SELECT pressed
	CLR SLKPR		;clear SELECT pressed flag
SL2:	CLR C
	RET

SL1:	JB SLKPR,SL2
	LCALL DEL15	;delay 15 mS
	JB KEYSL,SL2
	SETB SLKPR	;set SELECT pressed flag
	SETB C		;C <- 1 if key SELECT pressed
	RET

;Watchdog wakeup:

WAKEUP:	MOV C,T040M
	MOV WD,C
	RET

;  ------ 1-Wire bus support ------

;Send reset pulse to OWP and receive presence pulse
;Out: C = 1 if OK

TRESET:   PUSH B
	CLR OWP		;OWP <- 0
	MOV B,#CLK_KHZ/48
	DJNZ B,$		;delay 500 uS
	CLR EA		;interrupt disable
	SETB OWP		;OWP <- 1  (0uS)

	MOV B,#CLK_KHZ/2000
	DJNZ B,$		;delay
	MOV C,OWP		;read OWP  (14 uS)
	JNC FAIL		;fail if OWP = 0

	MOV B,#CLK_KHZ/500
	DJNZ B,$		;delay
	MOV C,OWP		;read OWP  (66 uS)
	JC FAIL		;fail if OWP = 1

	SETB EA		;interrupts enable
	MOV B,#CLK_KHZ/100
	DJNZ B,$		;delay
	MOV C,OWP		;read OWP  (312 uS or more)
	JC RESOK		;OK if OWP = 1

FAIL:	CLR C
	SETB EA		;interrupts enable (if fail)
RESOK:	POP B
	RET

;Read/Write byte via 1-Wire bus
;Input: A  - input byte
;       R4 - CRC
;Out:   A  - output byte
;       R4 - updated CRC

TBYTE:    PUSH B
	MOV B,#8		;perform to read 8 bit
TBYTE1:	RRC A		;C <- bit
	LCALL TBIT	;transmit bit
	DJNZ B,TBYTE1	;next bit
	RRC A		;A <- last bit
	POP B
	RET

;Read/Write bit via 1-Wire bus
;Input: C  - input bit.
;       R4 - CRC
;Out:   C  - output bit
;       R4 - updated CRC

TBIT:     PUSH ACC
	CLR EA		;interrupts disable
	CLR OWP		;OWP <- 0 (begin of time slot)
	NOP		;delay to be sure...
	NOP		;that thermometr...
	NOP		;looks low level
	JC TB_1
	SJMP TB_0		;if data bit = 0 then OWP <- 0
TB_1:	SETB OWP		;if data bit = 1 then OWP <- 1
	NOP      		;delay for data setup
TB_0:	NOP
	NOP
	NOP
	NOP
;	NOP		;10 MHz !
;	NOP
	MOV C,OWP		;read port 15 uS later
	PUSH B  		;save register B
	MOV B,#CLK_KHZ/706
	DJNZ B,$ 		;delay to complete 60 uS slot
	POP  B   		;restore register B
	SETB OWP		;OWP <- 1, end of time slot
	SETB EA		;interrupts enable

	PUSH PSW		;update CRC, save C
	RLC A		;ACC.0 <- C
	XRL A,R4		;~ACC.0 if CRC.0 = 1
	RRC A		;ACC.0 -> C
	MOV A,R4		;A <- CRC
	JNC BCRC0
	XRL A,#18H	;update CRC
BCRC0:	RRC A		;shift CRC
	MOV R4,A		;CRC <- new value
	POP PSW		;restore C

	POP ACC
	RET

;  Accesses to DALLAS DS1990A touch memory.
;  Returns C=1 in case of valid code, else C=0.
;  Out:  ROMD (8 bytes)

ACCESS90: CLR C		;indicate failure
	MOV A,AENTM	;check access enable timer
	JNZ RET90		;exit if AENTM > 0
	MOV AENTM,#AENTMV	;reload access enable timer

	LCALL TRESET	;issue reset pulse
	JNC   RET90	;leave if no parts on bus

	CLR C		;indicate failure
	MOV A,TMATM	;check TM access timer
	JNZ DIS90		;exit if TMATM > 0

	MOV   A,#033H	;read ROM command
	LCALL TBYTE	;send command byte

	MOV R0,#ROMD	;init pointer
	MOV R1,#8		;init counter
	MOV R4,#0		;initialize CRC variable

RDMORE:	MOV   A,#0FFH	;prepare to read a byte
	LCALL TBYTE	;read byte
	MOV @R0,A		;save byte
	INC R0		;next address
	DJNZ R1,RDMORE	;repeat until finished

	MOV  A,R4		;get CRC value in ACC
	JZ   CRC_OK	;jump if successful
	CLR  C		;indicate failure
	SJMP RET90

CRC_OK:	LCALL CHKZ	;check code for zero
	JNC RET90		;invalid code

DIS90:	MOV TMATM,#TMATMV
RET90:	RET		;return to caller

;Check TM code for zero:

CHKZ:	MOV R0,#ROMD	;init pointer
	MOV R1,#8		;init counter
NEXTZ:	MOV A,@R0
	ADD A,#0FFH	;C = 1 if A>0
	JC OKZ
	INC R0		;next address
	DJNZ R1,NEXTZ
OKZ:	RET

;  I2C NVM memory 24C02 support:
;
;  I2C - bus supported subroutines:
;
;  I2C_WR   - Write byte from A via I2C bus
;  I2C_RD   - Read byte to A via I2C bus
;  I2C_LRD  - Read last byte to A via I2C bus (no ASK)
;  I2C_SUB  - Send subaddress from R1 to I2C device
;  I2C_SUBR - Send subaddress from R1 and perform read.
;  I2C_STOP - Stop condition generation on I2C bus
;
;  I2C slave address  I2C_ADDR (0A0H for 24C02 A0,A1,A2=0)
;  I2C data line	  SDA
;  I2C clock line	  SCL

;Send subaddress to I2C device.
;Input: R1 - subaddress.

I2C_SUB:  MOV A,#I2C_ADDR	;I2C device address, write mode
	LCALL I2C_WR	;send device address
	MOV A,R1		;subaddress
	LCALL I2C_WR	;send subaddress
	RET

;Send subaddress and perform read.
;Input: R1 - subaddress.

I2C_SUBR:	LCALL I2C_SUB	;send subaddress
	LCALL I2C_STOP	;stop
	MOV A,#I2C_ADDR+1	;I2C device address, read mode
	LCALL I2C_WR	;send device address
	RET

;Send byte from A via I2C bus.

I2C_WR:   PUSH B
	MOV B,#9H		;bit counter load
	SETB C		;set C, for bit 9 = 1 (when ACK)
	LCALL SDA0	;SDA 1 -> 0 - start
I2CWR1:	LCALL SCL0	;SCL 1 -> 0
	RLC A
	JC OUTP1		;jump if bit = 1
	LCALL SDA0	;else SDA=0
	SJMP OUTP0
OUTP1:	LCALL SDA1	;SDA=1, if bit = 1
OUTP0:	LCALL SCL1	;SCL 0 -> 1
	DJNZ B,I2CWR1	;loop
	POP B
	RET

;Read byte via I2C to A.

I2C_RD:	MOV A,#1H		;A init to receive 8 bit
I2CRD1:	LCALL SCL0	;SCL 1 -> 0
	LCALL SDA1	;SDA=1 - SDA line release
	LCALL SCL1	;SCL 0 -> 1
	MOV C,SDA		;move bit from SDA line to C
	RLC A		;shift bit C into A
	JNC I2CRD1	;loop until C = 1
	LCALL SCL0	;SCL 1 -> 0
	LCALL SDA0	;SDA=0 - ACK generation
	LCALL SCL1	;SCL 0 -> 1
	RET

;Read byte via I2C to A without ASK
;(receive last byte).

I2C_LRD:	MOV A,#1H		;A init to receive 8 bit
I2CLRD1:	LCALL SCL0	;SCL 1 -> 0
	LCALL SDA1	;SDA=1 - SDA line release
	LCALL SCL1	;SCL 0 -> 1
	MOV C,SDA		;move bit from SDA line to C
	RLC A		;shift bit C into A
	JNC I2CLRD1	;loop until C = 1
	LCALL SCL0	;SCL 1 -> 0
	LCALL SCL1	;SCL 0 -> 1 when SDA=1: no ACK
	RET

;STOP condition generation:

I2C_STOP:	LCALL SCL0
	LCALL SDA0
	LCALL SCL1
	LCALL SDA1
	RET

;SDA and SCL lines control:

SDA0:	CLR SDA	;SDA 1/0
	RET
SDA1:	SETB SDA	;SDA 0/1
	RET
SCL0:	CLR SCL	;SCL 1/0
	RET
SCL1:	SETB SCL	;SCL 0/1
	RET

;EEPROM address map:

;00H - page not used
;08H - touch memory code 1
;10H - touch memory code 2
;...

;Save new key:
;NUM - key number (1..9)

SAVE:     MOV A,NUM
	MOV B,#8
	MUL AB		;NUM x 8
	MOV R1,A		;R1 <- subaddress
	LCALL I2C_SUB	;send subaddress
	MOV R1,#8		;init counter
	MOV R0,#ROMD	;init pointer
DOWR:	MOV A,@R0
	LCALL I2C_WR	;send data
	INC R0
	DJNZ R1,DOWR
	LCALL I2C_STOP	;stop
	LCALL DEL15	;delay 15mS to page write
	RET

;Check for member:
;Returns C=1 if code O.K.

CHKMEM:	MOV R2,#1		;init key counter

NEXT:	MOV A,R2
	MOV B,#8
	MUL AB		;NUM x 8
	MOV R1,A		;R1 <- subaddress
	LCALL I2C_SUBR	;send subaddress and read mode
	MOV R1,#8		;init counter
	MOV R0,#ROMD	;init pointer

DORD:	LCALL I2C_RD	;read data
	XRL A,@R0
	JNZ INVAL		;jump if invalid code
	INC R0		;next address
	DJNZ R1,DORD
	LCALL I2C_LRD	;stop read
	LCALL I2C_STOP
	SETB C		;valid code, C <- 1
	RET

INVAL:    LCALL I2C_LRD	;stop read
	LCALL I2C_STOP
	INC R2		;next key
	MOV A,R2
	CJNE A,#MAXK+1,NEXT
	CLR C		;invalid code, C <- 0
	RET

;Check for master:

CHKMAS:   MOV R1,#8		;init counter
	MOV DPTR,#MK	;init pointer to ROM
	MOV R0,#ROMD+7	;init pointer to RAM
DOCM:	CLR A
	MOVC A,@A+DPTR	;read ROM
	XRL A,@R0		;ROM[DPTR] = RAM[R0] ?
	JNZ INVM		;jump if invalid code
	INC DPTR		;next ROM address
	DEC R0		;next RAM address
	DJNZ R1,DOCM
	SETB C		;valid master code, C <- 1
	RET

INVM:	CLR C		;invalid master code, C <- 0
	RET

;Display support:
;Input: NUM = value
;if PROG = 0 display is blanked
;if PROG = 1 and BLINK = 1 display is blinking

DISP:     MOV A,#BLANK	;blank display if PROG = 0
	JNB PROG,IND
	MOV A,#CH_P	;display "P" if SNUM = 0
	JNB SNUM,IND
	MOV A,NUM		;display NUM if PROG = 1 and SNUM = 1

IND:	MOV C,T320M	;load blink period bit
	ORL C,/BLINK	;check blink enable bit
	JC IND1
	MOV A,#BLANK
IND1:	MOV DPTR,#FONT	;font table pointer
	MOVC A,@A+DPTR	;read font
	MOV DDATA,A	;indicator control
	RET

;Delay 15mS:

DEL15:	PUSH  B
	PUSH  ACC
	MOV   B,#30
DEL05:	MOV   A,#CLK_KHZ/48
	DJNZ  ACC,$
	LCALL WAKEUP	;watchdog wakeup
	DJNZ  B,DEL05
	POP   ACC
	POP   B
	RET

;  ------ Interrupt Holders ------

;  TIMER 0 Interrupt
;  System clock (20mS)

RTC:      PUSH ACC
	CLR TR0          	;timer 0 stop
	MOV TH0,#HI(RTCV)  	;timer 0 load for 20mS
	MOV TL0,#LO(RTCV)
	SETB TR0         	;timer start

	INC RTPC         	;advance Real Time Program Counter

;  20mS program counters

RTC1:	MOV A,TMATM
	JZ RTC2
	DEC TMATM

RTC2:	MOV A,AENTM
	JZ RTC3
	DEC AENTM

RTC3:	MOV A,OPNTM
	JZ RTC4
	DEC OPNTM

RTC4:	MOV A,RETTM
	JZ RTC5
	DEC RETTM

RTC5:	POP ACC
	RETI

;	    FGABSCDE
FONT	.DB 01001000B	;code 00H, character 0
	.DB 11101011B	;code 01H, character 1
	.DB 10001100B	;code 02H, character 2
	.DB 10001001B	;code 03H, character 3
	.DB 00101011B	;code 04H, character 4
	.DB 00011001B	;code 05H, character 5
	.DB 00011000B	;code 06H, character 6
	.DB 11001011B	;code 07H, character 7
	.DB 00001000B	;code 08H, character 8
	.DB 00001001B	;code 09H, character 9
	.DB 00001010B	;code 0AH, character A
	.DB 00111000B	;code 0BH, character B
	.DB 01011100B	;code 0CH, character C
	.DB 10101000B	;code 0DH, character D
	.DB 00011100B	;code 0EH, character E
	.DB 00011110B	;code 0FH, character F
	.DB 11111111B	;code 10H, character blank
	.DB 10111111B	;code 11H, character -
	.DB 00001110B	;code 12H, character P

;Characters codes table:

BLANK	.EQU 010H		;character "blank" code
CH_MIN	.EQU 011H		;character "-" code
CH_P	.EQU 012H		;character "P" code

;Master key code:

MK	.DB 062H,000H,000H,002H,0D6H,089H,029H,001H

#IF (DEBUG>1)
#INCLUDE "LIBDBG16.ASM"
#ENDIF
	.END
