; TEST: In der RTC-Stellroutine wird das Buzzer-Flag einmalig
;gesetzt, um herauszufinden, ob die Routine versehentlich aufgerufen wird

.include "c:\avrtools\appnotes\2313def.inc"
;
; Berlin-Uhr 
; (c) 2002 Stefan Huebner
; Portions (c) Atmel Norway (mpy8u, div8u)
;
; Die Multiplikations- und Divisionsroutinen werden benoetigt, um die verschiedenen Zahlensysteme
; fuer die Minuten- und Stundenangaben umzuwandeln. Bei Netzsynchronisation wird zB mit Binaer-
; zahlen gerarbeitet, bei Verwendung der PCF8583 RTC muessen BCD- in Binaerzahlen gewandelt
; werden, und fuer die Darstellung der Zeit auf der LED-Matrix werden DIV5 und MOD5 benoetigt.
;
; Eigene Definitionen
; Die Register werden etwas verschwenderisch benutzt, einige nur zur Haelfte etc., aber so lange
; noch genug da sind, spare ich lieber etwas Verarbeitungszeit zum Auseinanderdroeseln der Bits
; als an Registern. Die weniger universell eingesetzten Register sind aus der ersten Bank von
; r0-15 gewaehlt, die globalen aufgrund der Moeglichkeit der direkten Operationen aus r16-31.
; r15-r19 werden von den mathematischen und I2C-Unterprogrammen gebraucht
;
; Universelle Register
;
.def 	temp = r20	; Zwei allgemeine Zwischenspeicher
.def	temp2 = r21	
.def	count = r22 	; Der Multiplex-Zaehler, wird im Abstand von 1ms inkrementiert
.def 	minutes = r23	; Minuten (0-59, binaer !)
.def	hours = r24	; Stunden (0-23, binaer !)
.def	seconds = r25	; Sekunden, zur Zeit NICHT von BCD->Binaer gewandelt, da nur Bit0 ausgewertet wird !
.def	keybits = r26	; Bis zu 8 Tastenbits (wovon hier 4 benutzt werden)
.def 	keyprev = r27	; "Alte" Tastenbits merken
.def	keysvalid = r28	; Hier wird gespeichert, wie lange die derzeitige Tastenkombination
			; schon gueltig ist (in 8ms-Inkrementen)
