Pfad: Home => AVR-Übersicht => Anwendungen => Digitaluhr => Sourcecode
Digitaluhr

Digitaluhr mit ATmega16 - Quellcode



; *********************************************
; * Digitale AVR Uhr mit einem ATmega16       *
; * (C)2010 by info (at) avr-asm-tutorial.net *
; *********************************************
;
.nolist
.include "m16def.inc"
.list
;
; ===================================================
;      D e b u g g i n g   P a r a m e t e r
; ===================================================
;   dbg = 0: Debugging aus, normale Programmausfuehrung
;   dbg = 1: Erstes Display an
;   dbg = 2: Zweites Display an
;   dbg = 3: Drittes Display an
;   dbg = 4: Viertes Display an
;   dbg = 5: Sekundenanzeige auf der Minutenposition der Weckzeit
;   dbg = 6: Zeige die ADC-Werte in Hex auf dem Weckzeit-Display an
;
.equ dbg = 0
;
; ===================================================
;                H a r d w a r e
; ===================================================
;  Processortyp: ATmega16
;               _________
;             /          |
; Taste rt --|PB0    ADC0|-- Potentiometereingang
; Taste sw --|PB1    ADC1|-- Fototransistoreingang
; Taste ws --|PB2        |--
; Lautspr. --|OC0        |--
;          --|        PA4|-- 1 Digit Anodentreiber
; ISP MOSI --|MOSI    PA5|-- 2
;     MISO --|MISO    PA6|-- 3
;      SCK --|SCK     PA7|-- 4
;    RESET --|RESET  AREF|-- +5V
;      VCC --|VCC     GND|-- GND
;      GND --|GND    AVCC|-- +5V
;  Quarz 2 --|XTAL2   PC7|-- Grosser LED Doppelpunkt
;  Quarz 1 --|XTAL1   PC6|-- g Grosse
; Kleine a --|PD0     PC5|-- f Sieben-
; Sieben b --|PD1     PC4|-- e Segment-
; Segm.  c --|PD2     PC3|-- d Anzeige
; Anzeiged --|PD3     PC2|-- c Kathoden
; Kath.  e --|PD4     PC1|-- b
;        f --|PD5     PC0|-- a
;        g --|PD6     PD7|-- Kleiner LED Doppelpunkt
;            |___________
;            
; ===================================================
;  B e s c h r e i b u n g   d e r   F u n k t i o n
; ===================================================
; 
; a) Anzeigen und Anzeigenmodi
;    Die obere, grosse Siebensegmentanzeige zeigt die Zeit
;    an, die untere kleine zeigt die Weckzeit an.
;    Der grosse Doppelpunkt in der oberen Anzeige blinkt
;    in Sekundenintervallen. Der kleinere Doppelpunkt
;    zeigt an, ob der Weckalarm aktiviert ist. Sind diese
;    LEED dauernd an, ist der Weckalarm ausgeschaltet.
; b) Anzeigenmultiplex
;    Die Anzeige verwendet Zaehler/Zeitgeber 1 im CTC-
;    Modus, um alle 5 ms eine Unterbrechung auszuloesen.
;    Die vier Anzeigen werden alle 20 ms erneuert, was
;    zu einer Multiplexfrequenz von 50 Hz fuehrt.
;    Die Anzeige wird zuerst ausgeschaltet, der Zeiger
;    auf die angezeigte Ziffer wird erhoeht und das
;    Byte an der Ziffernposition wird an Port C (grosse
;    Zeitanzeige) bzw. D (kleine Weckalarmanzeige) kopiert.
;    Dann wird das Portbit fuer den richtigen Anodentreiber
;    im oberen Nibble von Port A aktiviert (=0).
; c) Anzeige dimmen
;    Das Abblenden der Anzeige verwendet den Zaehler/Zeitgeber
;    1 im CTC-Modus und die Unterbrechung bei Compare Match B
;    und schaltet die Anodentreiber alle fruehzeitig aus. Der
;    Wert fuer den Compare Match B wird aus dem ADC-Wert des
;    Fototransistors ermittelt: je weniger Licht am Foto-
;    transistor ankommt, desto hoeher ist seine Kollektor-
;    spannung, desto groesser ist der ADC-Wert und der
;    Wert von Compare Match B. Beachte, dass dies nicht-linear
;    ist (es gibt nur zwei Helligkeitsstufen).
; d) Zeitbestimmung
;    Die Zeit wird vom Zaehler/Zeitgeber 1 und der Compare
;    Match A Unterbrechung gesteuert. Die Unterbrechung
;    zaehlt einen Zaehler von 200 auf Null abwaerts. Wenn
;    Null erreicht wird, wird eine Flagge gesetzt und der
;    5-ms-Zaehler neu gestartet.
;    Ausserhalb der Unterbrechungsroutine wird die Zeit
;    um eine Sekunde erhoeht, beim Erreichen von 60 wird
;    die Zeit um eine Minute erhoeht. Wenn das Wecken akti-
;    viert ist, wird die Zeit mit der Weckzeit verglichen
;    und bei Gleichheit die Alarmierung aktiviert.
; e) Tasten
;    Die Tasten werden im Anschluss an jede Unterbrechung
;    gelesen. Eine gedrueckte Taste wird nach drei
;    Unterbrechungen erkannt und gespeichert. Die entsprechende
;    Aktion wird ausgefuehrt wenn fuer mindestens 15 ms keine
;    der Tasten mehr gedrueckt ist.
;    Die folgende Tabelle zeigt die ausgefuehrten Aktionen in
;    den verschiedenen Betriebszustaenden:
;    Modus           Taste    Aktion
;    ----------------------------------------------------------
;    Normal          Schwarz  Ein-/Ausschalten Wecken
;                    Rot      Beginne Einstellung der Zeit
;                    Weiss    Beginne Einstellung der Weckzeit
;    Zeiteinstellung Schwarz  Beende Zeiteinstellung vorzeitig
;                    Rot      Stelle Zeit (Stunden oder Minuten)
;    Weckeinstellung Schwarz  Beende Weckzeiteinstellung vorzeitig
;                    Weiss    Stelle Weckzeit (Stunden oder Minuten)
;    Alarmierung an  Schwarz  Stelle Alarmierung aus
;                    Rot      Stelle Alarmzeit um 5 Minuten spaeter
;    Alarmiert       Schwarz  Alarm aus und Weckzeit auf Original
;                    Rot      Alarm aus und Weckzeit um 5 Minuten spaeter
; f) AD-Wandlung
;    Der AD-Wandler laeuft mit einem Vorteiler von 128, misst zwei
;    Kanaele und wird mittels Unterbrechung bedient.
;    Die Analogspannungen am Potentiometer (Kanal ADC0) und am
;    Kollektor des Fototransistors (Kanal ADC1) werden gemessen,
;    die oberen acht Byte des linksbuendigen Ergebnisses eingelesen
;    und zu einer 16-Bit-Summe addiert. Wenn 256 Messungen pro
;    Kanal addiert sind, werden die oberen 8 Bit der Summe an
;    eine Routine ausserhalb der Unterbrechungsroutine uebergeben.
;    Dies wird verwendet um bei
;    Kanal 0: falls Zeit- oder Weckzeiteinstellung aktiv sind,
;      wird das Ergebnis in Stunden bzw. Minuten umgerechnet und
;      auf der jeweiligen Stunden-/Minutenposition ausgegeben.
;    Kanal 1: in laengeren Abstaenden wird daraus der Compare
;      Match B Wert zu bestimmen und zu setzen (Dim-Funktion).
;    Messfrequenz: f = 2457600 / 128 / 13 / 256 / 2 = 2,88 Hz
;      (beide Kanaele)
; g) Wecken ueber Melodie
;    Der Zaehler/Zeitgeber 0 wird als programmierbarer Tongenerator
;    betrieben, so dass er im CTC-Modus bei aktiviertem Ausgabeport
;    (Umpolung bei Compare Match) Melodien spielen kann. Der TC0
;    wird mit einem Vorteiler von 8 betrieben, so dass NF zwischen
;    600 (OCR0=255) und 9600 (OCR=16) Hz erzeugt werden kann.
;    Wenn der oberste CTC-Wert erreicht wird, wird die naechste Note
;    und die naechste Spieldauer des Tons aus dem EEPROM gelesen.
;    Ist der letzte Tabellenwert gespielt, wird TC0 stillgelegt
;    und der OC0-Ausgang abgeschaltet.
;
; ===================================================
;               K o n s t a n t e n
; ===================================================
;
.equ clock = 2457600 ; Quarzfrequenz
.equ cSnooze = 5 ; Zeit in Minuten fuer Schlummern
.equ cPauseLong = $4000 ; lange Pause fuer Melodiewiederholung
;
; ===================================================
;               R e g i s t e r
; ===================================================
;
; verwendet mit LPM fuer Lesen aus dem Flash und fuer Berechnungen
; verwendet fuer Berechnungen
; frei R2..R8
.def rFChk = R9 ; Zaehler fuer Dimm-Funktion
.def rAdcC = R10 ; ADC Zaehler
.def rAdcFL = R11 ; LSB ADC-Summe Fototransistor
.def rAdcFH = R12 ; dto., MSB
.def rAdcPL = R13 ; LSB ADC-Summe Potentiometer
.def rAdcPH = R14 ; dto., MSB
.def rSreg = R15 ; SREG temporaer innerhalb Unterbrechung
.def rmp = R16 ; Vielzweckregister ausserhalb Unterbrechungen
.def rimp = R17 ; Vielzweckregister innerhalb Unterbrechungen
.def rFlag = R18 ; Flaggenregister
	.equ bArmed = 0 ; Alarm ist aktiviert
	.equ bAlarm = 1 ; Alarm ist ausgeloest
	.equ bSetC  = 2 ; Stelle Uhrzeit
	.equ bSetCm = 3 ; Stelle Uhrzeit Minuten
	.equ bSetA  = 4 ; Stelle Weckzeit
	.equ bSetAm = 5 ; Stelle Weckzeit Minuten
	.equ bSec   = 6 ; Naechste Sekunde erreicht
	.equ bAdc   = 7 ; Neues ADC-Ergebnis fertig
