 LIST P=16F914, R=DEC
 INCLUDE "p16F914.inc"
 __CONFIG _DEBUG_OFF & _FCMEN_OFF & _IESO_OFF & _BOD_OFF & _CPD_OFF &  _CP_OFF & _MCLRE_ON & _PWRTE_ON  & _WDT_OFF & _INTOSCIO

#define thrs 	0x12					; initial clock time setup
#define tmin 	0x00

#define updateH 0x22					; clock time of time sync
#define updateM 0x01

#define maxAttempts 3					; log of # of consequtive sync attempts
#define RXout 	PORTB,5					; setup RX control pins
#define RXhold 	PORTC,1
#define RXpower	PORTD,1

#define tZone  	-6						; setup for CST time zone
#define zeroL  	2						; zero durtion threshold for PIC @ 32 KHz
#define zeroH	4
#define oneL	7						; one duration threshold
#define oneH	9
#define synL	2						; sync pulse duration threshold
#define synH	14

 CBLOCK 0x20							; data segment in BANK_0
  w_save, stat_save						; saving regs while in interrupt
  hrs:2, min, days:2, year:2			; storage for RX time
  temp									; temp variable
 ENDC

 CBLOCK 0x70             	 			; data segment in the shared area
  minutes, hours24, hours12, sec15		; time keeping variables
  start, length  						; storage for RX pulse start & width
  pulse									; storage for received pulse value
  pulseNo, wwvbByte						; pulse number and byte received
  state									; receiver FSM state
  synCnt								; counter for # of sync attempts
 ENDC

 ORG  0
	goto 	main

 ORG 4									; ISR
	bcf		RXhold						; put RX on hold
	movwf	w_save						; save Wreg
	swapf	STATUS, w					; save stat reg without
	movwf	stat_save					; changing flags

	btfss	INTCON, RBIF				; PORTB interrupt?
	 goto	TMR1_int					; NO - check for timer interrupt
	btfsc	RXout						; end of RX pulse?
	 goto	pulseEnd					; YES - process end of pulse

pulseStart		
	movf	TMR1H, w					; get pulse start timestamp
	movwf	start		
	goto	TMR1_int					; check for timer interrupt

pulseEnd
	movf	start, w
	subwf	TMR1H, w					; compute pulse duration					
	btfss	STATUS, C					; is it < 0 ?
	 addlw	0xEF						; YES - correct it
	movwf	length
	call	processPulse

TMR1_int
	btfss	PIR1, TMR1IF				; TMR1 interrupt?
	 goto	ISR_end						; NO - exit

	bcf		PIR1, TMR1IF				; clear TMR1 interrupt flag
	movlw	0x10						; setup next TMR1 int in 15 seconds
	movwf	TMR1H
	incf	sec15, f
	btfss	sec15, 2					; has 1 minute passed?
	 goto	ISR_end						; NO - exit

	incf	synCnt, f					; update sync counter
	clrf	sec15
	movlw	7							; increment minutes
	addwf	minutes, w					; with BCD correction
	btfss	STATUS, DC
	 addlw	-6
	movwf	minutes

	sublw	0x60						
	btfsc	STATUS, Z					; minutes = 60?
	 clrf	minutes						; YES - clear minutes
	call	displayMinutes

	movf 	minutes, w					
	btfss	STATUS, Z					; is hours update needed?
	 goto	ISR_end						; NO - proceed

	movlw	7							; YES - increment hours
	addwf	hours24, w					; with BCD correction
	btfss	STATUS, DC
	 addlw	-6
	movwf	hours24

	movf	hours24, w
	sublw	0x24						; hours = 24?
	btfsc	STATUS, Z
	 clrf	hours24						; YES - clear hours
	call	displayHours

ISR_end
	btfss	INTCON, RBIF				; was it PORTB interrupt?
	 goto	$+3							; NO - exit
	bsf		RXhold						; YES - re-enable RX AGC
	bcf		INTCON, RBIF				;   and clear interrupt flag
	swapf	stat_save, w				; get original flags in STATUS
	movwf	STATUS
	swapf	w_save, f					; restore Wreg		
	swapf	w_save, w
	retfie

