;********************************************************************
;*                                                                  *
;* Title       : DCF                                                *
;* Version     : 1.4nix    12. Mai 2007                             *
;* Author      : Uwe Nagel, Parkstr. 46, D-68766 Hockenheim         *
;* Assembler   : MPASM (c) Microchip                                *
;*                                                                  *
;********************************************************************
; 0.1    21.05.02 erster Entwurf
; 0.2    30.05.02 erste lauffähige Version mit LCD
; 0.3    29.09.02 Kosmetik am Quellcode und an der Homepage, keine
;                 Änderung der Funktion
; 0.4    01.04.03 Antennen Symbol auf Wunsch von Christof Rueß
; 0.5    24.07.03 Zeitimpule auf Wunsch von CIOVALLADOLID <CIOVALLADOLID@terra.es>
;                 wahlweise zwei feste Schaltausgänge auf Wunsch von Herrn Eichler
; 0.6    30.08.03 Ports löschen beim Einschalten
;                 PTT-Signal auf PORTA,4 (CIOVALLADOLID)
; 0.7    26.09.03 Filter gegen unsichere Flanken
; 0.8    01.10.03 diverse Optimierungen
; 0.9    15.08.04 anderes Ausgabeformat auf Wunsch von Peter Hilkmann (PE1DCD)
; 0.9a   02.11.04 Fehler bei Monatsausgabe beseitigt, neue Sprachen
; 0.a    06.12.04 Spezialversion mit 16F874 und 4MHz (hier nicht integriert)
; 0.b    03.04.05 serielle Ausgabe der Zeit, Sprachen ausgelagert
; 0.ba   23.10.05 neue Sprache 'swedish', Dank an Volker Lange-Janson
; 0.bb   09.02.06 fehlendes 'RETURN' eingefügt, Dank an Peter Engels
; 0.bc   10.02.06 Antennensymbol auch bei PE1DCD ermöglichen
;                 Programm adaptiert auf 16F628 (Nachfolger 16F84)
; 0.bd   21.02.06 useTimer1 eingeführt
; 0.c    18.03.06 erster Versuch mit Nebenuhr
; 1.0    08.04.06 Hauptuhr mit Sommer-Winterzeit-Umstellung
; 1.1    19.06.06 andere Impulsausgabe für Mutteruhr (PULS500)
; 1.2    02.09.06 blinkendes Antennensymbol
; 1.3    28.09.06 Plausibilitätskontrolle von Puls- und Pausenzeiten
; 1.3a   09.11.06 Fehlerkorrektur MOVLF bei 16f628 raus
; 1.4    25.03.07 Ausgabe von Tag im Jahr und Kalenderwoche
;                 Anzeige der Zeit in UTC
; 1.4a   02.05.07 Fehlerkorrektur: Tabellen für Jahrestag
; 1.4nix 13.05.07 Version für 6 Nixies mit BCD-Dekoder
;********************************************************************


#define TITEL "-DCF77-Uhr 1.4nix-"


;--- useTimer1 definieren, um Timer1 in 16F628 oder anderen neuen PICs 
; zu verwenden. Dann muss ein 4MHz Quarz angeschlossen werden, nicht 3,2768MHz
; geht nicht mit PIC16F84!
#define useTimer1


;--- MUTTERUHR definieren um auf PORTA,3 einen Minutentakt zum Ansteuern 
; von Nebenuhren auszugeben. PORTA,2 muss dazu auf 1 liegen. Zieht man PORTA,2
;  auf Masse, dann wird jede Sekunde ein Takt zum Stellen ausgegeben.
#define MUTTERUHR
; wenn PULS500 definiert ist, wird jede Minute ein 500ms-Puls ausgegeben
; sonst wechselt PORTA,3 jede Minute den Pegel
;#define PULS500

;--- KALENDERWOCHE definieren, um Tag im Jahr und Kalenderwoche zu ermitteln
; und während der Sekunden x8 und x9 in der zweiten Zeile anzuzeigen
; funktioniert wegen Speichermangel nur mit 16F628 !
; nicht im Anzeigeformat PE1DCD implementiert
;#define KALENDERWOCHE


	title		TITEL

#ifdef __16F877
; configs für 16F877
	__config	_XT_OSC & _WDT_ON & _PWRTE_ON & _CP_OFF & _LVP_OFF
	include	<P16F877.INC>
RAM_START equ	0x20
#endif

	errorlevel	-302		; keine bank select message
	radix	dec