.def rC5ms = R19 ; 5 ms Zaehler fuer Sekunde
.def rDCnt = R20 ; Anzeige Anodentreiber
.def rKey = R21 ; Tastenspeicher letzte aktive Taste
.def rEep = R23 ; EEPROM-Leseadresse
.def rDurL = R24 ; LSB Zaehler fuer Dauer Toene
.def rDurH = R25 ; dto., MSB
; R27:R26 verwendet als Zeiger ausserhalb Unterbrechung
; R29:R28 verwendet als Zeiger fuer Anzeigenmultiplex innerhalb Unterbrechung
; R31:R30 verwendet als Zeiger ausserhalb Unterbrechung
;
; ===================================================
;           S R A M   A d r e s s e n
; ===================================================
;
.DSEG
.ORG Sram_Start
sTime:
.byte 4 ; vier Ziffern fuer die grosse Anzeige
sAlarm:
.byte 4 ; vier Ziffern fuer die kleine Anzeige
sCs:
.byte 1 ; Uhrzeit Sekunden
sCm:
.byte 1 ; Uhrzeit Minuten
sCh:
.byte 1 ; Uhrzeit Stunden
sSm:
.byte 1 ; Eingestellte Weckzeit Minuten
sSh:
.byte 1 ; Eingestellte Weckzeit Stunden
sAm:
.byte 1 ; Aktuelle Weckzeit Minuten
sAh:
.byte 1 ; Aktuelle Weckzeit Stunden
sAdcP:
.byte 1 ; Adc Ergebnis Potentiometer
sAdcF:
.byte 1 ; Adc Ergebnis Fototransistor
sKey:
.byte 1 ; gedrueckte Taste
sKeyC:
.byte 1 ; Zaehler fuer Tastendruecke
sKeyS:
.byte 1 ; letzte aktive Taste
;
; ===================================================
;    R e s e t   u n d   I n t - V e k t o r e n 
; ===================================================
;
.cseg
.org $0000
	rjmp start ; Reset Vektor
	nop
	reti ; INT0
	nop
	reti ; INT1
	nop
	reti ; TC2COMP
	nop
	reti ; TC2OVF
	nop
	rjmp TC1Capt ; TC1CAPT
	nop
	rjmp TC1CompA ; TC1COMPA
	nop
	rjmp TC1CompB ; TC1COMPB
	nop
	reti ; TC1OVF
	nop
	reti ; TC0OVF
	nop
	reti ; SPI, STC
	nop
	reti ; USART RXC
	nop
	reti ; USART UDRE
	nop
	reti ; USART TXC
	nop
	rjmp AdcInt ; ADC
	nop
	reti ; EERDY
	nop
	reti ; ANACOMP
	nop
	reti ; TWI
	nop
	reti ; INT2
	nop
	rjmp Tc0Comp ; TC0COMP
	nop
	reti ; SPM RDY
	nop