encode7seg	; pabcdefg
	andlw	0x0F
	addwf 	PCL, f
	retlw	b'01111110'					; code for 0
	retlw	b'00110000'					; code for 1
	retlw	b'01101101'					; code for 2
	retlw	b'01111001'					; code for 3
	retlw	b'00110011'					; code for 4
	retlw	b'01011011'					; code for 5
	retlw	b'01011111'					; code for 6
	retlw	b'01110000'					; code for 7
	retlw	b'01111111'					; code for 8
	retlw	b'01111011'					; code for 9
	retlw	b'01110111'					; code for A
	retlw	b'00011111'					; code for B
	retlw	b'01001110'					; code for C
	retlw	b'00111101'					; code for d
	retlw	b'01000111'					; code for F

main
	clrf	PORTA
	clrf	PORTB
	clrf	PORTC
	clrf 	PORTD
	clrf	ADCON0						; ADC off

  	bsf    	STATUS, RP0        			; change to BANK 1
	movlw	7
	movwf	CMCON0 ^ 0x80				; comparators off
	clrf	ANSEL ^ 0x80				; all inputs digital

	movlw	b'00000001'
	movwf	OSCCON ^ 0x80				; internal oscillator @31KHz

    	clrf	TRISA ^ 0x80				; STEP 1: set I/O ports
  	clrf    TRISB ^ 0x80        
	clrf	TRISC ^ 0x80
	clrf	TRISD ^ 0x80
	bsf		PIE1 ^ 0x80, TMR1IE			; enable TMR1 interrupt
	bsf		IOCB ^ 0x80, IOCB5			; enable interrupt on change on PORTB:5

	bcf		STATUS, RP0
  	bsf		STATUS, RP1					; switch to BANK 2
	bsf		LCDCON ^ 0x100, VLCDEN		; STEP 2: enable LCD bias voltage pins
	bcf		LCDCON ^ 0x100, CS1			; STEP 3: select clock source (TMR1/32)	
	bsf		LCDCON ^ 0x100, CS0
	bcf		LCDCON ^ 0x100, LMUX1		; STEP 4: select multiplex mode (static)
	bcf		LCDCON ^ 0x100, LMUX0

	bcf		LCDPS ^ 0x100, 	WFT			; STEP 5: select waveform type (A)
	bcf		LCDPS ^ 0x100,	BIASMD		; STEP 6: select Bias mode (static)
	bcf		LCDPS ^ 0x100, 	LP3			; STEP 7: select refresh rate (64Hz)
	bcf		LCDPS ^ 0x100, 	LP2			;    1:3	
	bsf		LCDPS ^ 0x100,	LP1
	bsf		LCDPS ^ 0x100,	LP0

	movlw	0xFF						; step 8: enable LCD segment lines
	movwf	LCDSE0 ^ 0x100
	movwf	LCDSE1 ^ 0x100
	bcf		LCDSE1 ^ 0x100, SE15		; disable SEG15
	movwf	LCDSE2 ^ 0x100
	clrf	LCDDATA0 ^ 0x100			; step 9: clear segment data registers
	clrf	LCDDATA1 ^ 0x100
	clrf	LCDDATA2 ^ 0x100

	bsf		LCDCON ^ 0x100,	LCDEN		; step 10: turn on the LCD driver module
	bcf		LCDCON ^ 0x100, SLPEN		; enable LCD in sleep mode

	bcf		STATUS, RP1					; back to BANK 0
	clrf	sec15						; setup initial time
	movlw	tmin
	movwf 	minutes
	movlw	thrs
	movwf	hours24
	call	displayMinutes
	call	displayHours

	clrf	TMR1L						; setup TMR1 overflow in 15 seconds
	movlw	0x10
	movwf	TMR1H
	movlw	b'00111111'					; 1:8 prescaler. LP oscillator
	movwf	T1CON						; start timer

	bcf		PIR1, TMR1IF				; clear interrupt flag
	bsf		INTCON, PEIE				; enable periferal interrupt
	bsf		INTCON, GIE					; enable interrupts
	call	syncTime

