 LIST P=12F683, R=DEC
 INCLUDE "p12F683.inc"
 __CONFIG _BOD_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTOSCIO

#define RXout	GPIO,4			; RX output pin
tZone 	EQU -6					; setup for CST time zone
SCL		EQU 2					; Clock line setup
SDA 	EQU 5					; Data line setup
zeroL 	EQU 150 * 1000/16		; zero threshold for PIC @ 4 MHz
zeroH	EQU zeroL + 100*1000/16
oneL	EQU	450 * 1000/16		; one threshold
oneH	EQU	oneL + 100*1000/16
synL	EQU 750 * 1000/16		; sync threshold
synH	EQU synL + 100*1000/16

 CBLOCK 0x20                 	; data segment  
 w_save, stat_save				; storage for saving CPU state
 length:2  						; storage for pulse width
 pulse							; storage for received pulse value
 temp:2							; temp variable
 del							; delay variable
 state							; receiver FSM state
 seconds, minutes, hours:2		; storade for the time/datae data
 days:2, year:2, day, month
 pulseNo, wwvbByte				; pulse number and byte received
 dataIO, cnt					; LCD interface
 ENDC

 ORG 0                      	; code segment
	goto main
	
 ORG 4							; ISR
	movwf	w_save				; save Wreg
	swapf	STATUS, w			; save stat reg without
	movwf	stat_save			; changing flags

	btfsc	RXout
	 goto	pulseEnd			; YES - process end of pulse

pulseStart				
	btfsc	pulse, 0			; display previously received pulse
	 bsf	GPIO, 0
	btfsc	pulse, 1
	 bsf	GPIO, 1
	call	delay100msec
	bcf		GPIO, 0
	bcf		GPIO, 1
		
	movlw	7					; increment seconds in BCD
	addwf	seconds, w
	btfss	STATUS, DC
	 addlw 	-6
	movwf	seconds
	movlw	6*16
	subwf	seconds, w		
	btfsc	STATUS, Z			; seconds = 60 ?
	 clrf	seconds				; YES - clear seconds

process
;	call	processPulse
;	call	updateLCD
	goto	ISR_end+1

pulseEnd
	bcf		T1CON, 0			; disable timer
	movf	TMR1L, w			; save pulse duration
	movwf	length
	movf	TMR1H, w
	movwf	length+1	
	clrf	TMR1L				; clear timer
	clrf	TMR1H

	movlw	4
	movwf	pulse
	btfsc	PIR1, TMR1IF		; was there timer overlow?
	 goto	ISR_end				; YES - exit

	movlw	LOW zeroL			; check for received 0
	subwf	length, w 
	movlw	HIGH zeroL	
	movwf	temp	
	btfss	STATUS, C
	 incfsz	temp, w		
	  subwf	length+1, w
	btfss 	STATUS, C
	 goto	srt					; too short pulse - ignore it

	movlw	LOW zeroH			; check for received 0
	subwf	length, w 
	movlw	HIGH zeroH	
	movwf	temp	
	btfss	STATUS, C
	 incfsz	temp, w		
	  subwf	length+1, w
	btfss 	STATUS, C
	 goto	r0					; accept pulse as 0	

	movlw	LOW oneL			; check for received 1
	subwf	length, w 
	movlw	HIGH oneL	
	movwf	temp	
	btfss	STATUS, C
	 incfsz	temp, w		
	  subwf	length+1, w
	btfss 	STATUS, C
	 goto	srt					; too short for 1 - ignore

	movlw	LOW oneH			; check for received 1
	subwf	length, w 
	movlw	HIGH oneH	
	movwf	temp	
	btfss	STATUS, C
	 incfsz	temp, w		
	  subwf	length+1, w
	btfss 	STATUS, C
	 goto	r1					; accept pulse as 1

	movlw	LOW synL			; check for received sync
	subwf	length, w 
	movlw	HIGH synL	
	movwf	temp	
	btfss	STATUS, C
	 incfsz	temp, w		
	  subwf	length+1, w
	btfss 	STATUS, C
	 goto	srt					; too short for sync - ignore

	movlw	LOW synH			; check for received sync
	subwf	length, w 
	movlw	HIGH synH	
	movwf	temp	
	btfss	STATUS, C
	 incfsz	temp, w		
	  subwf	length+1, w
	btfss 	STATUS, C
	 goto	rS					; accept pulse as sync
;	goto	ISR_end

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

	call	processPulse
	call	updateLCD

ISR_end
	bsf		T1CON, 0			; enable timer
	bcf		INTCON, GPIF		; clear interrupt flag	
	bcf		PIR1, TMR1IF		; clear TMR1 interrupt flag
	swapf	stat_save, w		; get original flags in STATUS
	movwf	STATUS
	swapf	w_save, f			; restore Wreg		
	swapf	w_save, w
	retfie