;
; ===================================================
; I n t e r r u p t   S e r v i c e   R o u t i n e n
; ===================================================
;
; TC1: ICR Periodenende, zeige naechste Ziffer an
;
TC1Capt:
	in rsreg,SREG ; rette SREG
	ldi rimp,0xF0 ; Zifferntreiber aus
	out PORTA,rimp
	in rKey,PINB ; lese Tasten
	ori rDCnt,0x08 ; Setze Bit 3 Anodenregister
	lsl rDCnt ; Schiebe Anodenregister links
	brcc TC1Capt1 ; Wenn 0, dann Zyklusende
	adiw YL,1 ; naechste Ziffer
	ld rimp,Y ; Lese naechste Ziffer Uhrzeit
	out PORTC,rimp ; Schreibe auf Port
	ldd rimp,Y+4 ; Lese naechste Ziffer Weckzeit
	out PORTD,rimp ; Schreibe auf Port
	out PORTA,rDCnt ; Setze aktiven Anodentreiber
	out SREG,rsreg ; stelle SREG wieder her
	reti
TC1Capt1:
	ldi YH,HIGH(sTime) ; Neustart Zeiger auf Ziffer
	ldi YL,LOW(sTime)
	ld rimp,Y ; Lese erste Ziffer Uhrzeit
	out PORTC,rimp ; Schreibe auf Port
	ldd rimp,Y+4 ; Lese erste Ziffer Weckzeit
	out PORTD,rimp ; Schreibe auf Port
	ldi rDCnt,0xE0 ; Starte Anodentreiberport mit Bit 4 = 0
	out PORTA,rDCnt ; an Anodentreiber
	out SREG,rsreg ; stelle SREG wieder her
	reti
;
; TC1 Comp A erreicht
;
TC1CompA:
	in rsreg,SREG ; Rette SREG
	dec rC5ms ; Zaehle 5-ms-Zaehler herunter
	brne TC1CompA1 ; Nicht Null
	sbr rFlag,1<<bSec ; Setze Sekundenflagge
	ldi rC5ms,200 ; Neustart
TC1CompA1:
	out SREG,rSreg ; stelle SREG wieder her
	reti
;
; TC1 Comp B erreicht
;
TC1CompB:
	ldi rimp,0xF0 ; Schalte Anodentreiber aus
	out PORTA,rimp
	reti
;
; ADC Ready Unterbrechung
;
AdcInt:
	in rsreg,SREG ; Rette SREG
	in rimp,ADMUX ; Lese gemessenen Kanal
	sbrc rimp,MUX0 ; Ueberspringe bei Kanal ADC0
	rjmp AdcInt1
	in rimp,ADCH ; Lese MSB ADC Ergebnis
	add rAdcPL,rimp ; Addiere zur Summe
	ldi rimp,0 ; Addiere Uebertrag zu MSB
	adc rAdcPH,rimp
	ldi rimp,(1<<ADLAR)|(1<<MUX0) ; Messe Kanal ADC1
	out ADMUX,rimp
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) ; Start Wandlung
	out ADCSRA,rimp
	out SREG,rsreg ; Stelle SREG wieder her
	reti
AdcInt1:
	in rimp,ADCH ; Lese MSB ADC Ergebnis
	add rAdcFL,rimp ; Addiere zur Summe
	ldi rimp,0 ; Addiere Uebertrag zu MSB
	adc rAdcFH,rimp
	dec rAdcC ; Erniedrige Zaehler
	brne AdcInt2 ; Nicht Null
	sts sAdcP,rAdcPH ; Kopiere MSB Potentiometer
	sts sAdcF,rAdcFH ; Kopiere MSB Fototransistor
	clr rAdcPL ; Leere Summen
	clr rAdcPH
	clr rAdcFL
	clr rAdcFH
	sbr rFlag,1<<bAdc ; Setze ADC-Flagge
AdcInt2:
	ldi rimp,1<<ADLAR ; Setze Kanal ADC0
	out ADMUX,rimp
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) ; Starte Wandlung
	out ADCSRA,rimp
	out SREG,rsreg ; Stelle SREG wieder her
	reti
;
; TC0 Compare Match Unterbrechung
;
Tc0Comp:
	in rSreg,SREG ; Rette SREG
	tst rDurL ; Restdauer Null?
	brne Tc0Comp0 ; Nein
	tst rDurH ; MSB Null?
	breq Tc0CompR ; Ja, ueberspringe
Tc0Comp0:
	sbiw rDurL,1 ; erniedrige Dauerzaehler
	brne Tc0CompR ; Nicht Null, weiter
	inc rEep ; Erhoehe EEPROM Adresse
	mov rimp,rEep ; Kopiere EEPROM Leseadresse
	lsl rimp ; Multipliziere mit 2
	out EEARL,rimp ; an Leseadress-Port
	ldi rimp,0 ; Ueberlauf auf oberes Byte
	adc rimp,rimp
	out EEARH,rimp
	ldi rimp,1<<EERE ; Ermoegliche Lesen EEPROM
	out EECR,rimp
	in rimp,EEDR ; Lese erstes Byte
	tst rimp ; Teste auf Null
	brne Tc0Comp01 ; nicht Null
	dec rimp
	out OCR0,rimp ; Schreibe auf Compare Match Register
	ldi rimp,(1<<WGM01)|(1<<COM01)|(1<<CS01) ; Loesche COM Ausgang
	out TCCR0,rimp ; Setze TC0-Modus und COM
	rjmp Tc0Comp2
Tc0Comp01:
	out OCR0,rimp ; Schreibe auf Compare Match Register
	ldi rimp,(1<<WGM01)|(1<<COM00)|(1<<CS01) ; COM output torkeln
	out TCCR0,rimp ; Setze TC0 Modus und COM