loop
	sleep
	nop
	movf 	hours24, w			; time for sync?
	sublw	updateH 			; sync is performed as soon as the clock time
	btfss	STATUS, Z			; is updateH:updateM:45
	 goto	loop				; thus giving 15 seconds for receiver to warm up
	movf	minutes, w
	sublw	updateM
	btfss	STATUS, Z
	 goto	loop
	movf	sec15, w
	sublw	3
	btfss	STATUS, Z
	 goto	loop
	call 	syncTime			; YES - do time synchronization
	goto	loop

	
;////////////////// Time setting procedures
syncTime
	bsf		STATUS, RP0			; switch to BANK_1
	bsf		RXout				; enable input at this pin
	bcf		STATUS, RP0			; back to BANK_0
		
	bsf		RXpower				; turn on receiver
	bsf		RXhold				; turn on AGC
	btfss	RXout				; wait for the pulse absence
	 goto	$-1

	clrf	state				; start in state 0
	clrf	synCnt				; clear the attempts counter
	bcf		INTCON, RBIF		; clear interrupt flag
	bsf		INTCON, RBIE		; enable PORTB onchange interrupt

st_loop
	sleep						; wait for reaching state 3
	nop							; or sync failure
	movf	state, w
	andlw	3
	sublw	3
	btfsc	STATUS, Z
	 goto	$+3		
	btfss	synCnt, maxAttempts
	 goto	st_loop

	bcf		INTCON, RBIE		; disable PORTB onchange interrupt
	bcf		RXhold				; turn off AGC
	bcf		RXpower				; turn off receiver
	bcf		RXout				; preset 0 at this pin
	bcf		INTCON, RBIF		; clear interrupt flag
	bsf		STATUS, RP0			; switch to BANK_1
	bcf		RXout				; configure this pin for output
	bcf		STATUS, RP0			; back to BANK_0

	btfsc	synCnt, maxAttempts	; was sync syccessful?
	 return						; NO - keep old time

	movf	min, w				; YES - use received time to set clock
	andlw	0x7F
	movwf	minutes
	movf	hrs, w
	movwf	hours24

	movlw	3					; force the minute update
	movwf	sec15
	clrf	TMR1L
	bsf		PIR1, TMR1IF		; set TMR1 interrupt flag
	movf	minutes, w
	btfss	STATUS, Z
	 call	displayHours
	return

processPulse
	movf	state, w
	andlw	3
	sublw	3
	btfsc	STATUS, Z
 	 return						; stay in state 3 once it is reached	

	movlw	4
	movwf	pulse				; initialize pulse value

	movlw	zeroL				; check for received 0
	subwf	length, w 
	btfss 	STATUS, C
	 goto	srt					; too short pulse - ignore it

	movlw	zeroH+1				; check for received 0
	subwf	length, w 
	btfss 	STATUS, C
	 goto	r0					; accept pulse as 0	

	movlw	oneL				; check for received 1
	subwf	length, w 
	btfss 	STATUS, C
	 goto	srt					; too short for 1 - ignore

	movlw	oneH+1				; check for received 1
	subwf	length, w 
	btfss 	STATUS, C
	 goto	r1					; accept pulse as 1

	movlw	synL				; check for received sync
	subwf	length, w 
	btfss 	STATUS, C
	 goto	srt					; too short for sync - ignore

	movlw	synH+1				; check for received sync
	subwf	length, w 
	btfss 	STATUS, C
	 goto	rS					; accept pulse as sync

srt decf	pulse, f			; out of range duration: pulse=0
r0	decf	pulse, f			; received 0:  pulse=1
r1	decf	pulse, f			; received 1:  pulse=2
rS	decf	pulse, f			; sync signal: pulse=3

	btfss	STATUS, Z			; validate pulse duration
 	 goto	accept
	clrf	state				; illegal duration - return to state 0
	return

accept
	movf	state, w			; at this point the pulse duration is legal
	andlw	0x03
	addwf	PCL, f				; get to one of the FSM states
	goto	sync1
	goto	sync2
	goto	getByte

sync1							; state 0: waiting for the first sync pulse
	movlw 	3	
	subwf	pulse, w			; Z=0: sync
	btfsc	STATUS, Z			; was it sync pulse ?
	 incf	state, f			; YES - enter state 1
	return						; otherwise keep waiting for sync
	
