 TITLE  "clock.asm"             ; digital alarm clock
 List P=16F684, R=DEC
 INCLUDE "p16f684.inc"

SCL_5v		EQU	0				; Clock lines setup
SCL_3v		EQU	4
SDA_5v		EQU 1				; Data lines setup
SDA_3v		EQU	3
I2C_port	EQU 0x87			; I2C port setup	
#define		vvv 27777
#define		timeOutDelay 10

; data segment
 CBLOCK 0x20                   
 del:2, debounce:2, FSR_save	; local variables 
 w_save, stat_save, pc_save		; saving regs while in interrupt
 dataIO, cnt, ACK_NAK			; I2C registers
 hours, minutes, colon			; clock display
 regPTR							; PCA8565 interface variable
 timeOut						; activate clock display for 2 min
 ENDC

; code segment
 PAGE
 __CONFIG _FCMEN_OFF & _IESO_OFF & _BOD_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_OSC_NOCLKOUT

 ORG 0                      	; start program at the beginning of mem
	goto start

 ORG 4							; ISR
	movwf	w_save				; save Wreg
	swapf	STATUS, w			; save stat reg without
	movwf	stat_save			; changing flags
	movf	FSR, w
	movwf	FSR_save

	btfsc	INTCON, INTF		; alarm interrupt?	
	 goto 	alarmINT
	btfss	INTCON, RAIF		; PORTA IOC interrupt?
	 goto	ISR_end				; NO - exit	

	call	debounceDelay		
	bcf		INTCON, RAIF		; clear interrupt flag
	movlw	3					; setup reg pointer to
	movwf	regPTR				; current time register
	movlw 	6					; or to alarm register
	btfss	PORTA, 1			; depending on the switch
	 addwf	regPTR, f

	movlw	timeOutDelay
	movwf	timeOut

	comf	PORTA, w
	andlw	b'00110000'
	btfsc	STATUS, Z			; no button is pressed - exit
	 goto	ISR_end

	comf	PORTA, w			; ignore the buttons status
	andlw	3					; if the switch is in the
	btfsc	STATUS, Z			; middle position
	 goto	ISR_end

setTimeINT
	call	getTime				; time/alarm time setup
	btfss	PORTA, 5
	 call	incHours			; update hours
	btfss	PORTA, 4
	 call	incMinutes			; update minutes
	call	updateDisplay
	call	setTime
	bcf		INTCON, RAIF		; clear interrupt flag
	goto	ISR_end	

alarmINT						; alarm interrupt processing
	bsf		PORTC, 5			; activate the alarm device
	movlw	255
	movwf	debounce
	btfsc	PORTA, 3			; wait for complete turn 
	 goto	$-1
	decfsz	debounce, f			; check for same state N times
	 goto	$-3

	movlw	255
	movwf	debounce
	btfss	PORTA, 3    
	 goto	$-1
	decfsz	debounce, f			; check for same state N times
	 goto	$-3
    bcf		PORTC, 5			; deactivate the device	

	call	start_I2C_3v		; clear alarm flag in RTC
	movlw	b'10100010'
	movwf	dataIO
	call	write_I2C_3v		; send slave address/write to PCA8565
	movlw	1
	movwf	dataIO				; set addr ptr to control/status 2
	call	write_I2C_3v
	movlw	b'00010010'			; clear AF & TF, 
	movwf	dataIO				; enable alarm and disable timer
	call	write_I2C_3v
	call	stop_I2C_3v
	bcf		INTCON, INTF		; clear interrupt flag

ISR_end	
	movf	FSR_save, w
	movwf	FSR
	swapf	stat_save, w		; get original flags in STATUS
	movwf	STATUS
	swapf	w_save, f			; restore Wreg		
	swapf	w_save, w
	retfie

encodeHours						; encode digits 1-2 for 7-seg LED
	andlw	0x0F				
	addwf	PCL, f	
	retlw	b'01111110'			; code for 0
	retlw	b'01100000'			; code for 1
	retlw	b'11011100'			; code for 2
	retlw	b'11110100'			; code for 3
	retlw	b'11100010'			; code for 4
	retlw	b'10110110'			; code for 5
	retlw	b'10111110'			; code for 6
	retlw	b'01100100'			; code for 7
	retlw	b'11111110'			; code for 8
	retlw	b'11110110'			; code for 9
	dt		0,0,0,0,0,0			; blanks

