Pfad: Home => AVR-Übersicht => Anwendungen => DCF77-Weckuhr m16 => Assembler-Quellcode   This page in english: Flag EN
DCF77 Weckuhr Anwendungen von
AVR-Einchip-Prozessoren AT90S, ATtiny, ATmega und ATxmega
DCF77 Weckuhr mit LCD
Der Assembler-Quellcode

Logo

Der Assembler-Quellcode für die DCF77-Weckuhr mit LCD

asm-Link Funktion Link zum html-Format
dcf77_m16_v4_de.asm Hauptprogramm mit Debug-Schaltern, Konstanten, Init und den wichtigsten Routinen Hauptprogramm
lcd_8_routinen.inc Routinen zur Ansteuerung der 4*20-LCD, mit Init, Zeichen-, Text- und Zahlenausgabe in Dezimal und Hexadezimal LCD-Routinen
musik_code.inc Routinen zur Ausgabe von Musik über den Lautsprecher, mit Noten und Melodien Musik-Code
debug_code_de.inc Routinen zur Inbetriebnahme der Hardware und zur Fehlersuche in der Software Debug-Code

1 Das Hauptprogramm dcf77_m16_v4_de.asm

Dieser Code im asm-Format

;
; **********************************************
; * ATmega16-Weckuhr mit LCD und DCF77-Synchr. *
; * Version 4.0 vom Mai 2018                   *
; * (C)2018 by G.Schmidt, avr-asm-tutorial.net *
; **********************************************
;
; Include-Datei zum Prozessortyp
.NOLIST
.INCLUDE "m16def.inc" ; Header-Datei fuer ATmega16
.LIST
;
; ===================================
;         S C H A L T E R
; ===================================
;
; Anzeigesprache: 0 = Deutsch, 1 = Englisch
.equ LangEN = 0 ; Sprachauswahl
;
; ===================================
;  D E B U G G E R   S C H A L T E R
; ===================================
;
; Alle Debug-Schalter sind bei der Endversion Null!
;
; 1. Debuggen der LCD
.equ dbgLcd = 0 ; Debuggen der LCD, 0=aus, 1=ein
;
; 2. Debuggen des AD-Wandlers
.equ dbgAdc = 0 ; Debuggen des ADC
  .equ debugAdcChannel = 0  ; Auswahl des Kanals: 0, 1 oder 2
;
; 3. Debuggen der Tasten
.equ dbgKey = 0 ; Debuggen der angeschlossenen Tasten
.equ dbgKeyState = 0 ; Debuggen des Tastenstatusses in Zeile 4
;
; 4. Debuggen des Lautsprechers
.equ dbgSpk = 0 ; Debuggen des angeschlossenen Lautsprechers
;
; 5. Debuggen des DCF77-Signals
.equ dbgDcfDur = 0 ; Debuggen der DCF77-Signaldauer
.equ dbgDcfSig = 0 ; Debuggen von DCF77-Signalfehlern
.equ dbgDcfBits = 0 ; Debuggen emfangener DCF77-Bits
.equ dbgDcfRcvd = 0 ; Debuggen emfangener DCF77-Daten
;
; 6. Debuggen des Abspielen von Musikstuecken
.equ dbgMusic = 0 ; Debuggen des Musikabspielens
.equ dbgPlayStat = 0 ; Debuggen des Status von TC1
;
;
; =============================================
;  H A R D W A R E - I N F O R M A T I O N E N
; =============================================
;
;         ATmega16__________
;              1 /          |40
;       Sw-W o--|PB0    ADC0|--o Vpot
;       Sw-Y o--|PB1    ADC1|--o Vopto
;       Sw-R o--|PB2     PA2|--o DCF-signal strength
;      LCD-E o--|PB3     PA3|--o NC
;     LCD-RS o--|PB4     PA4|--o NC
;  ISP6-MOSI o--|MOSI    PA5|--o NC
;  ISP6-MISO o--|MISO    PA6|--o NC
;   ISP6-SCK o--|SCK     PA7|--o NC
;      RESET o--|RST    AREF|--o AREF
;        VCC o--|VCC     GND|--o GND
;        GND o--|GND    AVCC|--o AVCC
;      XTAL2 o--|XTAL2   PC7|--o LCD-D7
;      XTAL1 o--|XTAL1   PC6|--o LCD-D6
;         NC o--|PD0     PC5|--o LCD-D5
;         NC o--|PD1     PC4|--o LCD-D4
; INT0/DCF77 o--|INT0    PC3|--o LCD-D3
;         NC o--|PD3     PC2|--o LCD-D2
;         NC o--|PD4     PC1|--o LCD-D1
;        LSP o--|OC1A    PC0|--o LCD-D0
;         NC o--|PD6     OC2|--o LCD-LED-K
;             20|___________|21
;
; ============================================
;      P O R T S   U N D   P I N S
; ============================================
;
; Tastenports
.equ pKeyO = PORTB ; Port Tastenausgaenge
.equ pKeyD = DDRB ; Port Tastenrichtungen
.equ pKeyI = PINB ; Port Tasteneingaenge
.equ bKeyWO = PORTB0 ; Weisse Taste Ausgang-Pin
.equ bKeyWD = DDB0 ; Weisse Taste Richtung-Pin
.equ bKeyWI = PINB0 ; Weisse Taste Eingang-Pin
.equ bKeyYO = PORTB1 ; Gelbe Taste Ausgang-Pin
.equ bKeyYD = DDB1 ; Gelbe Taste Richtung-Pin
.equ bKeyYI = PINB1 ; Gelbe Taste Eingang-Pin
.equ bKeyRO = PORTB2 ; Rote Taste Ausgang-Pin
.equ bKeyRD = DDB2 ; Rote Taste Richtung-Pin
.equ bKeyRI = PINB2 ; Rote Taste Eingang-Pin
.equ cKeyMO = (1<<bKeyWO)|(1<<bKeyYO)|(1<<bKeyRO) ; Maske Ausgaenge
.equ cKeyMI =(1<<bKeyWI)|(1<<bKeyYI)|(1<<bKeyRI) ; Maske Eingaenge
;
; LCD-Ports
.EQU pLcdCO = PORTB ; LCD-Kontrollport Ausgang Port
.EQU pLcdCD = DDRB ; LCD-Kontrollport Richtung Port
.EQU bLcdE = PORTB3 ; LCD-Kontrollportbit Enable Pin
.EQU bLcdRs = PORTB4 ; LCD-Kontrollportbit RS Pin
.EQU bLcdRw = PORTB5 ; LCD-Kontrollportbit R/W in
.EQU pLcdDO = PORTC ; LCD-Datenport Ausgang
.EQU pLcdDD = DDRC ; LCD-Datenport Richtung
;
; DCF77-Ports
.EQU pDcfO = PORTD ; DCF77-Empfaenger-Port Ausgang
.EQU pDcfD = DDRD ; DCF77-Empfaenger-Port Richtung
.EQU pDcfI = PIND ; DCF77-Empfaenger-Port Eingang
.EQU bDcfD = DDD2 ; DCF77-Emfaenger-Portpin Richtung
.EQU bDcfI = PIND2 ; DCF77-Empfaenger-Portpin Eingang
;
; Lautsprecher-Port
.equ pSpkO = PORTD ; Lautsprecher-Port Ausgang
.equ pSpkD = DDRD ; Lautsprecher-Port Richtung
.equ bSpkO = PORTD5 ; Lautsprecher-Portpin Ausgang
.equ bSpkD = DDD5 ; Lautsrecher-Portpin Richtung
;
; LED LCD Hintergrundbeleuchtung
.EQU pLedO = PORTD ; LED-Kathode Port Ausgang
.EQU pLedD = DDRD ; LED-Kathode Port Richtung
.EQU bLedO = PORTD7 ; LED-Kathode Portpin Ausgang
.EQU bLedD = DDD7 ; LED-Kathode Portpin Richtung
;
; ADC Ports
.EQU pAdcO = PORTA ; ADC Port Ausgang
.EQU pAdcD = DDRA ; ADC Port Richtung
.EQU bAdcP = 0 ; ADC Portbit Poti-Anschluss
.EQU bAdcO = 1 ; ADC Optosensor
.equ bAdcS = 2 ; ADC DCF77-Empfaenger-Signalpegel
;
; ================================================
;    T I M I N G    U N D   T I M E R 
; ================================================
;
; TC0: 8-Bit-Timer fuer Bestimmung
;   a) des Sekundenimpulses fuer die Uhr
;   b) der DCF-Pulsdauer
;
;   Quarzoszillator ==> TC0-Presc ==> TC0-Tick ==>
;     f  3276800 Hz         64        51.200 Hz
;     t  305,1758 ns                  19,53125 us
;   TC0-Overflow (256)
;     f   200 Hz
;     t   5 ms
;   a) Sekundenimpuls ==> Abwaertszaehlen Sekundenteiler,
;      wenn Null: neu starten und bSec-Flagge setzen
;   b) DCF77-Zeitzaehler: Erhoehe DCF77-Zaehler,
;      wenn Maximum Dauer erreicht: setze bDcfTO-Flagge
;
; TC1: 16-Bit-Timer fuer Tonerzeugung
;   Quarz   ==> TC1-Presc ==> TC1-CTC ==> 25 Hz bis
;   3276800         1         0..65535    1,6384 MHz
;   CTC-COMPARE-A Interrupt:
;     Erniedrigen R25:R24, wenn Null:
;       Lese naechsten Compare-A-Wert (Tonhoehe),
;         wenn MSB = 0xFF: Lautsprecher auf Clear (Pause)
;         wenn nicht: Lautsprecher-Ausgang auf Toggle
;       Lese Dauerzaehler und schreibe in R25:R24
;         wenn LSB = 0x00: Musik ausschalten
;
; TC2: 8-Bit-Timer als PWM fuer die LCD-Hintergrundbeleuchtung
;      und fuer Tastenerkennung
;   Quarz   ==> TC2-Presc ==> TC2-Ueberlauf ==> OvFlw-Int
;   3276800 ==>    64     ==>     256            200 Hz
;   Lese Tasteneingaenge, wenn
;     Keine Taste:
;       wenn rKeyCnt nicht 0: rKeyCnt abwaerts, wenn Null:
;         Flagge rKeyAct loeschen
;     Irgendeine Taste:
;       wenn Flagge bKeyAct Null: setze Flagge rKeyAct,
;         Lade rKeyCnt
;
; ADC: Messen der drei Analogkanaele
;   Quarz   ==> ADC-Presc ==> ADC-1m ==> ADC-64m ==> 3 Ch
;   3276800       128           13          64        3
;   = 10.256 cs/s or 97 ms per round
;
; ==========================================
;    E I N S T E L L K O N S T A N T E N
; ==========================================
;
; Prozessortakt externer Quarz
.equ Clock = 3276800 ; Quarzfrequenz
;
; DCF77 Signaldauern in Millisekunden
.equ cDcfIgnoreShortTime = 20 ; Ignoriere Kurzimpulse mit weniger als 20 ms
.equ cDcfMinTime = 50 ; Minimum Signaldauer NUll- oder Eins-Bit
.equ cDcfMaxZeroTime = 150 ; Maximum Zeit Null-Bit
.equ cDcfMaxOneTime = 250 ; Maximum Zeit Eins-Bit
.equ cDcfMinInactiveTime = 700 ; Minimum inaktive Zeit bis naechstes Bit
.equ cDcfMaxInactiveTime = 1000 ; Maximum inaktive Zeit bis naechstes Bit
.equ cDcfMin59Time = 1700 ; Minimum Zeit fuer 59ste Sekunde
.equ cDcfMax59Time = 2000 ; Maximum Zeit fuer 59ste Sekunde
.equ cDcfTimeOutTime = 2500 ; Time-Out am DCF77-Signaleingang
;
; LCD Hintergrundbeleuchtung
.equ cBackPeriodsTime = 3000 ; Millisekunden Ansteuerung
.equ cBackMin = 10 ; Mindest-Wert der Helligkeit, 10 bis 255
;
; DCF77-Signalstaerke-Ausgabe in S-Stufen
.equ cSigStrTime = 2000 ; Erneuerung der Ausgabe alle ... Millisekunden
;
; Tastenprellunterdrueckung
.equ cKeyCntTime = 30 ; Taste inaktiv fuer ... Millisekunden
;
; Weckwiederholungen
.equ cAlarmRepet = 3 ; Anzahl Minuten Weckwiederholung
;
; Musikstueck-Auswahl bei Neustart
.equ cDefaultMelody = 8 ; Melodie, die beim Neustart gespielt wird
;
; =========================================================
;  F E S T E + A B G E L E I T E T E   K O N S T A N T E N 
; =========================================================
;
; Konstanten fuer den TC0-Betrieb
.equ cTc0Presc = 64 ; TC0-Vorteiler
.equ cTc0Divider = 256 ; TC0-8-Bit-Teiler
.equ cSecDiv = clock / cTc0Presc / cTc0Divider ; Teiler bis Sekunde
.if cSecDiv>255
  .error "cSecDiv out of range!"
  .endif
;
; Konstanten fuer TC1-Betrieb (Tongenerator)
.equ cTc1Presc = 1 ; Vorteiler 1
;
; Konstanten fuer TC2-Betrieb (Hintergrundbeleuchtung PWM)
.equ cTc2Presc = 64 ; TC2-Vorteiler LCD-Hintergrundbeleuchtung PWM
.equ cTc2Cnt = Clock/cTc2Presc/256 ; 200 Interrupts pro Sekunde
.equ cBackMult = (255-cBackMin) ; Multiplikator fuer Optosensor-Spannung 
;
; DCF-Signaldauern
.equ cPulse=(cTc0Presc*cTc0Divider*1000+clock/2)/clock ; Pulsdauer in ms
.equ cDcfIgnoreShort = cDcfIgnoreShortTime/cPulse ; Ignoriere kuerzere Pulse
.equ cDcfMin = cDcfMinTime/cPulse ; Minimum Pulse Anzahl
.equ cDcfMaxZero = cDcfMaxZeroTime/cPulse ; Maximum Null-Bit Pulse
.equ cDcfMaxOne = cDcfMaxOneTime/cPulse ; Maximum Eins-Bit Pulse
.equ cDcfMinInactive = cDcfMinInactiveTime/cPulse ; Minimum Inaktive Pulse
.equ cDcfMaxInactive = cDcfMaxInactiveTime/cPulse ; Maximum Inaktive Pulse
.equ cDcfMin59 = cDcfMin59Time/cPulse ; Minimum 59ster Sekunde Pulse
.equ cDcfMax59 = cDcfMax59Time/cPulse ; Maximum 59ster Sekunde Pulse
.equ cDcfTimeOut = cDcfTimeOutTime/cPulse ; Time-Out DCF-Eingang Pulse
;
; Tastenprell-Unterdrueckung
.equ cKeyCnt = cKeyCntTime/cPulse ; Pulse ueber die keine Taste aktiv sein darf
;
; Erneuerungszeit LCD-Hintergrundbeleuchtung
.equ cAdcPulse = 1000*128*13*64*3/clock ; Dauer einer ADC-Messrunde in ms
.equ cBackPeriods = cBackPeriodsTime/cAdcPulse ; Anzahl Perioden bis Update
.if cBackPeriods > 255
  .error "cBackPeriodsTime too long!"
  .endif
;
; DCF77-Signalstaerke Erneuerung
.equ cSigStr = cSigStrTime/cAdcPulse ; Anzahl Perioden bis Update
;
; Debugging aktiv?
.set fDbg=(dbgLcd>0)||(dbgAdc>0)||(dbgKey>0)||(dbgKeyState>0)
.set fDbg=fDbg||(dbgSpk>0)||(dbgDcfDur>0)||(dbgDcfSig>0)
.set fDbg=fDbg||(dbgDcfBits>0)||(dbgDcfRcvd>0)||(dbgMusic>0)
.set fDbg=fDbg||(dbgPlayStat>0)
;
; ===========================================
;  R E G I S T E R - D E F I N I T I O N E N
; ===========================================
;
; R0, R1 verwendet fuer Berechnungen und diverse Zwecke
; frei: R2, R3
.def rMelody = R4 ; Melodie-Auswahl zufaellig, gesetzt durch Tasten
.def rSigStr = R5 ; Signalstaerke Update Zaehler
.def rBackCnt = R6 ; LCD-Hintergrundbeleuchtung Update Zaehler
.def rKeyCnt = R7 ; Tastendruck Zaehler
.def rDcfBitCnt = R8 ; Bit-Zaehler DCF-Bits
.def rDcfErr = R9 ; DCF77-Signal- und Auswertefehler
.def rAdc = R10 ; ADC MSB des Ergebnisses
.def rAdcL = R11 ; ADC Summe, LSB
.def rAdcH = R12 ; dto., MSB
.def rDcfL = R13 ; Letzte Puls-/Pausendauer-Laenge DCF-Signal, LSB
.def rDcfH = R14 ; dto., MSB
.def rSreg = R15 ; Sichern SREG in Interrupt-Service-Routinen
.def rmp = R16 ; Vielzweckregister
.def rimp = R17 ; Vielzweckregister in Interrupts
.def rFlag = R18 ; Flaggen
  .equ bSec = 0 ; Sekunde vorbei
  .equ bDcf = 1 ; Aktiver DCF77 Pulsflagge
  .equ bDcfTO = 2 ; DCF77-Signal Time-Out-Flagge
  .equ bAdc = 3 ; ADC 64 Messungen komplett-Flagge
  .equ bKey = 4 ; Eine Taste ist gedrueckt-Flagge
  .equ bKeyAct = 5 ; Tasten-Auswertung aktiv-Flagge
  .equ bMinute = 6 ; Minutenwechsel, Wecken pruefen-Flagge
.def rSecDiv = R19 ; Abwaertszahler Sekunden
.def rDcfCntL = R20 ; DCF77-Pulsdauer-Zaehler, LSB
.def rDcfCntH = R21 ; dto., MSB
.def rAdcC = R22 ; ADC Messungen Zaehler
; frei: R23
.def rDurL = R24 ; Dauer von Toenen, LSB
.def rDurH = R25 ; dto., MSB
; benutzt: R27:R26 = X fuer diverse Zwecke
; benutzt: R29:R28 = Y fuer Musikspielen als Zeiger in SRAM
; benutzt: R31:R30 = Z fuer diverse Zwecke
;
; ===================================
;  S R A M - D E F I N I T I O N E N
; ===================================
;
.DSEG
.ORG  SRAM_START
; Aktuelle Datum- und Zeit-Information fuer Anzeige
sDateTime:
sWeekday:
.byte 1
sDay:
.byte 1
sMonth:
.byte 1
sYear:
.byte 1
sHour:
.byte 1
sMin:
.byte 1
sSec:
.byte 1
sDateTimeEnd:
sAlrmHr:
.byte 1
sAlrmMin:
.byte 1
;
; DCF77-Bits Schieberegisterbereich
sDcfBits:
.byte 8
;
; DCF77 empfangene und dekodierte Teile
sDcfRcv:
.byte 1 ; Wochentag 0..6
.byte 1 ; Tag 1..31
.byte 1 ; Monat 1..12
.byte 1 ; Jahr 0..99
.byte 1 ; Stunde 0..23
.byte 1 ; Minute 0..59
.byte 1 ; Sekunde 0..59
;
; Verarbeitung von Tastenereignissen
sKeyMode:
.byte 1 ; 0=Aus/Normal
;         1=Wecken aktiviert
;         2=Eingabe von Datum und Uhrzeit
;         3=Eingabe der Weckzeit
;
sKeyPos:
.byte 1 ; Mode 1  Mode 2  Mode 3
;         min=4   min=0   min=7
;         max=6   max=3   max=8
;
sKeyCnt:
.byte 1 ; ADC Periodenzaehler fuer Tastenaktionen
;
sAlarm:
.byte 2 ; Gespeicherte Weckzeit
;
sInputData:
.byte 9 ; Datum (Wochentag, Tag, Monat, Jahr),
        ; Zeit (Stunde, Minute, Sekunde),
        ; Weckzeit (Stunde, Minute)