Tc0Comp2:
	in rimp,EEARL ; Lese LSB EEPROM-Adresse
	inc rimp ; Erhoehe Adresse
	out EEARL,rimp ; Setze LSB Adresse
	ldi rimp,1<<EERE ; Lesen ermoeglichen
	out EECR,rimp ; in Kontrollregister
	in rDurH,EEDR ; Lese Tondauer
	clr rDurL
	tst rDurH ; Test auf Ende der Melodie
	brne Tc0Comp3 ; Nein, weiter
	ldi rimp,(1<<WGM01)|(1<<COM01)|(1<<CS01) ; Loesche COM Ausgang
	out TCCR0,rimp ; Setze TC0-Modus und COM
	ldi rDurH,High(cPauseLong) ; Lange Pause
	ldi rDurL,Low(cPauseLong)
	ldi rEep,0xFF ; Neustart der Melodie nach der Pause
	rjmp Tc0CompR
Tc0Comp3:
	lsr rDurH ; / 2 auf Dauer
	ror rDurL
	lsr rDurH ; / 4
	ror rDurL
	lsr rDurH ; / 8
	ror rDurL
	lsr rDurH ; / 16
	ror rDurL
	lsr rDurH ; / 32
	ror rDurL
Tc0CompR:
	out SREG,rSreg ; Stelle SREG wieder her
	reti
;
; ===================================================
;  H a u p t p r o g r a m m  I n i t i i e r u n g
; ===================================================
;
Start:
	; Richte Stapel ein
	ldi rmp,HIGH(RAMEND) ; MSB Stapelzeiger
	out SPH,rmp
	ldi rmp,LOW(RAMEND) ; LSB Stapelzeiger
	out SPL,rmp
	; Initiiere Ports
	ldi rmp,0xFF ; Ports C und D als Ausgaenge
	out DDRC,rmp
	out DDRD,rmp
	ldi rmp,0xF0 ; Port A obere vier Bit Ausgang
	out DDRA,rmp
	ldi rmp,0xE0 ; Anodentreiber Ziffer 1 an
	out PORTA,rmp
	ldi rmp,0x00 ; Ports C und D Kathoden aktiv
	out PORTC,rmp
	out PORTD,rmp
	; Debug teste Anzeigen
	.if dbg==1
	ldi rmp,0xE0
	out PORTA,rmp
ex1:	rjmp ex1
	.endif
	.if dbg==2
	ldi rmp,0xD0
	out PORTA,rmp
ex2: rjmp ex2
	.endif
	.if dbg==3
	ldi rmp,0xB0
	out PORTA,rmp
ex3: rjmp ex3
	.endif
	.if dbg==4
	ldi rmp,0x70
	out PORTA,rmp
ex4: rjmp ex4
	.endif
    ; Ende Debug Test Anzeigen
	ldi rmp,0x08 ; Setze Port B Bits 0..2 als Eingang, 3 als Ausgang
	out DDRB,rmp
	ldi rmp,0x07 ; Setze Pull-up-Widerstaende auf Tasteneingaenge an
	out PORTB,rmp
	; Initiiere Anzeigentreiber, alle Anzeigen auf 0
	ldi YH,HIGH(sCs) ; Zeige auf Uhrzeit/Weckzeit
	ldi YL,LOW(sCs)
	clr R0
	ldi rmp,7 ; alle Zeiten/Weckzeiten auf Null
Start1:
	st Y+,R0
	dec rmp
	brne Start1
	rcall UpDateTime ; Zeit anzeigen
	rcall UpDateAlarm ; Weckzeit anzeigen
	out PORTC,rmp ; Anzeigen auf 8
	out PORTD,rmp
	ldi rDCnt,0x70 ; Setze letzte Anzeigentreiber
	ldi YH,HIGH(sTime+3) ; Zeige auf letzte Anzeigenziffer
	ldi YL,LOW(sTime+3)
	clr rFlag ; Setze alle Flaggen inaktiv
	clr rmp ; Schalte Watchdog aus
	out WDTCR,rmp
	; Initiiere ADC
	ldi rmp,1<<ADLAR ; Linksbuendig, Kanal 0, externe Referenzspannung
	out ADMUX,rmp
	clr rmp ; Setze Spezial-IO-Register auf Null
	out SFIOR,rmp
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) ; starte ADC
	out ADCSRA,rmp ; 
	ldi rmp,1 ; Setze Dimm-Funktion aktiv fuer ersten ADC-Zyklus
	mov rFChk,rmp
	; Initiiere TC 0
	clr rDurL ; Setze Tondauer auf Null und blockiere TC0
	clr rDurH
	ldi rmp,175 ; Setze Vergleichswert auf 175 (880 Hz)
	out OCR0,rmp
	ldi rmp,(1<<WGM01)|(1<<COM00) ; Stop TC0 und Ausgang
	out TCCR0,rmp ; Setze TC0 Modus und COM
	; Initiiere TC1 als Uhr und Anzeigentreiber
.equ divider = (5 * clock + 500) / 1000 ; 5 ms Zeitintervall
	ldi rmp,HIGH(divider) ; Setze Compare Match Register ICR1
	out ICR1H,rmp
	ldi rmp,LOW(divider)
	out ICR1L,rmp
	ldi rmp,HIGH(divider / 2) ; Setze Compare Match A als 5-ms-int
	out OCR1AH,rmp
	ldi rmp,LOW(divider / 2)
	out OCR1AL,rmp
	ldi rmp,HIGH(divider - 2) ; Setze Compare Match B als Dimmer
	out OCR1BH,rmp
	ldi rmp,LOW(divider - 2)
	out OCR1BL,rmp
	clr rmp ; Loesche Modebits WGM11 and WGM10
	out TCCR1A,rmp
	ldi rmp,(1<<WGM13)|(1<<WGM12)|(1<<CS10) ; ICR1-CTC, Vorteiler = 1
	out TCCR1B,rmp
	; Ermoegliche Timer-Unterbrechungen
	ldi rmp,(1<<OCIE1A)|(1<<OCIE1B)|(1<<TICIE1)|(1<<OCIE0) ; ICR1-,COMP1-,TC0 int enable
	out TIMSK,rmp
	; Setze Schlafmodus
	ldi rmp,1<<SE ; Schlafen ermoeglichen und Modus 0
	out MCUCR,rmp
	; Ermoegliche Unterbrechungen
	sei
; ===================================================
;      H a u p t p r o g r a m m s c h l e i f e
; ===================================================
Loop:
	sleep ; schlafen
	nop ; Nach Aufwachen