encodeMinutes					; encode digits 3-4 for 7-seg LED
	andlw	0x0F				
	addwf	PCL, f	
	retlw	b'11101101'			; code for 0
	retlw	b'10001000'			; code for 1
	retlw	b'11000111'			; code for 2
	retlw	b'11001110'			; code for 3
	retlw	b'10101010'			; code for 4
	retlw	b'01101110'			; code for 5
	retlw	b'01101111'			; code for 6
	retlw	b'11001000'			; code for 7
	retlw	b'11101111'			; code for 8
	retlw	b'11101110'			; code for 9
	dt		0,0,0,0,0,0			; blanks

start
	clrf	PORTA
	clrf	PORTC
	bsf		PORTC, 2			; turn off display
	bcf		PORTC, 5			; turn off alarm device
	movlw	0x07
	movwf	CMCON0				; comparators OFF
  	bsf    	STATUS, RP0      	; switch to BANK 1
	clrf	ANSEL ^ 0x80		; all inputs digital
	movlw	b'00111111'
	movwf	TRISA ^ 0x80		; enable PORTA for input

	movlw	b'00011011'	
  	movwf   TRISC ^ 0x80      	; enable RC4:3 and RC1:0 for input 
	movlw	b'00000000'		
	movwf	OPTION_REG^0x80		; falling edge INT setup
	movlw	b'00110111'
	movwf	WPUA^0x80			; enable weak pull-ups on PORTA
	movlw	b'00110011'
	movwf	IOCA ^ 0x80			; enable interrupt on change on RA5:4
	movlw	b'00011000'
	movwf	INTCON 				; enable IOC and INT
	bcf		OSCCON^0x80, 4		; set oscillator freq 1MHz
	bcf		OSCCON^0x80, 5
	bsf		OSCCON^0x80, 6
  	bcf    	STATUS, RP0        	; back to BANK 0

	movlw	I2C_port			; setup FSR 
	movwf	FSR
	call	debounceDelay
	clrf	colon
	call	initRTC
	movlw	3
	movwf	regPTR
	movlw	timeOutDelay
	movwf	timeOut
	bcf		INTCON,	RAIF		; clear interrupt flags
	bcf		INTCON, INTF
	bsf		INTCON, GIE			; enable interrupts globally

loop
	call	getTime
	bcf		PORTC, 2			; turn display on
	call	updateDisplay
	comf	colon, f
	call	delay1sec
	call 	updateDisplay
	comf	colon, f
	call	delay1sec	
	decfsz	timeOut, f
	 goto	loop

	bsf		PORTC, 2			; turn display off
	movlw	timeOutDelay
	movwf	timeOut
	sleep
	nop
	goto	loop

;;;;;;;;;;;;;PROCEDURES
initRTC							; initialize PCA8565 RTC
	call	start_I2C_3v
	movlw	b'10100010'
	movwf	dataIO
	call	write_I2C_3v		; send slave address/write to PCA8565
	clrf	dataIO
	call	write_I2C_3v		; activate control 1 register
	clrf	dataIO
	call	write_I2C_3v		; enable clock
	
	movlw	b'00010010'			; clear AF & TF, 
	movwf	dataIO				; enable alarm and disable timer
	call	write_I2C_3v
	call	stop_I2C_3v

	call	start_I2C_3v
	movlw	b'10100010'
	movwf	dataIO
	call	write_I2C_3v		; send slave address/write to PCA8565
	movlw	0x0D				; CLKOUT control register
	movwf	dataIO				; set addr ptr to control/status 2
	call	write_I2C_3v 
	clrf	dataIO				; disable CLKOUT
	call	write_I2C_3v
	movlw	3					; timer clock source = 1/16 Hz
	movwf	dataIO
	call	write_I2C_3v		; disable timer
	call	stop_I2C_3v
	return

updateDisplay
	call 	start_I2C_5v		; init I2C 5V interface
	movlw	b'01110000'			; send slave address/write to SAA1064
	movwf	dataIO
	call 	write_I2C_5v

	clrf	dataIO				; send the subaddress byte
	call	write_I2C_5v
	
	movlw	b'00100111'			; send control byte
	movwf	dataIO				
	call	write_I2C_5v

	swapf	hours, w			; retrieve tens of hours
	call	encodeHours
	movwf	dataIO
	call	write_I2C_5v		; send tens of hours

	movf	hours, w			; retrieve units of hours
	call	encodeHours
	movwf	dataIO
	btfsc	colon, 0
	 bsf	dataIO, 0			; decimal point (part of colon)
	call	write_I2C_5v		; send units of hours

	swapf	minutes, w			; retrieve tens of minutes		
	call	encodeMinutes
	movwf	dataIO
	btfsc	colon, 4
	 bsf	dataIO, 4			; decimal point (part of colon)
	call	write_I2C_5v		; send tens of minutes

	movf	minutes, w			; retrieve units of minutes
	call	encodeMinutes
	movwf	dataIO	
    call	write_I2C_5v		; send units of minutes
	call	stop_I2C_5v
	return