.def	alarmmin = r29	; Minuten der Alarmzeit
.def	alarmhr = r30	; und die Stunden
.def	keyrepeat = r31	; Ein Zaehlregister fuer die Tastenwiederholung
;
; Bei den weniger haeufig benutzten Registern muss man aufpassen, keine direkten Operationen durchzufuehren
;
.def	lastkeys = r13	; Ein Zwischenspeicher fuer die Tastenbits der vorherigen Durchlaufs
.def	nextkeys = r14	; und einer fuer den laufenden
;
; RTCADDR ist die Adresse der RTC PCF8583, 0xA0 oder 0xA2.
;
.equ 	RTCADDR = 0xA0	; A0-Leitung der RTC auf Masse
;
; Port- und Registerbits Namen zuordnen
;
; Port D
;
.equ	C1 = 0		; Column-(Spalten-)leitungen an Port D, low-aktiv
.equ 	C2 = 1
.equ 	C3 = 2
.equ 	C4 = 3
.equ    SYNC = 4	; Bei Netzfrequenz als Zeitbasis
.equ	SCLP = 4	; I2C-SCL-Pin (bei Verwendung der RTC)
.equ 	SDAP = 5	; I2C-SDA-Pin (dito)
.equ	buzzer = 6	; Reservierte Leitung fuer Alarm, erstmal Timing-Test !
;
; Port B
;
.equ	keys = 0	; Tastenabfrageleitung - wenn kein Analogkomparator benutzt wird !
.equ	R1 = 2		; Row-(Reihen-)leitungen an Port B
.equ	R2 = 3
.equ	R3 = 4
.equ	R4 = 5
.equ 	R5 = 6
.equ	R6 = 7
;
; Definitionen fuer mpy8u - Multiplizieren 8b x 8b -> 16b ohne Vorzeichen
;
.def	mc8u	=r16		;multiplicand
.def	mp8u	=r17		;multiplier
.def	m8uL	=r17		;result Low byte
.def	m8uH	=r18		;result High byte
;
; Definitionen fuer div8u - Dividieren 8b / 8b -> 8b + 8b ohne Vorzeichen
;
.def	drem8u	=r15		;remainder
.def	dres8u	=r16		;result
.def	dd8u	=r16		;dividend
.def	dv8u	=r17		;divisor
;
; Defintionen fuer I2C-Master-Funktionen
;
.def	i2cdelay= r16		; Delay loop variable
.def	i2cdata	= r17		; I2C data transfer register
.def	i2cadr	= r18		; I2C address and direction register
.def	i2cstat	= r19		; I2C bus status register
;
;
;
; Programmbeginn
;
.org 0
rjmp	RESET		; Zum Programmstart
;
.org OVF0addr		; Einsprungadresse fuer Timer0-Ueberlauf
;rjmp	LINE_CLOCK	; Falls mal die Netzfrequenz benutzt werden sollte ...
reti			; ... erstmal aber sicherheitshalber zurueck vom Interrupt
;
.org OC1addr		; Einsprungadresse fuer Timer1-Compare Match (1kHz-Zeitbasis)
rjmp	MUX		; Zur Multiplex-Routine fuer's Display !
;
;
.org 0x0b		; Hier gibt's keine Interrupts mehr
;
; MUX-Routine - Multiplexer fuer die LED-Matrix
;
MUX:
;
andi	count, 0x03	; Count soll nur von 0 bis 3 zaehlen !
ldi	temp, 0x00	; VORSICHT: temp=0 wird ein paar Zeilen tiefer noch einmal gebraucht !
out	PORTB, temp	; Alle Zeilen-...
sbi	PORTD, C1	; ... und alle Spaltenausgaenge deaktivieren
sbi	PORTD, C2
sbi 	PORTD, C3
sbi	PORTD, C4
;
; Anhand von count wird die Matrix-Spalte ausgewahlt. Ausserdem werden bei count=0 die Tastenbits geloescht,
; nachdem ihr vorheriger Zustand zwischengespeichert wurde. Schliesslich wird die Bitmaske der zu der jeweiligen
; Spalte gehoerenden Taste in temp abgelegt, so dass sich die spaetere Auswertung auf das ODERn der Tastenbits
; mit dieser Bitmaske beschraenkt - das spart eine Zaehl- und Schiebeschleife, die sonst noetig gewesen waere !
;
sbrc	count, 1	; Count pruefen: Bit 1 geloescht ?
rjmp	B1SET		; wenn nicht -> bei B1SET weitermachen
sbrc	count, 0	; Bit 0 geloescht ?
rjmp	COL1		; wenn nicht -> Spalte 1 auswaehlen
;COL0			; sonst Spalte 0
cbi	PORTD, C1
mov	lastkeys, keybits	; Erste Spalte: Die vorigen Tastenbits merken
mov	nextkeys, temp	; und "neue" Tastenbits auf 0 setzen VORSICHT: temp muss 0 sein 
ldi	temp, 0x01
rjmp	DOKEYS
COL1:			; Spalte 1
cbi	PORTD, C2
ldi	temp, 0x02
rjmp	DOKEYS
B1SET:
sbrc	count, 0	; Bit 1 ist gesetzt, ist Bit 0 geloescht ?
rjmp	COL3		; wenn nicht -> Spalte 3 auswaehlen
;COL2			; sonst Spalte 2
cbi	PORTD, C3
ldi 	temp, 0x04
rjmp	DOKEYS
COL3:			; Spalte 3
cbi 	PORTD, C4
ldi	temp, 0x08
; 								
; Hier wird das Sekundenregister geprueft, bei geraden Sekunden (Bit0 geloescht) soll die LED
; C4/R4 leuchten, sonst nicht (default). Die Ueberpruefung kann hier schon stattfinden,
; da im spaeteren Vergleich in Spalte 4 (temp = 4 im Programmteil ab ROWS:) die Minuten-
; Fuenfer immer kleiner (temp + 4 + 4 = 12) sein werden, schliesslich gib es keine 60. Minute
; in der Stunde.
;
sbrs	seconds, 0
sbi	PORTB, R4
;
DOKEYS:
;
; Erstmal muss das zu der jeweiligen Spalte gehoerende Tastenbit ggf. gesetzt werden, welches bereits in temp liegt
;
sbic	PINB, keys	; Ist die jeweilige Taste gedrueckt ?
rjmp	EVALKEYS	; Nein, also pruefen, ob schon ausgewertet werden darf
or	nextkeys, temp	; sonst Tastenbits mit Bitmaske ODERn
;							
EVALKEYS:
cpi	count, 3	; Sind wir mit der letzten Spalte durch und duerfen Tasten auswerten ?
brne	ROWS		; Nein, also gleich mit den Reihen weitermachen
;
; SONST: Ja, letzte Spalte. Tastenbits sind jetzt vollstaendig in nextkeys, erstmal nach keybits kopieren...
;
mov	keybits, nextkeys
;
; Jetzt steht in Keybits das Bitmuster der gedrueckten Taste(n), welches nun ausgewertet wird. Im Folgenden
; wird ein Zaehler (keysvalid) inkrementiert, solange die Tastenkombination von einem Durchlauf auf den naechsten
; unveraendert bleibt. Ist keine Taste gedrueckt, bleibt der Zaehler bei 0. So kann das Hauptprogramm die Tasten
; einerseits leicht entprellen, zum anderen auch laengere Tastendruecke bis 255*8ms auswerten. Bei 255 wird angehalten.
;
;cpi	keybits, 0	; Ist ueberhaupt eine Taste gedrueckt ?
;breq	INVALID		; Nein, also weiter bei Ungueltig
;
cp	keybits, lastkeys	; Mit den vorherigen Tastenbits vergleichen
brne	INVALID		; Tastenbits haben sich geaendert, sind also ungueltig
;
cpi	keysvalid, 255	; Zaehler schon am Ende ?
breq	REPEAT		; Wenn ja -> Mit Tastenwiederholung weitermachen, sonst erst inkrementieren
inc	keysvalid
;
REPEAT:
cpi	keyrepeat, 255	; Dieser Zaehler zaehlt die 8ms-Inkremente bis zur naechsten Tastenwiederholung
breq	ROWS		; Auch dieser zaehlt nur bis 255, damit er immer, wenn die Tastenwiederholung einsetzt,
inc	keyrepeat	; im Bereich ueber ( Wiederholrate * 8ms ) ist -> konsistentes Verhalten beim Einsetzen
rjmp	ROWS		; der Wiederholung. Einzige Bedingung: Abstand zwischen zwei Wiederholungen << Zeit bis
			; zum Wiederholen.