;
	rcall keys ; Pruefe Tasten nach jedem Aufwachen
    ; Pruefe Flaggen
	sbrc rFlag,bSec ; Sekundenflagge nicht gesetzt
	rcall Second
	sbrc rFlag,bAdc ; Neues ADC-Ergebnis nicht gesetzt
	rcall AdcNew
	rjmp Loop
;
; ===================================================
;            T a s t e n a u s w e r t u n g
; ===================================================
;
.equ cKeyRed = 0x06
.equ cKeyBlack = 0x05
.equ cKeyWhite = 0x03
keys:
	andi rKey,0x07 ; Isoliere Tasten
	lds rmp,sKey ; Lese gespeicherten letzten Wert
	cp rmp,rKey ; Vergleiche mit aktuellem Wert
	breq key1 ; Gleiche Taste
	; Taste ungleich letzte Taste
	sts sKey,rKey ; Speichere neue Taste
	ldi rmp,0 ; Loesche Zaehler
	sts sKeyC,rmp
	ret
key1: ; Taste gleich der letzten Taste
	lds rmp,sKeyC ; Lese Tastenzaehler
	inc rmp ; Erhoehe Tastenzaehler
	sts sKeyC,rmp ; Speichern
	cpi rmp,3 ; Drei identische Tastenereignisse?
	breq key2 ; Drei
	brcs key1a ; Weniger als drei
	ldi rmp,5 ; Mehr als drei, setze auf 5 zum Blockieren
	sts sKeyC,rmp
key1a:
	ret
key2: ; Taste drei mal gleich
	lds rmp,sKey ; Lese Tastencode
	cpi rmp,0x07 ; Keine Taste gedrueckt?
	breq key3 ; Ja
	sts sKeyS,rmp ; Speichere diese Taste
	ret
key3:
	lds rmp,sKeyS ; Lese gespeicherte Taste
	ldi ZL,0 ; Schreibe Null in Tastenspeicher
	sts sKeyS,ZL
	tst rmp ; Gespeicherte Taste Null?
	brne key4 ; Nein, werte aus
	ret
key4: ; Bedingte Spruenge bei verschiedenen Flaggen
	sbrc rFlag,bAlarm ; Ist gerade Alarm?
	rjmp keyAlarm ; Tastenauswertung bei aktivem Alarm
	sbrc rFlag,bArmed ; Alarm aktiviert?
	rjmp keyArmed ; Tastenauswertung bei gesetztem Alarm
	sbrc rFlag,bSetC ; Uhrzeiteinstellung aktiv?
	rjmp keySetC ; Tastenauswertung waehrend Uhrzeiteinstellung
	sbrc rFlag,bSetA ; Weckzeiteinstellung aktiv?
	rjmp keySetA ; Tastenauswertung waehrend Weckzeiteinstellung
	; Normale Tastenauswertung
	cpi rmp,cKeyRed ; Rote Taste?
	brne key5 ; Nein
	sbr rFlag,1<<bSetC ; Setze Flagge Uhrzeiteinstellung
	ret
key5:
	cpi rmp,cKeyWhite ; Weisse Taste?
	brne key6 ; Nein
	sbr rFlag,1<<bSetA ; Setze Flagge Weckzeiteinstellung
	ret
key6:
	cpi rmp,cKeyBlack ; Schwarze Taste?
	brne key7
	ldi rmp,1<<bArmed ; Torkele Alarm
	eor rFlag,rmp
	ret
key7: ; Alle Tasten ausgewertet
	ret
keySetC: ; Auswertung waehrend Uhrzeiteinstellung
	cpi rmp,cKeyBlack ; Schwarze Taste?
	brne keySetC1 ; Nein
	cbr rFlag,(1<<bSetC)|(1<<bSetCM) ; Abbruch Zeiteinstellung, loesche Flaggen
	rjmp UpDateTime ; zeige Uhrzeit an
keySetC1:
	cpi rmp,cKeyRed ; Rote Taste?
	brne keySetC3 ; Nein
	sbrs rFlag,bSetCm ; Minutenflagge gesetzt
	rjmp keySetC2 ; Nein, setze Stunden
	; Setze Minuten
	clr rmp ; Loesche Sekunden
	sts sCs,rmp
	lds rmp,sAdcP ; Lese Potentiometerwert
	ldi ZL,60 ; mal 60
	mul rmp,ZL
	sts sCm,R1 ; setze Minuten
	cbr rFlag,(1<<bSetC)|(1<<bSetCm) ; Loesche Flaggen
	rjmp UpDateTime ; zeige Uhrzeit an
keySetC2: ; Setze Stunden
	lds rmp,sAdcP ; lese Potentiometerwert
	ldi ZL,24 ; mal 24
	mul rmp,ZL
	sts sCh,R1 ; speichere Stunden
	clr rmp ; Loesche Sekunden
	sts sCs,rmp
	sbr rFlag,(1<<bSetC)|(1<<bSetCm) ; Setze Minuten-Einstellflagge
	ret
keySetC3: ; illegale Taste bei Zeiteinstellung
	ret
keySetA: ; Auswertung waehrend Weckzeiteinstellung
	cpi rmp,cKeyBlack ; Schwarze Taste?
	brne keySetA1 ; Nein
	cbr rFlag,(1<<bSetA)|(1<<bSetAm) ; Vorzeitiger Abbruch, loesche Flaggen
	rjmp UpDateAlarm ; zeige Alarmzeit an
keySetA1:
	cpi rmp,cKeyWhite ; Weisse Taste?
	brne keySetA3 ; Nein
	; Weisse Taste gedrueckt
	sbrs rFlag,bSetAm ; Setze Weckzeit Minuten?
	rjmp keySetA2 ; Nein, Setze Stunden
	; Setze Weckzeit Minuten
	lds rmp,sAdcP ; Lese Potentiometerwert
	ldi ZL,60 ; mal 60
	mul ZL,rmp
	sts sAm,R1 ; Setze Weckzeit Minute
	sts sSm,R1 ; Setze Snooze-Weckzeit auf gleichen Wert
	cbr rFlag,(1<<bSetA)|(1<<bSetAm) ; Loesche Flaggen
	sbr rFlag,1<<bArmed ; Setze Wecken-Flagge
	rjmp UpDateAlarm ; zeige Weckzeit an