setTime
	call	start_I2C_3v
	movlw	b'10100010'
	movwf	dataIO
	call	write_I2C_3v		; send slave address/write to PCA8565
	movf	regPTR, w
	movwf	dataIO				; set addr ptr to the minutes register
	call	write_I2C_3v

	movf	minutes, w
	movwf	dataIO
	call	write_I2C_3v		; send minutes to PCA8565

	movf	hours, w
	movwf	dataIO
	call	write_I2C_3v		; send hours to PCA8565

	btfss	regPTR, 3
	 goto	set_end
	movlw 	0x81
	movwf	dataIO
	call	write_I2C_3v		; disable day alarm
	movlw 	0x81
	movwf	dataIO
	call	write_I2C_3v		; disable weekday alarm

set_end
	call	stop_I2C_3v
	return	

getTime
	call	start_I2C_3v
	movlw	b'10100010'
	movwf	dataIO
	call	write_I2C_3v		; send slave address/write to PCA8565
	movf	regPTR, w
	movwf	dataIO				; set addr ptr to the minutes register
	call	write_I2C_3v
	call	start_I2C_3v
	movlw	b'10100011'
	movwf	dataIO
	call	write_I2C_3v		; send slave address/read to PCA8565
	clrf	ACK_NAK				; set acknowledge bit 0
	call	read_I2C_3v			; get minutes
	movf	dataIO, w
	andlw	b'01111111'
	movwf	minutes
	bsf		ACK_NAK, 0			; set NAK bit to 1
	call	read_I2C_3v			; get hours
	movf	dataIO, w
	andlw	b'00111111'
	movwf	hours
	call	stop_I2C_3v
	return

; 3V I2C interface routines
start_I2C_3v					; start setup for I2C interface
	movlw	I2C_port			; setup FSR to point to TRISC
	movwf	FSR
	bcf		I2C_port ^ 0x80, SCL_3v
	bcf		I2C_port ^ 0x80, SDA_3v
	bsf		INDF, SDA_3v		; pull high SDA and SCL
	bsf		INDF, SCL_3v
	goto	$+1					; delay slot
	bcf		INDF, SDA_3v		; set data low
	nop							; wait for slave to detect it
	bcf		INDF, SCL_3v		; set clock low
	return

stop_I2C_3v						; stop setup for I2C interface
	bcf		I2C_port ^ 0x80, SCL_3v
	bcf		I2C_port ^ 0x80, SDA_3v
	bcf		INDF, SCL_3v		; make sure that clock and data
	bcf		INDF, SDA_3v		;  lines are low
	goto	$+1
	bsf		INDF, SCL_3v		; release clock line
	goto	$+1
	bsf		INDF, SDA_3v		; release data line
	return

write_I2C_3v					; write byte to I2C line
	bcf		I2C_port ^ 0x80, SCL_3v
	bcf		I2C_port ^ 0x80, SDA_3v
	bcf		INDF, SCL_3v		; make sure that clock is low
	movlw	8
	movwf	cnt					; bits counter

w_loop3
	bcf		INDF, SDA_3v		; start w. data bit low
	rlf		dataIO, f			; get the rightmost data bit
	btfsc	STATUS, C
	 bsf	INDF, SDA_3v		; now the data bit is on the data bus
	bsf		INDF, SCL_3v		; raise clock up
	bcf		INDF, SCL_3v		; ... and low
	decfsz	cnt, f
	 goto	w_loop3				; repeat this 8 times

	bsf		INDF, SDA_3v		; get ACK from slave		
	goto	$+1					; let slave respond
	bsf		INDF, SCL_3v		; clock up
 	goto 	$+1					; slave responds OK (hopefully)
	bcf		INDF, SCL_3v		; clock down
	return

read_I2C_3v						; read byte from I2C line
	bcf		I2C_port ^ 0x80, SCL_3v
	bcf		I2C_port ^ 0x80, SDA_3v
	bcf		INDF, SCL_3v		; make sure that clock is low			
	movlw	8
	movwf	cnt					; bits counter	