;
sAlarmRepeat:
.byte 1 ; Wiederholzaehler Wecken
;
sMelody:
.byte 500 ; Puffer fuer Melodie zum Abspielen
sMelodyEnd:
;
; ===============================================
;  R E S E T -  U N D   I N T -  V E K T O R E N
; ===============================================
;
.CSEG
.ORG $0000
  jmp Main ; Reset-Vektor
  jmp Int0Isr ; INT0-Vektor
  reti ; INT1-Vektor
  nop
  reti ; TC2COMP-Vektor
  nop
  jmp Tc2OvfIsr ; TC2OVF-Vektor
  reti ; TC1CAPT-Vektor
  nop
  jmp Tc1CmpAIsr ; TC1 COMPA-Vektor
  reti ; TC1COMPB-Vektor
  nop
  reti ; TC1OVF-Vektor
  nop
  jmp Tc0OvfIsr ; TC0OVF-Vektor
  reti ; SPI-STC-Vektor
  nop
  reti ; USART-RXC-Vektor
  nop
  reti ; USART-UDRE-Vektor
  nop
  reti ; USART-TXC-Vektor
  nop
  jmp AdcIsr ; ADC-Vektor
  reti ; EE_RDY-Vektor
  nop
  reti ; ANA_COMP-Vektor
  nop
  reti ; TWI-Vektor
  nop
  reti ; INT2-Vektor
  nop
  reti ; TC0_COMP-Vektor
  nop
  reti ; SPM_RDY-Vektor
  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
; =====================================================
;
; DCF77-Empfaenger Puls-Eingang
Int0Isr:
  in rSreg,SREG ; Sichere SREG
  mov rimp,rDcfCntL ; Unterdruecke Kurzsignale
  cpi rimp,cDcfIgnoreShort ; Kuerzer als Minimum Puls?
  brcs Int0IsrRet ; Ja, ignoriere
  ; Valides DCF-Signal gefunden
  mov rDcfL,rDcfCntL ; Kopie Zaehler LSB
  mov rDcfH,rDcfCntH ; dto., MSB
  sbr rFlag,1<<bDcf ; Setze Flagge
  clr rDcfCntL ; Loeschen LSB Zaehler
  clr rDcfCntH ; dto., MSB
Int0IsrRet:
  out SREG,rSreg ; SREG wieder herstellen
  reti
;
; TC0-Ueberlauf-Interrupt, Sekunden/DCF77-PUlsdauer-Timer
Tc0OvfIsr:
  in rSreg,SREG ; Sichern Sreg
  dec rSecDiv ; Sekundenteiler abwaerts
  brne Tc0OvfIsr1 ; Nicht Null
  ldi rSecDiv,cSecDiv ; Neustart Sekundenteiler
  sbr rFlag,1<<bSec ; Setze Sekundenflagge
Tc0OvfIsr1:
  inc rDcfCntL ; Erhoehe DCF-Pulsdauer-Zaehler
  brne Tc0OvfIsr2 ; Kein Uebertrag zum MSB
  inc rDcfCntH ; Uebertrag ins MSB
Tc0OvfIsr2:
  cpi rDcfCntH,High(cDcfTimeOut) ; MSB bei Time-Out?
  brcs Tc0OvfIsr3 ; Nein
  cpi rDcfCntL,Low(cDcfTimeOut) ; LSB bei Time-Out?
  brcs Tc0OvfIsr3 ; Nein
  sbr rFlag,1<<bDcfTO ; Setze Time-Out-Flagge
  clr rDcfCntL ; Neustart DCF-Pulszaehler
  clr rDcfCntH
Tc0OvfIsr3:
  out SREG,rSreg ; Stelle SREG wieder her
  reti
;
; TC1 Compare Match Interrupt, spiele Musik
Tc1CmpAIsr:
  in rSreg,SREG ; Sichere SREG
  sbiw rDurL,1 ; Dauerzaehler abwaerts
  brne Tc1CmpAIsrRet
  ld rimp,Y+ ; Lese MSB zuerst
  out OCR1AH,rimp ; In MSB Vergleichsregister
  cpi rimp,0xFF ; MSB Lautsprecher aus?
  ld rimp,Y+ ; Lese LSB als nachstes
  out OCR1AL,rimp ; In LSB Vergleichsregister
  brne Tc1CmpAIsr1 ; Lautsprecher nicht abschalten
  cpi rimp,0xFF ; LSB Lautsrecher aus?
  brne Tc1CmpAIsr1 ; Lautsprecher nicht abschalten
  ldi rimp,(1<<COM1A1) ; Lautsprecher aus
  rjmp Tc1CmpAIsr2
Tc1CmpAIsr1:
  ldi rimp,(1<<COM1A0) ; Ausgang OCR1A auf Toggle
Tc1CmpAIsr2:
  out TCCR1A,rimp ; Setze OCR1A auf Toggle/Clear
  ld rDurH,Y+ ; Lese MSB Dauer
  ld rDurL,Y+ ; Lese LSB Dauer
  mov rimp,rDurH ; Teste auf 0x0000
  or rimp,rDurL
  brne Tc1CmpAIsrRet ; Musikstueck weiter spielen
  ; Musikausgabe ausschalten
  ldi rimp,1<<COM1A1 ; OCR1A Ausgang auf Clear
  out TCCR1A,rimp
  ldi rimp,(1<<TOIE0)|(1<<TOIE2) ; Abschalten TC1 int
  out TIMSK,rimp
  ldi rimp,High(clock / 2000 - 1) ; CTC auf 1 kHz
  out OCR1AH,rimp
  ldi rimp,Low(clock / 2000 - 1)
  out OCR1AL,rimp
Tc1CmpAIsrRet:
  out SREG,rSreg ; Stelle SREG wieder her
  reti
;
; TC2-Ueberlauf Interrupt, Hintergrundbeleuchtung und Tasten
Tc2OvfIsr:
  in rSreg,SREG ; Sichere SREG
  in rimp,pKeyI ; Lese Tasten-Eingang
  andi rimp,cKeyMI ; Isoliere Tasteneingaenge
  cpi rimp,0x07 ; Alle Tasten aus?
  brne Tc2OvfIsrKey ; Nein, Taste verarbeiten
  tst rKeyCnt ; Tastenzaehler auf Null?
  breq Tc2OvfIsrRet ; Ja, ignoriere
  dec rKeyCnt ; Erniedrige Tastenzaehler
  brne Tc2OvfIsrRet ; Nicht bei Null, raus
  cbr rFlag,1<<bKeyAct ; Loesche Taste-Aktiv-Flagge
  rjmp Tc2OvfIsrRet
Tc2OvfIsrKey:
  sbrs rFlag,bKeyAct ; Flagge bKeyAct schon gesetzt?
  sbr rFlag,(1<<bKeyAct)|(1<<bKey) ; Setze Tastenflagge
  ldi rimp,cKeyCnt ; Setze den Tastenzaehler auf Anfang
  mov rKeyCnt,rimp
  in rMelody,TCNT1L ; Lese Zaehler 1 LSB als Zufallsgenerator
Tc2OvfIsrRet:
  out SREG,rSreg ; Stelle SREG wieder her
  reti
;
; ADC-Messung komplett Interrupt Service Routine
AdcIsr:
  in rSreg,SREG ; Sichere SREG
  in rimp,ADCL ; Lese LSB Ergebnis
  add rAdcL,rimp ; Addiere zur Summe
  in rimp,ADCH ; Lese MSB Ergebnis
  adc rAdcH,rimp ; Addiere zur Summe mit Carry
  dec rAdcC ; Zaehler abwaerts
  breq AdcIsr1 ; Zaehler ist Null, kein Neustart
  ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(ADPS0)
  out ADCSRA,rimp ; Starte naechste Wandlung
  out SREG,rSreg ; Stelle SREG wieder her
  reti
AdcIsr1:
  mov rAdc,rAdcH ; Kopiere MSB
  clr rAdcL ; Loesche Summe
  clr rAdcH
  sbr rFlag,1<<bAdc ; Setze ADC-Flagge
  ldi rAdcC,64 ; Neustart Zaehler
  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
; =====================================
;
Main:
; Init Stapel
  ldi rmp, HIGH(RAMEND) ; Init MSB Stapel
  out SPH,rmp
  ldi rmp, LOW(RAMEND) ; Init LSB Stapel
  out SPL,rmp
  ; Init die Tasten-Eingangs-Pins
  cbi pKeyD,bKeyWD ; Weisse Taste als Eingang
  sbi pKeyO,bKeyWO ; Pull-Up Weisse Taste an
  cbi pKeyD,bKeyYD ; Gelbe Taste als Eingang
  sbi pKeyO,bKeyYO ; Pull-Up Gelbe Taste an
  cbi pKeyD,bKeyRD ; Rote Taste als Eingang
  sbi pKeyO,bKeyRO ; Pull-Up Rote Taste an
  ; Initiiere TC2 als LCD-Hintergrundbeleuchtungs-PWM
  sbi pLedD,bLedD ; Setze OCR2 Portbit als Ausgang
  ldi rmp,0x80 ; Halbe Helligkeit
  out OCR2,rmp
  ldi rmp,(1<<WGM21)|(1<<WGM20)|(1<<COM21)|(1<<COM20)|(1<<CS22) ; Fast PWM, Vort=64
  out TCCR2,rmp
  ; Initiiere die LCD-Ports und die LCD
  cbi pLcdCO,bLcdE
  sbi pLcdCD,bLcdE ; Enable-Pin als Ausgang
  sbi pLcdCD,bLcdRs ; RS-Pin als Ausgang
  cbi pLcdCO,bLcdRs ; RS auf Low
  sbi pLcdCD,bLcdRw ; R/W-Pin als Ausgang
  cbi pLcdCO,bLcdRw ; R/W-Pin auf Low
  ldi rmp,0xFF ; LCD-Datenport als Ausgang
  out pLcdDD,rmp
  call LcdInit ; Initieren der LCD
; Falls debugging aktiv
.if fDbg
  call DebugActive ; Ausgabe Meldung auf LCD
  .endif
; Fall debugging der LCD aktiv: Stop hier
.if dbgLcd == 1
  LoopLcd:
    rjmp LoopLcd ; Stop Betrieb
  .endif
  ; Verzoegerung zwischen Eroeffnung und Maske
  ldi rmp,20
Delay:
  call LcdWtMax
  dec rmp
  brne Delay
  call LcdFrame ; LCD-Ausgabemaske schreiben
  call LcdNoBlink ; Kein Blinken
; Falls debugging des ADC aktiv: Fuehre Messungen aus
.if dbgAdc == 1
  jmp DebugAdc ; Fuehre Messungen aus
  .endif
; Falls Tasten-debugging aktiv: Stelle Tastenstatus dar
.if dbgKey == 1
  jmp DebugKey ; Stelle Tastenstatus dar
  .endif
; Falls Lautsprecher-Debug aktiv: Spiele Ton ab
.if dbgSpk == 1
  jmp DebugSpeaker ; Ton an
  .endif
; Falls DCF77-Signal-Debug aktiv: Stelle Signale dar
.if dbgDcfDur == 1 ; Debug DCF signal
  call DebugDcfDur ; Zeige DCF77-Signale an
  .endif
  ; Starte AD-Wandlung
  clr rAdcL ; Loesche ADC-Summe
  clr rAdcH
  ldi rAdcC,64 ; 64 Messungen
  ldi rmp,1<<REFS0 ; Beginne mit Kanal 0
  out ADMUX,rmp
  ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(ADPS0)
  out ADCSRA,rmp ; Starte erste Wandlung
  ; Initiiere SRAM-Inhalte
  rcall InitDT
  lds rmp,sDateTime+7 ; Kopiere Weckzeit nach sAlarm
  sts sAlarm,rmp
  lds rmp,sDateTime+8
  sts sAlarm+1,rmp
  rcall SetAlarmOff ; Wecken ausschalten und anzeigen
  rcall DateTimeLcd
  rcall DcfClearBuffer ; Initiere den DCF-Emfangspuffer
   ; Initiiere Flaggen und Softwarezaehler
  clr rFlag ; Alle Flaggen aus
  clr rDcfErr ; Loesche DCF77-Fehler
  clr rDcfBitCnt ; Loesche DCF77 Bitzaehler
  ldi rmp,cBackPeriods ; Hintergrundbeleuchtungs-Updateperiode
  mov rBackCnt,rmp
  clr rmp ; Initiere Eingabemodus
  sts sKeyMode,rmp ; Eingabe aus
  sts sAlarmRepeat,rmp ; Weckwiederholung auf Null
  rcall DisplMenue ; Initiiere Menuezeile
  ; Initiiere TC0 als Zeitzaehler fuer Sekunde und DCF77-Signal
  ldi rSecDiv,cSecDiv ; Sekundenabwaertszaehler
  ldi rmp,(1<<CS01)|(1<<CS00) ; Vorteiler auf 64
  out TCCR0,rmp
  ; Initiiere TC1 als Musikspieler, CTC-Modus
  ldi rmp,High(clock/2000-1) ; Init Compare A Register
  out OCR1AH,rmp
  ldi rmp,Low(clock/2000-1)
  out OCR1AL,rmp
  ldi rmp,(1<<COM1A1) ; Compare match OCR1A auf Clear
  out TCCR1A,rmp
  ldi rmp,(1<<WGM12)|(1<<CS10) ; CTC-A, Vorteiler=1
  out TCCR1B,rmp
  sbi pSpkD,bSpkD ; OCR1A als Ausgang
  ; Initiiere Musik-Ausgabe
  ldi rmp,cDefaultMelody ; Zu Beginn zu startende Melodie 
  rcall MusicConvert ; In das SRAM konvertieren
  ; Ermoegliche Timer-Interrupts
  ldi rmp,(1<<TOIE2)|(1<<OCIE1A)|(1<<TOIE0) ; Timer-Interrupts TC0 und TC2
  out TIMSK,rmp
  ; Schlafmodus, Externe Interrupts
  ldi rmp,(1<<SE)|(1<<ISC00) ; Schlafmodus Idle, INT0 bei beiden Flanken
  out MCUCR,rmp
  ; Externe INT0 einschalten
  ldi rmp,1<<INT0
  out GICR,rmp
  sei ; Interrupts ermoeglichen
;
; =================================
;  P R O G R A M M S C H L E I F E
; =================================
;
Loop:
  sleep ; Schlafen legen
  nop ; Dummy fuer Aufwecken
  sbrc rFlag,bDcf ; DCF77-Impuls erkannt?
  rcall DcfAct ; Behandle aktiven DCF77-Impuls
  sbrc rFlag,bDcfTO ; DCF77-Time-Out?
  rcall DcfTimeOut ; Time-Out am DCF-Signaleingang
  sbrc rFlag,bSec ; Sekunde abgelaufen?
  rcall IncSec ; Erhoehe Sekunden
  sbrc rFlag,bMinute ; Minutenflagge fuer Wecken?
  rcall CheckMinute ; Ja, pruefe Wecken
  sbrc rFlag,bKey ; Taste gedrueckt?
  rcall KeyPress ; Gedrueckte Taste verarbeiten
  sbrc rFlag,bAdc ; ADC-Wandlung komplett?
  rcall AdcCmplt ; ADC Messung ist komplett
; Falls Tastenstatus dargestellt werden soll 
  .if dbgKeyState == 1
    call KeyState ; Tastenstatus ausgeben
    .endif
  rjmp loop ; Zurueck nach Loop
;
; ===========================================
;  V E R A R B E I T E  D C F  I M P U L S E
; ===========================================
;
; Aktiver Impuls DCF77
DcfAct:
  cbr rFlag,1<<bDcf ; Loesche Flagge
; Falls debugging der DCF-Signaldauer aktiv
  .if dbgDcfDur == 1
    call DebugDcfDurDispl ; Debuggen der DCF77-Signaldauer
    .endif
  ldi rmp,Low(cDcfMin) ; Pruefe Minimum
  sub rmp,rDcfL
  ldi rmp,High(cDcfMin)
  sbc rmp,rDcfH
  ldi rmp,1 ; Fehlernummer 1
  brcc GotoDcfErr
DcfAct1:
  ldi rmp,Low(cDcfMaxOne) ; Pruefe Eins-Bit 
  sub rmp,rDcfL
  ldi rmp,High(cDcfMaxOne)
  sbc rmp,rDcfH
  brcc Dcf01Ok ; Null oder Eins ok
  ldi rmp,Low(cDcfMinInactive) ; Inaktivdauer Minimum pruefen
  sub rmp,rDcfL
  ldi rmp,High(cDcfMinInactive)
  sbc rmp,rDcfH
  ldi rmp,2 ; Fehlernummer 2
  brcc GotoDcfErr
DcfAct2:
  ldi rmp,Low(cDcfMaxInactive) ; Inaktivdauer Maximum pruefen
  sub rmp,rDcfL
  ldi rmp,High(cDcfMaxInactive)
  sbc rmp,rDcfH
  brcc DcfOk ; Inaktiv-Dauer ok
  ldi rmp,Low(cDcfMin59) ; Fehlendes 59stes Bit Minimum?
  sub rmp,rDcfL
  ldi rmp,High(cDcfMin59)
  sbc rmp,rDcfH
  ldi rmp,3 ; Fehlernummer 3
  brcc GotoDcfErr
  ldi rmp,Low(cDcfMax59) ; Fehlendes 59stes Bit Maximum
  sub rmp,rDcfL
  ldi rmp,High(cDcfMax59)
  sbc rmp,rDcfH
  brcc Dcf59Ok ; Fehlendes Bit ok
  ldi rmp,4 ; Fehlernummer 4
  rjmp GotoDcfErr
DcfOk: ; Signaldauer ok
  ret ; Zeit ok
GotoDcfErr:
  mov rDcfErr,rmp ; Fehler Signaldauer
  rjmp DcfReport ; Fehler anzeigen
;
; Der DCF77-Empfaenger hat ein korrektes Bit empfangen
;   Schiebe dieses in den SRAM-Puffer
Dcf01Ok:
  ldi rmp,Low(cDcfMaxZero) ; Null oder Eins?
  sub rmp,rDcfL
  ldi rmp,High(cDcfMaxZero)
  sbc rmp,rDcfH            ; Carry ist gesetzt wenn Eins
  ldi ZH,High(sDcfBits+8) ; Zeige Z auf Bit-Puffer
  ldi ZL,Low(sDcfBits+8)
  ld rmp,-Z ; Schiebe in Byte 7
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Schiebe in Byte 6
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Schiebe in Byte 5
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Schiebe in Byte 4
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Schiebe in Byte 3
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Schiebe in Byte 2
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Schiebe in Byte 1
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Schiebe in Byte 0
  ror rmp
  st Z,rmp
  inc rDcfBitCnt ; Zaehle empfangene Bits
; Falls DCF77 Bit debugging eingeschaltet
.if dbgDcfBits == 1
  call DebugDcfBitEcho ; Echo DCF-Bits auf der LCD
  .endif
  ret
;
; Der DCF77-Empfaenger hat das fehlende Sekundenbit erkannt
;   Pruefe ob alle Bits korrekt sind und setze Datum und Zeit
Dcf59Ok:
  ; Pruefe alle Signaldauern
  tst rDcfErr ; Alle Signaldauern ok?
  breq Dcf59Ok1 ; Ja
  ; Signaldauern nicht ok
  clr rDcfBitCnt ; Neustart Bitzaehler
  clr rDcfErr ; Neustart Fehlerzaehler
  ret
Dcf59Ok1:
  ; Sind 59 Bits gezaehlt?
  mov rmp,rDcfBitCnt
  cpi rmp,59 ; Nur 58 oder weniger oder mehr als 59?
  breq Dcf59Ok2 ; Nein, Anzahl korrekt
  ; Fehler gefunden
  ldi rmp,6 ; Zeige Fehlermeldung Anzahl Bits an
  mov rDcfErr,rmp ; Fehlernummer 6
  rcall DcfReport ; Zeige Fehler an
  clr rDcfBitCnt ; Neustart Bitzaehler
  clr rDcfErr ; Neustart Fehlerzaehler
  ret