keySetA2: ; Setze Weckzeit Stunden
	lds rmp,sAdcP ; Lese Potentiometerwert
	ldi ZL,24 ; mal 24
	mul ZL,rmp
	sts sAh,R1 ; Setze Weckzeit Stunde
	sts sSh,R1 ; Setze Snooze-Weckzeit auf gleichen Wert
	sbr rFlag,1<<bSetAm ; Setze Minutenflagge
	ret
keySetA3: ; keine weitere Taste waehrend Weckzeiteinstellung
	ret
keyAlarm: ; Tasten bei Alarm
	cpi rmp,cKeyRed ; Rote Taste?
	brne keyAlarm1 ; Nein
	; Rote Taste bei Alarm: Snooze-Funktion
keyAlarmSnooze:
	lds rmp,sAm ; Lese Snooze-Weckzeit Minuten
	subi rmp,-cSnooze ; Addiere Snooze-Zeit
	sts sAm,rmp ; Und speichere
	cpi rmp,60 ; Groesser oder gleich 60 Minuten?
	brcs keyAlarmOff ; Nein, weiter
	subi rmp,60 ; Subtrahiere 60 Minuten
	sts sAm,rmp ; Und speichere
	lds rmp,sAh ; Lese Snooze-Stunden
	inc rmp ; Naechste Stunde
	sts sAh,rmp ; Und speichern
	cpi rmp,24 ; Groesser oder gleich 24?
	brcs keyAlarmOff ; Nein, weiter
	clr rmp ; Neustart Snnoze-Stunden
	sts sAh,rmp ; Und Speichern
keyAlarmOff:
	cbr rFlag,1<<bAlarm ; Loesche Alarmflagge
	ldi rmp,(1<<WGM01)|(1<<COM00) ; Beende Alarm
	out TCCR0,rmp ; Setze TC1 Modus und COM
	cbi PORTB,3 ; Loesche Lautsprecherportbit
	rjmp UpDateAlarm ; Zeige neue Weckzeit an
keyAlarm1: ; Schwarze Taste bei Alarm?
	cpi rmp,cKeyBlack ; Schwarze Taste?
	brne keyAlarm2 ; Nein
	; Schwarze Taste bei Alarm: Setze Alarm und Alarmierung aus
	lds rmp,sSm ; Lese Weckzeit Minuten Original
	sts sAm,rmp ; Schreibe Snnoze Weckzeit Minuten
	lds rmp,sSh ; Lese Weckzeit Stunden Original
	sts sAh,rmp ; Schreibe Snooze Weckzeit Stunden
	cbr rFlag,1<<bArmed ; Loesche Weckflagge
	rjmp keyAlarmOff ; schalte Alarm aus
keyAlarm2:
	; Alle anderen Tasten illegal
	ret
keyArmed: ; Tastenauswertung bei gesetzter Weckflagge
	cpi rmp,cKeyBlack ; Schwarze Taste?
	brne keyArmed1 ; Nein
	; Schwarze Taste be gesetzter Weckflagge
	cbr rFlag,1<<bArmed ; Setze Weckflagge auf Aus
	lds rmp,sSm ; Lese Weckzeit Original Minuten
	sts sAm,rmp ; Schreibe in Snooze-Weckzeit Minuten
	lds rmp,sSh ; Lese Weckzeit Original Minuten
	sts sAh,rmp ; Schreibe in Snooze-Weckzeit Stunden
	rjmp UpDateAlarm ; Zeige Weckzeit an
keyArmed1:
	cpi rmp,cKeyRed ; Rote Taste?
	brne keyArmed2 ; Nein
	; Rote Taste bei gesetzter Weckflagge: erhoehe Snooze-Zeit
	rjmp keyAlarmSnooze ; erhoehe Snooze Zeit
keyArmed2:
	; ignoriere andere Taste
	ret
;
; ===================================================
;    E i n e   S e k u n d e   i s t   v o r b e i
; ===================================================
;
Second:
	cbr rFlag,1<<bSec ; Loesche Sekundenflagge
	lds rmp,sCs ; Lese Sekunden
	inc rmp ; Naechste Sekunde
	sts sCs,rmp ; Speichern
	; Debug Sekunden anzeigen
	.if dbg == 5
	rcall dispsec
	.endif
	; Ende Debug
	cpi rmp,60 ; Naechste Minute?
	brcs Second1 ; Nein
	clr rmp ; Loesche Sekunden
	sts sCs,rmp ; Speichern
	lds rmp,sCm ; Lese Minuten
	inc rmp ; Naechste Minute
	sts sCm,rmp ; Speichern
	cpi rmp,60 ; Naechste Stunde?
	brne UpdateTime ; Nein, Zeit anzeigen
	clr rmp ; Loesche Minuten
	sts sCm,rmp ; Speichern
	lds rmp,sCh ; Lese Stunden
	inc rmp ; Naechste Stunde
	sts sCh,rmp ; Speichern
	cpi rmp,24 ; Naechster Tag?
	brne UpdateTime ; nein, Zeit anzeigen
	clr rmp ; Loesche Stunden
	sts sCh,rmp ; Speichern
	rjmp UpdateTime ; Zeit anzeigen
Second1: ; Blinken Doppelpunkt Zeitanzeige
	sbrc rFlag,bSetC ; Zeiteinstellung aktiv?
	rjmp Second4 ; ja
	ror rmp ; Niedrigstes Bit ins Carry
	lds rmp,sTime ; Ersten Anzeigenspeicher lesen
	brcs Second2 ; Niedrigstes Bit = 1?
	sbr rmp,1<<7 ; Nein, loesche LED-Bit
	rjmp Second3 ; weiter
Second2:
	cbr rmp,1<<7 ; Setze LED-Bit
Second3:
	sts sTime,rmp ; In erste Anzeigenposition
	; Blinken des Weckzeit-Doppelpunkts
Second4:
	sbrs rFlag,bArmed ; Wenn Wecken aktiv
	ret ; nein
	lsl rmp ; Bit 7 der Sekunde in das Carry
	lds rmp,sAlarm ; Lese Weckzeit Anzeigenspeicher 1
	brcs Second5 ; Bit 7 ist Eins
	andi rmp,0x7F ; Bit 7 ist 0, loesche Bit 7 Anzeige
	rjmp Second6 ; weiter