;****** Variablen ***************************************************
 cblock	RAM_START
	save_W		; w und
	save_STATUS	; STATUS während interrupt
	save_FSR	; FSR während Interrupt
	flag		; diverse Bits
	temp
	tmp1
	tmp2
	zweihund	; 200 Interupts sind 1 Sek
	puls_zeit	; misst Pulsdauer
	pause_zeit	; misst Pausendauer
;*** diese Zeit wird angezeigt
	dispSec, dispMin, dispStd, dispTag, dispMon, dispJahr, dispWtag, dispStat
;*** hier kommt die gerade empfangene Zeit rein
	dcfMin, dcfStd, dcfTag, dcfMon, dcfJahr, dcfWtag, dcfStat
;*** zum Vergleich, die letzte Minute
	dcf1Min, dcf1Std, dcf1Tag, dcf1Mon, dcf1Jahr, dcf1Wtag, dcf1Stat
	filter, altsec, flag2
 endc
#ifdef MUTTERUHR
 cblock
	pulshi, pulslo, pulsdauer
 endc
#endif


 cblock
	utcDay, utcMonth, utcYear, tmp3
 endc

#ifdef KALENDERWOCHE
 cblock
	a0,a1,a2,b0,b1,b2,c0,c1
	year,month,day
	tag0,tag1,calenderweek,weekday
 endc
cnt	equ	tmp2
#endif

;****** bits in flag ************************************************
#define	neusync flag,0
#define	alt     flag,1
#define	sekunde	flag,2
#define	syncd	flag,3
#define	dcfsign	flag,4
#define ms5	flag,5
#define	comm	flag,7

#define SommerWinter	flag2,0
#define	WinterSommer	flag2,1
#define Pulsstart	flag2,2
#define Pulsende	flag2,3

lc_data      equ tmp1       ; local in lcd_write
lcd_init_cnt equ tmp2       ; only used in lcd_init
temp_lcd     equ tmp2       ; temporary in write_lcd_char, clear_display
temp_byte    equ tmp2       ; temporary in write_byte


;****** Konstanten **************************************************
#define	TICKS	200	; für 4MHz bei Timer1

;********************************************************************
;* Programmcode ...
;********************************************************************
	org	0
	goto	main

;********************************************************************
;* interrupt :
;********************************************************************
	org	4
	goto	interrupt
;********************************************************************
;* Tabellen in Page 0:
;********************************************************************

#ifdef KALENDERWOCHE
getLo:	movf	temp,w
	addwf	PCL,f
	retlw	low 58
	retlw	low 89
	retlw	low 119
	retlw	low 150
	retlw	low 180
	retlw	low 211
	retlw	low 242
	retlw	low 272
	retlw	low 303
	retlw	low 333
	retlw	low 364
	retlw	low 395

getHi:	movf	temp,w
	addwf	PCL,f
	retlw	high 58
	retlw	high 89
	retlw	high 119
	retlw	high 150
	retlw	high 180
	retlw	high 211
	retlw	high 242
	retlw	high 272
	retlw	high 303
	retlw	high 333
	retlw	high 364
	retlw	high 395
#endif

;********************************************************************
;* interrupt  service Routine,
;* hinter Tabellen, damit die alle in Page 0 liegen können:
;********************************************************************
interrupt
	movwf	save_W		; W und STATUS sichern
	swapf	STATUS,w
	movwf	save_STATUS
	movf	FSR,w
	movwf	save_FSR

	bcf	STATUS,RP0	; Wähle register page 0

	btfss	PIR1,CCP1IF

	goto	no_timer_isr ; ist es timer interrupt ?


#ifdef PULS500
	movf	pulsdauer,w
	btfsc	STATUS,Z
	bcf	PORTE,0
	btfss	STATUS,Z
	decf	pulsdauer,f
#endif


; increment filter if port=1 and filter if less than 8
; decrement filter if port=0 and filter if greater than 0

	btfss	PORTA,0
	goto	port_is_0
port_is_1
	incf	filter,f
	btfsc	filter,3	; <8 ?
port_is_0
	decf	filter,f
	btfsc	filter,7
	clrf	filter

; set dcfsign if filter becomes 6
; clear dcfsign if filter becomes 1
; 0 1 2 3 4 5 6 7   6 5 4 3 2 1 0
; 0 0 0 0 0 0 1 1   1 1 1 1 1 0 0

	movf	filter,w
	xorlw	6
	btfsc	STATUS,Z
	bsf	dcfsign
	xorlw	6^1
	btfsc	STATUS,Z
	bcf	dcfsign