Dcf59Ok2:
  ; Starte Bitanalyse
  clr rDcfBitCnt ; Neustart Bitzaehler
  ldi rmp,7 ; Beginne mit Fehlernummer 7
  mov rDcfErr,rmp
  ; Pruefe ob Bit 1 = 0
  lds rmp,sDcfBits ; Lese aeltestes Byte
  sbrc rmp,5 ; DCF-Bit 1 ist Bit 5
  rjmp DcfReport ; Fehler #7
  ; Pruefe Minuten-Einer
  inc rDcfErr ; Naechste Fehler-#
  lds ZL,sDcfBits+3 ; Lese Minuten-Einer
  lsr ZL ; Rechtsschieben
  lsr ZL ; Noch mal Rechtsschieben
  andi ZL,0x0F ; Isoliere Einer
  cpi ZL,10 ; Groesser als 9?
  brcs Dcf59Ok4 ; Nein, Einer ok
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok4:
  ; Pruefe Minuten-Zehner
  inc rDcfErr ; Naechste Fehler-#
  lds ZH,sDcfBits+4 ; Lese Minuten-Zehner
  andi ZH,0x01 ; Isoliere letztes Bit
  lds rmp,sDcfBits+3 ; Lese Minuten-20er und 10er
  lsl rmp ; 20er Linksschieben
  rol ZH ; und in ZH einrollen
  lsl rmp ; 10er Linksschieben
  rol ZH ; und in ZH einrollen
  andi ZH,0x07 ; Isoliere 40/20/10 Minuten
  cpi ZH,6 ; Zehner groesser als 6?
  brcs Dcf59Ok5 ; Nein
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok5:
  rcall BitCombine ; Kombiniere Zehner und Einer
  sts sDcfRcv+5,ZL ; Speichere Minuten in Puffer
  ; Paritaet Minuten pruefen
  inc rDcfErr ; Naechste Fehler-#
  lds ZL,sDcfBits+3 ; Lese Minuten-Einer
  lds ZH,sDcfBits+4 ; Lese Minuten-Zehner
  lsr ZH ; Zusammenschieben
  ror ZL
  lsr ZH
  ror ZL
  rcall CheckParityZL ; Pruefe Paritaet in ZL
  breq Dcf59Ok6 ; In Ordnung
  rjmp DcfReport ; Paritaetsfehler Minuten
Dcf59Ok6:
  ; Konvertiere Stunden-Einer
  inc rDcfErr ; Naechste Fehler-#
  lds ZL,sDcfBits+4 ; Lese Stunden-Einer
  lsr ZL
  lsr ZL
  andi ZL,0x0F ; Isoliere Stunden-Einer
  cpi ZL,10 ; Groesser als 9?
  brcs Dcf59Ok7 ; Nein
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok7:
  ; Konvertiere Stunden-Zehner
  inc rDcfErr ; Naechste Fehler-#
  lds ZH,sDcfBits+4 ; Lese Stunden-Zehner
  lsl ZH ; Links schieben
  rol ZH ; und von rechts her einschieben 
  rol ZH
  andi ZH,0x03 ; Isoliere 20er und 10er
  cpi ZH,0x03 ; Mehr als 20?
  brcs Dcf59Ok8 ; Nein, ok
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok8:
  rcall BitCombine ; Kombiniere Zehner und Einer
  sts sDcfRcv+4,ZL ; Speichern Stunden in Puffer
  ; Pruefe Paritaet Stunden
  inc rDcfErr ; Naechste Fehler-#
  lds ZL,sDcfBits+4 ; Lese Stunden-Einer
  lds ZH,sDcfBits+5 ; und Zehner nochmals
  lsr ZH ; Kombiniere in ZL
  ror ZL
  lsr ZL
  rcall CheckParityZL ; Pruefe Paritaet in ZL
  breq Dcf59Ok9 ; Korrekt
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok9:
  ; Konvertiere Tag-Einer
  inc rDcfErr ; Naechste Fehler-#
  lds ZL,sDcfBits+5 ; Lese Tag-Einer
  lsr ZL ; Rechtsschieben
  andi ZL,0x0F ; Isoliere Tages-Einer
  cpi ZL,10 ; Groesser als 9?
  brcs Dcf59Ok10 ; Nein, ok
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok10:
  ; Konvertiere Tages-Zehner
  inc rDcfErr ; Naechste Fehler-#
  lds ZH,sDcfBits+5 ; Lese Tages-Zehner
  swap ZH ; Oberes in unteres Nibble
  lsr ZH ; Eine osition rechts
  andi ZH,0x03 ; Isoliere Bit 0 und 1
  rcall BitCombine ; Kombiniere ZH und ZL in ZL
  sts sDcfRcv+1,ZL ; Speichern Tag in Puffer
  tst ZL ; Tag = 0?
  brne Dcf59Ok11 ; Nein, ok
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok11:
  ; Konvertiere Wochentag
  inc rDcfErr ; Naechste Fehler-#
  lds ZL,sDcfBits+5 ; Lese Wochentag, Teil 1
  lds ZH,sDcfBits+6 ; Lese Wochentag, Teil 2
  lsl ZL ; Kombiniere in ZH
  rol ZH
  andi ZH,0x07 ; Isoliere Wochentag
  brne Dcf59Ok12 ; Nicht Null, ok
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok12:
  dec ZH ; Wochentag erniedrigen (0 bis 6)
  sts sDcfRcv,ZH ; Speichern Wochentag in Puffer
  ; Konvertiere Monat
  inc rDcfErr ; Naechste Fehler-#
  lds rmp,sDcfBits+6 ; Lese Monat
  andi rmp,0x7C ; Isolaiere Monat-Zehner und Einer
  lsr rmp ; Linksschieben
  lsr rmp
  sbrc rmp,4 ; Monat-Zehner = 1?
  subi rmp,-10-16 ; Ja, addiere zehn und loesche Bit 4
  sts sDcfRcv+2,rmp ; Speichern Monat in Puffer
  tst rmp ; Monat = 0?
  brne Dcf59Ok13 ; Nein, ok
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok13:
  ; Konvertiere Jahres-Einer
  inc rDcfErr ; Naechste Fehler-#
  lds ZL,sDcfBits+6 ; Lese Jahres-Einer, Teil 1
  lds ZH,sDcfBits+7 ; Lese Jahres-Einer, Teil 2
  lsl ZL
  rol ZH
  mov ZL,ZH
  andi ZL,0x0F ; Isoliere Jahres-Einer
  cpi ZL,10 ; Groesser als 9?
  brcs Dcf59Ok14 ; Nein, ok
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok14:
  ; Konvertiere Jahres-Zehner
  inc rDcfErr ; Naechste Fehler-#
  lds ZH,sDcfBits+7 ; Lese Jahres-Zehner
  lsr ZH
  lsr ZH
  lsr ZH
  andi ZH,0x0F ; Isoliere Jahres-Zehner
  cpi ZH,10 ; Greoesser als 9?
  brcs Dcf59Ok15 ; Nein, ok
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok15:
  rcall BitCombine ; Kombiniere Jahres-Zehner und -Einer
  sts sDcfRcv+3,ZL ; Speichern Jahr in Puffer
  ; Pruefe Datumsparitaet
  inc rDcfErr ; Naechste Fehler-#
  lds ZL,sDcfBits+5 ; Lese Datum Byte 1
  andi ZL,0xFE ; Loesche letztes Bit
  rcall CheckParityZL ; Beginne Paritaetspruefung
  lds ZL,sDcfBits+6 ; Lese Datum Byte 2
  rcall CheckParityZL1 ; Setze Paritaetspruefung fort
  lds ZL,sDcfBits+7 ; Lese Datum Byte 3
  rcall CheckParityZL1 ; Setze Paritaetspruefung fort
  breq Dcf59Ok16 ; Datumsparitaet ok
  rjmp DcfReport ; Fehlerausgabe
Dcf59Ok16:
  lds rmp,sKeyMode ; Eingabe-Modus aktiv?
  cpi rmp,2 ; Modus 2 aktiv?
  breq Dcf59Ok18 ; Ja, Datum/Zeit nicht aendern
  clr rmp ; Sekunden im Puffer auf Null
  sts sDcfRcv+6,rmp
  ldi XH,High(sDateTime) ; Zeige X auf Datum/Zeit
  ldi XL,Low(sDateTime)
  ldi ZH,High(sDcfRcv) ; Zeige Z auf Emfangspuffer
  ldi ZL,Low(sDcfRcv)
  ldi rmp,7 ; Kopiere sieben Bytes
Dcf59Ok17:
  ld R0,Z+ ; Lese empfangene Datum/Zeit
  st X+,R0 ; Kopiere nach Datum/Zeit
  dec rmp ; Anzahl abwaerts zaehlen
  brne Dcf59Ok17 ; Weiter kopieren
  ; Neustart der Sekunden bei 0
  ; Verzoegerung durch Auswertung: 486 Takte
  ; Entspricht 486/64 Timer 0 Ticks
  ; Entspricht 486/64/256 Timer 0 Overflows
  ; = 0,03 Verzoegerung durch Auswertung
  ; Liegt unterhalb von Korrekturbedarf fuer cSecDiv 
  ldi rSecDiv,cSecDiv ; Neustart Sekunden-Teiler
  ldi XH,High(sDateTime+3) ; Zeige mit X auf Jahr
  ldi XL,Low(sDateTime+3)
  rcall DateTimeLcd ; Erneuern der Datum/Zeit auf der LCD
Dcf59Ok18:
  sbr rFlag,1<<bMinute ; Setze Minutenwechsel-Flagge
  clr rDcfErr ; Error # = 0
  rjmp DcfReport
;
; Kombiniert Einer-Bits in ZL mit Zehner-Bits in ZH
;   Ergebnis in ZL
BitCombine:
  mov rmp,ZH ; Kopiere Zehner
  add ZH,ZH ; Multipliziere mit 2
  add ZH,ZH ; Multipliziere mit 4
  add ZH,rmp ; Addiere Zehner, 5-faches
  add ZH,ZH ; Multipliziere mit 2, 10-faches
  add ZL,ZH ; Addiere Zehner zu Einern
  ret
;
; Pruefe Paritaet von ZL, Z gesetzt wenn gerade
;   Zaehlt die Anzahl Einsen
;   Kehrt mit Z gesetzt zurueck wenn Anzahl gerade
CheckParityZL: ; Beginne Paritaetspruefung
  clr rmp
CheckParityZL1: ; Setze Paritaetspruefung fort
  tst ZL ; Schon keine Einsen mehr?
  breq CheckParityZL2 ; Ja
  lsr ZL ; Schiebe ein Bit ins Carry
  brcc CheckParityZL1 ; Bit ist eine Null
  inc rmp ; Bit ist Eins, zaehle
  rjmp CheckParityZL1 ; Weiter pruefen
CheckParityZL2:
  andi rmp,0x01 ; Letztes Bit des Zaehlers=Null?
  ret
;
; Loesche den DCF77-Empfangspuffer im SRAM
DcfClearBuffer:
  ldi XH,High(sDcfRcv) ; An den Beginn des Puffers
  ldi XL,Low(sDcfRcv)
  ldi ZL,0xFF ; Mit FF fuellen
  ldi rmp,6 ; Sechs Bytes
DcfClearBuffer1:
  st X+,ZL
  dec rmp
  brne DcfClearBuffer1
  ret
;
; DCF77-Signal-Eingang hat Time-out
;   Erkennung in der TC0 Interrupt Service Routine
;   Loese Fehler #5 aus
DcfTimeOut:
  cbr rFlag,1<<bDcfTO ; Loesche die Flagge
  ldi rmp,5 ; Fehler # 5
  mov rDcfErr,rmp
  rjmp DcfReport ; Fehler anzeigen
;
; Anzeige von DCF77-Resultaten/Fehlern
;   Fehler # in rDcfErr
DcfReport:
  ldi ZH,2 ; Kurzbericht-Position auf LCD
  ldi ZL,cDcfPos ; Position der Ausgabe
  call LcdPos ; LCD positionieren
  mov rmp,rDcfErr ; Fehlernummer in rDcfErr nach rmp
  cpi rmp,6 ; Anzahl empfangener Bits falsch?
  brne DcfReport1 ; Nein, andere Fehler-#
  mov R0,rDcfBitCnt ; Kopiere Anzahl empfangener Bits
  call LcdDec2 ; Schreibe in Dezimalformat auf die LCD
  rjmp DcfReport3 ; Pruefe auf zusaetzliche Ausgabe
; Fehlernummer nicht 6
DcfReport1:
  cpi rmp,cDcfErrMax ; Pruefe ob innerhalb zulaessiger Grenzen
  brcs DcfReport2 ; Ja
  ldi rmp,cDcfErrMax-1 ; Ausserhalb, setze auf letzte Fehlernummer
DcfReport2:
  ; Multipliziere Fehlernummer mit 22
  clr R0 ; R0 ist Addierer fuer Carry
  lsl rmp ; Multipliziere mit 2
  lsl rmp ; Multipliziere mit 4
  mov XL,rmp ; Kopiere 4-faches nach X
  clr XH
  lsl XL ; Multipliziere mit 8
  rol XH
  lsl XL ; Multipliziere mit 16
  rol XH
  add XL,rmp ; Addiere 4-faches um 20-faches zu erhalten
  adc XH,R0 ; Addiere Null und Carry
  lsr rmp ; Division des 4-fachen durch 2
  add XL,rmp ; Addiere zum 22-fachen
  adc XH,R0 ; Addiere Null und Carry
  ; Addiere zum Tabellenanfang
  ldi ZH,High(2*DcfReports) ; Z auf Texttabelle
  ldi ZL,Low(2*DcfReports)
  add ZL,XL ; Addiere Tabellenablage
  adc ZH,XH
  ; Zeige Kuerzel an
  lpm rmp,Z+ ; Lese erstes Zeichen aus Tabelle
  call LcdChar ; und zeige an
  lpm rmp,Z+ ; Lese zweites Zeichen aus Tabelle
  rcall LcdChar ; und zeige an
DcfReport3:
; Wenn Langversion angezeigt werden soll
  .if dbgDcfSig == 1
    call DebugDcfSig2 ; Zeige lange Version an
    .endif
; Wenn empfangene Signale angezeigt werden sollen 
  .if dbgDcfRcvd == 1
    call DebugDcfRcvd ; Zeige empfangene Signale an
    rcall DcfClearBuffer ; Setze den DCF-Puffer auf FF
    .endif
  rjmp ActivatePos ; Wenn Eingabe dann Eingabecursor setzen
;
; Alle DCF77-Fehlerberichte
;   Zeichen 1+2: Kuerzel
;   Zeichen 3..22: Lange Version fuer Debug
DcfReports:
; Wenn die englische Version erzeugt werden soll
.if LangEN == 1
  .db "okAll DCF bits ok.  ",0,0 ; 0 DCF kein Fehler
  .db "s1Signal too short! ",0,0 ; 1
  .db "s2Signal in between!",0,0 ; 2
  .db "s3Signal too long!  ",0,0 ; 3
  .db "s4Signal minute!    ",0,0 ; 4
  .db "s5No signal!        ",0,0 ; 5
  .db "nnnn"
LcdErr6:
  .db " bits!          ",0,0 ; 6
  .db "E0Bit 1 not zero!   ",0,0 ; 7
  .db "MOMinute ones!      ",0,0 ; 8
  .db "MTMinute tens!      ",0,0 ; 9
  .db "PMMinute parity!    ",0,0 ; 10
  .db "SOHours ones!       ",0,0 ; 11
  .db "STHours tens!       ",0,0 ; 12
  .db "PHHours parity!     ",0,0 ; 13
  .db "T1Day ones!         ",0,0 ; 14
  .db "T0Day zero!         ",0,0 ; 15
  .db "W0Weekday zero!     ",0,0 ; 16
  .db "M0Month zero!       ",0,0 ; 17
  .db "YOYear ones!        ",0,0 ; 18
  .db "YTYear tens!        ",0,0 ; 19
  .db "PDParity date!      ",0,0 ; 20
  .db "ueUnknown error!    ",0,0 ; 21
  .else
; Wenn die deutsche Version erzeugt werden soll
  .db "okAlle DCF Bits ok. ",0,0 ; 0 DCF kein Fehler
  .db "s1Signal zu kurz!   ",0,0 ; 1
  .db "s2Signal dazwischen!",0,0 ; 2
  .db "s3Signal zu lang!   ",0,0 ; 3
  .db "s4Signal Minute!    ",0,0 ; 4
  .db "s5Kein Signal!      ",0,0 ; 5
  .db "nnnn"
LcdErr6:
  .db " Bits!          ",0,0 ; 6
  .db "E0Bit 1 nicht Null! ",0,0 ; 7
  .db "MEMinuten Einer!    ",0,0 ; 8
  .db "MZMinuten Zehner!   ",0,0 ; 9
  .db "PMMinuten Paritaet! ",0,0 ; 10
  .db "SEStunden Einer!    ",0,0 ; 11
  .db "SZStunden Zehner!   ",0,0 ; 12
  .db "PSStunden Paritaet! ",0,0 ; 13
  .db "TETag Einer!        ",0,0 ; 14
  .db "T0Tag ist Null!     ",0,0 ; 15
  .db "W0Wochentag Null!   ",0,0 ; 16
  .db "M0Monat gleich Null!",0,0 ; 17
  .db "JEJahr Einer!       ",0,0 ; 18
  .db "JZJahr Zehner!      ",0,0 ; 19
  .db "PDParitaet Datum!   ",0,0 ; 20
  .db "ufUnbek. Fehler!    ",0,0 ; 21
  .endif
.equ cDcfErrMax = 22 ; Letzter Fehler + 1
;
; =====================================
;  S E K U N D E N   B E H A N D E L N
; =====================================
;
IncSec:
  cbr rFlag,1<<bSec ; Sekundenflagge loeschen
; Wenn Debug der Musikausgabe gewaehlt Status ausgeben
  .if dbgMusic == 1
    call DebugMusic ; Anzeigen Status
    .endif
; Wenn Debug von TC1 Spielstatus gewaehlt Status ausgeben
  .if dbgPlayStat == 1
    call DebugPlayStat
    .endif
  lds rmp,sKeyMode ; Eingabe von Datum/Zeit?
  cpi rmp,2
  brne IncSec1 ; Eingabemodus nicht aktiv, erhoehen
  ret ; Eingabemodus aktiv, Zeit nicht aendern
IncSec1:
  ldi ZH,High(sDateTime) ; Z auf sDateTime
  ldi ZL,Low(sDateTime)
  ldd rmp,Z+6
  inc rmp ; Erhoehe Sekunden
  std Z+6,rmp ; und speichere
  cpi rmp,60 ; 60 Sekunden erreicht?
  brcc IncMin
  rjmp SecLcd ; Zeige Sekunden an
  ; Erhoehe Minuten
IncMin:
  clr rmp ; Sekunden auf Null
  std Z+6,rmp
  ldd rmp,Z+5 ; Lese Minuten
  inc rmp ; Erhoehe Minuten
  sbr rFlag,1<<bMinute ; Setze Minuten-Flagge
  std Z+5,rmp ; und sichere
  cpi rmp,60 ; 60 Minuten vorueber?
  brcs MinLcd ; Nein, zeige Minuten und Sekunden an
  ; Erhoehe Stunden
  clr rmp ; Neustart Minuten
  std Z+5,rmp ; und speichern
  ldd rmp,Z+4 ; Lese Stunden
  inc rmp ; Erhoehe Stunden
  std Z+4,rmp ; Speichern Stunden
  cpi rmp,24 ; 24 Stunden erreicht?
  brcs HourLcd ; Stunden/Minuten/Sekunden auf LCD
  clr rmp ; Loesche Stunden
  std Z+4,rmp ; Speichern
  ; Erhoehe Wochentag
  ld rmp,Z ; Lese Wochentag
  inc rmp ; Erhoehe
  cpi rmp,7 ; Wochentag = 7?
  brcs IncDay ; Nein, erhoehe Tag
  ldi rmp,0 ; Neustart Wochentag
  ; Erhoehe Tag
IncDay: ; Naechstet day
  st Z,rmp ; Speichere Wochentag
  ldd rmp,Z+1 ; Lese Tag
  inc rmp ; Erhoehe Tag
  std Z+1,rmp ; Speichern Tag
  rcall GetDaysOfMonth
  cp rmp,XH ; Vergleiche mit Tage des Monats
  brcs WeekDayLcd ; Gib Tag/Wochentag/Zeit auf LCD aus
  ; Erhoehe Monat
  ldi rmp,1 ; Tag = 1
  std Z+1,rmp ; Speichern
  ldd rmp,Z+2 ; Lese Monat
  inc rmp ; Erhoehe Monat
  std Z+2,rmp ; Speichern
  cpi rmp,13 ; Monat = 13?
  brcs MonthLcd ; Nein, alles auf LCD ausgeben
  ; Erhoehe Jahr
  ldi rmp,1 ; Monat = 1
  std Z+2,rmp ; Speichern
  ldd rmp,Z+3 ; Lese Jahr
  inc rmp ; Erhoehe Jahr
  std Z+3,rmp ; Speichern
  cpi rmp,100 ; Jahr = 100?
  brcs YearLcd ; Alles auf LCD ausgeben
  ldi rmp,0 ; Jahr = 0
  std Z+3,rmp ; Jahr speichern