INVALID:
ldi	keysvalid, 0	; Tastenbits ungueltig !
;
ROWS:
;
; Prinzip der folgenden Berechnungen: 
; in Bits 0 und 1 von temp steht die Spaltennummer der Matrix minus eins. count wird daher
; nach temp kopiert, Bits 0 und 1 maskiert und der dadurch entstehende Wert zwischen 0 und 3
; in eine Spaltennummer zwischen 1 und 4 umgesetzt. Anschliessend wird die Einer- und die
; Fuenferstelle der Stunden berechnet und in Abhaengigkeit von der Spaltennummer entschieden,
; welche Zeilenausgaenge aktiviert werden muessen. Dasselbe gilt fuer die Minutenstellen, nur
; dass hier drei Entscheidungen fuer die Minuten-Fuenfer benoetigt werden.
;
mov	temp, count	; count kopieren
inc	temp		; und Wert um eins erhoehen, um Spaltennummer zwischen 1 und 4 zu erhalten
;
mov	dd8u, hours	; Stunden durch 5 teilen, um DIV5 und MOD5 zu erhalten
ldi	dv8u, 0x05	
rcall	div8u		; Teilen - der Rest enthaelt die Stunden-Einer ...
cp	drem8u, temp	; ... sind die groesser/gleich der oben errechnete Wert in temp ?
brlt	NO_STD1		; nein, also die Stunden-Einer (Bit 6) nicht setzen
sbi	PORTB, R5	; sonst Bit 6 setzen
NO_STD1:
cp	dres8u, temp	; Nun die Stunden-Fuenfer - groesser/gleich temp ?
brlt	NO_STD5		; nein, also die Stunden-Fuenfer (Bit 7) nicht setzen
sbi	PORTB, R6	; sonst Bit 7 setzen
NO_STD5:
;
; Nun dasselbe fuer die Minutenstellen
;
mov	dd8u, minutes	; Minuten durch 5 teilen
ldi	dv8u, 0x05
rcall	div8u
cp	drem8u, temp	; Minuten-Einer mit der Spaltennummer vergleichen
brlt	NO_MIN1
sbi	PORTB, R1
NO_MIN1:
cp	dres8u, temp	; Minuten-Fuenfer kleiner als die Spaltennummer ?
brlt	NO_MIN5	
sbi	PORTB, R2
ldi	temp2, 0x04	; Vier zur Spaltennummer hinzuaddieren, um in die naechste Zeile zu kommen
add	temp, temp2
cp	dres8u, temp	; Minuten-Fuenfer auch kleiner als die erweiterte Spaltennummer ?
brlt	NO_MIN5
sbi	PORTB, R3
add	temp, temp2	; Nochmals addieren fuer die naechste Zeile (Minuten-Fuenfer 9 bis 11)
cp	dres8u, temp
brlt	NO_MIN5
sbi	PORTB, R4
NO_MIN5:
MUXEND:
inc	count		; Count inkrementieren
reti