;** teste auf steigende Flanke
	btfss	alt
	btfss	dcfsign
	goto	NoPos

;** Pause kürzer als 750ms ist ungültig	
	movf	pause_zeit,w
	addlw	-150		; >150*5ms=750ms
	btfss	STATUS,C
	goto	NoPos

;** positive Flanke...
	bsf	alt
	bsf	Pulsstart
	clrf	puls_zeit

	btfss	neusync	; erster Impuls nach 59. Sekunde?
	goto	NoNeg

	clrf	dispSec
	movf	dcfMin,w
	movwf	dispMin
	movf	dcfStd,w
	movwf	dispStd
	movf	dcfTag,w
	movwf	dispTag
	movf	dcfWtag,w
	movwf	dispWtag
	movf	dcfMon,w
	movwf	dispMon
	movf	dcfJahr,w
	movwf	dispJahr
	movf	dcfStat,w
	movwf	dispStat

	bcf	neusync
	movlw	TICKS	; 200 * 5ms
	movwf	zweihund
	goto	end_timer_isr

NoPos:
;** teste auf fallende Flanke
	btfsc	alt
	btfsc	dcfsign
	goto	NoNeg

;** Puls kürzer als 80ms ist ungültig	
	movf	puls_zeit,w
	addlw	-16		; >16*5ms=80ms
	btfss	STATUS,C
	goto	NoNeg

;** fallende Flanke
	bcf	alt
	clrf	pause_zeit
	bsf	Pulsende

;** teste ob 0 (<150ms) oder lang (>150ms)
	movf	puls_zeit,w
	addlw	-30		; >30*5ms
	call	shiftDCF

NoNeg:
	incfsz	pause_zeit,w	; bis max 255 zählen damit
	incf	pause_zeit,f	; bei längeren Pausen nicht wieder
				; bei 0 angefangen wird

	decfsz	zweihund,f
	goto	TimeOut

;** Sekunde abgelaufen
	movlw	TICKS
	movwf	zweihund
	call	tick

TimeOut:
	incfsz	puls_zeit,f	; 1,28 s keine steigende Flanke ?
	goto	end_timer_isr

	call	sec59		; überprüfe empfangene Zeit

end_timer_isr
	bcf	sekunde
	movf	dispSec,w
	xorwf	altsec,w	; hat sich die Sekunde geändert ?
	skpz
	bsf	sekunde
	xorwf	altsec,f	; aktuelle Sekunde merken

	bcf	PIR1,CCP1IF     ; lösche timer interrupt flag
no_timer_isr
	movf	save_FSR,w
	movwf	FSR
	swapf	save_STATUS,w
	movwf	STATUS
	swapf	save_W,f
	swapf	save_W,w
	retfie






;********************************************************************
;* Hauptprogramm :
;********************************************************************
main    
; goto test

	clrf	TMR1L
	clrf	TMR1H
	movlw	0x31
	movwf	T1CON	; prescaler 8, timer 1 enabled
	movlw	0x0b
	movwf	CCP1CON	; compare mode, trigger special event
	movlw	low 624
	movwf	CCPR1L  ; Fosz/4/(prescaler8)/(200Hz)-1
	movlw	high 624
	movwf	CCPR1H
	bsf	STATUS,RP0	; select Register-Page 1
	movlw	0x04
	movwf	PIE1	; enable Compare Interrupt

	movlw	0x03		; TMR0 internal clock/16 = 200Hz @ 3,2768MHz Quarz
	movwf	OPTION_REG
	movlw 	0x07
	movwf	ADCON1 	; Port A digital

	movlw	0x0f		; PORTA0 DCF-Input; 1=Mutteruhr stellen
	movwf	TRISA		; 
	clrf	TRISB		; Stunden BCD
	clrf	TRISC		; Minuten-Einer und Sekunden-Einer
	clrf	TRISD		; Minuten-Zehner und Sekunden-Zehner
	clrf	TRISE		; Mutteruhr

	bcf	STATUS,RP0	; select Register-Page 0

	clrf	PORTE
	clrf	PCLATH

	clrf	flag
	clrf	puls_zeit
	movlw	TICKS
	movwf	zweihund

	clrf	dispSec
	clrf	dispMin
	clrf	dispStd
	clrf	dispWtag
	clrf	dispJahr
	movlw	1
	movwf	dispMon
	movwf	dispTag

	clrf	flag2