; Datum und Zeit auf LCD ausgeben
; Format:  Tu-01.05.18-23:59:59
; Position 01234567890123456789
; X zeigt auf Position im SRAM
DateTimeLcd:
  ldi ZH,High(sDateTime) ; Zeige auf sDateTime
  ldi ZL,Low(sDateTime)
YearLcd:
  ldd rmp,Z+3 ; Lese Jahr
  ldi XL,9 ; Spalte Jahr auf LCD
  rcall NumberLcd ; Zahl dezimal anzeigen
MonthLcd:
  ldd rmp,Z+2 ; Lese Monat
  ldi XL,6 ; Spalte Monat auf LCD
  rcall NumberLcd ; Zahl dezimal anzeigen
WeekDayLcd:
  rcall DisplWeekday ; Wochentag anzeigen
DayLcd:
  ldd rmp,Z+1 ; Lese Tag
  ldi XL,3 ; Spalte Tag auf LCD
  rcall NumberLcd ; Zahl dezimal anzeigen
HourLcd:
  ldd rmp,Z+4 ; Lese Stunden
  ldi XL,12 ; Spalte Stunden auf LCD
  rcall NumberLcd ; Zahl dezimal ausgeben
MinLcd:
  ldd rmp,Z+5 ; Lese Minuten
  ldi XL,15 ; Spalte Minuten auf LCD
  rcall NumberLcd ; Zahl dezimal ausgeben
SecLcd:
  ldd rmp,Z+6 ; Lese Sekunden
  ldi XL,18 ; Salte Sekunden auf LCD
  rcall NumberLcd ; Zahl dezimal ausgeben
  rjmp ActivatePos ; Eingabeposition aktivieren
;
; Anzeige der Zahl in rmp in
;   Dezimalformat auf der LCD
;   in Zeile 1
NumberLcd:
  push rmp ; Sichern rmp
  lds rmp,sKeyMode ; Lese Eingabemodus
  cpi rmp,2 ; Im Normal- oder Weckbetrieb?
  brcs NumberLcd0 ; Ja, Zahl ausgeben
  pop rmp ; Nein, Zahl nicht ausgeben
  ret
NumberLcd0: ; Schreibe auf LCD
  push ZH ; Sichern Z
  push ZL
  ldi ZH,0 ; Zeile 1
  mov ZL,XL ; Spalte aus XL
  rcall LcdPos
  pop ZL ; Wiederhesrstellen von Z
  pop ZH
  ldi rmp,0x2F ; ASCII-Zehner-Zaehler
  mov R0,rmp ; in R0
  pop rmp ; Zahl wieder herstellen
NumberLcd1:
  inc R0 ; ASCII-Zaehler erhoehen
  subi rmp,10 ; Zehn abziehen
  brcc NumberLcd1 ; Kein Unterlauf, weiter
  push rmp ; Zweite Ziffer sichern
  rcall LcdR0 ; Ziffer in R0 auf LCD ausgeben
  pop rmp ; Zweite Ziffer wieder herstellen
  subi rmp,-58 ; 10 und 48 addieren
  rjmp LcdChar ; Zweite Ziffer ausgeben
;
; Tage im aktuellen Monat
;   Gibt Anzahl Tage im aktuellen Monat +1 zurueck
; 1:31, 2:28/29, 3:31, 4:30, 5:31, 6:30
; 7:30, 8:31, 9:30, 10:31, 11:30, 12:31
GetDaysOfMonth:
  ldd XL,Z+2 ; Lese Monat
  cpi XL,2 ; Februar?
  brne GetDaysOfMonth2 ; Nein
  ldd XL,Z+3 ; Februar, lade Jahr
  andi XL,0x03 ; Schaltjahr?
  brne GetDaysOfMonth1 ; Nein
  ldi XH,30 ; Schaltjahr, 29 Tage
  ret
GetDaysOfMonth1:
  ldi XH,29 ; Kein Schaltjahr, 28 Tage
  ret
GetDaysOfMonth2:
  cpi XL,7 ; Monat kleiner als Juli?
  brcs GetDaysOfMonth3 ; Ja
  dec XL ; 30/31-Folge umkehren
GetDaysOfMonth3:
  lsr XL ; Monat ungerade?
  brcc GetDaysOfMonth4 ; Nein
  ldi XH,32 ; 31 Tage
  ret
GetDaysOfMonth4:
  ldi XH,31 ; 30 Tage
  ret
;
; Zeige den Wochentag 0 bis 6 auf LCD an
;   Text ist in der WeekDayTable:
DisplWeekday:
  lds rmp,sKeyMode ; Eingabemodus
  cpi rmp,2 ; Im Datum-/Zeit-Eingabemodus?
  brne DisplWeekday0
  ret
DisplWeekday0:
  push ZH ; Sichern Z
  push ZL
  ld rmp,Z ; Lese Wochentag
  push rmp
  ldi ZH,0 ; Zeile 1
  ldi ZL,0 ; Spalte 1
  rcall LcdPos
  ldi ZH,High(2*WeekdayTable) ; Zeige auf Wochentagstabelle
  ldi ZL,Low(2*WeekdayTable)
  pop rmp ; Wochentag wieder herstellen
  lsl rmp ; Mal zwei
  add ZL,rmp ; Zu Tabelle addieren
  ldi rmp,0 ; MSB Null
  adc ZH,rmp ; Null mit Carry addieren
  lpm rmp,Z+ ; Erstes Zeichen Wochentag
  rcall LcdChar ; auf LCD
  lpm rmp,Z ; Zweites Zeichen Wochentag
  pop ZL ; Z wieder herstellen
  pop ZH
  rjmp LcdChar ; Und auf LCD
;
; Tabelle der Wochentagsabkuerzungen
WeekdayTable:
.if LangEN == 1
; Wenn Englisch verwende diese
  .db "MoTuWdThFrSaSu"
  .else
; Wenn Deutsch verwende diese
  .db "MoDiMiDoFrSaSo"
  .endif
;
; Initiiere Datum und Zeit
InitDT:
  ldi XH,High(sDateTime)
  ldi XL,Low(sDateTime)
  ldi ZH,High(2*InitDTTable)
  ldi ZL,Low(2*InitDTTable)
InitDT1:
  lpm rmp,Z+
  st X+,rmp
  cpi ZH,High(2*InitDTTableEnd-1)
  brne InitDT1
  cpi ZL,Low(2*InitDTTableEnd-1)
  brne InitDT1
  rcall DateTimeLcd
  rjmp DisplAlarm
;
; Starttabelle fuer Datum und Zeit
; Datum/Zeit = Mo, 30.04.18-23:59:59
; Weckzeit   =              00:03
InitDTtable:
.db 0,30,4,18,23,59,59,0,3,0
InitDTtableEnd:
;
; =====================================
;  W E C K Z E I T   E R R E I C H T ?
; =====================================
;
; Ueberpruefe jede Minute ob Wecken erreicht
CheckMinute:
  cbr rFlag,1<<bMinute ; Loesche Flagge
  lds rmp,sKeyMode ; Weckmodus lesen
  cpi rmp,1 ; Wecken eingeschaltet?
  brne CheckMinuteRet ; Wecken nicht aktiv
  lds rmp,sAlarmRepeat ; Weckwiederholung?
  tst rmp ; Weckwiederholungen = Null?
  brne CheckMinute1 ; Nein
  lds rmp,sDateTime+8 ; Lese Minuten Wecken
  lds R0,sDateTime+5 ; Lese Minuten Zeit
  cp rmp,R0 ; Vergleiche
  brne CheckMinuteRet ; Nicht gleich
  lds rmp,sDateTime+7 ; Lese Wecken Stunden
  lds R0,sDateTime+4 ; Lese Stunden Zeit
  brne CheckMinuteRet ; Nicht gleich
  ; Beginne Wecken
  ldi rmp,cAlarmRepet ; Wiederholungen starten
  sts sAlarmRepeat,rmp
  rcall IncAlarm ; Weckzeit um eine Minute erhoehen
  mov rmp,rMelody ; Lese Zufallsmelodie
  rjmp MusicConvert ; Spiele diese Melodie
CheckMinute1: ; Wecken ist noch aktiv
  ; Neustart der Weckmelodie
  rcall MusicPlay ; Neustart Musikausgabe
  lds rmp,sAlarmRepeat ; Lese Weckwiederholungen
  dec rmp ; Erniedrigen
  sts sAlarmRepeat,rmp ; und speichern
  breq CheckMinute2 ; Ist Null, Ausgabe aktualisieren
  rjmp IncAlarm ; Weckzeit um eine Minute erhoehen
CheckMinute2:
  ; Weckwiederholung ausschalten
  rjmp SetAlarmOff
CheckMinuteRet:
  ret
;
; Die Weckzeit um eine Minute erhoehen
IncAlarm:
  ldi ZH,High(sDateTime) ; Zeige auf sDateTime
  ldi ZL,Low(sDateTime)
  ldd rmp,Z+8 ; Lese Weckminute
  inc rmp ; Erhoehe
  std Z+8,rmp ; Speichere
  cpi rmp,60 ; Naechste Stunde?
  brcs IncAlarmRet ; Nein
  clr rmp ; Loesche Minute
  std Z+8,rmp
  ldd rmp,Z+7 ; Lese Stunde
  inc rmp ; Erhoehe Stunde
  std Z+7,rmp ; Speichere
  cpi rmp,24 ; Naechster Tag?
  brcs IncAlarmRet ; Nein
  clr rmp ; Loesche Stunde
  std Z+7,rmp
IncAlarmRet:
  rjmp DisplAlarm 
;
; ===================================
;   T A S T E N   A U S W E R T E N
; ===================================
;
; Eine Taste wurde gedrueckt
;
KeyPress:
  cbr rFlag,1<<bKey ; Loesche Flagge
  lds rmp,sKeyMode ; Lese Eingabemodus
  tst rmp ; Null?
  brne KeyPress1 ; Nein
  sbis pKeyI,bKeyRI ; Rote Taste?
  rjmp KeyPressRed ; Werte rote Taste aus
  sbis pKeyI,bKeyYI ; Gelbe Taste?
  rjmp KeyPressYellow ; Werte gelbe Taste aus
  sbis pKeyI,bKeyWI ; Weisse Taste?
  rjmp KeyPressWhite ; Werte weisse Taste aus
  ret
KeyPress1: ; Modus nicht Null
  cpi rmp,2 ; Modus = 2
  brcs KeyPressMode1 ; Nein, Null oder Eins
  breq KeyPressMode2 ; Ja
  rjmp KeyPressMode3 ; Modus = 3
  ret
;
KeyPressMode1:
  sbis pKeyI,bKeyRI ; Rote Taste in Mode 0 oder 1?
  rjmp KeyPress1Red ; Ja, werte rote Taste aus
  sbis pKeyI,bKeyYI ; Gelbe Taste in Mode 0 oder 1?
  rjmp KeyPress1Yellow ; Ja, werte gelbe Taste aus
  sbis pKeyI,bKeyWI ; Weisse Taste in Mode 0 oder 1?
  rjmp KeyPress1White ; Ja, werte weisse Taste aus
  ret
;
KeyPressMode2:
  sbis pKeyI,bKeyRI ; Rote Taste in Mode 2?
  rjmp KeyPress2Red ; Ja, werte rote Taste aus
  sbis pKeyI,bKeyYI ; Gelbe Taste in Mode 2?
  rjmp KeyPress2Yellow ; Ja, werte gelbe Taste aus
  sbis pKeyI,bKeyWI ; Weisse Taste in Mode 2?
  rjmp KeyPress2White ; Ja, werte weisse Taste aus
  ret
;
KeyPressMode3:
  sbis pKeyI,bKeyRI ; Rote Taste in Mode 3?
  rjmp KeyPress3Red ; Ja, werte rote Taste aus
  sbis pKeyI,bKeyYI ; Gelbe Taste in Mode 3?
  rjmp KeyPress3Yellow ; Ja, werte gelbe Taste aus
  sbis pKeyI,bKeyWI ; Weisse Taste in Mode 3?
  rjmp KeyPress3White ; Ja, werte weisse Taste aus
  ret
;
; Neue Tasten auswerten
;
; Rote Taste gedrueckt
KeyPressRed:
  lds rmp,sKeyMode ; Lese Eingabemodus
  cpi rmp,1 ; Modus = 1?
  brcs SetAlarmOn ; Nein, Modus = 0
  breq SetAlarmOff ; Ja, Modus = 1
  rjmp DisplAlarm ; Modus 2 oder 3
SetAlarmOff:
  ldi rmp,(1<<COM1A1) ; Lautsprecherausgang Clear
  out TCCR1A,rmp
  ldi rmp,(1<<TOIE0)|(1<<TOIE2) ; TC1 Interrupt aus
  out TIMSK,rmp
  ldi rmp,High(clock/2000-1) ; CTC TC1 auf 1000 Hz
  out OCR1AH,rmp
  ldi rmp,Low(clock/2000-1)
  out OCR1AL,rmp
  ldi rmp,0 ; Setze Modus = 0
  sts sKeyMode,rmp ; Wecken ausschalten
  sts sAlarmRepeat,rmp ; Weckwiederholungen aus
  lds rmp,sAlarm ; Neustart Weckzeit
  sts sDateTime+7,rmp
  lds rmp,sAlarm+1
  sts sDateTime+8,rmp
  rjmp DisplAlarm
SetAlarmOn:
  ldi rmp,1 ; Setze Modus = 1
  sts sKeyMode,rmp
  ldi rmp,0 ; Loesche Weckwiederholung
  sts sAlarmRepeat,rmp
  rjmp DisplAlarm
;
; Die gelbe Taste wurde gedrueckt
;   Beginne mit Datum-/Zeit-Eingabe
KeyPressYellow:
  ldi rmp,2 ; Mode 2
  sts sKeyMode,rmp
  clr rmp ; Eingabeposition auf Wochentag
  sts sKeyPos,rmp
  ldi ZH,High(sDateTime) ; Z zeigt auf Wochentag 
  ldi ZL,Low(sDateTime)
  ldi XH,High(sInputData) ; X zeigt auf Eingabepuffer
  ldi XL,Low(sInputData)
  ldi rmp,7 ; Sieben Bytes kopieren
KeyPressYellow1:
  ld R0,Z+ ; Lese Datum/Zeit
  st X+,R0 ; Schreibe in Eingabepuffer
  dec rmp ; Zaehle abwaerts
  brne KeyPressYellow1 ; Weiter lesen/schreiben
  rcall LcdBlink ; Cursor blinken an
  rjmp ActivatePos
;
; Die weisse Taste ist gedrueckt
;   Beginne mit Weckzeit-Eingabe
KeyPressWhite:
  ldi rmp,3 ; Modus = 3
  sts sKeyMode,rmp
  ldi rmp,7 ; Position im Eingabepuffer auf Stunden
  sts sKeyPos,rmp
  lds rmp,sDateTime+7 ; Weckzeit in Eingabepuffer kopieren 
  sts sInputData+7,rmp
  lds rmp,sDateTime+8
  sts sInputData+8,rmp
  rcall DisplAlarm ; Weckzeit anzeigen
  rcall DisplMenue ; Menue anzeigen
  rcall ActivatePos ; Aktivieren der Eingabeposition
  ldi ZH,1 ; Cursor auf Weckzeitstunden
  ldi ZL,12
  rcall LcdPos
  rjmp LcdBlink ; Blinken an
;
; Tasten in Modus 1
;
; Rote Taste in Modus 1
KeyPress1Red:
  ; Wecken ausschalten
  rjmp SetAlarmOff
;
; Den Weck-Status und die Weckzeit anzeigen
DisplAlarm:
  ldi ZH,1 ; Position Weckstatus einstellen
  ldi ZL,cAlarmPos
  rcall LcdPos
  ldi ZH,High(2*AlarmText0) ; Text fuer Wecken Aus
  ldi ZL,Low(2*AlarmText0)
  lds rmp,sKeyMode ; Ist Wecken aktive?
  cpi rmp,1
  brcs DisplAlarm1 ; Nein, gib Text aus
  ldi ZH,High(2*AlarmText1) ; Text fuer Wecken an
  ldi ZL,Low(2*AlarmText1)
  breq DisplAlarm1 ; Ja, gib Text aus
  ldi ZH,High(2*AlarmText2) ; Text fuer Wecken suspendiert
  ldi ZL,Low(2*AlarmText2)
DisplAlarm1:
  rcall LcdDisplZ ; Gib Text ab Z aus
  ldi rmp,' ' ; Leerzeichen ausgeben
  rcall LcdChar
  lds R0,sDateTime+7 ; Weckzeit Stunden
  rcall LcdDec2 ; dezimal ausgeben
  ldi rmp,':' ; Doppelpunkt ausgeben
  rcall LcdChar
  lds R0,sDateTime+8 ; Weckzeit Minuten
  rcall LcdDec2 ; dezimal ausgeben
  ldi rmp,' ' ; Leerzeichen ausgeben
  rcall LcdChar
  lds rmp,sAlarmRepeat ; Weckwiederholungen
  tst rmp ; auf Null?
  breq DisplAlarm3 ; Ja, gib zwei Leerzeichen aus
  dec rmp ; Um Eins erniedrigen
  cpi rmp,100 ; Mehr als 100 mal?
  brcs DisplAlarm2
  ldi rmp,99 ; Lade Maximum
DisplAlarm2:
  mov R0,rmp ; Kopiere nach R0
  rjmp LcdDec2 ; und dezimal ausgeben
DisplAlarm3:
  ldi rmp,' ' ; Gib zwei Leerzeichen aus
  rcall LcdChar
  ldi rmp,' '
  rjmp LcdChar
;
; Wecken aktiv/inaktiv/susendiert Text
.if LangEN == 1
; Wenn englische Version:
  AlarmText0:
  .db "off",0
  AlarmText1:
  .db " on",0
  AlarmText2:
  .db " ..",0
  .else
; Wenn deutsche Version:
  AlarmText0:
  .db "aus",0
  AlarmText1:
  .db " an",0
  AlarmText2:
  .db " ..",0
  .endif
;
; Gelbe Taste in Modus 1
KeyPress1Yellow:
  ; Erhoehen der Weckzeit um 5 Minuten
  lds rmp,sDateTime+8 ; Lese Minuten
  subi rmp,-5 ; Addiere fuenf Minuten
SetAlarmTime:
  sts sDateTime+8,rmp ; Speichere
  cpi rmp,60 ; Minuten kleiner als 60?
  brcs SetAlarmTimeFinal
  subi rmp,60 ; Subtrahiere 60
  sts sDateTime+8,rmp
  lds rmp,sDateTime+7 ; Stunden erhoehen
  inc rmp
  cpi rmp,24 ; Tagesende?
  brcs SetAlarmTimeFinal
  subi rmp,24 ; Subrahiere 24
  sts sDateTime+8,rmp
SetAlarmTimeFinal:
  rjmp DisplAlarm ; Weckzeit anzeigen
;
; Weisse Taste in Modus 1
KeyPress1White:
  ; Erhoehe Weckzeit um 10 Minuten
  lds rmp,sDateTime+8 ; Lese Minuten
  subi rmp,-10 ; Addiere zehn Minuten
  rjmp SetAlarmTime ; Setze Weckzeit
;
; Tasten in Modus 2
;
; Rote Taste in Modus 2
KeyPress2Red:
  ; Nach links
  lds rmp,sKeyPos ; Eingabeposition lesen
  tst rmp ; Bei Null?
  breq KeyPress2Red1 ; Ja, beende Eingabe
  dec rmp ; Eine Position links
  sts sKeyPos,rmp ; und speichern
  rjmp ActivatePos ; und aktivieren