RESET:
ldi	temp, 0xCF	; Stackpointer 16 Bytes vor dem SRAM-Ende initialisieren
out	spl, temp
;
ldi	temp, 0x00	; Port B: Alle Ausgaenge low, alle Eingaenge ohne Pullup
out	PORTB, temp
;
ldi	temp, 0x0F	; Port D: Ausgaenge 0-3 high, 6 low & Eingaenge ohne Pullup
out	PORTD, temp
;
ldi	temp, 0xFC	; Port B: Bits 2-6 sind Ausgaenge
out	DDRB, temp
;
ldi	temp, 0x4F	; Port D: Bits 0-3,6 sind Ausgaenge
out	DDRD, temp
;
ldi	temp, 0x00	; Keine PWM, keine Beeinflussung von irgendwelchen Pins
out	TCCR1A, temp	
;
ldi	temp, 0x0A	; Timer 1 nach Compare Match zuruecksetzen, Vorteiler durch 8
out	TCCR1B, temp
;
ldi	temp, 0x05	; High-Byte von 1500d fuer das Compare Match Register
out	OCR1AH, temp
ldi	temp, 0xDC	; und das Low-Byte hinterher
out	OCR1AL, temp
;
ldi	temp, 0x40	; Compare Match-Interrupt enablen
;ldi 	 temp, 0x42	; Compare Match- und Timer0-Ueberlauf-Interrupt enablen (Netztfrequenz-Version)
out	TIMSK, temp
;
rcall	RTC_INIT	; RTC_INIT uebertraegt die gespeicherte Alarmzeit in die entsprechenden Register
;
INTON:			; Nach dem Ausschalten der Interrupts muss hier hin gesprungen werden !
sei			; Interrupts einschalten - Zeitbasis laeuft !
SCHLEIFE:
;
;
; Pruefen, ob eine gueltige Tastenkombination vorliegt (Annahme: 48ms Entprellzeit)
;
cpi	keysvalid, 6
brlo	SCHLEIFE	; Entprellen
;
cli			; Die Tastenbits duerfen sich waehrend der Ueberpruefung nicht aendern !!!
;
; Testen, ob ueberhaupt eine Taste gedrueckt ist
;
cpi	keybits, 0
breq	NOKEYS
;
; Dann pruefen, ob die Kriterien fuer "Uhrzeit stellen" erfuellt sind
;			
sbrs	keybits, 0	; Notwendige Bedingung: 1. Taste gedrueckt
rjmp	ALARMKEY
sbrc	keybits, 3	; Hinreichende Bedingung: 4. Taste nicht gedrueckt
rjmp	INTON		; Siehe oben - Interrupts vor dem naechsten Schleifendurchlauf wieder einschalten
sbrs	keyprev, 3	; War Taste 4 zuvor gedrueckt ? 
rjmp	SETTIME		; Wenn nicht -> Weiter zum Einstellen
rjmp	UPDATE		; Sonst erstmal die Minuten- und Stundenregister neu laden
;
ALARMKEY:		
sbrs	keybits, 3	; Hinreichende Bedingung (1. nicht gedrueckt) erfuellt, notwendige testen
rjmp	UPDATE
sbrc	keyprev, 3	; War Taste 4 letztes Mal auch schon gedrueckt ?
rjmp	SETTIME
			; Nein, also erstmal die Alarmzeitregister kopieren