main
	bcf		STATUS, RP0         ; switch to BANK 0
	movlw	b'011000'
	movwf	GPIO				; initialize GPIO
	movlw	7
	movwf	CMCON0				; disable the comparator
	clrf	ADCON0				; ADC OFF
  	bsf    	STATUS, RP0      	; change to BANK 1
	clrf	ANSEL ^ 0x80 		; all inputs digital
	movlw	b'111100'
	movwf	TRISIO ^ 0x80		; enable GPIO<4:3> for input
	bsf		IOC ^ 0x80, 4		; enable interrupt on change at GPIO:4

	movlw	b'01010000'
	movwf	OSCCON ^ 0x80		; set clock freq = 2MHz  
	bcf    	STATUS, RP0        	; back to BANK 0

	movlw	b'01110100'			; setup timer TMR1 with 1:8 prescaler
	movwf	T1CON
	bsf		INTCON, GPIE		; enable interrupts on GPIO change

	call	delay100msec
	call	delay100msec
	clrf 	hours
	clrf	minutes
	clrf	seconds
	clrf	days
	clrf	days+1
	clrf	year	
	clrf	year+1
	clrf	pulse
	clrf	month
	clrf	day

; movlw	0x15
; movwf	hours
; goto correctHours

; movlw	0x33
; movwf 	days+1
; movlw	0x20
; goto	computeDate

	btfss	RXout				; wait for the pulse absence
	 goto	$-1

	clrf	state
	clrf	TMR1L				; clear timer
	clrf	TMR1H
	
	bcf		INTCON, GPIF		; clear interrupt flag
	bcf		PIR1, TMR1IF		; clear TMR1 interrupt flag	
	bsf		T1CON, 0			; enable timer
	bsf		INTCON, GIE			; enable interrupts globally

loop
;	sleep
	nop	
	goto	loop

;#################### procedures
processPulse
	movf	pulse, w			; check pulse duraction for being legal
	btfss	STATUS, Z
 	 goto	accept
	clrf	state				; illegal duration - return to state 0
	return

accept
	movf	state, w
	andlw	0x03
	addwf	PCL, f
	goto	sync1
	goto	sync2
	goto	getByte

sync1							; state 0: waiting for the first sync pulse
	clrf	seconds
	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
	clrf	seconds				; reset seconds counter: 1 second has been passed
	incf	seconds, f			; when a double sync is received
	movlw	9					; prepare to receive 9 pulses
	movwf	pulseNo
	return						

getByte
	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	minutes				; load minutes
	decf	temp, f
	btfsc	STATUS, Z
	 call	correctHours		; 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)
	 call	computeDate
	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
	clrf	state				; since the 59th sync bit is received
	incf	state, f			; we return back to state 1
	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

getNoDays
	movf	month, w
	addwf	PCL, f
	retlw	31					; January
	retlw	28
	retlw	31
	retlw	30
	retlw	31
	retlw	30
	retlw	31					; July
	retlw	31
	retlw	30
	retlw	31
	retlw	30
	retlw	31					; December

correctHours
	andlw	0x3F
	movwf	hours

	movwf	hours+1				; copy CBD(hours) in hours+1
	swapf	hours+1, f			; convert hours+1 into binary rep
	movlw	0x0F
	andwf	hours+1, w
	movwf	hours+1				; retrieve tens of hours
	bcf		STATUS, C
	rlf		hours+1, f
	rlf		hours+1, f
	addwf	hours+1, f			; hours+1 *= 5
	rlf		hours+1, f			; hours+1 *= 10
	movf	hours, w
	andlw	0x0F
	addwf	hours+1, f			; conversion is complete

	movlw	tZone				; correct hours wrt time zone
	btfsc	year, 0				; summer time correction
	 addlw	1
	addwf	hours, f
	movlw	36
	btfss	STATUS, C
	 addwf	hours, f
	movlw	-6
	btfss	STATUS, DC
	 addwf	hours, f
	return

computeDate
	movwf	days
	swapf	days+1, w			; convert the day number from BCD into decimal
	andlw	0x03				; we use Horner's schema
	movwf	temp				; temp = hundreds of days
	bcf		STATUS, C
	rlf		temp, f
	rlf		temp, f				; temp *= 4
	addwf	temp, f				; temp *= 5
	rlf		temp, f				; temp *= 10

	movf	days+1, w			; w = tens of days
	andlw	0x0F				
	addwf	temp, w				; w = temp = hund.*10 + tens
	movwf	temp				; still an 8-bit value	

	clrf	temp+1				; 16-bit multiply by 10
	bcf		STATUS, C
	rlf		temp, f
	rlf		temp, f
	rlf		temp+1, f	
	addwf	temp, f	
	btfsc	STATUS, C
	 incf	temp+1, f			; temp *= 5
	bcf		STATUS, C
	rlf		temp, f
	rlf		temp+1, f			; *temp *= 10
	swapf	days, w
	andlw	0x0F
	addwf	temp, f
	btfsc	STATUS, C
	 incf	temp+1, f			; temp = decimal of days (2 bytes)

	movf	temp, w				; decrement days # to make days counting
	btfsc	STATUS, Z			; start with 0
	 decf	temp+1, f
	decf	temp, w
	movwf	days
	movf	temp+1, w
	movwf	days+1

	clrf	month

	movlw	tZone				; correct days # according to UTC time
	addwf	hours+1, w
	btfsc	STATUS, C
	 goto	cdLoop1
	movf	days, w
	btfsc	STATUS, Z
	 decf	days+1, f
	decf	days, f