KeyPress2Red1:
  ; Eingabe abbrechen und in Normalmodus
  lds rmp,sAlarm ; Original-Weckzeit wieder herstellen
  sts sDateTime+7,rmp
  lds rmp,sAlarm+1
  sts sDateTime+8,rmp
  rcall SetAlarmOff ; Wecken ausschalten
  rcall DisplMenue ; Menue anzeigen
  rjmp LcdNoBlink ; Nicht mehr blinken
;
; Gelbe Taste in Modus 2
KeyPress2Yellow:
  ; Originalwert wieder herstellen
  lds rmp,sKeyPos ; Lese Eingabeposition
  ldi XH,High(sDateTime) ; Zeige X auf sDateTime:
  ldi XL,Low(sDateTime)
  ldi ZH,High(sInputData) ; Zeige Z auf Eingabedaten
  ldi ZL,Low(sInputData)
  clr R0 ; Null-Addierer
  add XL,rmp ; Zu X dazu addieren
  adc XH,R0 ; Plus Carry
  add ZL,rmp ; Zu Z dazu addieren
  adc ZH,R0 ; Plus Carry
  ld rmp,X ; aus X laden
  st Z,rmp ; in Z schreiben
  rcall DisplInp ; Anzeige aktuelle Eingabe
  rjmp KeyPress2White ; Naechste Position
;
; Zeige Eingabe an aktueller osition an
DisplInp:
  rcall ActivatePos ; Aktive Position
  lds rmp,sKeyPos ; Lese Eingabeposition
  tst rmp ; Wochentag?
  brne DisplInp1
  ; Zeige Wochentag an
  ldi ZH,High(2*WeekdayTable)
  ldi ZL,Low(2*WeekdayTable)
  lds rmp,sInputData ; Lese Wochentag
  lsl rmp ; Multipliziere mit 2
  add ZL,rmp ; Addiere Versatz
  ldi rmp,0
  adc ZH,rmp ; und Carry
  lpm rmp,Z+ ; Lese erstes Zeichen aus Tabelle
  rcall LcdChar
  lpm rmp,Z ; Lese zweites Zeichen aus Tabelle
  rjmp LcdChar
DisplInp1:
  ldi ZH,High(sInputData) ; Zeige auf Eingaebedaten
  ldi ZL,Low(sInputData)
  add ZL,rmp
  ldi rmp,0
  adc ZH,rmp
  ld R0,Z ; Lese Byte
  rjmp LcdDec2
;
; Weisse Taste in Modus 2
KeyPress2White:
  ; Naechste Position
  lds rmp,sKeyPos ; Lese Position
  cpi rmp,6 ; Letzte Position?
  brcc KeyPress2White1 ; Ja
  inc rmp ; Naechste Position
  sts sKeyPos,rmp ; Sichere neue Position
  rjmp ActivatePos ; Position aktivieren
KeyPress2White1:
  ldi XH,High(sInputData) ; Zeige auf Eingabedaten
  ldi XL,Low(sInputData)
  ldi ZH,High(sDateTime) ; Zeige auf Datum/Uhrzeit
  ldi ZL,Low(sDateTime)
  ldi rmp,7 ; Sechs Bytes zu uebertragen
KeyPress2White2:
  ld R0,X+ ; Byte lesen
  st Z+,R0 ; Byte schreiben
  dec rmp ; Zaehler abwaerts
  brne KeyPress2White2 ; Weiter kopieren
  ldi rmp,0 ; Weckmodus ausschalten
  sts sKeyMode,rmp
  rcall LcdNoBlink
  rcall DateTimeLcd ; Anzeige Datum/Zeit
  rjmp DisplMenue ; Anzeige Menue
;
; Tasten in Modus 3
;
; Rote Taste in Modus 3
KeyPress3Red:
  ; Eine Position links
  lds rmp,sKeyPos ; Lese Position
  cpi rmp,7 ; Linkeste Position?
  breq KeyPress3Red1 ; Ja, beende Eingabe
  dec rmp ; Eine Position links
  sts sKeyPos,rmp ; Speichern
  rjmp ActivatePos ; Und aktivieren
KeyPress3Red1:
  lds rmp,sAlarm ; Beende, restauriere Original-Weckzeit
  sts sDateTime+7,rmp
  lds rmp,sAlarm+1
  sts sDateTime+8,rmp
  rcall SetAlarmOff ; Alarm aus
  rcall DisplMenue ; Menue anzeigen
  rjmp LcdNoBlink ; Nicht mehr blinken
;
; Gelbe Taste in Modus 3
KeyPress3Yellow:
  ; Alten Wert wieder herstellen
  lds rmp,sKeyPos ; Lese Position
  cpi rmp,8 ; Letzte Stelle?
  brcc KeyPress3Yellow1 ; Nein, erste Stelle
  lds rmp,sDateTime+8 ; Alten MinutenwWert wieder herstellen
  sts sInputData+8,rmp
  sts sAlarm+1,rmp
  rcall DisplAlarmInp
  rjmp KeyPress3White
KeyPress3Yellow1:
  lds rmp,sDateTime+7 ; Alten Stundenwert wieder herstellen 
  sts sInputData+7,rmp
  sts sAlarm,rmp
  lds rmp,sKeyPos
  inc rmp ; Naechste Position
  sts sKeyPos,rmp
  rjmp DisplAlarmInp
;
; Display alarm input
DisplAlarmInp:
  ldi ZH,1 ; Alarm position hour
  ldi ZL,12
  rcall LcdPos
  lds R0,sInputData+8
  rcall LcdDec2
  ldi ZH,1 ; Alarm position minute
  ldi ZL,15
  rcall LcdPos
  lds R0,sInputData+9
  rcall LcdDec2
  rjmp ActivatePos
;
; Weisse Taste in Modus 3
KeyPress3White:
  ; Naechste Position
  lds rmp,sKeyPos ; Lese Position
  cpi rmp,8 ; Letzte Position?
  breq KeyPress3White1 ; Ja, beende Eingabe
  inc rmp ; Nein, erhoehe
  sts sKeyPos,rmp ; und schreibe
  rjmp ActivatePos ; Aktivieren
KeyPress3White1:
  lds rmp,sInputData+7 ; Lese Weckzeit Stunden
  sts sAlarm,rmp ; Setze neue Weckzeit
  sts sDateTime+7,rmp ; und in Anzeige
  lds rmp,sInputData+8 ; Lese Weckzeit Minuten
  sts sAlarm+1,rmp ; Setze neue Weckzeit
  sts sDateTime+8,rmp ; und in Anzeige
  rcall SetAlarmOn ; Wecken einschalten
  rcall DisplMenue ; Menue anzeigen
  rjmp LcdNoBlink ; Blinken ausschalten
;
; Menue fuer aktuellen Modus ausgeben
;
DisplMenue:
.if ! fDbg ; Display in non-debug mode only
  ldi ZH,3 ; Menue in line 3
  ldi ZL,0
  rcall LcdPos
  ldi ZH,High(2*Menuetext)
  ldi ZL,Low(2*Menuetext)
  clr R1
  lds R0,sKeyMode
  ldi rmp,22
DisplMenue1:
  tst R0
  breq DisplMenue2
  add ZL,rmp
  adc ZH,R1
  dec R0
  rjmp DisplMenue1
DisplMenue2:
  lds rmp,sKeyMode ; Lese Modus
  cpi rmp,2
  brcs DisplMenue3
  breq Displ2Menue
  lds rmp,sKeyPos ; Mode = 3
  cpi rmp,7
  ldi ZH,High(2*MenueFirst)
  ldi ZL,Low(2*MenueFirst)
  breq DisplMenue3
  ldi ZH,High(2*MenueLast)
  ldi ZL,Low(2*MenueLast)
  rjmp DisplMenue3
Displ2Menue:
  lds rmp,sKeyPos ; Mode = 2
  tst rmp
  brne Displ2Menue1
  ldi ZH,High(2*MenueFirst)
  ldi ZL,Low(2*MenueFirst)
  rjmp DisplMenue3
Displ2Menue1:
  cpi rmp,6
  brne DisplMenue3
  ldi ZH,High(2*MenueLast)
  ldi ZL,Low(2*MenueLast)
DisplMenue3:
  rjmp LcdDisplZ
  .else
  ret
  .endif
;
; Aktiviere die aktuelle Eingabeposition
;   auf der LCD zum Blinken
ActivatePos:
  lds rmp,sKeyPos
  cpi rmp,7
  brcs ActivatePos1
  ldi ZH,1
  ldi ZL,12
  breq ActivatePosSet
  ldi ZL,15
  rjmp ActivatePosSet
ActivatePos1:
  ldi ZH,0
  cpi rmp,1
  ldi ZL,0
  brcs ActivatePosSet
  ldi ZL,3
  breq ActivatePosSet
  ldi ZL,6
  cpi rmp,3
  brcs ActivatePosSet
  ldi ZL,9
  breq ActivatePosSet
  ldi ZL,12
  cpi rmp,5
  brcs ActivatePosSet
  ldi ZL,15
  breq ActivatePosSet
  ldi ZL,18
ActivatePosSet:
  rjmp LcdPos
;
Menuetext:
.if LangEN == 1
  .db "Alarm Time Alarmtime",0,0
  .db "NoAlarm +5min +10min",0,0
  .db "Prev   Default  Next",0,0
  .db "Prev   Default  Next",0,0
MenueFirst:
  .db "Skip   Default  Next",0,0
MenueLast:
  .db "Prev   Default   Set",0,0
  .else
  .db "Wecken Zeit Weckzeit",0,0
  .db "Weckaus +5min +10min",0,0
  .db "Links   Alt   Rechts",0,0
  .db "Links   Alt   Rechts",0,0
MenueFirst:
  .db "Abbr.Default Naechst",0,0
MenueLast:
  .db "Links   Alt   Setzen",0,0
  .endif
;
; ===================================================
;  V E R A R B E I T E   A D C   E R G E B N I S S E
; ===================================================
;
; ADC hat 64 Werte im aktuellen Kanal aufsummiert
;   MSB der summierten Ergebnisse ist in rAdc
;
AdcCmplt:
  cbr rFlag,1<<bAdc ; Loesche Flagge
  in rmp,ADMUX ; Read channel
  andi rmp,0x1F ; Isolate channel bits
  push rmp ; Retten Kanal
  inc rmp ; Naechster Kanal
  cpi rmp,0x03 ; Kanal = 3?
  brcs AdcCmplt1 ; Nein, kleiner
  clr rmp
AdcCmplt1:
  ori rmp,(1<<REFS0) ; Setze REFSO-Bit
  out ADMUX,rmp
  ldi rAdcC,64 ; 64 Messungen
  ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(ADPS0)
  out ADCSRA,rmp ; Starte naechste Wandlung
  pop rmp ; Vorherigen Kanal
  cpi rmp,1 ; Kanal = 1?
  brcs AdcSet0 ; Kanal = 0
  brne AdcSet2 ; Kanal = 2
  rjmp AdcSet1 ; Kanal = 1
;
; Kanal 2 komplett
;   Zeige Signalstaerke an
AdcSet2:
  ; Abwaertszaehlen Signalstaerke-Zaehler
  dec rSigStr
  breq AdcSet2A ; Null erreicht
AdcSet2Ret:
  ret ; Fertig
AdcSet2A:
  ldi rmp,cSigStr ; Neustart Zaehler
  mov rSigStr,rmp
  lds rmp,sKeyMode ; Betriebsmodus?
  cpi rmp,2 ; Eingabe Datum/Zeit/Weckzei?
  brcc AdcSet2Ret ; Ja, keine Anzeige
  ; Kanal 2
  ldi ZH,2 ; Ausgabeposition Zeile 3 fuer +
  ldi ZL,7
  rcall LcdPos
  mov rmp,rAdc ; Lese MSB ADC-Summe 
  cpi rmp,30 ; Groesser oder gleich 30?
  ldi rmp,' ' ; Schreibe Leerzeichen
  brcs SignalNoPlus ; Kleiner 30
  ldi rmp,'+' ; Schreibe +
SignalNoPlus:
  rcall LcdChar
  ldi ZH,2 ; Ausgabeposition S-Wert
  ldi ZL,6
  rcall LcdPos
  ldi ZH,High(2*SignalTable) ; Tabelle mit Signalstaerken
  ldi ZL,Low(2*SignalTable)
  clr R0 ; R0 ist Zaehler
SignalStrength:
  inc R0 ; Zaehler erhoehen
  lpm rmp,Z+ ; Lese Tabellenwert
  cp rAdc,rmp ; Vergleiche mit Tabellenwert
  brcc SignalStrength ; rAdc ist groesser oder gleich Tabellenwert
  ldi rmp,'0'-1 ; Addiere ASCII-Null - 1
  add rmp,R0
  rjmp LcdChar ; Zahl ausgeben
;
; Tabelle mit Signalstaerken
SignalTable:
.db 3,6,9,12,15,18,21,24,27,255
;
; Channel 0: Potentiometer-Stellung
;   Wenn im Input-Modus: Erneuern Byte an der aktuellen Position
AdcSet0:
  ; Pruefe Eingabemodus aktiv
  lds rmp,sKeyMode
  cpi rmp,2
  brcc AdcSet0_1 ; Im Eingabemodus
AdcSet0Ret:
  ret ; Fertig
AdcSet0_1:
  lds rmp,sKeyCnt ; Tastenzaehler lesen
  tst rmp ; Tastenzaehler Null?
  breq AdcSet0a ; Bei Null, weitermachen
  dec rmp ; Tastenzaehler abwaerts
  sts sKeyCnt,rmp ; und speichern
  rjmp AdcSet0Ret ; Keine weitere Aktion
AdcSet0a:
  ldi rmp,5 ; Neustart mit 5 Zyklen Verzoegerung
  sts sKeyCnt,rmp
  rcall GetMinMax ; Hole Minimum (R0) und Maximum (R1)
  mov rmp,R1 ; Maximum - Minimum
  sub rmp,R0 ; Subtrahiere Minimum
  mov XL,rAdc ; Multipliziere rAdc mit Differenz
  clr XH
  clr ZL ; Z ist Resultat
  mov ZH,R0 ; Addiere Minimum zum MSB
AdcSet0b:
  tst rmp ; Null erreicht?
  breq AdcSet0d ; Ja, Multiplikation fertig
  lsr rmp ; Schiebe niedrigstes Bit in Carry
  brcc AdcSet0c
  add ZL,XL ; Addiere den Multiplikanten
  adc ZH,XH
AdcSet0c:
  lsl XL ; Schiebe Multiplikator links
  rol XH
  rjmp AdcSet0b ; Weiter multiplizieren
AdcSet0d:
  ldi XH,High(sInputData) ; Zeige auf Eingabedaten
  ldi XL,Low(sInputData)
  lds rmp,sKeyPos
  add XL,rmp ; Addiere Position
  ldi rmp,0
  adc XH,rmp ; und Carry
  st X,ZH ; Schreibe Multiplikationsergebnis
  rcall DisplInp
  rjmp ActivatePos ; Aktiviere erneut
;
; Hole Minimum- und Maximum-Wert fuer aktuelle Position
;    Minimum in R0, Maximum +1 in R1
GetMinMax:
  ldi ZH,High(2*MinMaxTab) ; Z zeigt auf Tabelle
  ldi ZL,Low(2*MinMaxTab)
  clr R0 ; R0 ist Carry-Addierer
  lds rmp,sKeyPos ; Aktuelle Position
  lsl rmp ; Multiplikation mit 2
  add ZL,rmp
  adc ZH,R0 ; Carry addieren
  lpm R0,Z+ ; Lese Minimum
  lpm R1,Z ; Lese Maximum
  ret
;
; Minima/Maxima-Tabelle nach Position
MinMaxTab:
;   wt  tag  mon  jahr  std  min  sec  alstd almin
.db 0,7,1,32,1,13,0,100,0,24,0,60,0,60,0,24,0,60
;
; ADC Kanal 1 komplett
;   Zeige Lichtsensor in hex an
AdcSet1:
  ; Pruefe ob Verzoegerungsperiode vorbei
  dec rBackCnt ; Zaehler abwaerts
  brne AdcSet1Ret ; Nicht Null
  ldi rmp,cBackPeriods ; Starte Zaehler neu
  mov rBackCnt,rmp ; Neustart Zaehler
  ldi ZH,2 ; Zeige Sensormessung auf LCD an
  ldi ZL,18
  rcall LcdPos
  mov rmp,rAdc
  rcall LcdHex2
AdcSet1a:
  tst rAdc ; Sensor bei Null?
  brne Light0 ; Nein
  inc rAdc ; Erhoehe auf Eins
Light0:
  neg rAdc ; Umkehrwert
  ; Multiplikation des Sensor-Ergebnisses
  ldi rmp,cBackMin ; Addiere Minimum zu MSB
  mov R1,rmp ; Ergebnis in R1:R0
  clr R0 ; LSB Ergebnis = 0
  ldi ZL,cBackMult ; Multiplikator in ZL
  clr ZH ; Null in  ZH
Light1: ; Multipliziere mit Konstante
  lsr rAdc ; Bit in Carry schieben
  brcc Light2 ; Null = nicht addieren
  add R0,ZL ; Addiere
  adc R1,ZH
  brcc Light2 ; Kein Uebertrag
  clr R1 ; Uebertrag, setze MSB auf FF
  dec R1
Light2:
  lsl ZL ; Multipliziere mit 2 
  rol ZH
  tst rAdc ; Fertig?
  brne Light1 ; Nein, weiter multilizieren
  out OCR2,R1 ; Hintergrundbeleuchtung PWM setzen
AdcSet1Ret:
  ret
;
; Musik-Code-Routinen hinzu laden
.include "musik_code.inc"
;
; LCD-Routinen hinzu laden
.include "lcd_8_routinen.inc"
;
.if fDbg
; Wenn Debuggen aktiv: Debug-Code hinzu laden
  .include "debug_code_de.inc"
   .endif
;
; Lcd texte
;
LcdTxtInit1:
.if LangEN == 1
; Englische Version
  .db " DCF77_m16 V4 clock ",0,0
  .else
; Deutsche Version
  .db "DCF77_m16 V4 Atomuhr",0,0
  .endif
.db "    ",7,"2018 DG4FAC    ",0,0
.db "                    ",0,0
.db "                    ",0,0
;
LcdTxtFrame:
.db "  -  .  .  -  :  :  ",0,0
.if LangEN == 1
; Englische Version
  .db " Alarm: ___   :     ",0,0
  .db "DCF: S0 __ Light:   ",0,0
  .else
; Deutsche Version
  .db "Wecken: ___   :     ",0,0
  .db "DCF: S0 __ Licht:   ",0,0
  .endif
.db "                    ",0,0
;
; Edit-Positionen
.equ cAlarmPos = 8
.equ cDcfPos = 8
;
; Ende des Quellcodes
; Copyright
.db "(C)2018 by Gerhard Schmidt  " ; menschenlesbar
.db "C(2)10 8ybG reahdrS hcimtd  " ; wortgerecht
;

Seitenanfang Hauptprogramm LCD-Routinen Musik-Code Debug-Code

2 Die LCD-Routinen lcd_8_routinen.inc

Dieser Code im asm-Format