sync2							; state 1: waiting for the second sync pulse
	clrf	state
	movlw 	3	
	subwf	pulse, w			; Z=0: sync
	btfss	STATUS, Z			; was it sync pulse ?
	 return						; NO - keep waiting
	incf	state, f			; YES - enter state 2
	incf	state, f
	movlw	9					; prepare to receive 9 pulses
	movwf	pulseNo
	return						

getByte							; state 2: receiving a bit
	call 	getBit				; analyse the received bit and shift it into
	addlw	0					; the wwvb byte if accepted
	btfss	STATUS, Z			; is byte complete ?
	 return						; NO - wait for another bit

	movf	state, w			; save the received byte wwvbByte
	andlw	0xF0				; in one of time holding variables depending
	movwf	temp				; on the byte number in temp
	swapf	temp, f				; the byte numbers start with 1
	movf	wwvbByte, w
	decf	temp, f
	btfsc	STATUS, Z
	 movwf	min					; load minutes
	decf	temp, f
	btfsc	STATUS, Z
	 movwf	hrs					; load hours
	decf	temp, f
	btfsc	STATUS, Z
	 movwf	days+1				; load day # (first byte)
	decf	temp, f
	btfsc	STATUS, Z
	 movwf	days				; load day # (second byte)
	decf	temp, f
	btfsc	STATUS, Z
	 movwf	year+1				; load year (first byte)
	decf	temp, f
	btfss	STATUS, Z
	 return
	movwf	year				; load year (second byte) + leap info
	incf	state, f			; the 59th sync bit is received
								; and we enter state 3
correctHours
	movlw	0x3F
	andwf	hrs, f

	movlw	tZone				; correct hours wrt time zone
	btfsc	year, 0				; summer time correction
	 addlw	1
	addwf	hrs, f
	movlw	36
	btfss	STATUS, C
	 addwf	hrs, f
	movlw	-6
	btfss	STATUS, DC
	 addwf	hrs, f
	return

getBit
	decf	pulseNo, f			; update # of pulses to receive
	movlw	5					
	subwf	pulseNo, w			; bit #5 is meaningless
	btfsc	STATUS, Z							
	 retlw	1					; ignore it 

	movlw	3					; if sync is received we either complete
	subwf	pulse, w			; the byte or return back to state 0
	btfss	STATUS, Z
	 goto	addBit				; the received bit is not sync - process it
	
	movf	state, w			; the received bit is sync - check for 
	clrf	state				; byte completion; in this case pulseNo=0
	incf	state, f
	movf	pulseNo, f		
	btfss	STATUS, Z			; pulseNo = 0?
	 retlw	1					; NO - error; return to state 1
	addlw	16					; YES - update the byte number
	movwf	state	
	movlw 	10
	movwf	pulseNo				; reset # of pulses to receive
	retlw	0					; signal processing request to the caller

addBit
	movlw	2					; now we know that either 1 or 0 is received
	subwf	pulse, w			; C = pulse
	rlf		wwvbByte, f			; shift in the received bit
	retlw	1

;///////////////// Time keeping procedures
displayMinutes
	bsf	STATUS, RP1					; switch to BANK 2
	movf	minutes, w
	call	encode7seg
	movwf	LCDDATA0 ^ 0x100
	swapf	minutes, w
	call	encode7seg
	movwf	LCDDATA1 ^ 0x100
	movlw	0x12						; display AM/PM
	subwf	hours24, w
	btfsc	STATUS, C
	 bsf	LCDDATA0 ^ 0x100, 7
	bcf		STATUS, RP1					; back to BANK 0
	return

displayHours
	bsf		STATUS, RP1					; switch to BANK 2
	movf	hours24, w
	sublw	0x12
	btfss	STATUS, C
	 addlw	0x12
	sublw	0x12						
	movwf	hours12						; hours12 = time in AM/PM

	andlw 	0x0E						; BCD correction
	sublw	0x0D
	movf	hours12, w
	btfss	STATUS, C
	 addlw	-6							; WREG = corrected time

	call	encode7seg
	movwf	LCDDATA2 ^ 0x100
	btfsc	hours12, 4
	 bsf	LCDDATA2 ^ 0x100, 7			; display tens of hours
	bcf	STATUS, RP1					; back to BANK 0
	return

 END