Second5:
	sbr rmp,1<<7 ; Setze Bit 7 in der Anzeige
Second6:
	sts sAlarm,rmp ; Schreibe Anzeigenspeicher 1
	ret
; Debug: zeige Sekunden im Display an
dispsec:
	lds rmp,sCs ; Lese Sekunden
	ldi XH,High(sAlarm+2) ; Zeige auf Weckzeit-Minuten
	ldi XL,Low(sAlarm+2)
	rcall To7Seg ; wandle rmp in Siebensegment
	lds rmp,sCs ; Lese Minuten in rmp
	ret
; Ende Debug
;
; ===================================================
;     A n z e i g e   a k t u a l i s i e r e n
; ===================================================
;
; Anzeigen der Weckzeit
;
UpDateAlarm:
	ldi XH,High(sAlarm) ; Zeige auf Weckzeit-Stunden
	ldi XL,Low(sAlarm)
	lds rmp,sAh ; Lese Weckzeit Stunden
	rcall To7Seg ; Wandle rmp in Siebensegment
	lds rmp,sAm ; Lese Weckzeit Minuten
	rjmp To7Seg ; Wandle rmp in Siebensegment
;
; Anzeigen der Uhrzeit
;
UpdateTime:
	sbrc rFlag,bArmed ; Wenn Wecken nicht aktiviert, springe
	rcall CheckAlarm ; Vergleiche Weckzeit und gib Alarm
	sbrc rFlag,bSetC ; Wenn Uhrzeiteinstellung inaktiv, springe
	ret
	ldi XH,High(sTime) ; Zeige auf Anzeige Zeit
	ldi XL,LOW(sTime)
	lds rmp,sCh ; Lese Zeit Stunde
	rcall To7Seg ; Wandle rmp in Siebensegment
	lds rmp,sCm ; Lese Zeit Minuten
To7Seg: ; Wandle rmp in Siebensegment und schreibe in SRAM
	clr R0 ; R0 ist Zaehler
To7Seg1:
	subi rmp,10 ; 10 abziehen
	brcs To7Seg2 ; wenn Unterlauf, fertig
	inc R0 ; noch nicht, ziehe weiter 10 ab
	rjmp To7Seg1 ; weiter
To7Seg2:
	subi rmp,-10 ; addiere wieder 10 wegen Unterlauf
	ldi ZH,HIGH(2*Tab7Seg) ; Lade Siebensegment-Tabelle
	ldi ZL,LOW(2*Tab7Seg)
	add ZL,R0 ; addiere Zehner
	brcc To7Seg3 ; Kein Ueberlauf
	inc ZH ; Ueberlauf in Tabelle
To7Seg3:
	lpm ; Lese Siebensegment-Code aus Tabelle
	st X+,R0 ; Speichere in Zeigerposition und erhoehe Zeiger
	ldi ZH,HIGH(2*Tab7Seg) ; Lade Siebensegmenttabelle
	ldi ZL,LOW(2*Tab7Seg)
	add ZL,rmp ; Addiere Einerrest
	brcc To7Seg4 ; kein Ueberlauf
	inc ZH ; Ueberlauf in Tabelle
To7Seg4:
	lpm ; Lese Siebensegmentcode aus Tabelle
	st X+,R0 ; Speichere in Zeigerposition und erhoehe Zeiger
	ret
; Umwandlungstabelle Dezimal/Hex in Siebensegment
Tab7Seg:
.db 0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10
; Hex Werte
.db 0x08,0x03,0x27,0x21,0x06,0x0E
;
; ===================================================
;      W e c k z e i t   e r r e i c h t ?
; ===================================================
;
CheckAlarm:
	lds R0,sCh ; Lese Uhrzeit Stunden
	lds rmp,sAh ; Lese Weckzeit Stunden
	cp R0,rmp ; Gleich?
	brne CheckAlarmRet ; Nein
	lds R0,sCm ; Lese Uhrzeit Minuten
	lds rmp,sAm ; Lese Weckzeit Minuten
	cp R0,rmp ; Gleich?
	brne CheckAlarmRet ; Nein
	sbr rFlag,1<<bAlarm ; Setze Alarmflagge
	ser rEep ; Adresse Melodie auf Ende
	ldi rDurL,1 ; Melodiestart bei naechstem Interruptzyklus 
	clr rDurH
	ldi rmp,(1<<WGM01)|(1<<COM01)|(1<<CS01) ; Starte TC0
	out TCCR0,rmp ; Setze TC0-Modus, COM und Vorteiler = 8
CheckAlarmRet:
	ret
;
; ===================================================
;   N e u e r   A D C - W e r t   v o r h a n d e n
; ===================================================
;
AdcNew:
	cbr rFlag,1<<bAdc ; ADC Flagge loeschen
	; Debug ADC-Wert in Anzeige anzeigen
	.if dbg == 6
	rcall dispadc
	.endif
	; Ende Debug
	dec rFChk ; Zaehler fuer Dimmwert auf Null?
	brne AdcNew2 ; Nein
	ldi rmp,0x10 ; Setze Zaehler fuer Dimmen neu
	mov rFChk,rmp
	lds rmp,sAdcF ; Lese Fototransistor-Wert
	cpi rmp,0xFF ; Dunkel schalten?
	brcs AdcNew1 ; Nein
	ldi rmp,HIGH(divider/2) ; Compare Match B auf halbe Zeit
	out OCR1BH,rmp
	ldi rmp,LOW(divider/2)
	out OCR1BL,rmp
	rjmp AdcNew2 ; weiter
AdcNew1:
	ldi rmp,HIGH(divider-30) ; Compare Match B auf volle Zeit
	out OCR1BH,rmp
	ldi rmp,LOW(divider-30)
	out OCR1BL,rmp
AdcNew2:
	lds rmp,sAdcP ; Lese Potentiometer-Wert
	sbrs rFlag,bSetC ; Zeiteinstellung aktiv?
	rjmp AdcNew4 ; Nein
	; Stelle Potentiometer-Wert in Anzeige dar
	sbrs rFlag,bSetCm ; Minuteneinstellung aktiv?
	rjmp AdcNew3 ; Nein
	; Minutendarstellung aus Poti-Wert
	ldi XH,High(sTime+2) ; Setze Zeiger auf Zeit Minuten Anzeige
	ldi XL,Low(sTime+2)
	ldi ZL,60 ; mal 60
	rjmp Mult ; Muliplizieren und in Anzeigenspeicher schreiben