#ifdef MUTTERUHR
	clrf	pulslo
	clrf	pulshi
#endif

#ifdef UTC
	movlw	1
	movwf	utcDay
	movwf	utcMonth
	movwf	utcWday
	clrf	utcYear
#endif

	movlw	0xc0		; Enable  Peripherial Int
	movwf	INTCON

;------ Display initialisieren und Einschaltmeldung ------
	call	display_init	; init LC-Display
	movlw	3			; etwa 3 Sekunden anzeigen
	movwf	temp
hallo:	clrwdt
	btfss	sekunde
	goto	hallo
	bcf		sekunde
	decfsz	temp,f
	goto	hallo

mainloop:
	clrwdt	

	btfsc	Pulsstart
	call	AntenneEin
	btfsc	Pulsende
	call	AntenneAus

	btfss	sekunde
	goto	mainloop

;--- hier kommt das Programm jede Sekunde genau einmal hin
	bcf	sekunde
	call	showtime
	call	special
#ifdef KALENDERWOCHE
	call	calcWeek
#endif
	goto	mainloop
;********************************************************************


;********************************************************************
;* Spezialprogramme für verschiedene Zwecke
;********************************************************************
special
#ifdef MUTTERUHR
	btfss	WinterSommer
	goto	Mutter1
	bcf	WinterSommer
	movlw	low 62
	movwf	pulslo
	movlw	high 62
	movwf	pulshi
Mutter1
	btfss	SommerWinter
	goto	Mutter2
	bcf	SommerWinter
	movlw	low 672
	movwf	pulslo
	movlw	high 672
	movwf	pulshi
Mutter2
	movf	pulslo,w
	iorwf	pulshi,w
	skpnz
	goto	Mutter3

	decf	pulslo,f
	incf	pulslo,w
	skpnz
	decf	pulshi,f

	goto	stellen

Mutter3

	btfss	PORTA,1
	goto	stellen

	movf	dispSec,w
	skpz
	return

stellen
	movlw	0x01
	xorwf	PORTE,f	;PORTE0 umdrehen
#ifdef PULS500
	movlw	100
	movwf	pulsdauer
#endif
	return
#else
	return
#endif

;********************************************************************
; solange nicht synchronisiert ist geht PORTE,1 aus, wenn ein DCF-Impuls 
; anliegt. Ist synchronisiert geht er an!
AntenneEin:
	bcf	Pulsstart
	btfss	syncd
	bcf	PORTE,1
	btfsc	syncd
	bsf	PORTE,1
	return
AntenneAus:
	bcf	Pulsende
	btfss	syncd
	bsf	PORTE,1
	btfsc	syncd
	bcf	PORTE,1
	return

;********************************************************************
;****** neue Sekunde anzeigen ***************************************
;********************************************************************
showtime:
	movlw	0x99
	btfsc	dispStat,4
	movlw	0x98
	movwf	tmp1
	movf	dispStd,w
	movwf	temp
	call	bcd_add
	btfss	temp,7	; 98 oder 99?
	goto	Utc1
	movlw	0x24	; einen Tag zurück
	movwf	tmp1
	call	bcd_add
	goto	Utc2

Utc1
	movf	dispJahr,w
	movwf	utcYear
	movf	dispMon,w
	movwf	utcMonth
	movf	dispTag,w
	movwf	utcDay

Utc2
	movf	temp,w
	btfsc	PORTA,2		; PORTA,2=0 : UTC    1:Lokalzeit
	movf	dispStd,w

	btfss	PORTA,3		; PORTA,3=0 : Datum   1:Zeit
	movf	dispTag,w
	movwf	tmp1		; tmp1 enthält Tag


	movf	dispMon,w
	btfss	PORTA,2		; PORTA,2=0 : UTC    1:Lokalzeit
	movf	utcMonth,w
	btfsc	PORTA,3		; PORTA,3=0 : Datum   1:Zeit
	movf	dispMin,w
	movwf	tmp2

	movf	dispJahr,w
	btfss	PORTA,2		; PORTA,2=0 : UTC    1:Lokalzeit
	movf	utcYear,w
	btfsc	PORTA,3		; PORTA,3=0 : Datum   1:Zeit
	movf	dispSec,w
	movwf	tmp3