; ****************************************************************
; * Include-Datei fuer LCD-Routinen DCF77-ATmega16, V. 1.1 05/18 *
; * (C)2018 by Gerhard Schmidt, info (bei) avr-asm-tutorial.net  *
; ****************************************************************
;
;
; --------------------------------------------------------------
; Routine   Register   Funktion
; --------------------------------------------------------------
; LcdInit   rmp,R0,Z   Initiiert das LCD-Display zu Beginn auf:
;                      8-Bit-Interface, 4 Zeilen, kein Shift,
;                      kein Cursor und nicht blinkend
; LcdR0     rmp        Gibt das Zeichen in R0 auf der aktuellen
;                      Position des LCD aus, R0 bleibt erhalten
; LcdChar   rmp,R0     Gibt das Zeichen in rmp auf der aktuellen
;                      Position des LCD aus, rmp wird geaendert,
;                      Zeichen bleibt in R0 erhalten
; LcdDisplZ rmp,R0,Z   Gibt den Text im Flash ab Z auf der LCD
;                      aus, stoppt beim NULL-Zeichen
; LcdLine   rmp        Setzt die Ausgabe der LCD auf die Zeile
;                      in rmp, erwartet 0..3 in rmp fuer die
;                      Zeilen 1..4
; LcdFrame  rmp        Gibt Standard-Frame aus
; LcdLineCl rmp,R0,Z   Loescht die Zeile des LCD, auf die rmp
;                      zeigt, durch Schreiben von Leerzeichen
; LcdPos    rmp,Z      Setzt die LCD auf die Position in Z,
;                      ZH ist die Zeile (0..3), ZL ist die Position
;                      in dieser Zeile (0..19)
; LcdSram   rmp,Z,X    Schreibt rmp Zeichen von Position X im
;                      SRAM an Position Z der LCD
; LcdDec2   rmp,ZL     Schreibt die Binaerzahl in R0 in dezimal
;                      (2 Ziffern) auf die LCD
; LcdDec16             Schreibt die Binaerzahl in R1:R0 in
;                      Dezimalformat auf die LCD
; LcdHex2   rmp        Schreibt den Inhalt von rmp in Hex an die
;                      aktuelle LCD-Position (debug)
; LcdHex4   Z,rmp      Schreibt die Binaerzahl in R1:R0 in
;                      Hex an die aktuelle LCD-Position
; LcdBlink  rmp        Blinken der LCD an
; LcdNoBlink           Blinken der LCD aus
; --------------------------------------------------------------
;
; Verzoegerungsroutinen fuer die Display-Initiierung
; Taktrate des ATmega16: 3,276800 MHz (Externer Quarz)
;   clock = 3276800 Hz im Hauptprogramm definiert
.EQU clk100 = clock / 100
.EQU clkDiv = 10000
;
; Verzoegerungsschleifen
;   Anzahl der Takte einschlieszlich rcall: 11+4*(z-1)+3+8 = 4*Z+18
;   Verzoegerung in Mikrosekunden: (clk100*(4*Z+18))/clkDiv
;   Berechnung von Z = (clk100*(n-18)/4)/clkDiv
;
; Verzoegerung maximal
;
.EQU cLcdWtMax = 64535
LcdWtMax:
  push ZH
  push ZL
  ldi ZH,HIGH(cLcdWtMax)
  ldi ZL,LOW(cLcdWtMax)
  rjmp LcdWt
;
; Verzoegerung 15 ms
;
.EQU cLcdWt15000 = (clk100*(15000-18+2)/4)/clkDiv ; = 9206
LcdWt15000:
  push ZH ; 2
  push ZL ; 4
  ldi ZH,HIGH(cLcdWt15000) ; 5
  ldi ZL,LOW(cLcdWt15000) ; 6
  rjmp LcdWt ; 8
;
; Verzoegerung 4,1 ms
;
.EQU cLcdWt4100 = (clk100*(4100-18+2)/4)/clkDiv ; = 2509
LcdWt4100:
  push ZH ; 2
  push ZL ; 4
  ldi ZH,HIGH(cLcdWt4100) ; 5
  ldi ZL,LOW(cLcdWt4100) ; 6
  rjmp LcdWt ; 8
;
; Verzoegerung 4,5 ms
;
.EQU cLcdWt4500 = (clk100*(4500-18+2)/4)/clkDiv ; = 2754
LcdWt4500:
  push ZH ; 2
  push ZL ; 4
  ldi ZH,HIGH(cLcdWt4500) ; 5
  ldi ZL,LOW(cLcdWt4500) ; 6
  rjmp LcdWt ; 8
;
; Verzoegerung 1,64 ms
;
.EQU cLcdWt1640 = (clk100*(1640-18+2)/4)/clkDiv ; = 997
LcdWt1640:
  push ZH ; 2
  push ZL ; 4
  ldi ZH,HIGH(cLcdWt1640) ; 5
  ldi ZL,LOW(cLcdWt1640) ; 6
  rjmp LcdWt ; 8
;
; Verzegerung 100 us
;
.EQU cLcdWt100 = (clk100*(100-18+2)/4)/clkDiv ; = 51
LcdWt100:
	push ZH ; 2
	push ZL ; 4
	ldi ZH,HIGH(cLcdWt100) ; 5
	ldi ZL,LOW(cLcdWt100) ; 6
	rjmp LcdWt ; 8
;
; Verzoegerung 40 us
;
.EQU cLcdWt40 = (clk100*(40-18+2)/4)/clkDiv ; = 14
LcdWt40:
  push ZH ; 2
  push ZL ; 4
  ldi ZH,HIGH(cLcdWt40) ; 5
  ldi ZL,LOW(cLcdWt40) ; 6
  nop ; 7 ; Ersatz rcall
  nop ; 8
;
; Wartet auf Z Verzoegerung
;
LcdWt:
  sbiw ZL,1 ; 2 Takte
  brne LcdWt ; 2/1 Takte
  pop ZL ; 2 Takte
  pop ZH ; 2 Takte
  ret ; 4 Takte
;
; Aktiviere E fuer 1 us
;
LcdE:
  sbi pLcdCO,bLcdE ; setze E aktiv
  nop ; One clock cycle delay
.if clock > 1000000
  nop
  .if clock > 2000000
    nop
    .if clock > 3000000
      nop
      .if clock > 4000000
        nop
        .if clock > 5000000
          nop
          .if clock > 8000000
            nop
            nop
            nop
            .if clock > 10000000
              nop
              nop
              nop
              nop
              nop
              .if clock > 15000000
                nop
                nop
                nop
                nop
                nop
                .endif
              .endif
            .endif
          .endif
        .endif
      .endif
    .endif
  .endif
  cbi pLcdCO,bLcdE ; loesche E
  ret
;
; Funktionseinstellung der LCD zu Beginn
;
LcdInitFunct:
  sbi pLcdCD,bLcdE ; Enable-Ausgang
  sbi pLcdCD,bLcdRs ; RS-Ausgang
  cbi pLcdCO,bLcdRs ; loesche RS
  ldi rmp,0x38 ; Setze 8 bit interface, 4 Zeilen
  out pLcdDO,rmp ; an Datenausgang
  rcall LcdE
  rcall LcdWt4100 ; warte 4100 us
  rcall LcdE
  rcall LcdWt4100 ; warte 100 us
  rcall LcdE
  rcall LcdWt4100 ; warte 100 us
  ldi rmp,0x08 ; Display aus
  out pLcdDO,rmp
  rcall LcdE
  rcall LcdWt4100 ; warte 40 us
  ldi rmp,0x01 ; loesche Display
  out pLcdDO,rmp
  rcall LcdE
  rcall LcdWt4500 ; warte 4,5 ms
  ldi rmp,0x06 ; Eingabemodus: erhoehen, keine Shift
  out pLcdDO,rmp
  rcall LcdE
  rcall LcdWt40
  ldi rmp,0x10 ; Display Cursor Shift
  out pLcdDO,rmp
  rcall LcdE
  rcall LcdWt40 ; warte 40 us
  ldi rmp,0x0C ; Display aktiv, Cursor aus, nicht blinkend
  out pLcdDO,rmp
  rcall LcdE
  rcall LcdWt40 ; warte 40 us
  ldi rmp,0x02 ; Display/Cursor Home
  out pLcdDO,rmp
  rcall LcdE
  rjmp LcdWt1640 ; warte fuer 1,64 ms
;
; Sende Kontrollanweisung in rmp an LCD
;
LcdCtrl:
  out pLcdDO,rmp ; Daten an Port
  cbi pLcdCO,bLcdRs ; loesche RS
  rcall LcdE ; aktiviere E fuer 1 us
  rjmp LcdWt100 ; Verzoegerung 100 us
;
; Schreibe Zeichen in rmp an LCD
;
LcdChar:
  push rmp
  push R0
  mov R0,rmp ; kopiere Zeichen in R0
  rcall LcdR0
  pop R0
  pop rmp
  ret
;
; Schreibe Zeichen in R0 an LCD
;
LcdR0:
  out pLcdDO,R0 ; Zeichen auf Ausgabeport
  sbi pLcdCO,bLcdRs ; Setze RS
  rcall LcdE ; aktiviere E fuer 1 µs
  rjmp LcdWt100 ; warten 40 us
;
; Schreibe Text im Flash ab Z auf Display
;
LcdDisplZ:
  lpm R0,Z+ ; lese ein Zeichen aus Flash
  tst R0 ; NULL-Zeichen?
  breq LcdDisplZ1
  rcall LcdR0 ; Zeichen an Display
  rjmp LcdDisplZ
LcdDisplZ1:
  adiw ZL,1 ; ueberlese zweites Null-Zeichen
  ret
;
; Initiiere die LCD und gib Begruessung aus
;
LcdInit:
  ; init LCD hardware
  cbi pLcdCO,bLcdRS ; init LCD control bits
  sbi pLcdCD,bLcdRS
  cbi pLcdCO,bLcdE
  sbi pLcdCD,bLcdE
  ldi rmp, 0xFF ; init data port als output
  out pLcdDD,rmp
  ; init LCD Betrieb
  rcall LcdInitFunct ; Setze 8-bit interface und Anzahl Zeilen
  rcall LcdWt15000 ; Warten fuer 15 ms
  rcall UserChars ; Init Spezialcharacter
  ; Ausgabe LCD init text
  ldi ZH,HIGH(2*LcdTxtInit1) ; Z auf Text
  ldi ZL,LOW(2*LcdTxtInit1)
LcdTxt: ; schreibt Lcd-Text ab Z auf Display
  ldi rmp,0 ; setze LCD-Zeile 1
  rcall LcdLine
  rcall LcdDisplZ ; schreibe Text auf LCD
  ldi rmp,1 ; setze LCD-Zeile 2
  rcall LcdLine
  rcall LcdDisplZ
  ldi rmp,2 ; setze LCD-Zeile 3
  rcall LcdLine
  rcall LcdDisplZ
  ldi rmp,3 ; setze LCD-Zeile 4
  rcall LcdLine
  rjmp LcdDisplZ
LcdFrame:
  ldi ZH,HIGH(2*LcdTxtFrame)
  ldi ZL,LOW(2*LcdTxtFrame)
  rjmp LcdTxt
;
; LcdLine setzt den Eingabecursor in die ausgewahlte Zeile in rmp
;
LcdLine:
  cpi rmp,1 ; rmp = 0 oder 1
  brcs LcdLine0 ; rmp=0, setzt Zeile 1
  breq LcdLine1 ; rmp=1, setzt Zeile 2
  cpi rmp,2
  breq LcdLine3 ; rmp=2, setzt Zeile 3
LcdLine4:
  ldi rmp,0x54 ; rmp>2, setzt Zeile 4
  rjmp LcdLineSet
LcdLine0:
  clr rmp ; Zeile 1 startet bei 0
  rjmp LcdLineSet
LcdLine1:
  ldi rmp,0x40 ; Zeile 2 startet bei 0x40
  rjmp LcdLineSet
LcdLine3:
  ldi rmp,20 ; Zeile 3 startet bei 20
LcdLineSet:
  sbr rmp,0x80 ; setze Kontrollbit
  rjmp LcdCtrl
;
; LCD loesche Zeile
;
LcdLineCl:
  mov ZH,rmp ; sichere Zeilennummer
  rcall LcdLine ; setze Zeile
  ldi rmp,' ' ; Leerzeichen
  mov R0,rmp ; nach R0
  ldi ZL,20 ; ZL ist Zaehler
LcdLineCl1:
  rcall LcdR0 ; schreibe Zeichen in R0
  dec ZL ; naechstes
  brne LcdLineCl1 ; noch mal
  mov rmp,ZH ; stelle rmp wieder her
  rjmp LcdLine
;
; LcdPos setzt die LCD auf die Position in Z
; ZH: Zeile, 0, 1, 2 oder 3; ZL: Spalte, 0 bis 19 
;
LcdPos:
  push rmp ; sichern rmp
  clr rmp
  cpi ZH,1 ; Zeile = 1 oder 2
  brcs LcdPos1 ; Zeile = 1
  ldi rmp,0x40 ; Zeile = 2
  breq LcdPos1
  ldi rmp,20 ; Zeile=3
  cpi ZH,3
  brcs LcdPos1
  ldi rmp,0x40+20
LcdPos1:
  sbr rmp,0x80
  add rmp,ZL
  rcall LcdCtrl
  pop rmp ; wiederherstellen rmp
  ret
;
; LcdChAt setzt die LCD auf die Position in Z und gibt
;   rmp als Zeichen aus
LcdChAt:
  rcall LcdPos
  rjmp LcdChar
;
; LcdSram schreibt rmp Zeichen von Position X im SRAM
;   an Position Z der LCD
LcdSram:
  rcall LcdPos ; setze Position LCD
LcdSram1:
  ld R0,X+ ; lese Zeichen
  rcall LcdR0 ; schreibe Zeichen
  dec rmp
  brne LcdSram1
  ret
;
; LcdDec2 schreibt die Binaerzahl in R0 mit
;   zwei Dezimalziffern auf die LCD
LcdDec2:
  ldi rmp,100 ; Zahlen groesser 99
  cp R0,rmp
  brcs LcdDec2a
  ldi rmp,99
  mov R0,rmp
LcdDec2a:
  ldi ZL,10 ; Teiler fuer Zehner
  ldi rmp,'0'-1
LcdDec2b:
  inc rmp
  sub R0,ZL
  brcc LcdDec2b
  add R0,ZL
  rcall LcdChar
  ldi rmp,'0'
  add rmp,R0
  rjmp LcdChar
;
; LCDDEC16 schreibt die Binaerzahl in R1:R0 auf
; die LCD
;
LcdDec16:
  set ; T Flagge zur Unterdrueckung fuehrender Nullen
  ldi ZH,High(10000)
  ldi ZL,Low(10000)
  rcall LcdDecZ
  ldi ZH,High(1000)
  ldi ZL,Low(1000)
  rcall LcdDecZ
  ldi ZH,High(100)
  ldi ZL,Low(100)
  rcall LcdDecZ
  ldi ZH,High(10)
  ldi ZL,Low(10)
  rcall LcdDecZ
  ldi rmp,'0'
  add rmp,R0
  rjmp LcdChar
;
; Ermittelt die Dezimalziffer und schreibt sie auf die LCD
;   Binaerzahl in R1:R0
;   Dezimalzahl in ZH:ZL
;   T-Flagge: Unterdrueckung fuehrender Nullen
LcdDecZ:
  ldi rmp,'0'-1
LcdDecZ1:
  inc rmp
  sub R0,ZL
  sbc R1,ZH
  brcc LcdDecZ1
  add R0,ZL
  adc R1,ZH
  brtc LcdDecZ3
  cpi rmp,'0'
  breq LcdDecZ2
  clt
  rjmp LcdDecZ3
LcdDecZ2:
  ldi rmp,' '
LcdDecZ3:
  rjmp LcdChar
;
; LCDHEX4 schreibt den Inhalt von Z in Hex
;
LcdHex4:
  mov rmp,ZH ; ZH ist High Byte
  rcall LcdHex2
  mov rmp,ZL ; schreibe Low Byte
;
; LCDHEX2 schreibt den Inhalt von rmp in Hex (debug)
;
LcdHex2:
  push rmp
  swap rmp
  rcall LcdHexN
  pop rmp
LcdHexN:
  andi rmp,0x0F
  cpi rmp,0x0A
  brcs LcdHexN1
  subi rmp,-7
LcdHexN1:
  subi rmp,-'0'
  rcall LcdChar
  ret
;
; Blinken der LCD einschalten
;
LcdBlink:
   ldi rmp,0x0F
   rjmp LcdCtrl
;
; Blinken der LCD ausschalten
;
LcdNoBlink:
   ldi rmp,0x0C
   rjmp LcdCtrl
;
; Schreibe User-Character
;
UserChars:
  ldi ZH,HIGH(2*CharTab)
  ldi ZL,LOW(2*CharTab)
UserChars1:
  lpm rmp,Z+
  tst rmp
  breq UserChars3
  rcall LcdCtrl
  ldi XL,8
UserChars2:
  rcall LcdWt40
  lpm rmp,Z+
  rcall LcdChar
  dec XL
  brne UserChars2
  lpm rmp,Z+
  rjmp UserChars1
UserChars3:
  rcall LcdWt100
  ret

CharTab:
.DB 72,4,4,4,4,21,14,4,0,0 ; 0x01 Pfeil abwaerts
.DB 80,4,14,21,4,4,4,4,0,0 ; 0x02 Pfeil aufwaerts
.DB 88,5,11,19,23,16,8,4,0,0 ; 0x03 Drehpfeil rechts
.DB 96,20,26,17,29,1,10,4,0,0 ; 0x04 Drehpfeil links
.DB 120,4,10,23,25,25,23,10,4,0 ; 0x07 Copyright
.DW 0
;
; Ende der LCD Include-Datei
;

Seitenanfang Hauptprogramm LCD-Routinen Musik-Code Debug-Code

3 Die Musik-Codes musik_code.inc

Dieser Code im asm-Format

;
; ***********************************
; * Musik spielen mit dem ATmega16  *
; * (C)2018 by avr-asm-tutorial.net *
; ***********************************
;
; Ein Musikstueck in das RAM kopieren und Ausgabe starten
;   rmp zeigt auf eine Melodie (0..8 direkt, ab 9 mit modulo)
MusicConvert:
  cpi rmp,cMaxMelody+1 ; rmp innerhalb der Anzahl Melodien?
  brcs MusicConvert1
  subi rmp,cMaxMelody
  brcc MusicConvert
  subi rmp,-cMaxMelody ; Addiere Anzahl Melodien
MusicConvert1:
  ; Melody to play
  ldi ZH,High(2*MelodyTab)
  ldi ZL,Low(2*MelodyTab)
  lsl rmp
  add ZL,rmp
  ldi rmp,0
  adc ZH,rmp
  lpm R0,Z+
  lpm ZH,Z
  mov ZL,R0
  ; Z points to melody
  ldi XH,High(sMelody) ; Auf SRAM-Melodie zeigen
  ldi XL,Low(sMelody)
MusicConvert2:
  lpm rmp,Z+ ; Lese Note
  cpi rmp,MelodyEnd ; Ende der Melodie?
  breq MusicConvertEnd
  rcall ConvertNote ; CTC nach Y, Dauer nach R1:R0
  lpm rmp,Z ; Lese Dauer-Multiplikator
  andi rmp,0x0F ; Niedrigeres Nibble
MusicConvert3:
  tst rmp
  breq MusicConvert6
  lsr R1 ; Teile Dauer durch zwei
  brne MusicConvert5 ; Nicht Null, ohne runden
  ror R0 ; MSB Null, Rollen mit Runden
  brcc MusicConvert4 ; Keine Eins im Carry
  inc R0 ; Aufrunden
  brne MusicConvert4 ; Kein Carry
  inc R1 ; Erhoehe MSB
MusicConvert4:
  dec rmp ; Vermindere Anzahl
  rjmp MusicConvert3
MusicConvert5:
  ror R0 ; MSB groesser als 0, rolle ohne Runden
  dec rmp ; Vermindere Anzahl
  rjmp MusicConvert3
MusicConvert6:
  lpm rmp,Z+ ; Teiler nochmals lesen
  swap rmp ; Oberes Nibble in unteres Nibble
  andi rmp,0x0F ; Isoliere Addierer
  breq MusicConvert8 ; Kein Addieren
  push ZH ; Sichern Z zum Addieren
  push ZL
  mov ZH,R1 ; Kopiere aktuellen Zaehler
  mov ZL,R0
MusicConvert7:
  add R0,ZL ; Addiere einmal
  adc R1,ZH
  dec rmp ; Vermindere Addierer
  brne MusicConvert7 ; Addiere weiter
  pop ZL ; Wiederherstellen von Z
  pop ZH
MusicConvert8:
  mov rmp,R0 ; Zaehler bei Null?
  or rmp,R1 ; Oder mit MSB
  brne MusicConvert9
  inc R0 ; Minimum ein CTC-Zyklus
MusicConvert9:
  st X+,YH ; Schreibe MSB CTC in SRAM
  st X+,YL ; Dann LSB CTC in SRAM
  st X+,R1 ; MSB Dauer in SRAM
  st X+,R0 ; LSB Dauer in SRAM
  cpi XH,High(sMelodyEnd+4) ; MSB Ende des Puffers erreicht?
  brcs MusicConvert2 ; Nein
  cpi XL,Low(sMelodyEnd+4) ; LSB? 
  brcs MusicConvert2 ; Nein