mov	minutes, alarmmin ; Alarmzeit in minutes und hours laden
mov	hours, alarmhr
;
SETTIME:
andi	seconds, 0xFE	; Sekunden-LED einschalten waehrend des Stellens
sbrs	keybits, 1	; Minuten-Taste gedrueckt ?
rjmp	NOMIN
sbrc	keybits, 2	; Stunden etwa auch ? Wenn ja, lieber nichts machen (Schoenheitsmassnahme)
rjmp	NOHR		; Ja, NOHR ist richtig ! Zumindest solange es dort direkt zu INTON geht...
sbrs	keyprev, 1	; War sie beim letzten Durchlauf auch schon gedrueckt ?
rjmp	DOMINUTES	; Nein, also gleich die Minuten verarbeiten
cpi	keysvalid, 200	; Taste war gedrueckt - schon laenger als 1.6 Sekunden ? (Repeat-Kriterium)
brlo	INTON		; Nein, also ganz normal weitermachen
cpi	keyrepeat, 40	; Schon 320ms seit dem letzten Repeat verstrichen ?
brlo	INTON		; Nein, also auch weiter wie gehabt
ldi	keyrepeat, 0	; Ja, also Wiederholungstimer zuruecksetzen und Register inkrementieren
;
DOMINUTES:		
cpi	minutes, 59	; Sind die Minuten schon bei 59 ?
brsh	MIN59
inc	minutes		; Nein, also eine Minute weiter
mov	keyprev, keybits
rjmp	INTON
MIN59:
ldi	minutes, 0
mov	keyprev, keybits
rjmp	INTON
;
NOMIN:				
sbrs	keybits, 2	; Stunden-Taste gedrueckt ?
rjmp	NOHR
sbrs	keyprev, 2	; War sie beim letzten Durchlauf auch schon gedrueckt ?
rjmp	DOHOURS		; Nein, also gleich die Stunden verarbeiten
cpi	keysvalid, 200	; Taste war gedrueckt - schon laenger als 1.6 Sekunden ? (Repeat-Kriterium)
brlo	INTON		; Nein, also ganz normal weitermachen
cpi	keyrepeat, 40	; Schon 320ms seit dem letzten Repeat verstrichen ?
brlo	INTON		; Nein, also auch weiter wie gehabt
ldi	keyrepeat, 0	; Ja, also Wiederholungstimer zuruecksetzen und Register inkrementieren
;
DOHOURS:
cpi	hours, 23	; Sind die Stunden schon bei 23 ?
brsh	HR23
inc	hours		; Nein, also eine Stunde weiter
mov	keyprev, keybits
rjmp	INTON
HR23:
ldi	hours, 0
NOHR:
mov	keyprev, keybits
rjmp	INTON
;
NOKEYS:
;
; Keine Taste -> Testen, ob zuvor SET oder ALARM gedrueckt war und ggf. RTC einstellen
; Vorsicht ! Wurden Taste 1 UND 4 gedrueckt und zusammen losgelassen, darf gar nichts aktualisiert werden !
;
sbrs	keyprev, 3	; Wurde eine Alarmzeiteinstellung vorgenommen ?
rjmp 	NOALARM
sbrc	keyprev, 0	; Und war Taste 1 NICHT gedrueckt ?
rjmp	UPDATE
mov	alarmmin, minutes
mov	alarmhr, hours	; Alarmzeitregister mit neuer Alarmzeit laden
;						
;rcall   RTC_ALARM	; RTC_ALARM aktualisiert die Alarmzeit-Register der RTC und darf
;			; natuerlich nur aufgerufen werden, wenn die RTC PCF8583 benutzt wird.
NOALARM:
sbrs	keyprev, 0	; Wurde die Uhrzeit eingestellt ?
rjmp	UPDATE		; Auch nicht, also nur minutes und hours aktualisieren
sbrc	keyprev, 3	; Uhrzeit nicht uebernehmen, wenn Taste 1 UND 4 gedrueckt waren !
rjmp	UPDATE
;
rcall	RTC_TIME	; RTC_TIME aktualisiert die Uhrzeit-Register der RTC, sofern vorhanden
;
UPDATE:
cpi	keyrepeat, 3	; Erst pruefen, ob 24ms (Aktualisierungsintervall) schon vergangen sind
brlo	UPTODATE	; keyrepeat kann hier mitverwendet werden, da dieser Punkt beim Stellen nicht angesprungen wird !
;
rcall	RTC_READ	; RTC_READ uebernimmt die RTC-Uhrzeit nach minutes und hours
;
ldi	keyrepeat, 0	; Timer fuer Aktualisierungsintervall zuruecksetzen
UPTODATE:
mov	keyprev, keybits; Damit bei bestimmten Tastendruck-Abfolgen ein konsistentes Verhalten entsteht
rjmp	INTON
;
;
;
; Unterprogramme
;
; RTC_INIT: RTC initialisieren und Alarmzeitregister der RTC nach alarmmin und alarmhours kopieren
;
RTC_INIT:
cli
ldi	i2cadr, RTCADDR
rcall	i2c_start
ldi	i2cdata, 0	; Control-Register an Adresse 0 
rcall	i2c_write
ldi	i2cdata, 0	; auf 0 setzen -> 24h Echtzeituhr-Modus, 32768Hz Quarzfrequenz
rcall	i2c_write
rcall	i2c_stop
sei
ret
;
; RTC_READ: Uhrzeitregister der RTC nach minutes und hours kopieren
;
RTC_READ:
cli			; Zeitkritisch, bitte nicht stoeren ! (Kann aber eigentlich nicht passieren...)
ldi	i2cadr, RTCADDR
rcall	i2c_start
ldi	i2cdata, 2	; Ab Register 2 (Sekunden) mit dem Lesen beginnen
rcall	i2c_write
ldi	i2cadr, RTCADDR+1
rcall	i2c_rep_start	; Repeated Start zum Lesen (Adressbit 0 gesetzt) senden
;
clc			; Acknowledge senden
rcall	i2c_read	; jetzt stehen die Sekunden in i2cdata
mov	seconds, i2cdata
;
clc
rcall	i2c_read	; und jetzt die Minuten - Achtung: BCD-Format !
mov	minutes, i2cdata	; Wert sichern (hier sind die Minuten noch nicht gueltig, es soll nur ein mov gespart werden !)
swap	i2cdata		; Zehner und Einer vertauschen
andi	i2cdata, 0x0F	; und nur die Zehner behalten
ldi	mc8u, 10	; Multiplikanten mit 10 laden - praktisch: i2c_read und mp8u (Faktor) sind identisch, spart ein mov
rcall	mpy8u		; in m8uL stehen jetzt die Zehner-Minuten
andi	minutes, 0x0F	; vom gesicherten Wert nur die Einer behalten
add	minutes, m8uL	; und Zehner hinzuaddieren
;
sec			; Letzter Lesevorgang vor i2c_stop sendet kein Acknowledge
rcall	i2c_read	; und zu guter letzt die Stunden
rcall	i2c_stop
mov	hours, i2cdata	; Dasselbe fuer die Stunden
swap	i2cdata		; Zehner und Einer vertauschen
andi	i2cdata, 0x03	; und nur die Zehner behalten, allerdings ist hier nur 0,1,2 moeglich
ldi	mc8u, 10	; Multiplikanten mit 10 laden - praktisch: i2c_read und mp8u (Faktor) sind identisch, spart ein mov
rcall	mpy8u		; in m8uL stehen jetzt die Zehner-Stunden
andi	hours, 0x0F	; vom gesicherten Wert nur die Einer behalten
add	hours, m8uL	; und Zehner hinzuaddieren
sei			; jetzt darf wieder unterbrochen werden
ret
;
; RTC_TIME: RTC-Uhrzeit einstellen (mit minutes und hours abgleichen)
;
RTC_TIME:
cli			; Interrupts aus waehrend I2C-Zugriffen
ldi	i2cadr, RTCADDR
rcall	i2c_start
ldi	i2cdata ,2	; Ab Register 2 (Sekunden) mit dem Schreiben beginnen
rcall	i2c_write
ldi	i2cdata, 1	; Mit Sekunde 1 beginnen (damit die Sekunden-LED erstmal kurz ausgeht !)
rcall	i2c_write
;
mov	dd8u, minutes	; Minuten mit Rest durch 10 teilen
ldi	dv8u, 10
rcall	div8u
swap	dres8u		; Zehner-Stelle in obere Byte-Haelfte uebernehmen
add	dres8u, drem8u 	; Zehner und Einer zu BCD wandeln
mov	i2cdata, dres8u	
rcall	i2c_write	; Minuten schreiben
;				
mov	dd8u, hours	; Stunden mit Rest durch 10 teilen
ldi	dv8u, 10	
rcall	div8u
swap	dres8u		; Zehner-Stelle in obere Byte-Haelfte uebernehmen
add	dres8u, drem8u 	; Zehner und Einer zu BCD wandeln
mov	i2cdata, dres8u	
rcall	i2c_write	; Stunden schreiben
rcall	i2c_stop				
sei			; Interrupts wieder erlauben
ret