; PORTE,2 wird gesetzt, wenn Tag/Stunden-Zehner>0
; ermöglicht die Unterdrückung dieser Stelle
	movf	tmp1,w
	andlw	0xf0
	btfss	STATUS,Z
	bsf	PORTE,2
	btfsc	STATUS,Z
	bcf	PORTE,2

	movf	tmp3,w	; Nibble werden getauscht wegen
	andlw	0x0f		; Pinanordnung am Prozessor
	movwf	temp
	swapf	tmp2,w
	andlw	0xf0
	iorwf	temp,w
	movwf	PORTC

	swapf	tmp3,w
	andlw	0x0f
	movwf	temp
	movf	tmp2,w
	andlw	0xf0
	iorwf	temp,w
	movwf	PORTD

	movf	tmp1,w
	movwf	PORTB

	return


;********************************************************************
display_init:
	clrf	PORTA
	movlw	0x01
	movwf	PORTB
	movlw	0x35
	movwf	PORTC
	movlw	0x24
	movwf	PORTD
	return
;********************************************************************

shiftDCF:
	rrf	dcfJahr,f
	rrf	dcfMon,f
	rrf	dcfTag,f
	rrf	dcfStd,f
	rrf	dcfMin,f
	rrf	dcfStat,f
	return

;********************************************************************
;* in der 59. Sekunde aufgerufen, sortiert erst mal den Empfangspuffer,
;* um die Zeitwerte in einzelnen Bytes zu erhalten
;********************************************************************
;55555555 54444444 44433333 33333222 22222221 11111111
;87654321 09876543 21098765 43210987 65432109 87654321   Sekunde
;PJJJJJJJ JMMMMMWW WTTTTTTP SSSSSSPm mmmmmm1a ooaR0000   DCF-Bits
;
;JJJJJJJJ 000MMMMM 00TTTTTT 00SSSSSS 0mmmmmmm 1aooaR00 00000WWW
; Jahr     Monat    Tag      Stunde   Minute   Status   Wtag
;********************************************************************

sec59:	rlf	dcfMon,w
	rlf	dcfJahr,f	; =Jahr
	rlf	dcfTag,w
	rlf	dcfMon,w
	andlw	0x07
	movwf	dcfWtag		; =Wochentag Mo..So = 1..7
	rrf	dcfMon,f
	rrf	dcfMon,f
	movlw	0x1f
	andwf	dcfMon,f	; =Monat
	rrf	dcfTag,f
	movlw	0x3f
	andwf	dcfTag,f	; =Tag
	rrf	dcfStd,w
	rrf	dcfMin,f
	rrf	dcfStat,f
	rrf	dcfMin,f
	rrf	dcfStat,f	; =Status
	movlw	0x7f
	andwf	dcfMin,f	; =Minute
	rrf	dcfStd,f
	rrf	dcfStd,f
	movlw	0x3f
	andwf	dcfStd,f	; =Stunde

	movlw	dcf1Min
	movwf	FSR
	call	tickMin		; letzte Zeit weiterrechnen

	movf	dcfMin,w	; und mit gerade empfangener
	xorwf	dcf1Min,w	; Zeit vergleichen
	skpz
	goto	ungleich
	movf	dcfStd,w
	xorwf	dcf1Std,w
	skpz
	goto	ungleich
	movf	dcfTag,w
	xorwf	dcf1Tag,w
	skpz
	goto	ungleich
	movf	dcfMon,w
	xorwf	dcf1Mon,w
	skpz
	goto	ungleich
	movf	dcfJahr,w
	xorwf	dcf1Jahr,w
	skpz
	goto	ungleich

;*** zwei aufeinanderfolgende Zeiten haben zueinander gepasst
;*** die aktuelle Zeit bei nächster Sekunde ins Display kopieren

	bsf	syncd
	bsf	neusync
	return

ungleich:
;*** zwei aufeinanderfolgende Zeiten haben nicht eine Minute unterschied
;*** merken wir die gerade empfangene Zeit für die nächste Minute
	movf	dcfMin,w
	movwf	dcf1Min
	movf	dcfStd,w
	movwf	dcf1Std
	movf	dcfTag,w
	movwf	dcf1Tag
	movf	dcfMon,w
	movwf	dcf1Mon
	movf	dcfJahr,w
	movwf	dcf1Jahr
	movf	dcfStat,w
	movwf	dcf1Stat
	bcf	syncd
	return

;********************************************************************
;* hier wird eine Zeit um eine Sekunde weitergezählt.
;********************************************************************
tick:	movlw	dispSec
	movwf	FSR

	call	IncDec	; Sekunde
	movlw	0x60
	xorwf	INDF,w
	skpz
	return
	clrf	INDF	; Sekunde=0

	incf	FSR,f	; ->Minute