MusicConvertEnd:
  ldi rmp,0xFF ; Ende der Melodie, 0xFFFF ins SRAM
  st X+,rmp
  st X+,rmp
  clr rmp ; Und 0x0000 dazu
  st X+,rmp
  st X,rmp
;
; Spiele Musik im SRAM (neu oder erneut)
MusicPlay:
  ldi YH,High(sMelody) ; Y zeigt auf SRAM-Puffer
  ldi YL,Low(sMelody)
  ldi rmp,(1<<TOIE0)|(1<<OCIE1A)|(1<<TOIE2) ; Enable Int TC1
  out TIMSK,rmp ; in Timer-Int-Maske
  ldi rDurL,1 ; Starte mit Dauer = 1
  clr rDurH
  ret
;
; Konvertiert die Musiknote in rmp
;   Liefert CTC 16-Bit-Wert in Y
;           Dauer 16-Bit-Wert in R1:R0
ConvertNote:
  push ZH ; Sichern Z
  push ZL
  ldi ZH,High(2*NotesTimerTable)
  ldi ZL,Low(2*NotesTimerTable)
  mov R0,rmp
  clr R1
  lsl R0 ; Multiplikation mit 4
  rol R1
  lsl R0
  rol R1
  add ZL,R0 ; Addiere zur Notentabelle
  adc ZH,R1
  lpm YL,Z+
  lpm YH,Z+
  lpm R0,Z+
  lpm R1,Z
  pop ZL
  pop ZH
  ret
;
; Notenfrequenztabelle
;   Zur Frequenzberechnung der Noten aus dem Takt
;   fe wegen Konflikt mit FE (Framing Error, UART)
;   in fee umbenannt!
NoteFrequencyTable:
.equ fA2m=55 ; #0, f = 27,5
.equ fH2m=62 ; #1, f = 30,8677
.equ fC1m=65 ; #2, f = 32,7032
.equ fD1m=73 ; #3, f = 36,7081
.equ fE1m=82 ; #4, f = 41,2034
.equ fF1m=87 ; #5, f = 43,6535
.equ fG1m=98 ; #6, f = 48,9994
.equ fA1m=110 ; #7, f = 55
.equ fH1m=123 ; #8, f = 61,7354
.equ fCm=131 ; #9, f = 65,4064
.equ fDm=147 ; #10, f = 73,4162
.equ fEm=165 ; #11, f = 82,4069
.equ fFm=175 ; #12, f = 87,3071
.equ fGm=196 ; #13, f = 97,9989
.equ fAm=220 ; #14, f = 110
.equ fHm=247 ; #15, f = 123,471
.equ fc=262 ; #16, f = 130,813
.equ fd=294 ; #17, f = 146,832
.equ fee=330 ; #18, f = 164,814
.equ ff=349 ; #19, f = 174,614
.equ fg=392 ; #20, f = 195,998
.equ fa=440 ; #21, f = 220
.equ fh=494 ; #22, f = 246,942
.equ fc1=523 ; #23, f = 261,626
.equ fd1=587 ; #24, f = 293,665
.equ fe1=659 ; #25, f = 329,628
.equ ff1=698 ; #26, f = 349,228
.equ fg1=784 ; #27, f = 391,995
.equ fa1=880 ; #28, f = 440
.equ fh1=988 ; #29, f = 493,883
.equ fc2=1047 ; #30, f = 523,251
.equ fd2=1175 ; #31, f = 587,33
.equ fe2=1319 ; #32, f = 659,255
.equ ff2=1397 ; #33, f = 698,456
.equ fg2=1568 ; #34, f = 783,991
.equ fa2=1760 ; #35, f = 880
.equ fh2=1976 ; #36, f = 987,767
.equ fc3=2093 ; #37, f = 1046,5
.equ fd3=2349 ; #38, f = 1174,66
.equ fe3=2637 ; #39, f = 1318,51
.equ ff3=2794 ; #40, f = 1396,91
.equ fg3=3136 ; #41, f = 1567,98
.equ fa3=3520 ; #42, f = 1760
.equ fh3=3951 ; #43, f = 1975,53
.equ fc4=4186 ; #44, f = 2093
.equ fd4=4699 ; #45, f = 2349,32
.equ fe4=5274 ; #46, f = 2637,02
.equ ff4=5588 ; #47, f = 2793,83
.equ fg4=6272 ; #48, f = 3135,96
.equ fa4=7040 ; #49, f = 3520
.equ fh4=7902 ; #50, f = 3951,07
.equ fc5=8372 ; #51, f = 4186,01
.equ fd5=9397 ; #52, f = 4698,65
.equ fe5=10548 ; #53, f = 5274,05
.equ ff5=11175 ; #54, f = 5587,67
.equ fg5=12544 ; #55, f = 6271,93
.equ fa5=14080 ; #56, f = 7040
.equ fh5=15804 ; #57, f = 7902,13
.equ fc6=16744 ; #58, f = 8372,02
.equ fd6=18795 ; #59, f = 9397,28
.equ fe6=21096 ; #60, f = 10548,08
.equ ff6=22351 ; #61, f = 11175,3
.equ fg6=25088 ; #62, f = 12543,86
.equ fa6=28160 ; #63, f = 14080
.equ fh6=31609 ; #64, f = 15804,26
.equ fc7=33488 ; #65, f = 16744,03
.equ fd7=37589 ; #66, f = 18794,56
.equ fe7=42192 ; #67, f = 21096,16
.equ ff7=44701 ; #68, f = 22350,59
.equ fg7=50175 ; #69, f = 25087,71
;
; Notentabelle: liefert den CTC-Wert fuer TC1 und
;   Anzahl CTC-Durchlaeufe fuer eine Dauer von
;   einer Sekunde fuer alle spielbaren Noten
; Abgestimmt auf 16-Bit-Timer, Taktfrequenz 3,2768 MHz
;   und Vorteiler = 1
; Timerabelle fuer die Musiknoten 0 bis 70
;   Erstes Wort:  Compare-Wert fuer CTC
;   Zweites Wort: Dauer = Anzahl CTC-Zyklen fuer 1 Sekunde
;                = 2*Notenfrequenz
NotesTimerTable:
.dw clock/fA2m-1, 55 ; #0, f=27,5
.dw clock/fH2m-1, 62 ; #1, f=30,8677
.dw clock/fC1m-1, 65 ; #2, f=32,7032
.dw clock/fD1m-1, 73 ; #3, f=36,7081
.dw clock/fE1m-1, 82 ; #4, f=41,2034
.dw clock/fF1m-1, 87 ; #5, f=43,6535
.dw clock/fG1m-1, 98 ; #6, f=48,9994
.dw clock/fA1m-1, 110 ; #7, f=55
.dw clock/fH1m-1, 123 ; #8, f=61,7354
.dw clock/fCm-1, 131 ; #9, f=65,4064
.dw clock/fDm-1, 147 ; #10, f=73,4162
.dw clock/fEm-1, 165 ; #11, f=82,4069
.dw clock/fFm-1, 175 ; #12, f=87,3071
.dw clock/fGm-1, 196 ; #13, f=97,9989
.dw clock/fAm-1, 220 ; #14, f=110
.dw clock/fHm-1, 247 ; #15, f=123,471
.dw clock/fc-1, 262 ; #16, f=130,813
.dw clock/fd-1, 294 ; #17, f=146,832
.dw clock/fee-1, 330 ; #18, f=164,814
.dw clock/ff-1, 349 ; #19, f=174,614
.dw clock/fg-1, 392 ; #20, f=195,998
.dw clock/fa-1, 440 ; #21, f=220
.dw clock/fh-1, 494 ; #22, f=246,942
.dw clock/fc1-1, 523 ; #23, f=261,626
.dw clock/fd1-1, 587 ; #24, f=293,665
.dw clock/fe1-1, 659 ; #25, f=329,628
.dw clock/ff1-1, 698 ; #26, f=349,228
.dw clock/fg1-1, 784 ; #27, f=391,995
.dw clock/fa1-1, 880 ; #28, f=440
.dw clock/fh1-1, 988 ; #29, f=493,883
.dw clock/fc2-1, 1047 ; #30, f=523,251
.dw clock/fd2-1, 1175 ; #31, f=587,33
.dw clock/fe2-1, 1319 ; #32, f=659,255
.dw clock/ff2-1, 1397 ; #33, f=698,456
.dw clock/fg2-1, 1568 ; #34, f=783,991
.dw clock/fa2-1, 1760 ; #35, f=880
.dw clock/fh2-1, 1976 ; #36, f=987,767
.dw clock/fc3-1, 2093 ; #37, f=1046,5
.dw clock/fd3-1, 2349 ; #38, f=1174,66
.dw clock/fe3-1, 2637 ; #39, f=1318,51
.dw clock/ff3-1, 2794 ; #40, f=1396,91
.dw clock/fg3-1, 3136 ; #41, f=1567,98
.dw clock/fa3-1, 3520 ; #42, f=1760
.dw clock/fh3-1, 3951 ; #43, f=1975,53
.dw clock/fc4-1, 4186 ; #44, f=2093
.dw clock/fd4-1, 4699 ; #45, f=2349,32
.dw clock/fe4-1, 5274 ; #46, f=2637,02
.dw clock/ff4-1, 5588 ; #47, f=2793,83
.dw clock/fg4-1, 6272 ; #48, f=3135,96
.dw clock/fa4-1, 7040 ; #49, f=3520
.dw clock/fh4-1, 7902 ; #50, f=3951,07
.dw clock/fc5-1, 8372 ; #51, f=4186,01
.dw clock/fd5-1, 9397 ; #52, f=4698,65
.dw clock/fe5-1, 10548 ; #53, f=5274,05
.dw clock/ff5-1, 11175 ; #54, f=5587,67
.dw clock/fg5-1, 12544 ; #55, f=6271,93
.dw clock/fa5-1, 14080 ; #56, f=7040
.dw clock/fh5-1, 15804 ; #57, f=7902,13
.dw clock/fc6-1, 16744 ; #58, f=8372,02
.dw clock/fd6-1, 18795 ; #59, f=9397,28
.dw clock/fe6-1, 21096 ; #60, f=10548,08
.dw clock/ff6-1, 22351 ; #61, f=11175,3
.dw clock/fg6-1, 25088 ; #62, f=12543,86
.dw clock/fa6-1, 28160 ; #63, f=14080
.dw clock/fh6-1, 31609 ; #64, f=15804,26
.dw clock/fc7-1, 33488 ; #65, f=16744,03
.dw clock/fd7-1, 37589 ; #66, f=18794,56
.dw clock/fe7-1, 42192 ; #67, f=21096,16
.dw clock/ff7-1, 44701 ; #68, f=22350,59
.dw clock/fg7-1, 50175 ; #69, f=25087,71
; Pause
.dw 0xFFFF,clock/0xFFFF ; #70, f = clock/131130
;
; Note names
NotesNameTable:
.equ nA2m = 0 ; A2, f = 27,5 Hz, #0
.equ nH2m = 1 ; H2, f = 30,8677 Hz
.equ nC1m = 2 ; C1, f = 32,7032 Hz
.equ nD1m = 3 ; D1, f = 36,7081 Hz
.equ nE1m = 4 ; E1, f = 41,2034 Hz
.equ nF1m = 5 ; F1, f = 43,6535 Hz
.equ nG1m = 6 ; G1, f = 48,9994 Hz
.equ nA1m = 7 ; A1, f = 55 Hz
.equ nH1m = 8 ; H1, f = 61,7354 Hz
.equ nCm = 9 ; C, f = 65,4064 Hz
.equ nDm = 10 ; D, f = 73,4162 Hz
.equ nEm = 11 ; E, f = 82,4069 Hz
.equ nFm = 12 ; F, f = 87,3071 Hz
.equ nGm = 13 ; G, f = 97,9989 Hz
.equ nAm = 14 ; A, f = 110 Hz
.equ nHm = 15 ; H, f = 123,471 Hz
.equ nc = 16 ; c, f = 130,813 Hz
.equ nd = 17 ; d, f = 146,832 Hz
.equ ne = 18 ; e, f = 164,814 Hz
.equ nf = 19 ; f, f = 174,614 Hz
.equ ng = 20 ; g, f = 195,998 Hz
.equ na = 21 ; a, f = 220 Hz
.equ nh = 22 ; h, f = 246,942 Hz
.equ nc1 = 23 ; c1, f = 261,626 Hz
.equ nd1 = 24 ; d1, f = 293,665 Hz
.equ ne1 = 25 ; e1, f = 329,628 Hz
.equ nf1 = 26 ; f1, f = 349,228 Hz
.equ ng1 = 27 ; g1, f = 391,995 Hz
.equ na1 = 28 ; a1, f = 440 Hz
.equ nh1 = 29 ; h1, f = 493,883 Hz
.equ nc2 = 30 ; c2, f = 523,251 Hz
.equ nd2 = 31 ; d2, f = 587,33 Hz
.equ ne2 = 32 ; e2, f = 659,255 Hz
.equ nf2 = 33 ; f2, f = 698,456 Hz
.equ ng2 = 34 ; g2, f = 783,991 Hz
.equ na2 = 35 ; a2, f = 880 Hz
.equ nh2 = 36 ; h2, f = 987,767 Hz
.equ nc3 = 37 ; c3, f = 1046,5 Hz
.equ nd3 = 38 ; d3, f = 1174,66 Hz
.equ ne3 = 39 ; e3, f = 1318,51 Hz
.equ nf3 = 40 ; f3, f = 1396,91 Hz
.equ ng3 = 41 ; g3, f = 1567,98 Hz
.equ na3 = 42 ; a3, f = 1760 Hz
.equ nh3 = 43 ; h3, f = 1975,53 Hz
.equ nc4 = 44 ; c4, f = 2093 Hz
.equ nd4 = 45 ; d4, f = 2349,32 Hz
.equ ne4 = 46 ; e4, f = 2637,02 Hz
.equ nf4 = 47 ; f4, f = 2793,83 Hz
.equ ng4 = 48 ; g4, f = 3135,96 Hz
.equ na4 = 49 ; a4, f = 3520 Hz
.equ nh4 = 50 ; h4, f = 3951,07 Hz
.equ nc5 = 51 ; c5, f = 4186,01 Hz
.equ nd5 = 52 ; d5, f = 4698,65 Hz
.equ ne5 = 53 ; e5, f = 5274,05 Hz
.equ nf5 = 54 ; f5, f = 5587,67 Hz
.equ ng5 = 55 ; g5, f = 6271,93 Hz
.equ na5 = 56 ; a5, f = 7040 Hz
.equ nh5 = 57 ; h5, f = 7902,13 Hz
.equ nc6 = 58 ; c6, f = 8372,02 Hz
.equ nd6 = 59 ; d6, f = 9397,28 Hz
.equ ne6 = 60 ; e6, f = 10548,08 Hz
.equ nf6 = 61 ; f6, f = 11175,3 Hz
.equ ng6 = 62 ; g6, f = 12543,86 Hz
.equ na6 = 63 ; a6, f = 14080 Hz
.equ nh6 = 64 ; h6, f = 15804,26 Hz
.equ nc7 = 65 ; c7, f = 16744,03 Hz
.equ nd7 = 66 ; d7, f = 18794,56 Hz
.equ ne7 = 67 ; e7, f = 21096,16 Hz
.equ nf7 = 68 ; f7, f = 22350,59 Hz
.equ ng7 = 69 ; g7, f = 25087,71 Hz
; Pause
.equ p = 70 ; Pause, stumm, #70
; End
.equ MelodyEnd = 0xFF
;
; Melodien
;   Melodien die die Uhr zufaellig spielen kann
;   Maximum 16 Melodien (0..15)
;
.equ cMaxMelody = 8 ; Anzahl der programmierten Melodien
;
; Melodietabelle
;   Tabelleneintraege zeigen auf den Beginn der Melodie
;   im Flash mal zwei
;
MelodyTab:
  .dw 2*Melody0
.if cMaxMelody>=1
  .dw 2*Melody1
  .endif
.if cMaxMelody>=2
  .dw 2*Melody2
  .endif
.if cMaxMelody>=3
  .dw 2*Melody3
  .endif
.if cMaxMelody>=4
  .dw 2*Melody4
  .endif
.if cMaxMelody>=5
  .dw 2*Melody5
  .endif
.if cMaxMelody>=6
  .dw 2*Melody6
  .endif
.if cMaxMelody>=7
  .dw 2*Melody7
  .endif
.if cMaxMelody>=8
  .dw 2*Melody8
  .endif
.if cMaxMelody>=9
  .dw 2*Melody9
  .endif
.if cMaxMelody>=10
  .dw 2*Melody10
  .endif
.if cMaxMelody>=11
  .dw 2*Melody11
  .endif
.if cMaxMelody>=12
  .dw 2*Melody12
  .endif
.if cMaxMelody>=13
  .dw 2*Melody13
  .endif
.if cMaxMelody>=14
  .dw 2*Melody14
  .endif
.if cMaxMelody>=15
  .dw 2*Melody15
  .endif
.if cMaxMelody>=16
  .error "Too many melodies"
  .endif
;
; Dauerteiler fuer Toene und Pausen:
.equ d1   = 0    ; 16/16
.equ d2   = 1    ;  8/16
.equ d4   = 2    ;  4/16
.equ d8   = 3    ;  2/16
.equ d16  = 4    ;  1/16
.equ d32  = 5    ;  1/32
.equ d316 = 0x34 ;  3/16 
.equ d38  = 0x33 ;  6/16
;
; Melodies
;   Each melody encodes
;     - notes to be played (nx) and one duration byte
;     - pauses (p) to be made and one duration byte
;     - an end signature (MelodyEnd)
;
Melody0: ; Ode an die Freude
  ;   Freu-      de         schoe-      ner
  .db nh,d4,p,d8,nh,d4,p,d8,nc1,d4,p,d8,nd1,d4,p,d8
  ;   Goet-       ter-        fun-       ken
  .db nd1,d4,p,d8,nc1,d4,p,d8,nh,d4,p,d8,na,d4,p,d8
  ;   Toch-      ter        aus        E-
  .db ng,d4,p,d8,ng,d4,p,d8,na,d4,p,d8,nh,d4,p,d8
  ;   ly-        si-        um,
  .db nh,d4,p,d8,na,d8,p,d8,na,d4,p,d1
  .db MelodyEnd,MelodyEnd
;
Melody1: ; Marmor, Stein und Eisen bricht
  ;   Wei-       ne         nicht,     wenn       der
  .db na,d8,p,d8,na,d4,p,d8,na,d4,p,d8,ng,d8,p,d8,na,d4,p,d8
  ;   Re-         gen        faellt,
  .db nc1,d8,p,d8,na,d4,p,d8,na,d38,p,d4
  ;   dam,       dam,       dam,        dam
  .db na,d4,p,d2,nh,d4,p,d2,nd1,d4,p,d8,nc1,d2,p,d2
  ;   Mar-        mor,        Stein        und
  .db na1,d8,p,d8,na1,d4,p,d8,na1,d38,p,d8,na1,d4,p,d8
  ;   Ei-         sen         bricht,
  .db ng1,d8,p,d8,nf1,d4,p,d8,nf1,d38,p,d8
  ;   a-          ber         un-         se-         re
  .db ng1,d8,p,d8,ng1,d4,p,d8,ng1,d4,p,d8,nf1,d8,p,d8,ng1,d4,p,d8
  ;   Lie-          be          nicht.
  .db na1,d4,ne1,d4,nf1,d4,p,d8,ne1,d38,p,d2
  ;   Al-          les,        al-          les         geht
  .db na1,d4,p,d16,na1,d4,p,d4,na1,d4,p,d16,na1,d4,p,d4,ng1,d8,p,d8
  ;   vor-        bei,        doch        wir
  .db nf1,d4,p,d8,nf1,d38,p,6,ng1,d4,p,d8,ng1,d4,p,d8
  ;   sind        uns         treu
  .db nf1,d8,p,d8,ng1,d4,p,d8,na1,d2
  .db MelodyEnd,MelodyEnd