;
; RTC_ALARM: RTC-Alarmzeit einstellen (mit alarmmin und alarmhours abgleichen)
;
RTC_ALARM:
ret
;
;
;
; mpy8u - Multiplizieren 8b x 8b -> 16b ohne Vorzeichen
;
mpy8u:	clr	m8uH		;clear result High byte
	lsr	mp8u		;shift multiplier
	;	
	brcc	noad80		;if carry set
	add	m8uH,mc8u	;    add multiplicand to result High byte
noad80:	ror	m8uH		;shift right result High byte 
	ror	m8uL		;rotate right result L byte and multiplier
	;
	brcc	noad81		;if carry set
	add	m8uH,mc8u	;    add multiplicand to result High byte
noad81:	ror	m8uH		;shift right result High byte 
	ror	m8uL		;rotate right result L byte and multiplier
	;
	brcc	noad82		;if carry set
	add	m8uH,mc8u	;    add multiplicand to result High byte
noad82:	ror	m8uH		;shift right result High byte 
	ror	m8uL		;rotate right result L byte and multiplier
	;
	brcc	noad83		;if carry set
	add	m8uH,mc8u	;    add multiplicand to result High byte
noad83:	ror	m8uH		;shift right result High byte 
	ror	m8uL		;rotate right result L byte and multiplier
	;
	brcc	noad84		;if carry set
	add	m8uH,mc8u	;    add multiplicand to result High byte
noad84:	ror	m8uH		;shift right result High byte 
	ror	m8uL		;rotate right result L byte and multiplier
	;
	brcc	noad85		;if carry set
	add	m8uH,mc8u	;    add multiplicand to result High byte
noad85:	ror	m8uH		;shift right result High byte 
	ror	m8uL		;rotate right result L byte and multiplier
	;
	brcc	noad86		;if carry set
	add	m8uH,mc8u	;    add multiplicand to result High byte
noad86:	ror	m8uH		;shift right result High byte 
	ror	m8uL		;rotate right result L byte and multiplier
	;
	brcc	noad87		;if carry set
	add	m8uH,mc8u	;    add multiplicand to result High byte
noad87:	ror	m8uH		;shift right result High byte 
	ror	m8uL		;rotate right result L byte and multiplier
	ret
;
; div8u - Dividieren 8b / 8b -> 8b + 8b ohne Vorzeichen
;
div8u:	sub	drem8u,drem8u	;clear remainder and carry
	;	
	rol	dd8u		;shift left dividend
	rol	drem8u		;shift dividend into remainder
	sub	drem8u,dv8u	;remainder = remainder - divisor
	brcc	d8u_1		;if result negative
	add	drem8u,dv8u	;    restore remainder
	clc			;    clear carry to be shifted into result
	rjmp	d8u_2		;else
d8u_1:	sec			;    set carry to be shifted into result
	;
d8u_2:	rol	dd8u		;shift left dividend
	rol	drem8u		;shift dividend into remainder
	sub	drem8u,dv8u	;remainder = remainder - divisor
	brcc	d8u_3		;if result negative
	add	drem8u,dv8u	;    restore remainder
	clc			;    clear carry to be shifted into result
	rjmp	d8u_4		;else