;* hier wird eine Zeit um eine Minute weitergezählt.
tickMin:
	call	IncDec	; Minute
	movlw	0x60
	xorwf	INDF,w
	skpz
	return
	clrf	INDF	; Minute=0
	incf	FSR,f	; -> Stunde
	call	IncDec	; Stunde

	movf	FSR,w	; Zeiger auf Stunde sichern
	movwf	tmp2

	movlw	4
	addwf	FSR,f	; -> WTag
	movf	INDF,w
	xorlw	7	; Sonntag?
	skpz
	goto	keinWechsel

; es ist ein Sonntag
	decf	FSR,f	; ->Jahr
	decf	FSR,f	; ->Monat
	decf	FSR,f	; ->Tag
	movf	INDF,w
	addlw	-0x25
	btfss	STATUS,C
	goto	keinWechsel	; Tag ist kleiner 25

; es ist mindestens der 25.
	incf	FSR,f	; -> Monat
	movf	INDF,w
	xorlw	0x03	; März?
	skpz
	goto	keinMaerz	; kein März

; es ist ein Sonntag >=25. im März
	decf	FSR,f	; -> Tag
	decf	FSR,f	; -> Stunde
	movf	INDF,w
	xorlw	0x02
	skpz
	goto	keinWechsel	; es ist nicht 2Uhr

;es ist 2 Uhr --> es wird Sommer
	incf	INDF,f	; Uhr eine Stunde vorstellen
	bsf	WinterSommer
	movlw	5
	addwf	FSR,f	; -> Status
	movlw	0x30
	xorwf	INDF,f	; Status umstellen
	goto	fertig

keinMaerz:
	xorlw	0x03^0x10	; Oktober?
	skpz
	goto	keinWechsel

; es ist ein Sonntag >=25. im Oktober
	decf	FSR,f	; -> Tag
	decf	FSR,f	; -> Stunde
	movf	INDF,w
	xorlw	0x03
	skpz
	goto	keinWechsel	; es ist nicht 3 Uhr

; es ist ein Sonntag >=25. im Oktober, 3 Uhr
	movlw	5
	addwf	FSR,f	; -> Status
	movf	INDF,w
	andlw	0x10	; ist noch Sommerzeit?
	skpnz
	goto	keinWechsel

	movlw	0x30
	xorwf	INDF,f	; Status umstellen
	movf	tmp2,w
	movwf	FSR	; -> Stunde
	decf	INDF,f	; Uhr eine Stunde zurück
	bsf	SommerWinter
	goto	fertig

keinWechsel:
	bcf		SommerWinter
	bcf		WinterSommer
fertig:
	movf	tmp2,w
	movwf	FSR		; Zeiger wieder auf Stunde


	movlw	0x24	; Stunde==24?
	xorwf	INDF,w
	skpz
	return
	clrf	INDF	; Stunde=0

	incf	FSR,f
tickTag:
	call	IncDec	; Tag
	
	incf	FSR,f	; ->Monat
	incf	FSR,f	; ->Jahr
	incf	FSR,f	; ->WTag
	incf	INDF,f
	movlw	1
	btfsc	INDF,3	; WTag==8?
	movwf	INDF
	decf	FSR,f	; ->Jahr
	decf	FSR,f	; ->Monat

	movlw	8
	subwf	INDF,w
	movf	INDF,w
	skpnc
	addlw	-7
	andlw	0x01	
	addlw	0x31
	movwf	temp
	movf	INDF,w
	xorlw	2	; Februar ?
	skpz
	goto	nofeb
	movlw	0x30
	movwf	temp
	incf	FSR,f	; ->Jahr
	movf	INDF,w	; Jahr
	decf	FSR,f	; ->Monat
	decf	FSR,f	; -> Tag

; 00 04 08 12 16 sind Schaltjahre, danach wiederholt es sich
	andlw	0x13
	skpnz
	goto	leapyear

	xorlw	0x12
	skpnz
	goto	leapyear
	movlw	0x29
	movwf	temp
leapyear:
nofeb:	
	movf	temp,w
	xorwf	INDF,w
	skpz
	return
	movlw	1
	movwf	INDF	; Tag=1

	incf	FSR,f	; -> Monat
	call	IncDec	
	movf	INDF,w
	xorlw	0x13
	skpz
	return
	movlw	1
	movwf	INDF	; Monat=1

	incf	FSR,f	; -> Jahr