r_loop3
	bcf		INDF, SCL_3v		; set clock low for long enough
	goto	$+1
	bsf		INDF, SCL_3v		; set clock high
	bcf		STATUS, C
	btfsc	I2C_port ^ 0x80, SDA_3v
	 bsf	STATUS, C			; C = data bit
	rlf		dataIO, f			; insert it into the data byte
	decfsz	cnt, f
	 goto	r_loop3				; repeat this 8 times

	bcf		INDF, SCL_3v		; sending the ACK/NAK to slave
	bsf		INDF, SDA_3v
	movf	ACK_NAK, f			; the NAK signal
	btfsc	STATUS, Z
	 bcf	INDF, SDA_3v		; the ACK signal
	nop
	bsf		INDF, SCL_3v		; raise clock up
	nop
	bcf		INDF, SCL_3v		; ... and low
	nop
	bsf		INDF, SDA_3v		; release the data line
	return

; 5V I2C interface routines
start_I2C_5v					; start setup for I2C interface
	movlw	I2C_port			; setup FSR to point to TRISC
	movwf	FSR
	bcf		I2C_port ^ 0x80, SCL_5v
	bcf		I2C_port ^ 0x80, SDA_5v
	bsf		INDF, SDA_5v		; pull high SDA and SCL
	bsf		INDF, SCL_5v
	goto	$+1					; delay slot
	bcf		INDF, SDA_5v		; set data low
	nop							; wait for slave to detect it
	bcf		INDF, SCL_5v		; set clock low
	return

stop_I2C_5v						; stop setup for I2C interface
	bcf		I2C_port ^ 0x80, SCL_5v
	bcf		I2C_port ^ 0x80, SDA_5v
	bcf		INDF, SCL_5v		; make sure that clock and data
	bcf		INDF, SDA_5v		;  lines are low
	goto	$+1
	bsf		INDF, SCL_5v		; release clock line
	goto	$+1
	bsf		INDF, SDA_5v		; release data line
	return

write_I2C_5v					; write byte to I2C line
	bcf		I2C_port ^ 0x80, SCL_5v
	bcf		I2C_port ^ 0x80, SDA_5v
	bcf		INDF, SCL_5v		; make sure that clock is low
	movlw	8
	movwf	cnt					; bits counter

w_loop5
	bcf		INDF, SDA_5v		; start w. data bit low
	rlf		dataIO, f			; get the rightmost data bit
	btfsc	STATUS, C
	 bsf	INDF, SDA_5v		; now the data bit is on the data bus
	bsf		INDF, SCL_5v		; raise clock up
	bcf		INDF, SCL_5v		; ... and low
	decfsz	cnt, f
	 goto	w_loop5				; repeat this 8 times

	bsf		INDF, SDA_5v		; get ACK from slave		
	goto	$+1					; let slave respond
	bsf		INDF, SCL_5v		; clock up
 	goto 	$+1					; slave responds OK (hopefully)
	bcf		INDF, SCL_5v		; clock down
	return

incHours
	incf	hours, f
	comf	hours, w
	andlw	b'00001010'
	btfss	STATUS, Z
	 goto	mod24
	movlw	6
	addwf	hours, f
mod24
	movlw	b'00100100'
	subwf	hours, w
	btfsc	STATUS, C
	 clrf	hours	
	return

incMinutes
	incf	minutes, f
	comf	minutes, w
	andlw	b'00001010'
	btfss	STATUS, Z
	 goto	mod60
	movlw	6
	addwf	minutes, f
mod60
	movlw	b'01100000'
	subwf	minutes, w
	btfsc	STATUS, C
	 clrf	minutes	
	return

debounceDelay
	movlw	10
	movwf	debounce+1
	clrf	debounce

	movf	debounce, w
	btfsc	STATUS, Z
	 decf	debounce+1, f
	decf	debounce, f
	movf	debounce, w
	iorwf	debounce+1, w
	btfss	STATUS, Z
 	 goto	$-7
	return


delay1sec
	movlw	HIGH vvv
	movwf	del+1
	movlw	LOW	vvv
	movwf	del

	movf	del, w
	btfsc	STATUS, Z
	 decf	del+1, f
	decf	del, f
	movf	del, w
	iorwf	del+1, w
	btfss	STATUS, Z
 	 goto	$-7
	return
 END