d8u_3:	sec			;    set carry to be shifted into result
	;
d8u_4:	rol	dd8u		;shift left dividend
	rol	drem8u		;shift dividend into remainder
	sub	drem8u,dv8u	;remainder = remainder - divisor
	brcc	d8u_5		;if result negative
	add	drem8u,dv8u	;    restore remainder
	clc			;    clear carry to be shifted into result
	rjmp	d8u_6		;else
d8u_5:	sec			;    set carry to be shifted into result
	;
d8u_6:	rol	dd8u		;shift left dividend
	rol	drem8u		;shift dividend into remainder
	sub	drem8u,dv8u	;remainder = remainder - divisor
	brcc	d8u_7		;if result negative
	add	drem8u,dv8u	;    restore remainder
	clc			;    clear carry to be shifted into result
	rjmp	d8u_8		;else
d8u_7:	sec			;    set carry to be shifted into result
	;
d8u_8:	rol	dd8u		;shift left dividend
	rol	drem8u		;shift dividend into remainder
	sub	drem8u,dv8u	;remainder = remainder - divisor
	brcc	d8u_9		;if result negative
	add	drem8u,dv8u	;    restore remainder
	clc			;    clear carry to be shifted into result
	rjmp	d8u_10		;else
d8u_9:	sec			;    set carry to be shifted into result
	;
d8u_10:	rol	dd8u		;shift left dividend
	rol	drem8u		;shift dividend into remainder
	sub	drem8u,dv8u	;remainder = remainder - divisor
	brcc	d8u_11		;if result negative
	add	drem8u,dv8u	;    restore remainder
	clc			;    clear carry to be shifted into result
	rjmp	d8u_12		;else
d8u_11:	sec			;    set carry to be shifted into result
	;
d8u_12:	rol	dd8u		;shift left dividend
	rol	drem8u		;shift dividend into remainder
	sub	drem8u,dv8u	;remainder = remainder - divisor
	brcc	d8u_13		;if result negative
	add	drem8u,dv8u	;    restore remainder
	clc			;    clear carry to be shifted into result
	rjmp	d8u_14		;else
d8u_13:	sec			;    set carry to be shifted into result
	;
d8u_14:	rol	dd8u		;shift left dividend
	rol	drem8u		;shift dividend into remainder
	sub	drem8u,dv8u	;remainder = remainder - divisor
	brcc	d8u_15		;if result negative
	add	drem8u,dv8u	;    restore remainder
	clc			;    clear carry to be shifted into result
	rjmp	d8u_16		;else
d8u_15:	sec			;    set carry to be shifted into result
	;
d8u_16:	rol	dd8u		;shift left dividend
	ret
;
;
;
; I2C-Master-Funktionen von Atmel (AVR Application Note 300)
;
;***************************************************************************
;*
;* FUNCTION
;*	i2c_hp_delay
;*	i2c_qp_delay
;*
;* DESCRIPTION
;*	hp - half i2c clock period delay (normal: 5.0us / fast: 1.3us)
;*	qp - quarter i2c clock period delay (normal: 2.5us / fast: 0.6us)
;*
;*	SEE DOCUMENTATION !!!
;*
;* USAGE
;*	no parameters
;*
;* RETURN
;*	none
;*
;***************************************************************************

i2c_hp_delay:
	ldi	i2cdelay,9		; 5us bei 6MHz-Quarz
i2c_hp_delay_loop:
	dec	i2cdelay
	brne	i2c_hp_delay_loop
	ret

i2c_qp_delay:
	ldi	i2cdelay,4		; 2.5us
i2c_qp_delay_loop:
	dec	i2cdelay
	brne	i2c_qp_delay_loop
	ret


;***************************************************************************
;*
;* FUNCTION
;*	i2c_rep_start
;*
;* DESCRIPTION
;*	Assert repeated start condition and sends slave address.
;*
;* USAGE
;*	i2cadr - Contains the slave address and transfer direction.
;*
;* RETURN
;*	Carry flag - Cleared if a slave responds to the address.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by i2c_start.
;*
;***************************************************************************

i2c_rep_start:
	sbi	DDRD,SCLP		; force SCL low
	cbi	DDRD,SDAP		; release SDA
	rcall	i2c_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
	rcall	i2c_qp_delay		; quarter period delay


;***************************************************************************
;*
;* FUNCTION
;*	i2c_start
;*
;* DESCRIPTION
;*	Generates start condition and sends slave address.
;*
;* USAGE
;*	i2cadr - Contains the slave address and transfer direction.
;*
;* RETURN
;*	Carry flag - Cleared if a slave responds to the address.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by i2c_write.
;*
;***************************************************************************

i2c_start:				
	mov	i2cdata,i2cadr		; copy address to transmitt register
	sbi	DDRD,SDAP		; force SDA low
	rcall	i2c_qp_delay		; quarter period delay