;	call	IncDec
;	return

IncDec:	incf	INDF,f		; Zähler erhöhen
	movlw	6
	addwf  	INDF,w		; +6 (Dez.-korrektur) --> Ergebnis vorerst in w
	btfsc	STATUS,DC	; Überlauf im Low-Nibble?
	movwf	INDF		; ja, Korr. Ergebn. zurück
	return

;********************************************************************


#ifdef KALENDERWOCHE

;********************************************************************
; addiert 24 Bit a2..a0 zu 24 bit b2..b1
;********************************************************************
add24:	
	movf	a0,w
	addwf	b0,f

	movf	a1,w
	btfsc	STATUS,C
	addlw	1
	addwf	b1,f

	movf	a2,w
	btfsc	STATUS,C
	addlw	1
	addwf	b2,f

	return

;********************************************************************
; multipliziert 24 Bit a2..a0 mit 16 Bit c1.c0
; Produkt in b2..b0
;********************************************************************
mul:	
	clrf	b0
	clrf	b1
	clrf	b2
	movlw	16
	movwf	cnt
mulloop:
	btfsc	c0,0
	call	add24

	bcf	STATUS,C
	rlf	a0,f
	rlf	a1,f
	rlf	a2,f
	rrf	c0,w
	rrf	c1,f
	rrf	c0,f
	decfsz	cnt,f
	goto	mulloop
	return

;********************************************************************
; teilt 24 Bit b2..b0 durch 16 Bit a1.a0
; Quotient in b2..b0,  Rest in c1..c0
;********************************************************************
div2416	
	clrf	c1
	clrf	c0

	movlw	25
	movwf	cnt

divloop	
	movf	a0,w
	subwf	c0,w
	movwf	tmp1
	movf	a1,w
	btfss	STATUS,C
	incf	a1,w
	subwf	c1,w
	btfss	STATUS,C
	goto	nosub
	movwf	c1
	movf	tmp1,w
	movwf	c0

nosub	
	rlf		b0,f
	rlf		b1,f
	rlf		b2,f
	rlf		c0,f
	rlf		c1,f

	decfsz	cnt,f
	goto	divloop

	rrf		c1,f
	rrf		c0,f
	return


;********************************************************************
; wandelt ein BCD-Byte nach binär  W->W
;********************************************************************
bcd2bin8:
	movwf	tmp1
	swapf	tmp1,w
	andlw	0x0f
	movwf	temp
	addwf	temp,f	;*2 no Carry!
	rlf		temp,f	;*4
	addwf	temp,f	;*5
	rlf		temp,f	;*10
	movf	tmp1,w
	andlw	0x0f
	addwf	temp,w	;w=binärwert der BCD-Zahl a
	return

;********************************************************************
;                  Binary To BCD Conversion Routine
; Converts ACCb to decimal BCD. Does not change ACCb.
; Modified version. The original was found in
; MICROCHIP 'Embedded Controll Handbook' 1993
;********************************************************************
bin2bcd:	
	bcf	STATUS,C	; clear the carry bit
	movlw	16
	movwf	cnt		; maximal 16 Bit
	clrf	a0		; in 6 BCD-Stellen wandeln
	clrf	a1
	clrf	a2

loopBCD	rlf	b1,w ; MSB -> Carry
	rlf	a0,f
	rlf	a1,f
	rlf	a2,f
        
	rlf	b1,w		; rotate ACCb
	rlf	b0,f
	rlf	b1,f

	decfsz	cnt,f
	goto	adjDEC
	return

adjDEC
	movlw	a0
	movwf	FSR
	call	adjBCD

	incf	FSR,f
	call	adjBCD

	incf	FSR,f
	call	adjBCD
  
	goto	loopBCD

adjBCD	movlw	3
	addwf	INDF,W
	movwf	temp
	btfsc	temp,3	; test if result > 7
	movwf	INDF
	movlw	0x30
	addwf	INDF,W
	movwf	temp
	btfsc	temp,7	; test if result > 7
	movwf	INDF		; save as MSD
	return

;********************************************************************
; teilt b2..b0 durch 2
;********************************************************************
div2:	
	bcf		STATUS,C
	rrf		b2,f
	rrf		b1,f
	rrf		b0,f
	return