cdLoop1
	call	getNoDays			; w = # of days in a month
	movwf	temp
	movf	year, w				; check for leap year and increment 
	andwf	month, w			; the # of days in February 
	andlw	1					; leave only 1 bit
	btfss	STATUS, Z
	 incf	temp, w				; temp = corrected # of days
	
	movf	temp, w
	movwf	temp+1
	subwf	days, f				; 16-bit subtract 
	clrw
	clrf	temp	
	btfss	STATUS, C
	 incfsz	temp, w		
	  subwf	days+1, f
	incf	month, f
	btfsc 	STATUS, C
	 goto	cdLoop1			

	movf	temp+1, w
	addwf	days, w				; days = day of the month
	addlw	1
	movwf	day

	movlw	10					; convert month into BCD
	clrf	temp
cdLoop2
	subwf	month, f
	incf	temp, f
	btfsc	STATUS, C
	 goto	cdLoop2
	addwf	month, w
	decf	temp, f
	swapf	temp, f
	addwf	temp, w
	movwf	month

	movlw	10					; convert date into BCD
	clrf	temp
cdLoop3
	subwf	day, f
	incf	temp, f
	btfsc	STATUS, C
	 goto	cdLoop3
	addwf	day, w
	decf	temp, f
	swapf	temp, f
	addwf	temp, w
	movwf	day
	return

delay100msec					; a 100-msec delay
	movlw	64
	movwf	del

	movlw	195
	addlw	-1
	btfss	STATUS, Z
 	 goto	$-2

	decfsz	del, f
	 goto	delay100msec+2
	return

;##################### custom LCD interface
updateLCD
	movlw	b'10000000'			; move cursor home
	call	writeLCD

	swapf	hours, w
	andlw	0x03
	addlw	48					; ASCII conversion
	call	writeLCD			; write tens of hours

	movf	hours, w
	andlw	0x0F
	addlw	48
	call	writeLCD			; write units of hours
	movlw	':'
	call	writeLCD

	swapf	minutes, w
	andlw	0x07
	addlw	48					; ASCII conversion
	call	writeLCD			; write tens of minutes

	movf	minutes, w
	andlw	0x0F
	addlw	48
	call	writeLCD			; write units of minutes
	movlw	':'
	call	writeLCD

	swapf	seconds, w
	andlw	0x0F
	addlw	48					; ASCII conversion
	call	writeLCD			; write tens of seconds

	movf	seconds, w
	andlw	0x0F
	addlw	48
	call	writeLCD			; write units of seconds

	movlw 	' '
	call	writeLCD
	movlw 	'C'
	call	writeLCD
	movlw 	'S'
	call	writeLCD
	movlw 	'T'
	call	writeLCD	

	movlw	0xC0
	call 	writeLCD			; move cursor to the second line

	swapf	month, w			; write month
	andlw	0x0F
	addlw	48
	call	writeLCD
	movf	month, w
	andlw	0x0F
	addlw	48
	call	writeLCD

	movlw 	'/'
	call	writeLCD

	swapf	day, w			; write month
	andlw	0x0F
	addlw	48
	call	writeLCD
	movf	day, w
	andlw	0x0F
	addlw	48
	call	writeLCD

	movlw 	'/'					; output year
	call	writeLCD

	movlw 	'2'
	call	writeLCD
	movlw 	'0'
	call	writeLCD
	movf	year+1, w
	andlw	0x0F
	addlw	48
	call	writeLCD
	swapf	year, w
	andlw	0x0F
	addlw	48
	call	writeLCD		

;	movlw 	' '					; output leap and time saving info
;	call	writeLCD
;	movf	year, w
;	andlw	0x09
;	addlw	48
;	call	writeLCD	

	return

writeLCD						; write byte in W to the LCD bus
	movwf	dataIO
	movlw	b'011011'
	andwf	GPIO, f
	movlw	0x85				; setup FSR to point to TRISIO
	movwf	FSR
	bsf		INDF, SCL			; make sure that clock is high
	movlw	8
	movwf	cnt					; bits counter

w_loop
	btfss	GPIO, SCL			; wait for release of the clock line
	 goto	$-1

	bcf		INDF, SCL			; set clock low
	rlf		dataIO, f			; get the rightmost data bit
	btfss	STATUS, C
	 bcf	INDF, SDA
	btfsc	STATUS, C
	 bsf	INDF, SDA			; now the data bit is on the line
	goto	$+1					; short delay
	bsf		INDF, SCL			; raise clock up

	movlw	6					; a 20 usec delay
	addlw	-1
	btfss	STATUS, Z
	 goto	$-2

	decfsz	cnt, f
	 goto	w_loop				; repeat this 8 times
	return


 END