;***************************************************************************
;*
;* FUNCTION
;*	i2c_write
;*
;* DESCRIPTION
;*	Writes data (one byte) to the I2C bus. Also used for sending
;*	the address.
;*
;* USAGE
;*	i2cdata - Contains data to be transmitted.
;*
;* RETURN
;*	Carry flag - Set if the slave respond transfer.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by i2c_get_ack.
;*
;***************************************************************************

i2c_write:
	sec				; set carry flag
	rol	i2cdata			; shift in carry and out bit one
	rjmp	i2c_write_first
i2c_write_bit:
	lsl	i2cdata			; if transmit register empty
i2c_write_first:
	breq	i2c_get_ack		;	goto get acknowledge
	sbi	DDRD,SCLP		; force SCL low

	brcc	i2c_write_low		; if bit high
	nop				;	(equalize number of cycles)
	cbi	DDRD,SDAP		;	release SDA
	rjmp	i2c_write_high
i2c_write_low:				; else
	sbi	DDRD,SDAP		;	force SDA low
	rjmp	i2c_write_high		;	(equalize number of cycles)
i2c_write_high:
	rcall	i2c_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
	rcall	i2c_hp_delay		; half period delay

	rjmp	i2c_write_bit


;***************************************************************************
;*
;* FUNCTION
;*	i2c_get_ack
;*
;* DESCRIPTION
;*	Get slave acknowledge response.
;*
;* USAGE
;*	(used only by i2c_write in this version)
;*
;* RETURN
;*	Carry flag - Cleared if a slave responds to a request.
;*
;***************************************************************************

i2c_get_ack:
	sbi	DDRD,SCLP		; force SCL low
	cbi	DDRD,SDAP		; release SDA
	rcall	i2c_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL

i2c_get_ack_wait:
	sbis	PIND,SCLP		; wait SCL high 
					;(In case wait states are inserted)
	rjmp	i2c_get_ack_wait

	clc				; clear carry flag
	sbic	PIND,SDAP		; if SDA is high
	sec				;	set carry flag
	rcall	i2c_hp_delay		; half period delay
	ret

;***************************************************************************
;*
;* FUNCTION
;*	i2c_read
;*
;* DESCRIPTION
;*	Reads data (one byte) from the I2C bus.
;*
;* USAGE
;*	Carry flag - 	If set no acknowledge is given to the slave
;*			indicating last read operation before a STOP.
;*			If cleared acknowledge is given to the slave
;*			indicating more data.
;*
;* RETURN
;*	i2cdata - Contains received data.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by i2c_put_ack.
;*
;***************************************************************************

i2c_read:
	rol	i2cstat			; store acknowledge
					; (used by i2c_put_ack)
	ldi	i2cdata,0x01		; data = 0x01
i2c_read_bit:				; do
	sbi	DDRD,SCLP		; 	force SCL low
	rcall	i2c_hp_delay		;	half period delay

	cbi	DDRD,SCLP		;	release SCL
	rcall	i2c_hp_delay		;	half period delay

	clc				;	clear carry flag
	sbic	PIND,SDAP		;	if SDA is high
	sec				;		set carry flag

	rol	i2cdata			; 	store data bit
	brcc	i2c_read_bit		; while receive register not full


;***************************************************************************
;*
;* FUNCTION
;*	i2c_put_ack
;*
;* DESCRIPTION
;*	Put acknowledge.
;*
;* USAGE
;*	(used only by i2c_read in this version)
;*
;* RETURN
;*	none
;*
;***************************************************************************

i2c_put_ack:
	sbi	DDRD,SCLP		; force SCL low

	ror	i2cstat			; get status bit
	brcc	i2c_put_ack_low		; if bit low goto assert low
	cbi	DDRD,SDAP		;	release SDA
	rjmp	i2c_put_ack_high
i2c_put_ack_low:			; else
	sbi	DDRD,SDAP		;	force SDA low
i2c_put_ack_high:

	rcall	i2c_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
i2c_put_ack_wait:
	sbis	PIND,SCLP		; wait SCL high
	rjmp	i2c_put_ack_wait
	rcall	i2c_hp_delay		; half period delay
	
	sbi	DDRD,SCLP		; force SCL low	
	nop
	cbi	DDRD,SDAP		;	release SDA	
	
	ret


;***************************************************************************
;*
;* FUNCTION
;*	i2c_stop
;*
;* DESCRIPTION
;*	Assert stop condition.
;*
;* USAGE
;*	No parameters.
;*
;* RETURN
;*	None.
;*
;***************************************************************************

i2c_stop:
	sbi	DDRD,SCLP		; force SCL low
	sbi	DDRD,SDAP		; force SDA low
	rcall	i2c_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
	rcall	i2c_qp_delay		; quarter period delay
	cbi	DDRD,SDAP		; release SDA
	rcall	i2c_hp_delay		; half period delay
	ret