AdcNew3: ; Stundendarstellung aus Poti-Wert
	ldi XH,High(sTime) ; Setze Zeiger auf Zeit Stunden Anzeige
	ldi XL,Low(sTime)
	ldi ZL,24 ; mal 24
	rjmp Mult ; Muliplizieren und in Anzeigenspeicher schreiben
AdcNew4:
	sbrs rFlag,bSetA ; Weckzeiteinstellung aktiv?
	ret ; Nein
	sbrs rFlag,bSetAm ; Weckzeiteinstellung Minuten?
	rjmp AdcNew5 ; Nein
	ldi XH,High(sAlarm+2) ; Setze Zeiger Weckzeit Minuten Anzeige
	ldi XL,Low(sAlarm+2)
	ldi ZL,60 ; mal 60
	rjmp Mult ; Muliplizieren und in Anzeigenspeicher schreiben
AdcNew5:
	ldi XH,High(sAlarm) ; Setze Zeiger Weckzeit Stunden Anzeige
	ldi XL,Low(sAlarm)
	ldi ZL,24 ; mal 24
	rjmp Mult ; Muliplizieren und in Anzeigenspeicher schreiben
;
; Muliplizieren und in Anzeigenspeicher schreiben
;
Mult:
	mul ZL,rmp ; ZL * rmp in R1:R0
	mov rmp,R1 ; MSB in rmp kopieren
	rjmp To7Seg ; in Siebensegmentcode wandeln und in Zeigerposition
;
; ===================================================
;  D e b u g :   A D C - W e r t e   a n z e i g e n
; ===================================================
;
dispadc:
	ldi XH,HIGH(sAlarm) ; Zeiger Position Weckzeit Stunden
	ldi XL,LOW(sAlarm)
	lds rmp,sAdcP ; Potentiometer-Wert lesen
	rcall dispadc1 ; in Hex in Display schreiben 
	lds rmp,sAdcF ; Fototransistor-Wert lesen
dispadc1:
	push rmp ; Wert in rmp auf Stapel
	swap rmp ; Oberes und unteres Nibble vertauschen
	rcall dispadc2 ; Unteres Nibble in Hex schreiben
	pop rmp ; rmp wieder herstellen
dispadc2:
	andi rmp,0x0F ; Unteres Nibble isolieren
	ldi ZH,HIGH(2*Tab7Seg) ; Zeiger auf Siebensegmenttabelle
	ldi ZL,LOW(2*Tab7Seg)
	add ZL,rmp ; Unteres Nibble addieren
	brcc dispadc3 ; Kein Ueberlauf
	inc ZH ; Ueberlauf
dispadc3:
	lpm ; Code aus Tabelle lesen
	st X+,R0 ; Und in SRAM schreiben
	ret
;
; ===================================================
;      C o p y r i g h t   I n f o r m a t i o n
; ===================================================
;
.db "C(2)10 0ybh tt:p//ww.wva-rsa-mutotirlan.te"
;
; ===================================================
;         M e l o d i e   i m   E E P R O M
; ===================================================
;
.eseg
.org $0000
;
.equ cPa = 10 ; Pausenlaenge
.equ cD = 9216 ; Tondauerkonstante
;
; Frequenztabelle
.equ cf2e = 659
.equ cf2f = 698
.equ cf2g = 784
.equ cf2a = 880
.equ cf2h = 988
.equ cf3c = 1047
.equ cf3d = 1175
.equ cf3e = 1319
.equ cf3f = 1397
.equ cf3g = 1568
.equ cf3a = 1760
.equ cf3h = 1976
.equ cf4c = 2093
.equ cf4d = 2349
.equ cf4e = 2637
.equ cf4f = 2794
.equ cf4g = 3136
.equ cf4a = 3520
.equ cf4h = 3951
.equ cf5c = 4186
; CTC Tabelle fuer diese Frequenzen
.equ cc2e = clock/16/cf2e
.equ cc2f = clock/16/cf2f
.equ cc2g = clock/16/cf2g
.equ cc2a = clock/16/cf2a
.equ cc2h = clock/16/cf2h
.equ cc3c = clock/16/cf3c
.equ cc3d = clock/16/cf3d
.equ cc3e = clock/16/cf3e
.equ cc3f = clock/16/cf3f
.equ cc3g = clock/16/cf3g
.equ cc3a = clock/16/cf3a
.equ cc3h = clock/16/cf3h
.equ cc4c = clock/16/cf4c
.equ cc4d = clock/16/cf4d
.equ cc4e = clock/16/cf4e
.equ cc4f = clock/16/cf4f
.equ cc4g = clock/16/cf4g
.equ cc4a = clock/16/cf4a
.equ cc4h = clock/16/cf4h
.equ cc5c = clock/16/cf5c
; Tondauerkonstanten
.equ cd2e = cd/cc2e
.equ cd2f = cd/cc2f
.equ cd2g = cd/cc2g
.equ cd2a = cd/cc2a
.equ cd2h = cd/cc2h
.equ cd3c = cd/cc3c
.equ cd3d = cd/cc3d
.equ cd3e = cd/cc3e
.equ cd3f = cd/cc3f
.equ cd3g = cd/cc3g
.equ cd3a = cd/cc3a
.equ cd3h = cd/cc3h
.equ cd4c = cd/cc4c
.equ cd4d = cd/cc4d
.equ cd4e = cd/cc4e
.equ cd4f = cd/cc4f
.equ cd4g = cd/cc4g
.equ cd4a = cd/cc4a
.equ cd4h = cd/cc4h
.equ cd5c = cd/cc5c
; Melodie
; 4g  4g  4f   3a
; 4e  4d 4d
; 4c  3h  4c   4f
; 4f 4g-4f
; Melodietabelle
.db cc3g,cd3g,cc3g,cd3g,cc3f,cd3f,cc2a,cd2a
.db cc3e,cd3e,cc3d,cd3d,cc3d,cd3d
.db cc3c,cd3c,cc2h,cd2h,cc3c,cd3c,cc3f,cd3f
.db cc3f,cd3f,cc3g,cd3g,cc3f,cd3f
; Ende der Tabelle
.db 0,0
;
; Ende des Quellcodes
;



©2010 by http://www.avr-asm-tutorial.net