;
Melody2: ; When I'm sixty four
  ;   When        I            get         old-       er
  .db nc,d4,p,d16,nhm,d4,p,d16,nc,d4,p,d16,ne,d4,p,d8,ne,d4,p,d8
  ;   loo-        sing        my          hair...
  .db ne,d4,p,d16,nf,d4,p,d16,ne,d4,p,d16,na,d2,p,d8
  ;   ma-        ny          years...    from       now,
  .db na,d8,p,d8,nc1,d4,p,d8,na,d38,p,d8,nf,d4,p,d8,nh,d2,p,d2
  ;   will        you         still       be          send-
  .db ng,d4,p,d16,na,d4,p,d16,na,d4,p,d16,nc1,d4,p,d8,ng,d4,p,d16
  ;   ing         me          a           val-       en-        tine...
  .db na,d4,p,d16,na,d4,p,d16,nc1,d4,p,d8,ng,d8,p,d8,ng,d4,p,d8,nf,d38,p,d8
  ;   birth-     day        greet-     ings,
  .db ng,d4,p,d8,na,d4,p,d8,na,d4,p,d8,nh,d4,p,d8
  ;   bott-        le           of          wine?
  .db nc1,d4,p,d16,nc1,d4,p,d16,nh,d4,p,d16,na,d38,p,d2
  ;   If          I'd          been        out...
  .db nc,d4,p,d16,nhm,d4,p,d16,nc,d4,p,d16,ne,d4,p,d8
  ;   till       quar-       ter         to          three...
  .db nc,d4,p,d8,ne,d4,p,d16,nf,d4,p,d16,ne,d4,p,d16,na,d8,na,d4,p,d2
  ;   would       you           lock       the         door?
  .db nc1,d8,p,d8,nc1,d316,p,d8,nh,d4,p,d8,nf,d4,p,d16,na,d2,p,d2
  ;   will        you         still       need       me,
  .db na,d4,p,d16,nf,d4,p,d16,na,d4,p,d16,nc1,d38,p,d8,nh,d4,p,d8
  ;   will        you         still       feed       me
  .db na,d4,p,d16,nf,d4,p,d16,na,d4,p,d16,ng,d38,p,d16,nf,d4,p,d8
  ;   when        I'm           six-        ty          four?
  .db nc1,d8,p,d8,nc1,d316,p,d8,nc1,d8,p,d8,nc1,d4,p,d8,na,d2
  .db MelodyEnd,MelodyEnd
;
Melody3: ; Smoke on the water
  ;   Smoke       on           the         wa-         ter
  .db ne1,d2,p,d8,nd1,d4,p,d16,nc1,d4,p,d8,ne1,d4,p,d8,nc1,d4,p,d2
  ;   A          Fi-         re         in          the        sky
  .db ng,d8,p,d8,nh,d4,p,d16,ng,d4,p,d8,nh,d4,p,d16,ng,d4,p,d8,nf,d4,ng,d2
  .db MelodyEnd,MelodyEnd
;
Melody4: ; The wall
  ;   We         don't      need       no 
  .db nd,d8,p,d8,ne,d4,p,d8,nf,d4,p,d8,ne,d4,p,d8
  ;   ed-        u-          ca-         tion
  .db nd,d4,p,d8,ne,d4,p,d16,nf,d38,p,d8,ne,d4,p,d2
  ;   We         don't      need       no
  .db nd,d8,p,d8,ne,d4,p,d8,nf,d4,p,d8,ne,d4,p,d8
  ;   thought    control,
  .db nd,d4,p,d8,ne,d4,p,d8
  ;   dark       sar-        ca-         sm
  .db nd,d4,p,d8,ne,d4,p,d16,nf,d4,p,d16,ne,d4,p,d4
  ;   in         the         class-     room
  .db nd,d4,p,d8,ne,d38,p,d8,nf,d4,p,d8,ne,d4,p,d2
  ;   teach      or          leave       them
  .db nd,d4,p,d8,ne,d4,p,d16,nf,d38,p,d8,ne,d4,p,d4
  ;   kids       a-         lone
  .db nd,d4,p,d8,ne,d4,p,d16,nf,d2
  .db MelodyEnd,MelodyEnd
;
Melody5: ; Emanuela
  ;   Lass       die         Fin-
  .db nf,d4,p,d4,nc1,d4,p,d4,nc1,d4,p,d16
  ;   ger          von         E-
  .db nc1,d4,p,d16,na,d4,p,d16,nf,d38,p,d4
  ;   ma-         nu-         e-          la
  .db nc1,d4,p,d4,nd1,d8,p,d4,nc1,d4,p,d4,na,d4
  .db MelodyEnd,MelodyEnd
;
Melody6: ; Morse
  ; G    e r   h    a  r   d
  ; --.  . .-. .... .- .-. -..
  ; '.' = 1/16 = d16, '-' = 3/16 = d316, p = 1/16 = d16, ' ' = 3/16 = d316 
  ;   -              -              .
  .db nc3,d316,p,d16,nc3,d316,p,d16,nc3,d16,p,d316 ; G
  ;   .
  .db nc3,d16,p,d316 ; e
  ;   .             -              .
  .db nc3,d16,p,d16,nc3,d316,p,d16,nc3,d16,p,d316 ; r
  ;   .             .             .             .
  .db nc3,d16,p,d16,nc3,d16,p,d16,nc3,d16,p,d16,nc3,d16,p,d316 ; h
  ;   .             -
  .db nc3,d16,p,d16,nc3,d316,p,d316 ; a
  ;   .             -             .
  .db nc3,d16,p,d16,nc3,d316,p,d16,nc3,d16,p,d316 ; r
  ;   -              .             .
  .db nc3,d316,p,d16,nc3,d16,p,d16,nc3,d16,p,d1 ; d
  ;   .             .             .
  .db nc3,d16,p,d16,nc3,d16,p,d16,nc3,d16,p,d316 ; S
  ;   -              .             -              .
  .db nc3,d316,p,d16,nc3,d16,p,d16,nc3,d316,p,d16,nc3,d16,p,d316 ; c
  ;   .             .             .             .
  .db nc3,d16,p,d16,nc3,d16,p,d16,nc3,d16,p,d16,nc3,d16,p,d316 ; h
  ;   -              -
  .db nc3,d316,p,d16,nc3,d316,p,d316 ; m
  ;   .             .
  .db nc3,d16,p,d16,nc3,d16,p,d316 ; i
  ;   -              .             .
  .db nc3,d316,p,d16,nc3,d16,p,d16,nc3,d16,p,d316 ; d
  ;   -
  .db nc3,d316,p,d316 ; t
  ;   .             .             .
  .db nc3,d16,p,d16,nc3,d16,p,d16,nc3,d16,p,d16
  ;   -              .             -
  .db nc3,d316,p,d16,nc3,d16,p,d16,nc3,d316,p,d1
  .db MelodyEnd,MelodyEnd
;
Melody7:
  ;   Voel-         ker         hoert
  .db ne1,d4,ne1,d8,nd1,d4,p,d8,nc1,d2,p,d8
  ;   die         Sig-       na-        le!
  .db ng,d38,p,d8,ne,d8,p,d8,na,d2,p,d8,nf,d4,p,d4
  ;   Auf          zum         letzt-
  .db nd1,d38,p,d8,nc1,d4,p,d8,nh,d2,p,d8
  ;   ten         Ge-        fecht!
  .db na,d38,p,d8,ng,d8,p,d8,ng,d2
  ;   Die        In-         ter-
  .db ng,d4,p,d8,ne1,d4,p,d4,ne1,d8,p,d8
  ;   na-         tio-        na-        le
  .db ne1,d8,p,d8,nd1,d4,p,d8,ng,d4,p,d8,nc1,d2,p,d8
  ;   er-        kaempft     das
  .db nh,d8,p,d8,na,d38,p,d8,ng,d8,p,d8
  ;   Men-       schen-      recht!
  .db na,d4,p,d8,nd1,d4,p,d8,nd1,d2
  .db MelodyEnd,MelodyEnd
;
Melody8: ; The whole gamut
  .db nA2m,d2,p,d16,nH2m,d2,p,d4
  .db nC1m,d2,p,d16,nD1m,d2,p,d16,nE1m,d2,p,d16,nF1m,d2,p,d16
    .db nG1m,d2,p,d16,nA1m,d2,p,d16,nH1m,d2,p,d4
  .db nCm,d2,p,d16,nDm,d2,p,d16,nEm,d2,p,d16,nFm,d2,p,d16
    .db nGm,d2,p,d16,nAm,d2,p,d16,nHm,d2,p,d4
  .db nc,d2,p,d16,nd,d2,p,d16,ne,d2,p,d16,nf,d2,p,d16
    .db ng,d2,p,d16,na,d2,p,d16,nh,d2,p,d4
  .db nc1,d2,p,d16,nd1,d2,p,d16,ne1,d2,p,d16,nf1,d2,p,d16
    .db ng1,d2,p,d16,na1,d2,p,d16,nh1,d2,p,d4
  .db nc2,d2,p,d16,nd2,d2,p,d16,ne2,d2,p,d16,nf2,d2,p,d16
    .db ng2,d2,p,d16,na2,d2,p,d16,nh2,d2,p,d4
  .db nc3,d2,p,d16,nd3,d2,p,d16,ne3,d2,p,d16,nf3,d2,p,d16
    .db ng3,d2,p,d16,na3,d2,p,d16,nh3,d2,p,d4
  .db nc4,d2,p,d16,nd4,d2,p,d16,ne4,d2,p,d16,nf4,d2,p,d16
    .db ng4,d2,p,d16,na4,d2,p,d16,nh4,d2,p,d4
  .db nc5,d2,p,d16,nd5,d2,p,d16,ne5,d2,p,d16,nf5,d2,p,d16
    .db ng5,d2,p,d16,na5,d2,p,d16,nh5,d2,p,d4
  .db nc6,d2,p,d16,nd6,d2,p,d16,ne6,d2,p,d16,nf6,d2,p,d16
    .db ng6,d2,p,d16,na6,d2,p,d16,nh6,d2,p,d4
  .db nc7,d2,p,d16,nd7,d2
  .db MelodyEnd,MelodyEnd
;
; Note: Update cMaxMelody after adding/removing melodies
;
; End of include file
;

Seitenanfang Hauptprogramm LCD-Routinen Musik-Code Debug-Code

4 Die Debugging-Routinen debug_code_de.inc

Dieser Code im asm-Format

;
; *********************************
; * Debug Codes fuer dcf77_m16_v4 *
; * (C)2018 by Gerhard Schmidt    *
; *********************************
;
; Debuggen der Auswertung von DCF-Signalbits in Datum/Zeit-Format
; Setze empfangene DCF-Signale auf Mo, 31.12.99-23:59
; DCF-Bytes 7:0 = 4C C8 E3 8D 64 00 00 00
DebugDcfBitConversion:
  ldi rmp,0x4C
  sts sDcfBits+7,rmp
  ldi rmp,0xC8
  sts sDcfBits+6,rmp
  ldi rmp,0xE3
  sts sDcfBits+5,rmp
  ldi rmp,0x8D
  sts sDcfBits+4,rmp
  ldi rmp,0x64
  sts sDcfBits+3,rmp
  ldi rmp,0x00
  sts sDcfBits+2,rmp
  sts sDcfBits+1,rmp
  sts sDcfBits,rmp
  mov rDcfErr,rmp
  ldi rmp,58
  mov rDcfBitCnt,rmp
  call Dcf59Ok ; Debuggen der Auswerteroutine
DbgMinuteLoop: ; Weitere Ausfuehrung anhalten
  rjmp DbgMinuteLoop
;
; Debuggen des ADC durch Anzeige der Rohresultate
;   des ausgewaehlten Kanals
DebugAdc:
  ldi rmp,debugAdcChannel|(1<<REFS0) ; Kanalauswahl
  out ADMUX,rmp
  ldi ZH,3
  ldi ZL,0
  rcall LcdPos
  ldi rmp,'A'
  rcall LcdChar
  ldi rmp,'D'
  rcall LcdChar
  ldi rmp,'C'
  rcall LcdChar
  ldi rmp,debugAdcChannel+48
  rcall LcdChar
  ldi rmp,':'
  rcall LcdChar
  LoopAdc:
    ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(ADPS0)
    out ADCSRA,rmp ; Start first conversion
    WaitAdc:
      sbic ADCSRA,ADSC
      rjmp WaitAdc
    ldi ZH,3
    ldi ZL,5
    rcall LcdPos
    in ZL,ADCL
    in ZH,ADCH
    rcall LcdHex4
    rjmp LoopAdc
;
; Debuggen der angeschlossenen Tasten durch fortlaufende
;    Anzeige ihres aktuellen Zustands
DebugKey:
  ldi ZH,3
  ldi ZL,0
  rcall LcdPos
  in ZL,pKeyI
  ldi rmp,'0'
  sbrc ZL,bKeyRI
  ldi rmp,'1'
  rcall LcdChar
  ldi rmp,'0'
  sbrc ZL,bKeyYI
  ldi rmp,'1'
  rcall LcdChar
  ldi rmp,'0'
  sbrc ZL,bKeyWI
  ldi rmp,'1'
  rcall LcdChar
  rjmp DebugKey
;
; Lautsprecher auf 1.000 Hz
DebugSpeaker:
  sbi pSpkD,bSpkD ; Ausgangspin aktivieren
  ldi rmp,High(clock/1000/2-1)
  out OCR1AH,rmp
  ldi rmp,Low(clock/1000/2-1)
  out OCR1AL,rmp
  ldi rmp,1<<COM1A0 ; Toggle bei COM1A
  out TCCR1A,rmp
  ldi rmp,(1<<WGM12)|(1<<CS10) ; CTC, Prescaler = 1
  out TCCR1B,rmp
DebugSpeaker1:
  rjmp DebugSpeaker1
;
; Initiierung des Debugging von DCF-Signaldauern
DebugDcfDur:
  ldi ZH,3
  ldi ZL,12
  rcall LcdPos
  ldi ZH,High(2*DebugDcfText)
  ldi ZL,Low(2*DebugDcfText)
  rjmp LcdDisplZ
;
; Anzeige der DCF-Signaldauer
DebugDcfDurDispl:
  ldi ZH,3
  ldi ZL,0
  sbic pDcfI,bDcfI
  ldi ZL,6
  rcall LcdPos
  mov R0,rDcfL ; Kopiere Pulsdauer
  mov R1,rDcfH
  lsl R0 ; Multiplikation mit 5
  rol R1
  lsl R0
  rol R1
  add R0,rDcfL
  adc R1,rDcfH
  rjmp LcdDec16 ; Anzeige in ms auf LCD
;
; DCF-Bits anzeigen
DebugDcfBitEcho:
  ldi ZH,3
  ldi ZL,0
  rcall LcdPos
  mov R0,rDcfBitCnt ; Bitanzahl anzeigen
  rcall LcdDec2
  ldi rmp,':'
  rcall LcdChar
  ldi rmp,' '
  rcall LcdChar
  lds ZL,sDcfBits+7 ; Neuestes Byte lesen
  ldi ZH,8
Dcf01Ok1:
  ldi rmp,'0' ; Null anzeigen
  lsl ZL
  brcc Dcf01Ok2
  ldi rmp,'1' ; Eins anzeigen
Dcf01Ok2:
  rcall LcdChar
  dec ZH
  brne Dcf01Ok1
  ret
;
; Debuggen des DCF-Signals durch Anzeige der letzten 8 Bits
DebugDcfSig1:
  ldi ZH,3 ; Langversion des Berichts auf Zeile 4
  ldi ZL,0
  rcall LcdPos
  mov R0,rDcfBitCnt ; Anzeige der Bitanzahl
  rcall LcdDec2
  ldi ZH,High(2*LcdErr6) ; Fuege Rest des Textes hinzu
  ldi ZL,Low(2*LcdErr6)
  rjmp LcdDisplZ
;
; Debuggen des DCF-Signals mit langen Fehlermeldungen
DebugDcfSig2:
  mov XH,ZH ; Sichere Z-Position im Flash nach X
  mov XL,ZL
  ldi ZH,3
  ldi ZL,0
  rcall LcdPos
  mov ZH,XH ; Stelle Z-Position im Flash wieder her
  mov ZL,XL
  rjmp LcdDisplZ
;
; Anzeige des Tastenstatus in Zeile 3
KeyState:
  ldi ZH,3
  ldi ZL,0
  rcall LcdPos
  ldi rmp,'0'
  sbic pKeyI,bKeyRI
  ldi rmp,'1'
  rcall LcdChar
  ldi rmp,'0'
  sbic pKeyI,bKeyYI
  ldi rmp,'1'
  rcall LcdChar
  ldi rmp,'0'
  sbic pKeyI,bKeyWI
  ldi rmp,'1'
  rcall LcdChar
  ldi rmp,' '
  rcall LcdChar
  ldi rmp,'f'
  rcall LcdChar
  ldi rmp,'0'
  sbrc rFlag,bKeyAct
  ldi rmp,'1'
  rcall LcdChar
  ldi rmp,' '
  rcall LcdChar
  ldi rmp,'C'
  rcall LcdChar
  mov rmp,rKeyCnt
  rcall LcdHex2
  ldi rmp,' '
  rcall LcdChar
  ldi rmp,'m'
  rcall LcdChar
  lds rmp,sKeyMode
  subi rmp,-'0'
  rcall LcdChar
  ldi rmp,' '
  rcall LcdChar
  ldi rmp,'P'
  rcall LcdChar
  lds rmp,sKeyPos
  subi rmp,-'0'
  rcall LcdChar
  ret
;
; Zeige Debug-Aktivierung in Zeile 3 an
;
DebugActive:
  ldi rmp,2
  rcall LcdLine
  ldi ZH,High(2*DebugActiveText)
  ldi ZL,Low(2*DebugActiveText)
  rjmp LcdDisplZ
;
DebugActiveText:
  .db "Debugging activated!",0,0
;
DebugDcfText:
  .db "ms hi/lo ",0
;
; Debuggen der empgfangenen DCF77-Informationen
;
DebugDcfRcvd:
  ldi ZH,3
  ldi ZL,0
  rcall LcdPos
  ldi XH,High(sDcfRcv)
  ldi XL,Low(sDcfRcv)
  ldi rmp,6
DebugDcfRcvd1:
  push rmp
  ld rmp,X+
  rcall LcdHex2
  ldi rmp,' '
  rcall LcdChar
  pop rmp
  dec rmp
  brne DebugDcfRcvd1
  ret
;
; Teste Blinken der LCD
Blinktest:
  clr XL
  rcall LcdBlink
Blinktest1:
  ldi rmp,20
DelayA:
  rcall LcdWtMax
  dec rmp
  brne DelayA
  rcall LcdFrame ; LCD auf Ausgabemaske
  ldi ZH,0
  inc XL
  mov ZL,XL
  rcall LcdPos
  rjmp Blinktest1
;
; Debugging Anzeige der Musikausgabe
DebugMusic:
  ldi ZH,3 ; Letzte Zeile
  clr ZL
  rcall LcdPos
  ldi rmp,'a'
  rcall LcdChar
  mov ZH,YH
  mov ZL,YL
  rcall LcdHex4
  ldi rmp,' '
  rcall LcdChar
  ldi rmp,'c'
  rcall LcdChar
  in ZL,OCR1AL
  in ZH,OCR1AH
  rcall LcdHex4
  ldi rmp,' '
  rcall LcdChar
  ldi rmp,'d'
  rcall LcdChar
  mov ZH,rDurH
  mov ZL,rDurL
  rjmp LcdHex4
;
; Debuggen des Statusses von TC1
DebugPlayStat:
  ldi ZH,3
  clr ZL
  rcall LcdPos
  in ZL,TIMSK
  ldi rmp,'i'
  sbrc ZL,OCIE1A
  ldi rmp,'I'
  rcall LcdChar
  in ZL,TCCR1A
  ldi rmp,'t'
  sbrc ZL,COM1A0
  ldi rmp,'T'
  sbrc ZL,COM1A1
  ldi rmp,'C'
  rjmp LcdChar
;
; Zum Datum/Uhrzeit-Debugging
DebugDateTime:
  ldi ZH,3
  ldi ZL,0
  rcall LcdPos
  ldi XH,High(sDateTime)
  ldi XL,Low(sDateTime)
  ldi ZL,7
DebugDateTime1:
  ld rmp,X+
  rcall LcdHex2
  cpi ZL,1
  breq DebugDateTime2
  ldi rmp,' '
  rcall LcdChar
  dec ZL
  rjmp DebugDateTime1
DebugDateTime2:
  ret
;
; Ende des Include-Files
;

Seitenanfang Hauptprogramm LCD-Routinen Musik-Code Debug-Code


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