;********************************************************************
; berechnet aus year, month, day
; die Tage seit dem 1.1.1970
; für das julianische Datum muss man noch 2440588=0x253D8C addieren
;********************************************************************
days:
	movf	year,w
	addlw	30
	movwf	a0
	clrf	a1
	clrf	a2		; a=year+30

	movf	month,w	; 0x01..0x0c
	addlw	-3
	movwf	temp

	btfss	temp,7	; negativ?
	goto	goon

	movlw	12
	addwf	temp,f
	decf	a0,f	; year-1

goon:	
	movlw	low 1461 ;0xb5
	movwf	c0
	movlw	high 1461 ;0x05
	movwf	c1		; c=1461
	call	mul		; b=year*1461
	movlw	2
	movwf	a0
	clrf	a1
	clrf	a2
	call	add24	; b=year*1461+2
	call	div2
	call	div2	; b=(year*1461+2)/4

	call	getLo
	movwf	a0
	call	getHi
	movwf	a1
	clrf	a2
	call	add24	; b+=(month*153+2)/5+58

	movf	day,w
	movwf	a0
	clrf	a1
	clrf	a2
	call	add24	; b+=day
	return


;********************************************************************
;test:
;	movlw	0x07
;	movwf	dispJahr
;	movlw	0x12
;	movwf	dispMon
;	movlw	0x31
;	movwf	dispTag
calcWeek:
#ifdef UTC
	movf	utcYear,w
	call	bcd2bin8
	movwf	year
	movf	utcMonth,w
	call	bcd2bin8
	movwf	month
	movf	utcDay,w
	call	bcd2bin8
	movwf	day
#else
	movf	dispJahr,w
	call	bcd2bin8
	movwf	year
	movf	dispMon,w
	call	bcd2bin8
	movwf	month
	movf	dispTag,w
	call	bcd2bin8
	movwf	day
#endif
	call	days

	movlw	3
	movwf	a0
	clrf	a1
	clrf	a2
	call	add24	; b=days+3

	movf	b0,w
	movwf	tag0
	movf	b1,w
	movwf	tag1

	movlw	7
	movwf	a0
	call	div2416	; b=(days+3)/7;  c=(days+3)%7;

	incf	c0,w	; w=Wochentag 1=Montag
	movwf	weekday

	movf	b0,w
	movwf	calenderweek
;
	movlw	1
	movwf	month
	movlw	7
	movwf	day
	call	days	; 1.Woche im Jahr

	movf	b0,w
	subwf	tag0,f
	movf	b1,w
	btfss	STATUS,C
	incf	b1,w
	subwf	tag1,f	; 

	movf	tag0,w
	addlw	4
	movwf	tag0
	btfsc	STATUS,C
	incf	tag1,f	; Tag im Jahr

	movlw	7
	movwf	a0
	clrf	a1
	clrf	a2
	call	div2416

	movf	b0,w
	subwf	calenderweek,f
	incf	calenderweek,f	; Kalenderwoche roh

	movf	calenderweek,w
	xorlw	53
	btfss	STATUS,Z
	goto	not53

#ifdef UTC
	movlw	utcDay
#else
	movlw	dispTag
#endif
	call	bcd2bin8
	movwf	temp
	movf	weekday,w
	subwf	temp,w
	addlw	-28		; >=28
	btfss	STATUS,C
	goto	not53

	movlw	1
	movwf	calenderweek

not53:	
	return

#endif


;****************************************** 
;bcd_add 
; 
; Computes  z = x + y 
; where x,y,z are all 8-bit packed BCD numbers 
; Exits with C=1 if x+y > 0x99 and with z=1 if 
; x+y = 0x100. 
; Note that z can be aliased to x or y so that 
; it's possible to calculate x = x+y or y = x+y 
; This routine forms the BCD two's complement of 
; y and then uses the bcd_subtract routine to 
; find the sum. e.g. z = x - (-y) = x + y 
; 
; 10 cycles 

x equ temp
y equ tmp1
z equ temp

bcd_add 
	comf	y, w	;W = ~y 
	addlw	0x9a+1	;W = ~y + 0x9a +1 
					;W = (~y + 1) + 0x9a 
					;W = 0x9a - y 
	subwf	x, w	;W = x - (0x9a - y) 
					;W = x + y - 0x9a 
					;W = x + y + 0x66 
	rlf		z, f    ;Get the carry 
	skpdc			;if lsn of x + lsn of y < 10 (dec) 
	addlw	-0x06	; then remove the extra 6 added above 
	btfss	z,0		;Similarly for the msn 
	addlw	-0x60 
	rrf		z, f    ;restore the carry 
	movwf	z
	return

;********************************************************************
	end	
