Path: Home => AVR-EN => Applications => DCF77 clock m16 => Assembler source code
DCF77 clock Applications of
AVR single chip controllers AT90S, ATtiny, ATmega and ATxmega
DCF77 clock with LCD
Assembler source code

Logo

All assembler source codes for the DCF77 clock with LCD on an ATmega16

asm source link Function Link to html listing
dcf77_clock_m16_v5_en.asm Main program with debug switches, adjustable constants, hardware init, all include directives and the most relevant routines Main program
lcd.inc Routines for the 4*20-LCD access, with init, character, text and binary conversion and display in decimal and hex format LCD routines
music_code.inc Routines for designing, converting and playing musical notes and melodies via the speaker Music code
debug_code_en.inc Routines for hardware debugging and for error detection by software tools Debug code

1 The main program dcf77_clock_m16_v5_en.asm

Link to this code in asm format

;
; ***********************************************
; * ATmega16 clock with LCD and DCF77-Synchron- *
; * nization, Version 5.0 as of August 2018     *
; * (C)2018 by G.Schmidt, avr-asm-tutorial.net  *
; ***********************************************
;
; Include file for target controller type
.NOLIST
.INCLUDE "m16def.inc" ; Header file for ATmega16
.LIST
;
; ===================================
;         S W I T C H E S
; ===================================
;
; Display language 0 = German, 1 = English
.equ LangEN = 1 ; Select language
;
; ===================================
;  D E B U G G E R   S W I T C H E S
; ===================================
;
; All debug switches zero for final version
;
; 1. Debug the LCD
.equ dbgLcd = 0 ; Debug the LCD
;
; 2. Debug the ADC
.equ dbgAdc = 0 ; Debug the ADC
  .equ debugAdcChannel = 0  ; Select channel 0, 1 or 2
;
; 3. Debug the keys
.equ dbgKey = 0 ; Debug the keys attached
.equ dbgKeyState = 0 ; Debug the key's state on line 3
;
; 4. Debug the speaker
.equ dbgSpk = 0 ; Debug the speaker attached
;
; 5. Debug the DCF77 signal
.equ dbgDcfDur = 0 ; Debug the DCF77 signal input duration
.equ dbgDcfSig = 0 ; Debug DCF77 signal errors
.equ dbgDcfBits = 0 ; Debug incoming DCF77 bits
.equ dbgDcfRcvd = 0 ; Debug received DCF77 data
;
; 6. Debug music play
.equ dbgMusic = 0 ; Debug the play process of music
.equ dbgPlayStat = 0 ; Debug the status of TC1
;
;
; ============================================
;   H A R D W A R E   I N F O R M A T I O N
; ============================================
;
;         ATmega16__________
;                /          |
;       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
;               |___________|
;
; ============================================
;      P O R T S   A N D   P I N S
; ============================================
;
; Key ports
.equ pKeyO = PORTB ; Switch port output
.equ pKeyD = DDRB ; Switch direction
.equ pKeyI = PINB ; Switch input
.equ bKeyWO = PORTB0 ; White key output pin
.equ bKeyWD = DDB0 ; White key direction pin
.equ bKeyWI = PINB0 ; White key input pin
.equ bKeyYO = PORTB1 ; Yellow key output pin
.equ bKeyYD = DDB1 ; Yellow key direction pin
.equ bKeyYI = PINB1 ; Yellow key input pin
.equ bKeyRO = PORTB2 ; Red key output pin
.equ bKeyRD = DDB2 ; Red key direction pin
.equ bKeyRI = PINB2 ; Red key input pin
.equ cKeyMO = (1<<bKeyWO)|(1<<bKeyYO)|(1<<bKeyRO) ; Switch mask key outputs
.equ cKeyMI =(1<<bKeyWI)|(1<<bKeyYI)|(1<<bKeyRI) ; Input pin mask
;
; LCD ports
;   (see lcd configuration section)
;
; DCF77 ports
.EQU pDcfO = PORTD ; DCF77 Receiver Port Output
.EQU pDcfD = DDRD ; DCF77 Receiver Direction Port
.EQU pDcfI = PIND ; DCF77 Receiver Input Port
.EQU bDcfD = DDD2 ; DCF77 Reciver portpin Direction
.EQU bDcfI = PIND2 ; DCF77 Receiver portpin Input
;
; Speaker port
.equ pSpkO = PORTD ; Speaker output port
.equ pSpkD = DDRD ; Speaker direction port
.equ bSpkO = PORTD5 ; Speaker portpin output
.equ bSpkD = DDD5 ; Speaker portpin direction
;
; LED port
.EQU pLedO = PORTD ; LED cathode output port
.EQU pLedD = DDRD ; LED cathode direction port
.EQU bLedO = PORTD7 ; LED cathode output portpin
.EQU bLedD = DDD7 ; LED cathode direction portpin
;
; ADC port
.EQU pAdcO = PORTA ; ADC output port
.EQU pAdcD = DDRA ; ADC direction port
.EQU bAdcP = 0 ; ADC poti portbit
.EQU bAdcO = 1 ; ADC brightness portbit
.equ bAdcS = 2 ; DCF77 receiver signal strength
;
; ================================================
;        L C D   C O N F I G U R A T I O N
; ================================================
;
; Standard parameter set of properties/definitions
  .equ Clock = 3276800 ; X-tal clock
; LCD size:
  .equ LcdLines = 4 ; Number of lines (1, 2, 4)
  .equ LcdCols = 20 ; Number of characters per line (8..24)
; LCD bus interface
  .equ LcdBits = 8 ; Bus size (4 or 8)
  ; If 4 bit bus:
    ;.equ Lcd4High = 1 ; Bus nibble (1=Upper, 0=Lower)
  .equ LcdWait = 1 ; Access mode (0 with busy, 1 with delay loops)
; LCD data ports
  .equ pLcdDO = PORTC ; Data output port
  .equ pLcdDD = DDRC ; Data direction port
; LCD control ports und pins
  .equ pLcdCEO = PORTB ; Control E output port
  .equ bLcdCEO = PORTB3 ; Control E output portpin
  .equ pLcdCED = DDRB ; Control E direction port
  .equ bLcdCED = DDB3 ; Control E direction portpin
  .equ pLcdCRSO = PORTB ; Control RS output port
  .equ bLcdCRSO = PORTB4 ; Control RS output portpin
  .equ pLcdCRSD = DDRB ; Control RS direction port
  .equ bLcdCRSD = DDB4 ; Control RS direction portpin
; If LcdWait = 0:
  ;.equ pLcdDI = PINA ; Data input port
  ;.equ pLcdCRWO = PORTB ; Control RW output port
  ;.equ bLcdCRWO = PORTB2 ; Control RW output portpin
  ;.equ pLcdCRWD = DDRB ; Control RW direction port
  ;.equ bLcdCRWD = DDB2 ; Control RW direction portpin
; If you need binary to decimal conversion:
  .equ LcdDecimal = 1 ; If defined: include those routines
; If you need binary to hexadecimal conversion:
  .equ LcdHex = 1 ; If defined: include those routines
; If simulation in the SRAM is desired:
  ;.equ avr_sim = 1 ; 1=Simulate, 0 or undefined=Do not simulate
;
; ================================================
;    C O N S T A N T S   T O   A D J U S T
; ================================================
;
; Controller clock X-tal frequency
;   Defined under LCD configuration:
;   .equ Clock = 3276800 ; X-tal clock
;
; DCF signal durations
.equ cDcfIgnoreShortTime = 20 ; Ignore short pulses less than 20 ms
.equ cDcfMinTime = 50 ; Minimum signal duration time
.equ cDcfMaxZeroTime = 150 ; Maximum zero time
.equ cDcfMaxOneTime = 250 ; Maximum one time
.equ cDcfMinInactiveTime = 700 ; Minimum inactive time
.equ cDcfMaxInactiveTime = 1000 ; Maximum inactive time
.equ cDcfMin59Time = 1700 ; Minimum 59th second time
.equ cDcfMax59Time = 2000 ; Maximum 59th second time
.equ cDcfTimeOutTime = 2500 ; Time out
;
; LCD backlight brightness, 80 to 200
.equ cBackPeriodsTime = 3000 ; ms for renewal of backlight setting
.equ cBackMin = 10 ; Minimum brightness of the backlight
;
; DCF77 signal strength display update
.equ cSigStrTime = 2000 ; Update LCD every milliseconds
;
; Key processing: delay on key cleared
.equ cKeyCntTime = 30 ; key must be inactive for 30 ms
;
; Alarm repetitions
.equ cAlarmRepet = 3 ; Number of alarm repetions
;
; Default music play at startup
.equ cDefaultMelody = 8 ; Melody to be played at startup
;
; ===============================================
;  F I X E D + D E R I V E D   C O N S T A N T S 
; ===============================================
;
; Constants for TC0 operation
.equ cTc0Presc = 64 ; TC0 prescaler
.equ cTc0Divider = 256 ; TC0 8 bit divider
.equ cSecDiv = clock / cTc0Presc / cTc0Divider ; Divider until second over
.if cSecDiv>255
  .error "cSecDiv out of range!"
  .endif
;
; Constants for TC1 operation (audio generation)
.equ cTc1Presc = 1 ; Default prescaler
;
; Constants for TC2 operation (backlight PWM)
.equ cTc2Presc = 64 ; TC2 LCD backlight PWM
.equ cTc2Cnt = Clock/cTc2Presc/256 ; 200 Interrupts per second
.equ cBackMult = (255-cBackMin) ; Multiplier for opto sensor
;
; DCF signal durations
.equ cPulse=(cTc0Presc*cTc0Divider*1000+clock/2)/clock ; Pulse in ms
.equ cDcfIgnoreShort = cDcfIgnoreShortTime/cPulse ; Ignore shorter pulses
.equ cDcfMin = cDcfMinTime/cPulse ; Minimum pulse count
.equ cDcfMaxZero = cDcfMaxZeroTime/cPulse ; Maximum zero pulse
.equ cDcfMaxOne = cDcfMaxOneTime/cPulse ; Maximum one pulse
.equ cDcfMinInactive = cDcfMinInactiveTime/cPulse ; Minimum inactive time
.equ cDcfMaxInactive = cDcfMaxInactiveTime/cPulse ; Maximum inactive time
.equ cDcfMin59 = cDcfMin59Time/cPulse ; Minimum 59th second pulse
.equ cDcfMax59 = cDcfMax59Time/cPulse ; Maximum 59th second pulse
.equ cDcfTimeOut = cDcfTimeOutTime/cPulse ; Time out pulse
;
; Key debouncing
.equ cKeyCnt = cKeyCntTime/cPulse ; Pulses over which key has to be inactive
;
; LCD backlight update
.equ cAdcPulse = 1000*128*13*64*3/clock ; Duration of ADC rounds in ms
.equ cBackPeriods = cBackPeriodsTime/cAdcPulse ; Number of periods to update
.if cBackPeriods > 255
  .error "cBackPeriodsTime too long!"
  .endif
;
; DCF77 signal strength display update
.equ cSigStr = cSigStrTime/cAdcPulse ; Number of periods for signal strength update
;
; Debugging active
.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)
;
; ================================================
;    T I M I N G    A N D   T I M E R S
; ================================================
;
; TC0: 8-Bit-Timer for measuring
;   a) Seconds pulse advancing the clock
;   b) DCF pulse length measurements
;
;   Crystal osc ==> 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) Seconds pulse ==> downcount second divider,
;      if zero restart and set flag
;   b) DCF77: Increase DCF77 counter,
;      if excxeeding maximum count set flag
;
; TC1: 16-Bit-Timer for tone generation
; Crystal ==> TC1-Presc ==> TC1-CTC ==> 25 Hz to
; 3276800         1         0..65535    1.6384 MHz
;   CTC-Compare A int:
;     Decrease R25:R24, if zero:
;       Read next ICR value, if MSB and LSB = 0:
;         end of music, switch to speaker off
;         if MSB = 0xF4: turn speaker off for pause
;         if not: turn speaker on for tone
;       If not zero:
;         read duration word and transfer to R25:R24
;
; TC2: 8-Bit-Timer as PWM for LCD background light
;      and key processing
;   Crystal ==> TC2-Presc ==> TC2-OvFlw ==> OvFlw-Int
;   3276800 ==>    64     ==>   256         200 Hz
;   Read key input, if
;     no key pressed: if rKeyCnt>0
;       downcount rKeyCnt, if zero clear flag rKeyAct
;     any one key pressed:
;       if bKeyAct flag = clear set flag rKeyAct
;                       = set reload rKeyCnt
;
; ADC: Measuring three analogue input channels
;   Crystal ==> ADC-Presc ==> ADC-1m ==> ADC-64m ==> 3 Ch
;   3276800       128           13          64        3
;   = 10.256 cs/s or 97 ms per round
;
; ============================================
;   R E G I S T E R   D E F I N I T I O N S
; ============================================
;
; R0, R1 used for calculations and diverse purposes
; free: R2, R3
.def rMelody = R4 ; Melody select, set by key events
.def rSigStr = R5 ; Signal strength update counter
.def rBackCnt = R6 ; LCD backlight update counter
.def rKeyCnt = R7 ; Key pressed counter
.def rDcfBitCnt = R8 ; Bit counter DCF
.def rDcfErr = R9 ; DCF77 signal error counter
.def rAdc = R10 ; ADC MSB result
.def rAdcL = R11 ; ADC sum LSB
.def rAdcH = R12 ; dto., MSB
.def rDcfL = R13 ; Last pulse/pause length DCF, LSB
.def rDcfH = R14 ; dto., MSB
.def rSreg = R15 ; Saving SREG in Interrupts
.def rmp = R16 ; multi purpose register
.def rimp = R17 ; multi purpose, interrupts
.def rFlag = R18 ; flags
  .equ bSec = 0 ; Second over
  .equ bDcf = 1 ; Active DCF77 pulse flag
  .equ bDcfTO = 2 ; DCF77 signal time out
  .equ bAdc = 3 ; ADC 64 measurements complete
  .equ bKey = 4 ; A key has been pressed
  .equ bKeyAct = 5 ; Key input is active
  .equ bMinute = 6 ; Minute changed, check alarm
.def rSecDiv = R19 ; down counter seconds
.def rDcfCntL = R20 ; DCF77 pulse duration counter LSB
.def rDcfCntH = R21 ; dto., MSB
.def rAdcC = R22 ; ADC counter
; free: R23
.def rDurL = R24 ; Duration of tones, LSB
.def rDurH = R25 ; dto., MSB
; used: R27:R26 = X for various purposes
; used: R29:R28 = Y for music play
; used: R31:R30 = Z for various purposes
;
; ============================================
;       S R A M   D E F I N I T I O N S
; ============================================
;
.DSEG
.ORG  SRAM_START
; Real date and time for display
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
sDcfBits:
.byte 8
;
; DCF77 received parts
sDcfRcv:
.byte 1 ; Weekday 0..6
.byte 1 ; Day 1..31
.byte 1 ; Month 1..12
.byte 1 ; Year 0..99
.byte 1 ; Hour 0..23
.byte 1 ; Minute 0..59
.byte 1 ; Seconds 0..59
;
; Process key events
sKeyMode:
.byte 1 ; 0=off/normal
;         1=alarm activated
;         2=input date/time
;         3=input alarm time
;
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 period counter for key actions
;
sAlarm:
.byte 2 ; Stored alarm time
;
sInputData:
.byte 9 ; Date (weekday, day, month, year),
        ; time (hour, min, sec),
        ; alarm time (hour, min)
;
sAlarmRepeat:
.byte 1 ; Repeat alarm every minute
;
sMelody:
.byte 500 ; Buffer for melody to play
sMelodyEnd:
;
; ==============================================
;   R E S E T   A N D   I N T   V E K T O R S
; ==============================================
;
.CSEG
.ORG $0000
  jmp Main ; Reset vector
  jmp Int0Isr ; INT0 Vector
  reti ; INT1 Vector
  nop
  reti ; TC2COMP Vector
  nop
  jmp Tc2OvfIsr ; TC2OVF Vector
  reti ; TC1CAPT Vector
  nop
  jmp Tc1CmpAIsr ; TC1 COMPA Vector
  reti ; TC1COMPB Vector
  nop
  reti ; TC1OVF Vector
  nop
  jmp Tc0OvfIsr ; TC0OVF Vector
  reti ; SPI-STC Vector
  nop
  reti ; USART-RXC Vector
  nop
  reti ; USART-UDRE Vector
  nop
  reti ; USART-TXC Vector
  nop
  jmp AdcIsr ; ADC Vector
  reti ; EE_RDY Vector
  nop
  reti ; ANA_COMP Vector
  nop
  reti ; TWI Vector
  nop
  reti ; INT2 Vector
  nop
  reti ; TC0_COMP Vector
  nop
  reti ; SPM_RDY Vector
  nop
;
; ==========================================
;    I N T E R R U P T   S E R V I C E
; ==========================================
;
; DCF77 receiver pulse input
Int0Isr:
  in rSreg,SREG ; Save SREG
  mov rimp,rDcfCntL ; Suppress zero counts
  cpi rimp,cDcfIgnoreShort ; Shorter than minimum pulses?
  brcs Int0IsrRet ; Yes, ignore
  ; Valid DCF signal detected
  mov rDcfL,rDcfCntL ; Copy counter LSB
  mov rDcfH,rDcfCntH ; dto., MSB
  sbr rFlag,1<<bDcf ; Set flag
  clr rDcfCntL ; Clear LSB counter
  clr rDcfCntH ; dto., MSB
Int0IsrRet:
  out SREG,rSreg ; Restore SREG
  reti
;
; TC0 overflow int, DCF77 pulse width timer
Tc0OvfIsr:
  in rSreg,SREG ; Save Sreg
  dec rSecDiv ; Decrease seconds divider
  brne Tc0OvfIsr1 ; Not zero
  ldi rSecDiv,cSecDiv ; Restart counter
  sbr rFlag,1<<bSec ; Set seconds flag
Tc0OvfIsr1:
  inc rDcfCntL ; Increase DCF pulse counter
  brne Tc0OvfIsr2 ; No carry to MSB
  inc rDcfCntH ; Carry to MSB
Tc0OvfIsr2:
  cpi rDcfCntH,High(cDcfTimeOut) ; MSB at time out?
  brcs Tc0OvfIsr3 ; No
  cpi rDcfCntL,Low(cDcfTimeOut) ; LSB at time out?
  brcs Tc0OvfIsr3 ; No
  sbr rFlag,1<<bDcfTO ; Set time-out-flag
  clr rDcfCntL ; Restart DCF counter
  clr rDcfCntH
Tc0OvfIsr3:
  out SREG,rSreg ; Restore SREG
  reti
;
; TC1 compare match A interrupt, playing music
Tc1CmpAIsr:
  in rSreg,SREG ; Save SREG
  sbiw rDurL,1 ; Downcount duration counter
  brne Tc1CmpAIsrRet
  ld rimp,Y+ ; Read MSB first
  out OCR1AH,rimp ; To MSB compare A
  cpi rimp,0xFF ; MSB speak off?
  ld rimp,Y+ ; Read LSB next
  out OCR1AL,rimp ; To LSB compare A
  brne Tc1CmpAIsr1 ; Sprecher not off
  cpi rimp,0xFF ; LSB speaker off?
  brne Tc1CmpAIsr1 ; Do not turn speaker off
  ldi rimp,(1<<COM1A1) ; Speaker off
  rjmp Tc1CmpAIsr2
Tc1CmpAIsr1:
  ldi rimp,(1<<COM1A0) ; OCR1A to toggle
Tc1CmpAIsr2:
  out TCCR1A,rimp ; Set OCR1A toggle/clear
  ld rDurH,Y+ ; Read MSB duration
  ld rDurL,Y+ ; Read LSB duration
  mov rimp,rDurH ; Test for 0x0000
  or rimp,rDurL
  brne Tc1CmpAIsrRet ; Continue music lay
  ; Music play off
  ldi rimp,1<<COM1A1 ; OCR1A to clear
  out TCCR1A,rimp
  ldi rimp,(1<<TOIE0)|(1<<TOIE2) ; Disable TC1 Int
  out TIMSK,rimp
  ldi rimp,High(clock / 2000 - 1) ; CTC to 1 kHz
  out OCR1AH,rimp
  ldi rimp,Low(clock / 2000 - 1)
  out OCR1AL,rimp
Tc1CmpAIsrRet:
  out SREG,rSreg ; Stelle SREG wieder her
  reti
;
; TC2 overflow int, Backligth PWM, key rocessing
Tc2OvfIsr:
  in rSreg,SREG ; Save SREG
  in rimp,pKeyI ; Read key input
  andi rimp,cKeyMI ; Isolate keys in input port
  cpi rimp,0x07 ; All keys clear?
  brne Tc2OvfIsrKey ; No, process key
  tst rKeyCnt ; Key count clear?
  breq Tc2OvfIsrRet ; Yes, ignore
  dec rKeyCnt ; Decrease key counter
  brne Tc2OvfIsrRet ; Not at zero
  cbr rFlag,1<<bKeyAct ; Clear key active flag
  rjmp Tc2OvfIsrRet
Tc2OvfIsrKey:
  sbrs rFlag,bKeyAct ; Flag bKeyAct already set?
  sbr rFlag,(1<<bKeyAct)|(1<<bKey) ; Set key pressed flag
  ldi rimp,cKeyCnt ; Set key count to default delay
  mov rKeyCnt,rimp
  in rMelody,TCNT1L ; Read counter 1 as random generator
Tc2OvfIsrRet:
  out SREG,rSreg ; Restore SREG
  reti
;
; ADC complete interrupt service routine
AdcIsr:
  in rSreg,SREG ; Save SREG
  in rimp,ADCL ; Read LSB result
  add rAdcL,rimp ; Add to sum
  in rimp,ADCH ; Read MSB result
  adc rAdcH,rimp ; Add to sum with carry
  dec rAdcC ; Decrease counter
  breq AdcIsr1 ; Counter is zero
  ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(ADPS0)
  out ADCSRA,rimp ; Start next conversion
  out SREG,rSreg ; Restore SREG
  reti
AdcIsr1:
  mov rAdc,rAdcH ; Copy MSB
  clr rAdcL ; Clear sum
  clr rAdcH
  sbr rFlag,1<<bAdc ; Set ADC flag
  ldi rAdcC,64 ; Restart counter
  out SREG,rSreg ; Restore SREG
  reti
;
; ============================================
;    M A I N  P R O G R A M   I N I T
; ============================================
;
Main:
; Init Stack
  ldi rmp, HIGH(RAMEND) ; Init MSB stack
  out SPH,rmp
  ldi rmp, LOW(RAMEND) ; Init LSB stack
  out SPL,rmp
  ; Init the key input pins
  cbi pKeyD,bKeyWD ; White key as input
  sbi pKeyO,bKeyWO ; White key pull up on
  cbi pKeyD,bKeyYD ; Yellow key as input
  sbi pKeyO,bKeyYO ; Yellow key pull up on
  cbi pKeyD,bKeyRD ; Red key as input
  sbi pKeyO,bKeyRO ; Red key pull up on
  ; Init TC2 as LCD Backlight PWM
  sbi pLedD,bLedD ; set portbit as output
  ldi rmp,0x80 ; Half light
  out OCR2,rmp
  ldi rmp,(1<<WGM21)|(1<<WGM20)|(1<<COM21)|(1<<COM20)|(1<<CS22) ; Fast PWM, presc=64
  out TCCR2,rmp
  ; Previous version: Init the LCD-Ports and LCD
    ;cbi pLcdCO,bLcdE
    ;sbi pLcdCD,bLcdE ; Enable Output Ausgang
    ;sbi pLcdCD,bLcdRs ; RS output pin as output
    ;cbi pLcdCO,bLcdRs ; Clear RS
    ;sbi pLcdCD,bLcdRw ; R/W output pin as output
    ;cbi pLcdCO,bLcdRw ; R/W to low
    ;ldi rmp,0xFF ; LCD data port as output
    ;out pLcdDD,rmp
  call LcdInit ; Init the LCD
  ldi ZH,High(2*CodeChars) ; Special characters
  ldi ZL,Low(2*CodeChars) 
  call LcdSpec
  ldi ZH,0
  ldi ZL,0
  call LcdPos
  ldi ZH,High(2*LcdTxtInit1)
  ldi ZL,Low(2*LcdTxtInit1)
  call LcdText
; If any debugging enabled
.if fDbg
  call DebugActive ; Outut message on LCD
  .endif
; If debugging the LCD: stop here
.if dbgLcd == 1
  LoopLcd:
    rjmp LoopLcd ; Stop operation
  .endif
  ; Delay for LCD operation mask
  ldi rmp,100
Delay:
  call LcdWait50ms
  dec rmp
  brne Delay
; If debugging the ADC: perform measurements
.if dbgAdc == 1
  jmp DebugAdc ; Perform measurements
  .endif
; If debugging the keys: display key inputs 
.if dbgKey == 1
  jmp DebugKey ; Display key state
  .endif
; If debugging the speaker: play tone
.if dbgSpk == 1
  jmp DebugSpeaker ; Play tone
  .endif
; If debugging DCF77 signals: display DCF input signals
.if dbgDcfDur == 1 ; Debug DCF77 signal duration
  call DebugDcfDur ; Display signals
  .endif
  ; Start normal screen frame
  ldi ZH,0 ; Home position of the LCD
  ldi ZL,0
  call LcdPos
  ldi ZH,High(2*LcdTxtFrame) ; Point to text frame
  ldi ZL,Low(2*LcdTxtFrame)
  call LcdText ; LCD to output mask
  call LcdNoBlink ; No blinking
  ; Start AD conversion
  clr rAdcL ; Clear ADC sum
  clr rAdcH
  ldi rAdcC,64 ; Perform 64 measurements
  ldi rmp,1<<REFS0 ; Start with channel 0
  out ADMUX,rmp
  ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(ADPS0)
  out ADCSRA,rmp ; Start first conversion
  ; Init the SRAM content
  rcall InitDT
  lds rmp,sDateTime+7 ; Copy alarm time to sAlarm
  sts sAlarm,rmp
  lds rmp,sDateTime+8
  sts sAlarm+1,rmp
  rcall SetAlarmOff ; Clear alarm
  rcall DateTimeLcd
  rcall DcfClearBuffer ; Init the DCF receiver buffer
   ; Init flags and software counters
  clr rFlag ; Clear all flags
  clr rDcfErr ; Clear DCF77 error counter
  clr rDcfBitCnt ; Clear DCF77 bit counter
  ldi rmp,cBackPeriods ; Backlight update period
  mov rBackCnt,rmp
  clr rmp ; Init key input mode
  sts sKeyMode,rmp ; Key input mode off
  sts sAlarmRepeat,rmp ; Alarm repeat counter
  rcall DisplMenue ; Init menue line
  ; Init TC0 as time counter DCF77 signal duration timer
  ldi rSecDiv,cSecDiv ; Seconds down counter
  ldi rmp,(1<<CS01)|(1<<CS00) ; prescaler 64
  out TCCR0,rmp
  ; Init TC1 as music player, CTC mode
  ldi rmp,High(clock/2000-1) ; Init compare A
  out OCR1AH,rmp
  ldi rmp,Low(clock/2000-1)
  out OCR1AL,rmp
  ldi rmp,(1<<COM1A1) ; Compare match clear
  out TCCR1A,rmp
  ldi rmp,(1<<WGM12)|(1<<CS10) ; CTC-A, prescaler=1
  out TCCR1B,rmp
  sbi pSpkD,bSpkD ; OCR1A as output
  ; Init Music output
  ldi rmp,cDefaultMelody ; Melody to be played at startup
  rcall MusicConvert ; Play melody
  ; Enable timer interrupts
  ldi rmp,(1<<TOIE2)|(1<<OCIE1A)|(1<<TOIE0) ; Timer Ints TC0 and TC2
  out TIMSK,rmp
  ; Sleep mode, external interrupts
  ldi rmp,(1<<SE)|(1<<ISC00) ; Enable sleep and INT0
  out MCUCR,rmp
  ; External INT0 enable
  ldi rmp,1<<INT0
  out GICR,rmp
  sei ; Enable interrupts
;
; ==============================
;     P R O G R A M   L O O P
; ==============================
;
Loop:
  sleep ; Go to sleep
  nop ; Dummy for wake-up
  sbrc rFlag,bDcf ; DCF77 active pulse?
  rcall DcfAct ; Active pulse DCF77
  sbrc rFlag,bDcfTO ; DCF77 time-out?
  rcall DcfTimeOut ; Time out of the DCF signal
  sbrc rFlag,bSec ; second is over?
  rcall IncSec ; increase seconds
  sbrc rFlag,bMinute ; Check minute for alarm?
  rcall CheckMinute ; Yes check minute
  sbrc rFlag,bKey ; Has a key been pressed?
  rcall KeyPress ; Process the pressed key
  sbrc rFlag,bAdc ; ADC complete?
  rcall AdcCmplt ; ADC measurements complete
; If key state to be debugged
  .if dbgKeyState == 1
    call KeyState ; Display key state
    .endif
  rjmp loop ; Back to Loop
;
; ====================================
;  P R O C E S S   D C F  P U L S E S
; ====================================
;
; Active pulse DCF77
DcfAct:
  cbr rFlag,1<<bDcf ; clear flag
; If debugging DCF signal duration
  .if dbgDcfDur == 1
    call DebugDcfDurDispl ; Display DCF77 signal duration
    .endif
  ldi rmp,Low(cDcfMin) ; Check minimum
  sub rmp,rDcfL
  ldi rmp,High(cDcfMin)
  sbc rmp,rDcfH
  ldi rmp,1 ; Error number 1
  brcc GotoDcfErr
DcfAct1:
  ldi rmp,Low(cDcfMaxOne) ; Check One-Bit
  sub rmp,rDcfL
  ldi rmp,High(cDcfMaxOne)
  sbc rmp,rDcfH
  brcc Dcf01Ok ; Low- or High-Bit ok
  ldi rmp,Low(cDcfMinInactive) ; Check shorter than inactive?
  sub rmp,rDcfL
  ldi rmp,High(cDcfMinInactive)
  sbc rmp,rDcfH
  ldi rmp,2 ; Error number 2
  brcc GotoDcfErr
DcfAct2:
  ldi rmp,Low(cDcfMaxInactive) ; Check shorter than inactive?
  sub rmp,rDcfL
  ldi rmp,High(cDcfMaxInactive)
  sbc rmp,rDcfH
  brcc DcfOk ; Duration ok
  ldi rmp,Low(cDcfMin59) ; Check minimum 59th bit?
  sub rmp,rDcfL
  ldi rmp,High(cDcfMin59)
  sbc rmp,rDcfH
  ldi rmp,3 ; Error number 3
  brcc GotoDcfErr
  ldi rmp,Low(cDcfMax59) ; Check maximum 59th bit?
  sub rmp,rDcfL
  ldi rmp,High(cDcfMax59)
  sbc rmp,rDcfH
  brcc Dcf59Ok ; Missing bit ok
  ldi rmp,4 ; Error # 4
  rjmp GotoDcfErr
DcfOk: ; Signal duration ok
  ret ; Time ok
GotoDcfErr:
  mov rDcfErr,rmp ; Store error number
  rjmp DcfReport ; Report error
;
; The DCF77 receiver detected a correct bit
;   Shift this into the SRAM buffer
Dcf01Ok:
  ldi rmp,Low(cDcfMaxZero) ; Zero or one?
  sub rmp,rDcfL
  ldi rmp,High(cDcfMaxZero)
  sbc rmp,rDcfH            ; Carry set if one
  ldi ZH,High(sDcfBits+8) ; Point Z to bit buffer
  ldi ZL,Low(sDcfBits+8)
  ld rmp,-Z ; Shift into byte 7
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Shift into byte 6
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Shift into byte 5
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Shift into byte 4
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Shift into byte 3
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Shift into byte 2
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Shift into byte 1
  ror rmp
  st Z,rmp
  ld rmp,-Z ; Shift into byte 0
  ror rmp
  st Z,rmp
  inc rDcfBitCnt ; Count received bits
; If debugging DCF bits is on
.if dbgDcfBits == 1
  call DebugDcfBitEcho ; Echo DCF bits on LCD
  .endif
  ret
;
; The DCF77 receiver detected the missing 59th second
;   Check if all bits are correct and set date and time
Dcf59Ok:
  ; Check all signal durations
  tst rDcfErr ; All signals durations ok?
  breq Dcf59Ok1 ; Yes
  ; Signaldauer nicht ok
  clr rDcfBitCnt ; Restart bit counter
  clr rDcfErr ; Restart error counter
  ret
Dcf59Ok1:
  ; Have 59 bits been received correct?
  mov rmp,rDcfBitCnt
  cpi rmp,59 ; Only 58 or less bits received?
  breq Dcf59Ok2 ; Correct number of bits
  ; Error detected
  ldi rmp,6 ; Display error message number of bits
  mov rDcfErr,rmp ; Error # 6
  rcall DcfReport ; Display errors
  clr rDcfBitCnt ; Restart bit counter
  clr rDcfErr ; Restart error register
  ret
Dcf59Ok2:
  ; Start bit analysis
  clr rDcfBitCnt ; Restart bit count
  ldi rmp,7 ; Start with error #7
  mov rDcfErr,rmp
  ; Check bit 1 = 0
  lds rmp,sDcfBits ; Read least significant byte
  sbrc rmp,5 ; DCF bit 1 is bit 5
  rjmp DcfReport ; Error #7
  ; Check Minute ones
  inc rDcfErr ; Next error #
  lds ZL,sDcfBits+3 ; Read minute ones
  lsr ZL ; Shift right
  lsr ZL ; Another shift right
  andi ZL,0x0F ; Isolate ones
  cpi ZL,10 ; Larger than 9?
  brcs Dcf59Ok4 ; No, fine
  rjmp DcfReport ; Error report
Dcf59Ok4:
  ; Check Minute tens
  inc rDcfErr ; Next error #
  lds ZH,sDcfBits+4 ; Read Minute 40s
  andi ZH,0x01 ; Isolate last Bit
  lds rmp,sDcfBits+3 ; Read Minute 20s and 10s
  lsl rmp ; Left shift 20s
  rol ZH ; and roll into ZH
  lsl rmp ; Left shift 10s
  rol ZH ; and roll into ZH
  andi ZH,0x07 ; Isolate 40/20/10 minutes
  cpi ZH,6 ; Larger than 59?
  brcs Dcf59Ok5 ; No
  rjmp DcfReport ; Error reporting
Dcf59Ok5:
  rcall BitCombine ; Combine tens and ones
  sts sDcfRcv+5,ZL ; Save minutes in buffer
  ; Check parity bit of minutes
  inc rDcfErr ; Next error #
  lds ZL,sDcfBits+3 ; Read Minute ones
  lds ZH,sDcfBits+4 ; Read Minute tens
  lsr ZH ; DShift together
  ror ZL
  lsr ZH
  ror ZL
  rcall CheckParityZL ; Check minutes parity
  breq Dcf59Ok6 ; Fine
  rjmp DcfReport ; Error reporting
Dcf59Ok6:
  ; Get Hours ones
  inc rDcfErr ; Next error #
  lds ZL,sDcfBits+4 ; Read hour ones
  lsr ZL
  lsr ZL
  andi ZL,0x0F ; Isolate hour ones
  cpi ZL,10 ; Larger than 9
  brcs Dcf59Ok7 ; No
  rjmp DcfReport ; Error reporting
Dcf59Ok7:
  ; Get Hours tens
  inc rDcfErr ; Next error #
  lds ZH,sDcfBits+4 ; Read 10-hours
  lsl ZH ; Shift left
  rol ZH ; and roll in from the right
  rol ZH
  andi ZH,0x03 ; Isolate 20/10s
  cpi ZH,0x03 ; Larger than 20
  brcs Dcf59Ok8 ; Fine
  rjmp DcfReport ; Error reporting
Dcf59Ok8:
  rcall BitCombine ; Combine tens and ones
  sts sDcfRcv+4,ZL ; Save hours in buffer
  ; Check parity hours
  inc rDcfErr ; Next error #
  lds ZL,sDcfBits+4 ; Read hours ones
  lds ZH,sDcfBits+5 ; and tens again
  lsr ZH ; Combine in ZL
  ror ZL
  lsr ZL
  rcall CheckParityZL ; Check hours parity
  breq Dcf59Ok9 ; Fine
  rjmp DcfReport ; Error reporting
Dcf59Ok9:
  ; Get day ones
  inc rDcfErr ; Next error #
  lds ZL,sDcfBits+5 ; Read day ones
  lsr ZL ; Shift right
  andi ZL,0x0F ; Isolate day ones
  cpi ZL,10 ; Larger than 9?
  brcs Dcf59Ok10 ; No, fine
  rjmp DcfReport ; Error reporting
Dcf59Ok10:
  ; Get day tens
  inc rDcfErr ; Next error #
  lds ZH,sDcfBits+5 ; Read day tens
  swap ZH ; Upper to lower nibble
  lsr ZH ; One bit right
  andi ZH,0x03 ; Isolate bit 0 and 1
  rcall BitCombine ; Combine ZH and ZL in ZL
  sts sDcfRcv+1,ZL ; Save days in buffer
  tst ZL ; day = 0?
  brne Dcf59Ok11 ; No, fine
  rjmp DcfReport ; Error reporting
Dcf59Ok11:
  ; Get Weekday
  inc rDcfErr ; Next error #
  lds ZL,sDcfBits+5 ; Read weekday, part 1
  lds ZH,sDcfBits+6 ; Read weekday, part 2
  lsl ZL ; Combine in ZH
  rol ZH
  andi ZH,0x07 ; Isolate weekday
  brne Dcf59Ok12 ; Not zero, fine
  rjmp DcfReport ; Error reporting
Dcf59Ok12:
  dec ZH ; Decrease weekday (0 to 6)
  sts sDcfRcv,ZH ; Save weekday in buffer
  ; Get month
  inc rDcfErr ; Next error #
  lds rmp,sDcfBits+6 ; Read month
  andi rmp,0x7C ; Isolate month tens and ones
  lsr rmp ; Shift left
  lsr rmp
  sbrc rmp,4 ; Month tens?
  subi rmp,-10-16 ; Yes, add ten and clear bit 4
  sts sDcfRcv+2,rmp ; Store month in buffer
  tst rmp ; Month = 0?
  brne Dcf59Ok13 ; No, fine
  rjmp DcfReport ; Error reporting
Dcf59Ok13:
  ; Get year ones
  inc rDcfErr ; Next error #
  lds ZL,sDcfBits+6 ; Read year ones, part 1
  lds ZH,sDcfBits+7 ; Read year ones, part 2
  lsl ZL
  rol ZH
  mov ZL,ZH
  andi ZL,0x0F ; Isolate year ones
  cpi ZL,10 ; Larger than 9?
  brcs Dcf59Ok14 ; No, fine
  rjmp DcfReport ; Error reorting
Dcf59Ok14:
  ; Get year tens
  inc rDcfErr ; Next error #
  lds ZH,sDcfBits+7 ; Read year tens
  lsr ZH
  lsr ZH
  lsr ZH
  andi ZH,0x0F ; Isolate year tens
  cpi ZH,10 ; Larger than 9?
  brcs Dcf59Ok15 ; No, fine
  rjmp DcfReport ; Error reporting
Dcf59Ok15:
  rcall BitCombine ; Combine years ones and tens
  sts sDcfRcv+3,ZL ; Store in buffer
  ; Check date parity
  inc rDcfErr ; Next error #
  lds ZL,sDcfBits+5 ; Read date byte 1
  andi ZL,0xFE ; Clear last bit
  rcall CheckParityZL ; Start checking parity
  lds ZL,sDcfBits+6 ; Read date byte 2
  rcall CheckParityZL1 ; Continue checking parity
  lds ZL,sDcfBits+7 ; Read date byte 3
  rcall CheckParityZL1 ; Continue checking parity
  breq Dcf59Ok16 ; Date parity fine
  rjmp DcfReport ; Error reporting
Dcf59Ok16:
  lds rmp,sKeyMode ; Key input mode active?
  cpi rmp,2 ; Mode 2 active?
  breq Dcf59Ok18 ; Yes, skip setting date/time
  clr rmp ; Clear seconds in buffer
  sts sDcfRcv+6,rmp
  ldi XH,High(sDateTime) ; Point X to date/time buffer
  ldi XL,Low(sDateTime)
  ldi ZH,High(sDcfRcv) ; Point Z to received buffer
  ldi ZL,Low(sDcfRcv)
  ldi rmp,7 ; Copy seven bytes
Dcf59Ok17:
  ld R0,Z+ ; Read received
  st X+,R0 ; Copy to date/time
  dec rmp ; Count down
  brne Dcf59Ok17 ; Continue copying
  ; Restart at seconds = 0
  ; Delay by signal and byte analysis: 486 clock cycles
  ; Equals 486/64 Timer 0 ticks
  ; Equals 486/64/256 Timer 0 overflows
  ; = 0.03 Delay by analysis
  ; = Below correction necessity for cSecDiv 
  ldi rSecDiv,cSecDiv ; Restart seconds down counter
  ldi XH,High(sDateTime+3) ; Point X to Year
  ldi XL,Low(sDateTime+3)
  rcall DateTimeLcd ; Renew date and time on display
Dcf59Ok18:
  sbr rFlag,1<<bMinute ; Set minute change flag
  clr rDcfErr ; Error # = 0
  rjmp DcfReport
;
; Combines ones bits in ZL with tens bits in ZH
;   Result in ZL
BitCombine:
  mov rmp,ZH ; Copy tens
  add ZH,ZH ; Multiply by 2
  add ZH,ZH ; Multiply by 4
  add ZH,rmp ; Add one to 5
  add ZH,ZH ; Multiply by 10
  add ZL,ZH ; Add tens to ones
  ret
;
; Check even parity of ZL, Z set if even
;   Counts the number of ones
;   Returns Z set if number is even
CheckParityZL: ; Start parity check
  clr rmp
CheckParityZL1: ; Continue parity check
  tst ZL ; No ones any more?
  breq CheckParityZL2 ; Yes
  lsr ZL ; Shift to carry
  brcc CheckParityZL1 ; Bit is zero
  inc rmp ; Count bit
  rjmp CheckParityZL1 ; Continue
CheckParityZL2:
  andi rmp,0x01 ; Last bit of counter
  ret
;
; Clears the receiver buffer in SRAM
DcfClearBuffer:
  ldi XH,High(sDcfRcv) ; To start of buffer
  ldi XL,Low(sDcfRcv)
  ldi ZL,0xFF ; Fill with FF
  ldi rmp,6 ; Six bytes
DcfClearBuffer1:
  st X+,ZL
  dec rmp
  brne DcfClearBuffer1
  ret
;
; DCF signal has timed out
;   Detected in TC0 interrupt service routine
;   Display error #5
DcfTimeOut:
  cbr rFlag,1<<bDcfTO ; Clear that flag
  ldi rmp,5 ; Error # 5
  mov rDcfErr,rmp
  rjmp DcfReport ; Display
;
; Display DCF report
;   Error # in rDcfErr
DcfReport:
  ldi ZH,2 ; To short report position on LCD
  ldi ZL,cDcfPos ; Position of the report
  call LcdPos ; Position the LCD
  mov rmp,rDcfErr ; rDcfErr to text table
  cpi rmp,6 ; Number of received bits false?
  brne DcfReport1 ; No, other error #
  mov rmp,rDcfBitCnt ; Copy number of bits received
  call LcdDec2 ; Write in decimal to LCD
  rjmp DcfReport3 ; Check additional reorting
; Error number not 6
DcfReport1:
  cpi rmp,cDcfErrMax ; Check inside error numbers
  brcs DcfReport2 ; Fine
  ldi rmp,cDcfErrMax-1 ; Set to last error number
DcfReport2:
  ; Multiply error number by 22
  clr R0 ; R0 is Carry adder
  lsl rmp ; Multiply by 2
  lsl rmp ; Multiply by 4
  mov XL,rmp ; Copy to X = 4-fold
  clr XH
  lsl XL ; Multiply by 8
  rol XH
  lsl XL ; Multiply by 16
  rol XH
  add XL,rmp ; Add 4 to yield 20-fold
  adc XH,R0 ; Add zero and carry
  lsr rmp ; Division by 2 to yield 2-fold
  add XL,rmp ; Add to get 22-fold
  adc XH,R0 ; Add zero and carry
  ; Add to table start
  ldi ZH,High(2*DcfReports) ; Point Z to text table
  ldi ZL,Low(2*DcfReports)
  add ZL,XL ; Add table displacement
  adc ZH,XH
  ; Display short report
  lpm rmp,Z+ ; Read first character
  call LcdChar ; and display
  lpm rmp,Z+ ; Read second character
  rcall LcdChar ; and display
DcfReport3:
; If the long version shall be displayed
  .if dbgDcfSig == 1
    call DebugDcfSig2 ; Display long version
    .endif
; If received signals shall be dislayed
  .if dbgDcfRcvd == 1
    call DebugDcfRcvd ; Display received signals
    rcall DcfClearBuffer ; Set the DCF buffer to FF
    .endif
  rjmp ActivatePos ; If in input mode set cursor
;
; All DCF reports
;   char 1+2: short abbreviation
;   char 3..18: Long version for debug
DcfReports:
; If english version selected
.if LangEN == 1
  .db "okAll DCF bits ok.  ",0xFE,0xFE ; 0 DCF no errors
  .db "s1Signal too short! ",0xFE,0xFE ; 1
  .db "s2Signal in between!",0xFE,0xFE ; 2
  .db "s3Signal too long!  ",0xFE,0xFE ; 3
  .db "s4Signal minute!    ",0xFE,0xFE ; 4
  .db "s5No signal!        ",0xFE,0xFE ; 5
  .db "nnnn"
LcdErr6:
  .db " bits!          ",0xFE,0xFE ; 6
  .db "E0Bit 1 not zero!   ",0xFE,0xFE ; 7
  .db "MOMinute ones!      ",0xFE,0xFE ; 8
  .db "MTMinute tens!      ",0xFE,0xFE ; 9
  .db "PMMinute parity!    ",0xFE,0xFE ; 10
  .db "HOHours ones!       ",0xFE,0xFE ; 11
  .db "HTHours tens!       ",0xFE,0xFE ; 12
  .db "PHHours parity!     ",0xFE,0xFE ; 13
  .db "D1Day ones!         ",0xFE,0xFE ; 14
  .db "D0Day zero!         ",0xFE,0xFE ; 15
  .db "W0Weekday zero!     ",0xFE,0xFE ; 16
  .db "M0Month zero!       ",0xFE,0xFE ; 17
  .db "YOYear ones!        ",0xFE,0xFE ; 18
  .db "YTYear tens!        ",0xFE,0xFE ; 19
  .db "PDParity date!      ",0xFE,0xFE ; 20
  .db "ueUnknown error!    ",0xFE,0xFE ; 21
  .else
; If german version selected
  .db "okAlle DCF Bits ok. ",0xFE,0xFE ; 0 DCF ohne Fehler
  .db "s1Signal zu kurz!   ",0xFE,0xFE ; 1
  .db "s2Signal dazwischen!",0xFE,0xFE ; 2
  .db "s3Signal zu lang!   ",0xFE,0xFE ; 3
  .db "s4Signal Minute!    ",0xFE,0xFE ; 4
  .db "s5Kein Signal!      ",0xFE,0xFE ; 5
  .db "nnnn"
LcdErr6:
  .db " Bits!          ",0xFE,0xFE ; 6
  .db "E0Bit 1 nicht Null! ",0xFE,0xFE ; 7
  .db "MEMinuten Einer!    ",0xFE,0xFE ; 8
  .db "MZMinuten Zehner!   ",0xFE,0xFE ; 9
  .db "PMMinuten Paritaet! ",0xFE,0xFE ; 10
  .db "SEStunden Einer!    ",0xFE,0xFE ; 11
  .db "SZStunden Zehner!   ",0xFE,0xFE ; 12
  .db "PSStunden Paritaet! ",0xFE,0xFE ; 13
  .db "TETag Einer!        ",0xFE,0xFE ; 14
  .db "T0Tag ist Null!     ",0xFE,0xFE ; 15
  .db "W0Wochentag Null!   ",0xFE,0xFE ; 16
  .db "M0Monat gleich Null!",0xFE,0xFE ; 17
  .db "JEJahr Einer!       ",0xFE,0xFE ; 18
  .db "JZJahr Zehner!      ",0xFE,0xFE ; 19
  .db "PDParitaet Datum!   ",0xFE,0xFE ; 20
  .db "ufUnbek. Fehler!    ",0xFE,0xFE ; 21
  .endif
.equ cDcfErrMax = 22 ; Last error
;
; =====================================
;  S E C O N D S  P R O C E S S I N G
; =====================================
;
IncSec:
  cbr rFlag,1<<bSec ; Clear flag
; If music play debugging is selected
  .if dbgMusic == 1
    call DebugMusic ; Display status
    .endif
; If TC1 play stat debugging is selected
  .if dbgPlayStat == 1
    call DebugPlayStat
    .endif
  lds rmp,sKeyMode ; Key mode input on date/time?
  cpi rmp,2
  brne IncSec1 ; Input mode not active
  ret ; Input active, do not change time
IncSec1:
  ldi ZH,High(sDateTime) ; Point Z to sDateTime
  ldi ZL,Low(sDateTime)
  ldd rmp,Z+6
  inc rmp ; Increase seconds
  std Z+6,rmp ; and store
  cpi rmp,60 ; 60 seconds reached?
  brcc IncMin
  rjmp SecLcd ; Display seconds
  ; Increase minutes
IncMin:
  clr rmp ; Seconds to zero
  std Z+6,rmp
  ldd rmp,Z+5 ; Read minutes
  inc rmp ; Increase minutes
  sbr rFlag,1<<bMinute ; Set minute flag
  std Z+5,rmp ; and save
  cpi rmp,60 ; 60 minutes over?
  brcs MinLcd ; No, display minutes and seconds
  ; Increase hours
  clr rmp ; Restart minutes
  std Z+5,rmp ; and save in date/time buffer
  ldd rmp,Z+4 ; Read hours
  inc rmp ; Increase hours
  std Z+4,rmp ; Seichern Stunden
  cpi rmp,24 ; 24 Stunden erreicht?
  brcs HourLcd ; Hours to LCD
  clr rmp ; Clear hours
  std Z+4,rmp ; in sDateTime
  ; Increase weekday
  ld rmp,Z ; Read weekday
  inc rmp ; Increase
  cpi rmp,7 ; Weekday = 7?
  brcs IncDay ; No
  ldi rmp,0 ; Restart weekdays
  ; Increase day
IncDay: ; Next day
  st Z,rmp ; Store weekday
  ldd rmp,Z+1 ; Read day
  inc rmp ; Increase day
  std Z+1,rmp ; Save day
  rcall GetDaysOfMonth
  cp rmp,XH ; Compare with days of month
  brcs WeekDayLcd ; Display weekday
  ; Increase Month
  ldi rmp,1 ; Day = 1
  std Z+1,rmp ; Day = 1
  ldd rmp,Z+2 ; Read month
  inc rmp ; Increase month
  std Z+2,rmp ; Store month
  cpi rmp,13 ; Month = 13?
  brcs MonthLcd ; No, display on LCD
  ; Increase year
  ldi rmp,1 ; Month = 1
  std Z+2,rmp ; Store month
  ldd rmp,Z+3 ; Read year
  inc rmp ; Increase year
  std Z+3,rmp ; Store year
  cpi rmp,100 ; Year = 100?
  brcs YearLcd ; Dislay
  ldi rmp,0 ; Year = 0
  std Z+3,rmp ; Store year
; Date and time to LCD
; Format:  Tu-01.05.18-23:59:59
; Position 01234567890123456789
; Z points to position in SRAM
DateTimeLcd:
  ldi ZH,High(sDateTime) ; Point to sDateTime
  ldi ZL,Low(sDateTime)
YearLcd:
  ldd rmp,Z+3 ; Z points to buffer start
  ldi XL,9 ; Column year on LCD
  rcall NumberLcd ; Display decimal
MonthLcd:
  ldd rmp,Z+2 ; Read month
  ldi XL,6 ; Column month on LCD
  rcall NumberLcd ; Display decimal
WeekDayLcd:
  rcall DisplWeekday ; Display weekday
DayLcd:
  ldd rmp,Z+1 ; Read day
  ldi XL,3 ; Column day on LCD
  rcall NumberLcd ; Display decimal
HourLcd:
  ldd rmp,Z+4 ; Read hour
  ldi XL,12 ; Column hour on LCD
  rcall NumberLcd ; Display decimal
MinLcd:
  ldd rmp,Z+5 ; Read minute
  ldi XL,15 ; Column minute on LCD
  rcall NumberLcd ; Display decimal
SecLcd:
  ldd rmp,Z+6 ; Read seconds
  ldi XL,18 ; Column seconds on LCD
  rcall NumberLcd ; Display decimal
  rjmp ActivatePos ; Activate input cursor
;
; Display the number in rmp in
;   decimal format on the LCD
;   on line 1
NumberLcd:
  push rmp ; Save number
  lds rmp,sKeyMode ; Read key mode
  cpi rmp,2 ; In normal or alarm mode?
  brcs NumberLcd0 ; Yes
  pop rmp ; Do not update LCD
  ret
NumberLcd0: ; Write to LCD
  push ZH ; Save Z
  push ZL
  ldi ZH,0 ; Line 1
  mov ZL,XL ; Column from XL
  rcall LcdPos
  pop ZL ; Restore Z
  pop ZH
  ldi rmp,0x2F ; ASCII tens counter
  mov R0,rmp ; in R0
  pop rmp ; Restore number
NumberLcd1:
  inc R0 ; Increase ASCII
  subi rmp,10 ; Subtract 10
  brcc NumberLcd1 ; No carry, repeat
  push rmp ; Save rest of number
  mov rmp,R0 ; R0 to rmp
  call LcdChar ; Write ASCII in rmp to LCD
  pop rmp ; Restore rest of number
  subi rmp,-58 ; Add 10 and ASCII-0
  rjmp LcdChar ; Display second digit
;
; Days of current month
;   Returns days of current month + 1
; 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 ; Load Month
  cpi XL,2 ; February?
  brne GetDaysOfMonth2 ; No
  ldd XL,Z+3 ; February, load year
  andi XL,0x03 ; Leap year?
  brne GetDaysOfMonth1 ; No
  ldi XH,30 ; Leap year, 29 days
  ret
GetDaysOfMonth1:
  ldi XH,29 ; No leap year, 28 days
  ret
GetDaysOfMonth2:
  cpi XL,7 ; Month less than July?
  brcs GetDaysOfMonth3 ; Yes
  dec XL ; Reverse row
GetDaysOfMonth3:
  lsr XL ; Month uneven?
  brcc GetDaysOfMonth4 ; No
  ldi XH,32 ; 31 days
  ret
GetDaysOfMonth4:
  ldi XH,31 ; 30 days
  ret
;
; Display the weekday 0 to 6 on LCD
;   Text is in WeekDayTable
DisplWeekday:
  lds rmp,sKeyMode ; Read key mode
  cpi rmp,2 ; In date/time input mode?
  brne DisplWeekday0
  ret
DisplWeekday0:
  push ZH ; Save Z
  push ZL
  ld rmp,Z ; Read weekday
  push rmp
  ldi ZH,0 ; Line 1
  ldi ZL,0 ; Column 1
  rcall LcdPos
  ldi ZH,High(2*WeekdayTable) ; Point to table
  ldi ZL,Low(2*WeekdayTable)
  pop rmp ; Restore weekday
  lsl rmp ; Multiply by 2
  add ZL,rmp ; Add to weekday table
  ldi rmp,0 ; Carry adder
  adc ZH,rmp ; Add carry
  lpm rmp,Z+ ; Read first character
  rcall LcdChar ; and display
  lpm rmp,Z ; Read second character
  pop ZL ; Restore Z
  pop ZH
  rjmp LcdChar ; Display second character
;
; Table with the weekday abbreviations
WeekdayTable:
.if LangEN == 1
; If English use that
  .db "MoTuWdThFrSaSu"
  .else
; If German use that
  .db "MoDiMiDoFrSaSo"
  .endif
;
; Init date and time
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
  ldi rmp,8 ; Init alarm time
  sts sAlrmHr,rmp
  ldi rmp,20
  sts sAlrmMin,rmp
  rjmp DisplAlarm
;
; Start table for date/time
; Date/Time  = Mo-30.04.18-23:59:59
; Alarm time =             00:03
InitDTtable:
.db 0,30,4,18,23,59,59,0,3,0
InitDTtableEnd:
;
; =======================================
;  C H E C K   A L A R M   R E A C H E D
; =======================================
;
; Check each minute if alarm condition reached
CheckMinute:
  cbr rFlag,1<<bMinute ; Clear flag
  lds rmp,sKeyMode ; Check key mode
  cpi rmp,1 ; Alarm active?
  brne CheckMinuteRet ; Alarm is not active
  lds rmp,sAlarmRepeat ; Alarm to be repeated?
  tst rmp ; Alarm repetition still active?
  brne CheckMinute1 ; Yes
  lds rmp,sDateTime+8 ; Read minutes alarm
  lds R0,sDateTime+5 ; Read minutes time
  cp rmp,R0 ; Compare
  brne CheckMinuteRet ; Not equal
  lds rmp,sDateTime+7 ; Read hours alarm
  lds R0,sDateTime+4 ; Read hours time
  brne CheckMinuteRet ; Not equal
  ; Start alarm
  ldi rmp,cAlarmRepet ; Load repetition
  sts sAlarmRepeat,rmp
  rcall IncAlarm ; Alarm to next minute
  mov rmp,rMelody ; Read random melody
  rjmp MusicConvert ; Play this melody
CheckMinute1: ; Alarm active
  ; Alarm still active, restart melody
  rcall MusicPlay ; Restart current melody
  lds rmp,sAlarmRepeat ; Read repetitions left
  dec rmp ; Decrease repeats
  sts sAlarmRepeat,rmp ; and store
  breq CheckMinute2 ; Zero, update display
  rjmp IncAlarm ; Increase alarm minute
CheckMinute2:
  ; Switch alarm off
  rjmp SetAlarmOff
CheckMinuteRet:
  ret
;
; Increase the alarm minute
IncAlarm:
  ldi ZH,High(sDateTime) ; Point to sDateTime
  ldi ZL,Low(sDateTime)
  ldd rmp,Z+8 ; Read alarm minute
  inc rmp ; Increase
  std Z+8,rmp ; Store
  cpi rmp,60 ; Next hour?
  brcs IncAlarmRet ; No
  clr rmp ; Clear minute
  std Z+8,rmp
  ldd rmp,Z+7 ; Read hour
  inc rmp ; Increase hour
  std Z+7,rmp ; Store
  cpi rmp,24 ; Next day?
  brcs IncAlarmRet ; No
  clr rmp ; Clear hour
  std Z+7,rmp
IncAlarmRet:
  rjmp DisplAlarm 
;
; ===================================
;  P R O C E S S  K E Y  E V E N T S
; ===================================
;
; A key has been pressed
;
KeyPress:
  cbr rFlag,1<<bKey ; Clear flag
  lds rmp,sKeyMode ; Read input mode
  tst rmp ; Zero?
  brne KeyPress1 ; No
  sbis pKeyI,bKeyRI ; Red key
  rjmp KeyPressRed ; Perform red key
  sbis pKeyI,bKeyYI ; Yellow key
  rjmp KeyPressYellow ; Perform yellow key
  sbis pKeyI,bKeyWI ; White key
  rjmp KeyPressWhite ; Perform white key
  ret
KeyPress1: ; Mode not zero
  cpi rmp,2 ; Mode = 2?
  brcs KeyPressMode1 ; No, 0 or 1
  breq KeyPressMode2 ; Yes
  rjmp KeyPressMode3 ; No, mode = 3
  ret
;
KeyPressMode1:
  sbis pKeyI,bKeyRI ; Red key
  rjmp KeyPress1Red ; Yes, perform red in mode 1
  sbis pKeyI,bKeyYI ; Yellow key?
  rjmp KeyPress1Yellow ; Yes, perform yellow in mode 1
  sbis pKeyI,bKeyWI ; White key?
  rjmp KeyPress1White ; Yes, perform white in mode 1
  ret
;
KeyPressMode2:
  sbis pKeyI,bKeyRI ; Red key
  rjmp KeyPress2Red ; Perform red in mode 2
  sbis pKeyI,bKeyYI ; Yellow key?
  rjmp KeyPress2Yellow ; Perform yellow in mode 2
  sbis pKeyI,bKeyWI ; White key?
  rjmp KeyPress2White ; Perform white in mode 2
  ret
;
KeyPressMode3:
  sbis pKeyI,bKeyRI ; Red key
  rjmp KeyPress3Red ; Perform red in mode 3
  sbis pKeyI,bKeyYI ; Yellow key
  rjmp KeyPress3Yellow ; Perform yellow in mode 3
  sbis pKeyI,bKeyWI ; White key
  rjmp KeyPress3White ; Perform white in mode 3
  ret
;
; Keys in mode 0
;
; Red key pressed
KeyPressRed:
  lds rmp,sKeyMode ; Read current mode
  cpi rmp,1 ; Mode = 1?
  brcs SetAlarmOn ; No, mode = 0
  breq SetAlarmOff ; Yes, mode = 1
  rjmp DisplAlarm ; Mode 2 or 3
SetAlarmOff:
  ldi rmp,(1<<COM1A1) ; Speaker output clear
  out TCCR1A,rmp
  ldi rmp,(1<<TOIE0)|(1<<TOIE2) ; Interrupt off
  out TIMSK,rmp
  ldi rmp,High(clock/2000-1) ; CTC TC1 to 1000 Hz
  out OCR1AH,rmp
  ldi rmp,Low(clock/2000-1)
  out OCR1AL,rmp
  ldi rmp,0 ; Switch mode to normal
  sts sKeyMode,rmp ; Switch alarm off
  sts sAlarmRepeat,rmp ; Alarm repetition clear
  lds rmp,sAlarm ; Restart alarm time
  sts sDateTime+7,rmp
  lds rmp,sAlarm+1
  sts sDateTime+8,rmp
  rjmp DisplAlarm
SetAlarmOn:
  ldi rmp,1 ; Set mode = 1
  sts sKeyMode,rmp
  ldi rmp,0 ; Clear repeat
  sts sAlarmRepeat,rmp
  rjmp DisplAlarm
;
; The yellow key has been pressed
;   Start date/time input
KeyPressYellow:
  ldi rmp,2 ; Mode 2
  sts sKeyMode,rmp
  clr rmp ; Input position to weekday
  sts sKeyPos,rmp
  ldi ZH,High(sDateTime) ; Point Z to weekday
  ldi ZL,Low(sDateTime)
  ldi XH,High(sInputData) ; Point X to input
  ldi XL,Low(sInputData)
  ldi rmp,7 ; Copy seven bytes
KeyPressYellow1:
  ld R0,Z+ ; Read date/time
  st X+,R0 ; Write to input buffer
  dec rmp ; Count down
  brne KeyPressYellow1 ; Continue read/write
  rcall LcdBlink ; Blink cursor on
  rjmp ActivatePos
;
; The white key is pressed
;   Start with alarm time input
KeyPressWhite:
  ldi rmp,3 ; Mode = 3
  sts sKeyMode,rmp
  ldi rmp,7 ; Position in input buffer
  sts sKeyPos,rmp
  lds rmp,sDateTime+7 ; Alarm time hour
  sts sInputData+7,rmp
  lds rmp,sDateTime+8
  sts sInputData+8,rmp
  rcall DisplAlarm ; Display alarm time
  rcall DisplMenue ; Display menu
  rcall ActivatePos ; Activate cursor
  ldi ZH,1 ; Cursor to alarm hour
  ldi ZL,12
  rcall LcdPos
  rjmp LcdBlink ; Blinking on
;
; Keys in mode 1
;
; Red key mode 1
KeyPress1Red:
  ; Set alarm off
  rjmp SetAlarmOff
;
; Display the current alarm state and time
DisplAlarm:
  ldi ZH,1 ; Alarm state position
  ldi ZL,cAlarmPos
  rcall LcdPos
  ldi ZH,High(2*AlarmText0) ; Text for alarm off
  ldi ZL,Low(2*AlarmText0)
  lds rmp,sKeyMode ; Is the alarm active?
  cpi rmp,1
  brcs DisplAlarm1 ; No, display this text
  ldi ZH,High(2*AlarmText1) ; Text for alarm on
  ldi ZL,Low(2*AlarmText1)
  breq DisplAlarm1 ; Yes display this text
  ldi ZH,High(2*AlarmText2) ; Text for alarm suspended
  ldi ZL,Low(2*AlarmText2)
DisplAlarm1:
  call LcdText ; Display text from Z
  ldi rmp,' ' ; Display blank
  rcall LcdChar
  lds rmp,sDateTime+7 ; Alarm time hour
  rcall LcdDec2 ; Display decimal
  ldi rmp,':' ; Display colon
  rcall LcdChar
  lds rmp,sDateTime+8 ; Alarm time minute
  rcall LcdDec2 ; Display decimal
  ldi rmp,' ' ; Display blank
  rcall LcdChar
  lds rmp,sAlarmRepeat ; Repetitions
  tst rmp ; Zero?
  breq DisplAlarm3 ; Yes display two blanks
  dec rmp ; Decrease by one
  cpi rmp,100 ; Larger than 99?
  brcs DisplAlarm2
  ldi rmp,99 ; Load maximum
DisplAlarm2:
  rjmp LcdDec2 ; Display decimal
DisplAlarm3:
  ldi rmp,' ' ; Display two blanks
  rcall LcdChar
  ldi rmp,' '
  rcall LcdChar
  rjmp DisplMenue
;
; Alarm active/inactive text
.if LangEN == 1
; English version
  AlarmText0:
  .db "off",0xFE
  AlarmText1:
  .db " on",0xFE
  AlarmText2:
  .db " ..",0xFE
  .else
; German version
  AlarmText0:
  .db "aus",0xFE
  AlarmText1:
  .db " an",0xFE
  AlarmText2:
  .db " ..",0xFE
  .endif
;
; Yellow key mode 1
KeyPress1Yellow:
  ; Increase alarm time by 5 minutes
  lds rmp,sDateTime+8 ; Read minutes
  subi rmp,-5 ; Add five minutes
SetAlarmTime:
  sts sDateTime+8,rmp ; Store
  cpi rmp,60 ; smaller than 60?
  brcs SetAlarmTimeFinal
  subi rmp,60 ; Subtract 60
  sts sDateTime+8,rmp
  lds rmp,sDateTime+7 ; Increase hour
  inc rmp
  cpi rmp,24 ; End of day?
  brcs SetAlarmTimeFinal
  subi rmp,24 ; Subtract 24
  sts sDateTime+8,rmp
SetAlarmTimeFinal:
  rjmp DisplAlarm ; Display alarm time
;
; White key mode 1
KeyPress1White:
  ; Increase alarm time by 10 minutes
  lds rmp,sDateTime+8 ; Read minutes
  subi rmp,-10 ; Add ten minutes
  rjmp SetAlarmTime ; Set alarm time
;
; Keys in mode 2
;
; Red key mode 2
KeyPress2Red:
  ; Move left
  lds rmp,sKeyPos ; Read input position
  tst rmp ; At zero?
  breq KeyPress2Red1 ; Yes, skip input
  dec rmp ; One position left
  sts sKeyPos,rmp ; and store
  rjmp ActivatePos ; and activate
KeyPress2Red1:
  ; Skip input, normal mode
  lds rmp,sAlarm ; Restore original alarm time
  sts sDateTime+7,rmp
  lds rmp,sAlarm+1
  sts sDateTime+8,rmp
  rcall SetAlarmOff ; Alarm off
  rcall DisplMenue ; Display menu
  rjmp LcdNoBlink ; Blinking off
;
; Yellow key mode 2
KeyPress2Yellow:
  ; Restore original value
  lds rmp,sKeyPos ; Read input position
  ldi XH,High(sDateTime) ; Point X to Date/Time
  ldi XL,Low(sDateTime)
  ldi ZH,High(sInputData) ; Z to input buffer
  ldi ZL,Low(sInputData)
  clr R0 ; Zero-Adder for carry adder
  add XL,rmp ; Add to date/time pointer
  adc XH,R0 ; Add carry
  add ZL,rmp ; Add to input pointer
  adc ZH,R0 ; Add carry
  ld rmp,X ; Read from date/time buffer
  st Z,rmp ; Store to input buffer
  rcall DisplInp ; Display current input data
  rjmp KeyPress2White ; And move to next position
;
; Display input data at current position
DisplInp:
  rcall ActivatePos ; Active position
  lds rmp,sKeyPos ; Read input position
  tst rmp ; Weekday?
  brne DisplInp1
  ; Display weekday
  ldi ZH,High(2*WeekdayTable)
  ldi ZL,Low(2*WeekdayTable)
  lds rmp,sInputData ; Read data
  lsl rmp ; Multiply by 2
  add ZL,rmp ; Add displacement
  ldi rmp,0
  adc ZH,rmp ; and carry
  lpm rmp,Z+ ; Read first character from table
  rcall LcdChar
  lpm rmp,Z ; Read second character from table
  rjmp LcdChar
DisplInp1:
  ldi ZH,High(sInputData) ; Point to input buffer
  ldi ZL,Low(sInputData)
  add ZL,rmp
  ldi rmp,0
  adc ZH,rmp
  ld rmp,Z ; Read data
  rjmp LcdDec2
;
; White key mode 2
KeyPress2White:
  ; Next position
  lds rmp,sKeyPos ; Read position
  cpi rmp,6 ; Last position?
  brcc KeyPress2White1 ; Yes
  inc rmp ; Next position
  sts sKeyPos,rmp ; Save new position
  rjmp ActivatePos ; Activate position
KeyPress2White1:
  ldi XH,High(sInputData) ; Point to input
  ldi XL,Low(sInputData)
  ldi ZH,High(sDateTime) ; Point to date/time
  ldi ZL,Low(sDateTime)
  ldi rmp,7 ; Seven bytes to transfer
KeyPress2White2:
  ld R0,X+ ; Read byte from input buffer
  st Z+,R0 ; Write byte to date/time buffer
  dec rmp ; Count down
  brne KeyPress2White2 ; Continue copying
  ldi rmp,0 ; Alarm off
  sts sKeyMode,rmp
  rcall LcdNoBlink
  rcall DateTimeLcd ; Display date/time
  rjmp DisplMenue ; Display menu
;
; Keys in mode 3
;
; Red key mode 3
KeyPress3Red:
  ; One left
  lds rmp,sKeyPos ; Read input position
  cpi rmp,7 ; Leftmost position?
  breq KeyPress3Red1 ; Yes, skip input
  dec rmp ; One position left
  sts sKeyPos,rmp ; Store
  rjmp ActivatePos ; and activate
KeyPress3Red1:
  lds rmp,sAlarm ; Skip, restore original
  sts sDateTime+7,rmp
  lds rmp,sAlarm+1
  sts sDateTime+8,rmp
  rcall SetAlarmOff ; Alarm off
  rcall DisplMenue ; Display menu
  rjmp LcdNoBlink ; Do not blink any more
;
; Yellow key mode 3
KeyPress3Yellow:
  ; Restore previous
  lds rmp,sKeyPos ; Read position
  cpi rmp,8 ; Minute position?
  brcc KeyPress3Yellow1 ; No, hour
  lds rmp,sDateTime+8 ; Get old minute
  sts sInputData+8,rmp
  sts sAlarm+1,rmp
  rcall DisplAlarmInp
  rjmp KeyPress3White
KeyPress3Yellow1:
  lds rmp,sDateTime+7 ; Restore old alarm hour
  sts sInputData+7,rmp
  sts sAlarm,rmp
  lds rmp,sKeyPos
  inc rmp ; Next position
  sts sKeyPos,rmp
  rjmp DisplAlarmInp
;
; Display alarm input
DisplAlarmInp:
  ldi ZH,1 ; Alarm position hour
  ldi ZL,12
  rcall LcdPos
  lds rmp,sInputData+8
  rcall LcdDec2
  ldi ZH,1 ; Alarm position minute
  ldi ZL,15
  rcall LcdPos
  lds rmp,sInputData+9
  rcall LcdDec2
  rjmp ActivatePos
;
; White key mode 3
KeyPress3White:
  ; Next position
  lds rmp,sKeyPos ; Read position
  cpi rmp,8 ; Last position?
  breq KeyPress3White1 ; Copy input
  inc rmp ; No, increase
  sts sKeyPos,rmp ; and write
  rjmp ActivatePos ; and activate
KeyPress3White1:
  lds rmp,sInputData+7 ; Read alarm time hours
  sts sAlarm,rmp ; Store new alarm hour
  sts sDateTime+7,rmp ; and in date/time buffer
  lds rmp,sInputData+8 ; Read alarm time minutes
  sts sAlarm+1,rmp ; Store new alarm minute
  sts sDateTime+8,rmp ; and in date/time
  rcall SetAlarmOn ; Activate alarm
  rcall DisplMenue ; Display menu
  rjmp LcdNoBlink ; Blinking off
;
; Display menu for selected mode
;
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 ; Read mode
  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 LcdText
  .else
  ret
  .endif
;
; Activate the current position
; of the LCD for blinking
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",0xFE,0xFE
  .db "NoAlarm +5min +10min",0xFE,0xFE
  .db "Prev   Default  Next",0xFE,0xFE
  .db "Prev   Default  Next",0xFE,0xFE
MenueFirst:
  .db "Skip   Default  Next",0xFE,0xFE
MenueLast:
  .db "Prev   Default   Set",0xFE,0xFE
  .else
  .db "Wecken Zeit Weckzeit",0xFE,0xFE
  .db "Weckaus +5min +10min",0xFE,0xFE
  .db "Links   Alt   Rechts",0xFE,0xFE
  .db "Links   Alt   Rechts",0xFE,0xFE
MenueFirst:
  .db "Abbr.Default Naechst",0xFE,0xFE
MenueLast:
  .db "Links   Alt   Setzen",0xFE,0xFE
  .endif
;
; ======================================
;  P R O C E S S   A D C  R E S U L T S
; ======================================
;
; ADC has summed up 64 values in the current channel
;   MSB of the summed results is in rAdc
;
AdcCmplt:
  cbr rFlag,1<<bAdc ; Clear flag
  in rmp,ADMUX ; Read channel
  andi rmp,0x1F ; Isolate channel bits
  push rmp ; Save channel
  inc rmp ; Next channel
  cpi rmp,0x03 ; Channel 3?
  brcs AdcCmplt1 ; No, smaller
  clr rmp
AdcCmplt1:
  ori rmp,(1<<REFS0) ; Set REFS0 bit
  out ADMUX,rmp
  ldi rAdcC,64 ; 64 measurements
  ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(ADPS0)
  out ADCSRA,rmp ; Start next conversion
  pop rmp ; Old channel
  cpi rmp,1 ; Channel = 1?
  brcs AdcSet0 ; No, channel = 0
  brne AdcSet2 ; No, channel = 2
  rjmp AdcSet1 ; Yes, channel = 1
;
; Channel 2 complete
;   Display signal strength
AdcSet2:
  ; Downcount signal strength counter
  dec rSigStr
  breq AdcSet2A ; Reached zero count
AdcSet2Ret:
  ret ; Done
AdcSet2A:
  ldi rmp,cSigStr ; Restart counter
  mov rSigStr,rmp
  lds rmp,sKeyMode ; Key mode input?
  cpi rmp,2 ; Key mode input?
  brcc AdcSet2Ret ; Yes, no display
  ; Channel = 2
  ldi ZH,2 ; Line 3 for display
  ldi ZL,7
  rcall LcdPos
  mov rmp,rAdc ; Read MSB ADC sum
  cpi rmp,30 ; Larger or equal 30?
  ldi rmp,' ' ; Write blank
  brcs SignalNoPlus ; larger than 30
  ldi rmp,'+' ; Display plus
SignalNoPlus:
  rcall LcdChar
  ldi ZH,2 ; Position S value
  ldi ZL,6
  rcall LcdPos
  ldi ZH,High(2*SignalTable) ; Get signal strength from table
  ldi ZL,Low(2*SignalTable)
  clr R0 ; R0 is counter
SignalStrength:
  inc R0 ; Inc counter
  lpm rmp,Z+ ; Read table value
  cp rAdc,rmp ; Compare with table value
  brcc SignalStrength ; rAdc larger or equal
  ldi rmp,'0'-1 ; Add ASCII-0 minus 1
  add rmp,R0
  rjmp LcdChar ; Display character
;
; Table to display signal strength
SignalTable:
.db 3,6,9,12,15,18,21,24,27,255
;
; Channel 0: Potentiometer result
;   If in input mode: update byte at current position
AdcSet0:
  ; Check input mode active
  lds rmp,sKeyMode
  cpi rmp,2
  brcc AdcSet0_1 ; In input mode
AdcSet0Ret:
  ret ; Done
AdcSet0_1:
  lds rmp,sKeyCnt ; Read key counter
  tst rmp ; Key counter at zero?
  breq AdcSet0a ; At zero, go on
  dec rmp ; Decrease key counter
  sts sKeyCnt,rmp ; and store
  rjmp AdcSet0Ret ; No further action
AdcSet0a:
  ldi rmp,5 ; Restart 5 cycles delay
  sts sKeyCnt,rmp
  rcall GetMinMax ; Get the minimum (R0) and maximum (R1)
  mov rmp,R1 ; Maximum - Minimum
  sub rmp,R0 ; Subtract minimum
  mov XL,rAdc ; Multiply rAdc by difference
  clr XH
  clr ZL ; Z is result
  mov ZH,R0 ; Add minimum to MSB
AdcSet0b:
  tst rmp ; Check zero reached?
  breq AdcSet0d ; Yes, completed multiplication
  lsr rmp ; Shift lowest bit to carry
  brcc AdcSet0c
  add ZL,XL ; Add the multiplier
  adc ZH,XH
AdcSet0c:
  lsl XL ; Shift multiplier left
  rol XH
  rjmp AdcSet0b ; Continue multiplying
AdcSet0d:
  ldi XH,High(sInputData) ; point to input buffer
  ldi XL,Low(sInputData)
  lds rmp,sKeyPos
  add XL,rmp ; Add position
  ldi rmp,0
  adc XH,rmp ; with carry
  st X,ZH ; Write Multiplication result
  rcall DisplInp
  rjmp ActivatePos ; Activate again
;
; Get minimum and maximum for position
;    minimum in R0, maximum +1 in R1
GetMinMax:
  ldi ZH,High(2*MinMaxTab) ; Z to table
  ldi ZL,Low(2*MinMaxTab)
  clr R0 ; is carry adder
  lds rmp,sKeyPos ; Key position
  lsl rmp ; Multiply by 2
  add ZL,rmp
  adc ZH,R0 ; Add carry
  lpm R0,Z+ ; Read minimum
  lpm R1,Z ; Read maximum
  ret
;
; Maxima table by position
MinMaxTab:
;   wd  day  mon  year  hour min  sec  alhr almin
.db 0,7,1,32,1,13,0,100,0,24,0,60,0,60,0,24,0,60
;
; ADC channel 1 complete
;   Display light sensor in hex
AdcSet1:
  ; Check if delay period over
  dec rBackCnt ; Decrease counter
  brne AdcSet1Ret ; Not zero
  ldi rmp,cBackPeriods ; Backlight update period
  mov rBackCnt,rmp ; Restart counter
  ldi ZH,2 ; Display sensor result on LCD
  ldi ZL,18
  rcall LcdPos
  mov rmp,rAdc
  rcall LcdHex2
AdcSet1a:
  tst rAdc ; Result zero?
  brne Light0 ; No
  inc rAdc ; Set to 1
Light0:
  neg rAdc ; Revert
  ; Multiply light sensor result
  ldi rmp,cBackMin ; Add minimum
  mov R1,rmp ; To MSB in result in R1:R0
  clr R0 ; Clear LSB
  ldi ZL,cBackMult ; Load multiplicator
  clr ZH ; Clear MSB
Light1: ; Multiply with constant
  lsr rAdc ; Shift bit to carry
  brcc Light2 ; Bit = zero
  add R0,ZL ; Add to result
  adc R1,ZH
  brcc Light2 ; No carry
  clr R1 ; Set result to FF
  dec R1
Light2:
  lsl ZL ; Multiply by 2 
  rol ZH
  tst rAdc ; Check still ones?
  brne Light1 ; Yes, continue multiplication
  out OCR2,R1 ; Update backlight PWM
AdcSet1Ret:
  ret
;
; Music code include routines
.include "music_code.inc"
;
; LCD include routines
.include "lcd.inc"
;
; LCD subroutines
;
; Blinking of the LCD on
;
LcdBlink:
   ldi rmp,0x0F
   rjmp LcdCtrl
;
; Blinking of the LCD off
;
LcdNoBlink:
   ldi rmp,0x0C
   rjmp LcdCtrl
;
.if fDbg
; If debugging active include routines
  .include "debug_code_en.inc"
   .endif
;
; Lcd text
;
LcdTxtInit1:
.if LangEN == 1
; English version
  .db " DCF77_clock_m16 V5 ",0x0D,0xFF
  .else
; German version
  .db "DCF77_m16 V5 Atomuhr",0x0D,0xFF
  .endif
.db "    ",7,"2018 DG4FAC    ",0x0D,0xFF
.db "                    ",0x0D,0xFF
.db "                    ",0xFE,0xFE
;
LcdTxtFrame:
.db "  -  .  .  -  :  :  ",0x0D,0xFF
.if LangEN == 1
; English version
  .db " Alarm: ___   :     ",0x0D,0xFF
  .db "DCF: S0 __ Light:   ",0x0D,0xFF
  .else
; German version
  .db "Wecken: ___   :     ",0x0D,0xFF
  .db "DCF: S0 __ Licht:   ",0x0D,0xFF
  .endif
.db "                    ",0xFE,0xFE
;
; Special characters
CodeChars:
.db 64,0,21,10,21,10,21,10,21,0 ; C = 0, Multi1
.db 72,0,4,4,4,21,31,14,4,0 ; C = 1, Arrow down
.db 80,0,4,14,31,21,4,4,4,0 ; C = 2, Arrow up
.db 88,0,0,0,8,21,3,7,0,0 ; C = 3, Arrow right down
.db 96,0,0,0,2,21,24,28,0,0 ; C = 4, Arrow right down
.db 104,0,4,10,4,10,4,10,4,0 ; C = 5, Dummy
.db 112,0,10,21,10,21,10,21,10,0 ; C = 6, Multi2
.db 120,0,14,17,21,25,21,17,14,0 ; C = 7, Arrow left up
.db 0,0 ; End of Table
;
; Edit positions
.equ cAlarmPos = 8
.equ cDcfPos = 8
;
; End of source code
; Copyright
.db "(C)2018 by Gerhard Schmidt  " ; human readable
.db "C(2)10 8ybG reahdrS hcimtd  " ; wordwise
;

Page top Main LCD Music Debug

2 The LCD routines lcd.inc

Link to this Code in asm format

;
; *********************************
; * LCD include routines          *
; * (C)2018 avr-asm-tutorial.net  *
; *********************************
;
; ***********************************************
; *  L C D   I N T E R F A C E  R O U T I N E S *
; ***********************************************
;
; +-------+----------------+--------------+
; |Routine|Function        |Parameters    |
; +-------+----------------+--------------+
; |LcdInit|Inits the LCD   |Ports, pins   |
; |       |in the desired  |              |
; |       |mode            |              |
; +-------+----------------+--------------+
; |LcdText|Displays the    |Z=2*Table     |
; |       |text in flash   |  address     |
; |       |memory          |0x0D: Next    |
; |       |                |      line    |
; |       |                |0xFF: Ignore  |
; |       |                |0xFE: Text end|
; +-------+----------------+--------------+
; |LcdSram|Display the text|Z=SRAM-Address|
; |       |in SRAM         |R16: number of|
; |       |                |    characters|
; +-------+----------------+--------------+
; |LcdChar|Display charac- |R16: Character|
; |       |ter on LCD      |              |
; +-------+----------------+--------------+
; |LcdCtrl|Output control  |R16: Control  |
; |       |byte to LCD     |     byte     |
; +-------+----------------+--------------+
; |LcdPos |Set position on |ZH: Line 0123 |
; |       |the LCD         |ZL: Col 0..   |
; +-------+----------------+--------------+
; |LcdSpec|Generate special|Z: 2*Table    |
; |       |characters      |   address    |
; +-------+----------------+--------------+
; |    S W I T C H   L C D D E C I M A L  |
; +-------+----------------+--------------+
; |LcdDec2|Convert to two  |R16: Binary   |
; |       |decimal digits  |     8 bit    |
; +-------+----------------+--------------+
; |LcdDec3|Convert to three|R16: Binary   |
; |       |decimal digits, |     8 bit    |
; |       |supp. leading 0s|              |
; +-------+----------------+--------------+
; |LcdDec5|Convert to five |Z: Binary     |
; |       |decimal digits, |   16 bit     |
; |       |supp. leading 0s|              |
; +-------+----------------+--------------+
; |      S W I T C H   L C D H E X        |
; +-------+----------------+--------------+
; |LcdHex2|Convert to two  |R16: Binary   |
; |       |digits in hex   |     8 bit    |
; +-------+----------------+--------------+
; |LcdHex4|Convert to four |Z: Binary     |
; |       |digits in hex   |   16 bit     |
; +-------+----------------+--------------+
;
; *************************************
;  P A R A M E T E R - T E M P L A T E
; *************************************
;
; (Copy to your source code, remove ; and adjust
;  parameters to fit your hardware)
;
; Standard parameter set of properties/definitions
;.equ clock = 1000000 ; Clock frequency of controller in Hz
; LCD size:
  ;.equ LcdLines = 1 ; Number of lines (1, 2, 4)
  ;.equ LcdCols = 8 ; Number of characters per line (8..24)
; LCD bus interface
  ;.equ LcdBits = 4 ; Bus size (4 or 8)
  ; If 4 bit bus:
    ;.equ Lcd4High = 1 ; Bus nibble (1=Upper, 0=Lower)
  ;.equ LcdWait = 0 ; Access mode (0 with busy, 1 with delay loops)
; LCD data ports
  ;.equ pLcdDO = PORTA ; Data output port
  ;.equ pLcdDD = DDRA ; Data direction port
; LCD control ports und pins
  ;.equ pLcdCEO = PORTB ; Control E output port
  ;.equ bLcdCEO = PORTB0 ; Control E output portpin
  ;.equ pLcdCED = DDRB ; Control E direction port
  ;.equ bLcdCED = DDB0 ; Control E direction portpin
  ;.equ pLcdCRSO = PORTB ; Control RS output port
  ;.equ bLcdCRSO = PORTB1 ; Control RS output portpin
  ;.equ pLcdCRSD = DDRB ; Control RS direction port
  ;.equ bLcdCRSD = DDB1 ; Control RS direction portpin
; If LcdWait = 0:
  ;.equ pLcdDI = PINA ; Data input port
  ;.equ pLcdCRWO = PORTB ; Control RW output port
  ;.equ bLcdCRWO = PORTB2 ; Control RW output portpin
  ;.equ pLcdCRWD = DDRB ; Control RW direction port
  ;.equ bLcdCRWD = DDB2 ; Control RW direction portpin
; If you need binary to decimal conversion:
  ;.equ LcdDecimal = 1 ; If defined: include those routines
; If you need binary to hexadecimal conversion:
  ;.equ LcdHex = 1 ; If defined: include those routines
; If simulation in the SRAM is desired:
  ;.equ avr_sim = 1 ; 1=Simulate, 0 or undefined=Do not simulate
;
; *****************************************
;      T E X T   T E M P L A T E S
; *****************************************
;
; Tables to copy for diverse sizes
;
; --------------------------
; Single line LCD
;   8 chars per line
; Text_1_8:
;   .db "        ",0xFE,0xFF
;        01234567
;
;   16 chars per line
; Text_1_16:
;   .db "                ",0xFE,0xFF
;        0123456789012345
;
;   20 chars per line
; Text_1_20:
;   .db "                    ",0xFE,0xFF
;        01234567890123456789
;
;   24 chars per line
; Text_1_24:
;   .db "                        ",0xFE,0xFF
;        012345678901234567890123
;
; --------------------------
; Two line LCD
;   16 chars per line
; Text_2_16:
;   .db "                ",0x0D,0xFF
;   .db "                ",0xFE,0xFF
;        0123456789012345
;
;   20 chars per line
; Text_2_20:
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0xFE,0xFF
;        01234567890123456789
;
;   24 chars per line
; Text_2_24:
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0xFE,0xFF
;        012345678901234567890123
;
; --------------------------
; Four line LCD
;   16 chars per line
; Text_4_16:
;   .db "                ",0x0D,0xFF
;   .db "                ",0x0D,0xFF
;   .db "                ",0x0D,0xFF
;   .db "                ",0xFE,0xFF
;        0123456789012345
;
;   20 chars per line
; Text_4_20:
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0xFE,0xFF
;        01234567890123456789
;
;   24 chars per line
; Text_4_24:
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0xFE,0xFF
;        012345678901234567890123
;
; *******************************
;  P A R A M E T E R   C H E C K
; *******************************
;
; Are all parameters correct?
;
; Size defined?
.ifndef LcdLines
  .error "LCD line size (LcdLines) undefined!"
  .else
  .if (LcdLines!=1)&&(LcdLines!=2)&&(LcdLines!=4)
    .error "LCD illegal line size (LcdLines)!"
    .endif
  .endif
.ifndef LcdCols
  .error "LCD column size (LcdCols) undefined!"
  else
  .if (LcdCols<8)||(LcdCols>24)
    .error "LCD illegal column size (LcdCols)!"
    .endif
  .endif
;
; Clock defined?
.ifndef clock
  .error "Clock frequency (clock) undefined!"
  .endif
;
; 4- or 8-bit interface selected?
.ifndef LcdBits
  .error "LCD data bus bits (LcdBits) undefined!"
  .else
  .if (LcdBits != 4) && (LcdBits != 8)
    .error "LCD data bus bits (LcdBits) not 4 or 8!"
    .endif
  .if LcdBits == 4
    .ifndef Lcd4High
      .error "LCD 4 bit data bus nibble (Lcd4High) undefined!"
      .else
      .if (Lcd4High != 0) && (Lcd4High != 1)
        .error "LCD 4 bit data bus nibble (Lcd4High) not 0 or 1!"
        .endif
      .endif
    .endif
  .endif
;
; LCD data ports
.ifndef pLcdDO
  .error "LCD data output port (pLcdDO) undefined!"
  .endif
.ifndef pLcdDD
  .error "LCD data direction port (pLcdDD) undefined!"
  .endif
.if LcdWait == 0
  .ifndef pLcdDI
    .error "LCD data input port (pLcdDI) undefined!"
    .endif
  .endif
;
; LCD control ports und pins
.ifndef pLcdCEO
  .error "LCD control E output port (pLcdCEO) undefined!"
  .endif
.ifndef pLcdCED
  .error "LCD control E direction port (pLcdCED) undefined!"
  .endif
.ifndef bLcdCEO
  .error "LCD control E output pin (bLcdCEO) undefined!"
  .endif
.ifndef bLcdCED
  .error "LCD control E direction pin (bLcdCED) undefined!"
  .endif
.ifndef pLcdCRSO
  .error "LCD control RS output port (pLcdCRSO) undefined!"
  .endif
.ifndef pLcdCRSD
  .error "LCD control RS direction port (pLcdCRSD) undefined!"
  .endif
.ifndef bLcdCRSO
  .error "LCD control RS output pin (bLcdCRSO) undefined!"
  .endif
.ifndef bLcdCRSD
  .error "LCD control RS direction pin (bLcdCRSD) undefined!"
  .endif
.ifndef LcdWait
  .error "LCD operating property (LcdWait) undefined!"
  .else
  .if LcdWait == 0
    .ifndef pLcdCRWO
      .error "LCD control RW output port (pLcdCRWO) undefined!"
      .endif
    .ifndef bLcdCRWO
      .error "LCD control RW output pin (bLcdCRWO) undefined!"
      .endif
    .ifndef pLcdCRWD
      .error "LCD control RW direction port (pLcdCRWD) undefined!"
      .endif
    .ifndef bLcdCRWD
      .error "LCD control RW direction pin (bLcdCRWD) undefined!"
      .endif
    .endif
  .endif
;
; *************************************
;  S I M U L A T I O N   A V R _ S I M
; *************************************
;
; Check if simulation desired
.ifdef avr_sim
  .equ simulation = avr_sim
  .else
  .equ simulation = 0
  .endif
.if simulation == 1
  .dseg
  SimStart:
  SimDisplayPos:
    .byte 1
  SimCtrlClear:
    .byte 1
  SimCtrlReset:
    .byte 1
  SimCtrlInputmode:
    .byte 1
  SimCtrlDisplay:
    .byte 1
  SimCtrlCursorShift:
    .byte 1
  SimCtrlFunctionset:
    .byte 1
  SimCtrlCharGenRamAdr:
    .byte 1
  SimCtrlDisplayRamAdr:
    .byte 1
  SimDataDisplay:
    .byte LcdLines*LcdCols
  SimEnd:
  .cseg
  .endif
;
; *********************************
;     L C D   R O U T I N E S
; *********************************
;
; LcdInit: Init LCD ports and pins
;          Wait cycle for LCD start-up
;          Function set
;          Clear LCD
LcdInit:
  ; Init the LCD control bits
  cbi pLcdCEO,bLcdCEO ; E pin low
  sbi pLcdCED,bLcdCED ; E pin output
  cbi pLcdCRSO,bLcdCRSO ; RS pin low
  sbi pLcdCRSD,bLcdCRSD ; RS pin output
  .ifdef pLcdCRWO
    .ifdef bLcdCRWO
      cbi pLcdCRWO,bLcdCRWO ; RW pin low
      .endif
    .endif
  .ifdef pLcdCRWD
    .ifdef pLcdCRWD
      sbi pLcdCRWD,bLcdCRWD ; RW pin output
      .endif
    .endif
  ; Init the LCD data bus ports
  .if LcdBits == 8
    clr R16 ; Data bus to low
    .else
    in R16,pLcdDO ; Read output bits data bus
    .if Lcd4High == 1
      andi R16,0x0F ; Clear upper nibble
      .else
      andi R16,0xF0 ; Clear lower nibble
      .endif
    .endif
  out pLcdDO,R16 ; Data bus output clear
  .if LcdBits == 8
    ldi R16,0xFF ; Set all direction bits high
    .else
    in R16,pLcdDD ; Read direction bits data bus
    .if Lcd4High == 1
      ori R16,0xF0 ; Set upper nibble
      .else
      ori R16,0x0F ; Set lower nibble
      .endif
    .endif
  out pLcdDD,R16 ; Set direction bits data bus
  ; LCD-Startphase
  .if simulation == 0
    rcall LcdWait50ms
    .endif
  ; LCD to 8 bit data bus
  ldi R16,0x30
  rcall Lcd8Ctrl ; In 8 bit mode to LCD control
  .if simulation == 0
    rcall LcdWait5ms ; Wait for 5 ms
    .endif
  ldi R16,0x30
  rcall Lcd8Ctrl ; In 8 bit mode to LCD control
  .if simulation == 0
    rcall LcdWait5ms ; Wait for 5 ms
    .endif
  ldi R16,0x30
  rcall Lcd8Ctrl ; In 8 bit mode to LCD control
  .if simulation == 0
    rcall LcdWait5ms ; Wait for 5 ms
    .endif
  ldi R16,0x30
  rcall Lcd8Ctrl ; In 8 bit mode to LCD control
  .if simulation == 0
    rcall LcdWait5ms ; Wait for 5 ms
    .endif
  ; If 4 bit data bus interface: switch to 4 bit mode
  .if LcdBits == 4
    ldi R16,0x20 ; 4 bit interface
    rcall Lcd8Ctrl ; In 8 bit mode to LCD control
    .if simulation == 0
      rcall LcdWait5ms
      .endif
    .endif
  ; Function set
  .if LcdBits == 8
    ldi R16,0x30 ; 8 bit data bus
    .else
    ldi R16,0x20 ; 4 bit data bus
    .endif
  .if LcdLines > 1
     ori R16,0x08 ; LCDs with more than one line
    .endif
  rcall LcdCtrl
  ; Display mode
  ldi R16,0x0C ; Display on, underline off, cursor blink off
  rcall LcdCtrl
  ; LCD entry mode set
  ldi R16,0x06 ; Cursor right, cursor move not display shift
  rcall LcdCtrl
  ; Clear LCD
  ldi R16,0x01 ; LCD clear
  rjmp LcdCtrl
;
; LcdText
;   Displays the text in flash memory on the LCD
;     Z points to 2*Text table address
;     0x0D: Line feed and carriage return
;     0xFF: Ignore (fill character, ignored)
;     0xFE: End of the text
LcdText:
  push R0 ; Save R0
  ldi R16,LcdLines ; Maximum line number
  mov R0,R16 ; to R0
LcdText1:
  lpm R16,Z+ ; Read next char from flash
  cpi R16,0xFE ; End of text?
  breq LcdText3 ; Yes
  brcc LcdText1 ; Ignore fill characters
  cpi R16,0x0D ; Line feed and carriage return?
  brne LcdText2 ; No
  dec R0 ; Decrease line counter
  breq LcdText1 ; If zero, continue
  .if simulation == 1
    ; CR/LF in SRAM memory
    push ZH ; Save Z
    push ZL
    push R16 ; Save character
    ldi ZH,High(SimDataDisplay) ; Perform newline
    ldi ZL,Low(SimDataDisplay)
    ldi R16,LcdLines
    LcdText1a:
      adiw ZL,LcdCols
      dec R16
      cp R16,R0
      brne LcdText1a
    ldi R16,Low(SimDataDisplay)
    sub ZL,R16
    ldi R16,High(SimDataDisplay)
    sbc ZH,R16
    sts SimDisplayPos,ZL
    pop R16 ; Restore character
    pop ZL ; Restore Z
    pop ZH
    .endif
  ; CR/LF on LCD
  push ZH ; Save Z
  push ZL
  ldi ZH,LcdLines ; Calculate line
  sub ZH,R0
  clr ZL ; To line start
  rcall LcdPos ; Set LCD position
  pop ZL ; Restore Z
  pop ZH
  rjmp LcdText1 ; Next character in flash
LcdText2:
  rcall LcdChar ; Character in R16 to LCD
  rjmp LcdText1 ; Next character
LcdText3:
  pop R0 ; Restore R0
  ret
;
; LcdSRam displays text in SRAM at the current LCD position
;   Z points to SRAM address
;   R16: Number of characters
LcdSRam:
  push R16 ; Save R16
  ld R16,Z+ ; Read character
  rcall LcdChar ; Display on LCD
  pop R16 ; Restore R16
  dec R16 ; Downcount number of characters
  brne LcdSRam ; Further characters to display
  ret
;
; LcdChar displays character in R16 at the current position
;   R16: Character
LcdChar:
.if simulation == 1
  ; Simulation, write character to SRAM
  push ZH ; Save Z
  push ZL
  push R16 ; Save character
  ldi ZH,High(SimDataDisplay) ; SRAM position
  ldi ZL,Low(SimDataDisplay)
  lds R16,SimDisplayPos
  inc R16
  sts SimDisplayPos,R16
  dec R16
  add ZL,R16
  ldi R16,0
  adc ZH,R16
  pop R16
  st Z,R16 ; Write to SRAM
  pop ZL
  pop ZH
  .endif
  rjmp LcdData ; Write as data to LCD
;
; LcdCtrl writes control byte in R16 on the LCD
LcdCtrl:
.if simulation == 1
    cpi R16,0x80 ; Display RAM address write?
    brcs LcdCtrlSim1
    sts SimCtrlDisplayRamAdr,R16
    push ZH
    push ZL
    mov ZH,R16
    andi R16,0x40
    brne LcdCtrlSim0a
    clr ZL ; Line 1 or 3
    rjmp LcdCtrlSim0b
  LcdCtrlSim0a:
    ldi ZL,LcdCols ; Line 2 or 4
  LcdCtrlSim0b:
    mov R16,ZH
    andi R16,0x3F
    subi R16,LcdCols
    brcc LcdCtrlSim0c ; Line 3 or 4
    subi R16,-LcdCols
    rjmp LcdCtrlSim0d
  LcdCtrlSim0c:
    sbrc ZH,6 ; Line 4?
    subi R16,-LcdCols ; Add column length
  LcdCtrlSim0d:
    add ZL,R16
    sts SimDisplayPos,ZL
    pop ZL
    pop ZH
    rjmp LcdCtrlSim9
  LcdCtrlSim1:
    cpi R16,0x40 ; Character generator RAM address set?
    brcs LcdCtrlSim2
    sts SimCtrlCharGenRamAdr, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim2:
    cpi R16,0x20 ; Function set?
    brcs LcdCtrlSim3
    sts SimCtrlFunctionSet, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim3:
    cpi R16,0x10 ; Cursor shift?
    brcs LcdCtrlSim4
    sts SimCtrlCursorShift, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim4:
    cpi R16,0x08 ; Display control set?
    brcs LcdCtrlSim5
    sts SimCtrlDisplay, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim5:
    cpi R16,0x04 ; Display control?
    brcs LcdCtrlSim6
    sts SimCtrlInputmode, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim6:
    cpi R16,0x02 ; Reset?
    brcs LcdCtrlSim7
    sts SimCtrlReset,R16
    rjmp LcdCtrlSim9
  LcdCtrlSim7:
    cpi R16,0x01 ; Clear?
    brcs LcdCtrlSim8
    sts SimCtrlClear,R16
    ; LCD clear display
    push ZH
    push ZL
    ldi ZH,High(SimEnd)
    ldi ZL,Low(SimEnd)
    clr R16
  LcdCtrl7a:
    st -Z,R16 ; Fill with zeroes backwards
    cpi ZL,Low(SimStart)
    brne LcdCtrl7a
    cpi ZH,High(SimStart)
    brne LcdCtrl7a
    ldi R16,0x01
    pop ZL
    pop ZH
    rjmp LcdCtrlSim9
  LcdCtrlSim8: ; 00 command
  LcdCtrlSim9:
  .endif
  .if LcdWait == 0
    rcall LcdBusy ; Wait for busy flag clear
    .endif
  cbi pLcdCRSO,bLcdCRSO ; RS bit low
  .if LcdBits == 4
    push ZL
    push R16
    in ZL,pLcdDO ; Read data output port
    .if Lcd4High == 1
      andi ZL,0x0F ; Clear upper nibble port
      andi R16,0xF0 ; Clear lower nibble R16
      .else
      andi ZL,0xF0 ; Clear lower nibble port
      swap R16 ; Upper to lower nibble
      andi R16,0x0F ; Clear upper nibble R16
      .endif
    or R16,ZL ; Combine
    out pLcdDO,R16 ; and output
    rcall LcdPulseE ; Activate E
    pop R16 ; R16 restore
    push R16 ; and save again
    in ZL,pLcdDO ; Read data output port
    .if Lcd4High == 1
      andi ZL,0x0F ; Preserve lower nibble of port
      swap R16 ; Lower to upper nibble R16
      andi R16,0xF0 ; Preserve upper nibble R16
      .else
      andi ZL,0xF0 ; Preserve upper nibble of port
      andi R16,0x0F ; Preserve lower nibble R16
      .endif
    out pLcdDO,R16 ; To data bus
    rcall LcdPulseE ; Activate E
    pop R16 ; Restore R16
    pop ZL ; Restore ZL
    .else
    out pLcdDO,R16 ; Input on data bus
    rcall LcdPulseE ; Activate E
    .endif
  .if LcdWait == 1
    andi R16,0xFC ; The six upper bits of control command
    brne LcdCtrl1 ; are not zero, short delay
    rjmp LcdWait1640us ; Wait 1,64 ms
  LcdCtrl1:
    rjmp LcdWait40us ; Wait 40 us
    .else
    ret
    .endif
;
; LcdPos position the LCD cursor to the position in Z
;   ZH: Line (0 to number of lines - 1)
;   ZL: Column (0 to number of colums per line - 1)
LcdPos:
  cpi ZH,1 ; Line = 1?
  ldi R16,0x80 ; Address line 0
  .if LcdLines < 2 ; LCD has only one line
    rjmp LcdPos1
    .endif
  brcs LcdPos1 ; Line 1
  ldi R16,0xC0 ; Line 2 address
  .if LcdLines == 2
    rjmp LcdPos1 ; Lcd has only two lines
    .endif
  breq LcdPos1 ; Line 2 selected
  ldi R16,0x80+LcdCols ; Address line 3
  cpi ZH,2 ; Line = 3
  breq LcdPos1 ; Line 3
  ldi R16,0xC0+LcdCols ; Address line 4
LcdPos1:
  add R16,ZL ; Add column
  rjmp LcdCtrl ; Send as control to LCD
;
; LcdSpec generates special characters on the LCD
;   Z points to 2*Table address
;   Table format:
;     1. byte: Address of character, 0b01zzz000,
;        0: End of the table
;     2. byte: Dummy character, ignored
;     3. to 10 th byte: Pixel data of the lines 1 to 8
LcdSpec:
  push R0 ; R0 is counter
LcdSpec1:
  lpm R16,Z+ ; Read address of character
  tst R16 ; End of the table?
  breq LcdSpec3 ; Yes
  rcall LcdCtrl ; Write address
  adiw ZL,1 ; Overread dummy
  ldi R16,8 ; 8 byte per character
  mov R0,R16 ; R0 is counter
LcdSpec2:
  lpm R16,Z+ ; Read data byte from table
  rcall LcdData ; Output as data byte
  dec R0 ; Count down
  brne LcdSpec2 ; Further data bytes
  rjmp LcdSpec1 ; Next character
LcdSpec3:
  pop R0 ; Restore R0
  ldi ZH,0 ; Cursor to home position
  ldi ZL,0
  rjmp LcdPos
;
; ************************************
;  D E C I M A L  C O N V E R S I O N
; ************************************
;
; Routines for conversion and display decimals
;
.ifdef LcdDecimal
  ;
  ; LcdDec2 converts binary in R16 to a two digit decimal
  ;   without suppression of leading zeroes
  LcdDec2:
    mov ZL,R16 ; Copy number
    ldi R16,'0'-1 ; Tens counter
    cpi ZL,100 ; Larger that two digits?
    brcs LcdDec2a ; No
    ldi ZL,99 ; three digits, limit to max
  LcdDec2a:
    inc R16
    subi ZL,10
    brcc LcdDec2a
    rcall LcdData ; Display tens
    ldi R16,10+'0'
    add R16,ZL
    rcall LcdData ; Display ones
    ret
  ;
  ; LcdDec3 converts binary in R16 to three digits decimal
  ;   blanks leading zeroes
  LcdDec3:
    ldi ZH,1 ; Flag leading zeroes
  LcdDec3null: ; without changing leading zero flag
    push R0 ; Save R0
    mov ZL,R16 ; Binary in ZL
    ldi R16,100 ; Hundreds
    rcall LcdDec3a ; Convert to decimal
    rcall LcdData ; Display digit
    ldi R16,10 ; Tens
    rcall LcdDec3a ; Convert to decimal
    rcall LcdData ; Display digit
    ldi R16,'0'
    add R16,ZL ; Convert ones to ASCII
    rcall LcdData ; Display digit
    pop R0 ; Restore R0
    ret
  ;
  LcdDec3a: ; Calculate decimal digit
    clr R0 ; Counter
    dec R0 ; to - 1
  LcdDec3b:
    inc R0 ; Increase counter
    sub ZL,R16 ; Subtract 100 or 10
    brcc LcdDec3b ; No overflow, continue
    add ZL,R16 ; Recover last subtraction
    tst R0 ; Digit zero?
    breq LcdDec3c ; Yes, check blanking
    clr ZH ; No blanking any more
    ldi R16,'0' ; ASCII-0
    add R16,R0 ; Add counter
    ret
  LcdDec3c:
    tst ZH ; Zero blanking?
    breq LcdDec3d ; No
    ldi R16,' ' ; Blank char
    ret
  LcdDec3d:
    ldi R16,'0' ; Zero char
    ret
  ;
  ; LcdDec5 converts binary Z to five decimal digits
  ;   blank leading zeroes
  LcdDec5:
    push R2 ; Save registers
    push R1
    push R0
    clr R2 ; Leading zeroes
    inc R2 ; Blanking on
    mov R1,ZH ; Copy binary to R1:R0
    mov R0,ZL
    ldi ZH,High(10000) ; First decimal digit
    ldi ZL,Low(10000)
    rcall LcdDec5a ; Convert first digit
    ldi ZH,High(1000) ; Second decimal digit
    ldi ZL,Low(1000)
    rcall LcdDec5a ; Convert second digit
    ldi ZH,High(100) ; Third decimal digit
    ldi ZL,Low(100)
    rcall LcdDec5a ; Convert decimal digit
    ldi ZH,High(10) ; Fourth decimal digit
    ldi ZL,Low(10)
    rcall LcdDec5a ; Convert decimal digit
    ldi R16,'0' ; Convert fifth digit to ASCII
    add R16,R0
    rcall LcdChar ; Display digit
    pop R0 ; Restore registers
    pop R1
    pop R2
    ret
  ;
  LcdDec5a: ; Convert to decimal digit
    ldi R16,'0'-1 ; To ASCII-0 - 1
  LcdDec5b:
    inc R16 ; Increase counter
    sub R0,ZL ; Subtract decimal digit LSB
    sbc R1,ZH ; and MSB with Carry
    brcc LcdDec5b ; Repeat until carry
    add R0,ZL ; Take back last subtraction
    adc R1,ZH
    cpi R16,'0' ; Zero?
    breq LcdDec5c ; Yes, check blanking
    clr R2 ; End blanking
    rjmp LcdChar ; Display char in R16
  LcdDec5c:
    tst R2 ; Blanking active?
    breq LcdDec5d ; No
    ldi R16,' ' ; Blank character
  LcdDec5d:
    rjmp LcdChar ; Display char in R16
  ;
  .endif
;
; ****************************************
;  H E X A D E C I M A L  A U S G E B E N
; ****************************************
;
; Routines for conversion and displaying hexadecimals
;
.ifdef LcdHex
  ;
  ; LcdHex2 converts binary in R16 to hexadecimal
  ;   on the LCD
  LcdHex2:
    push R16 ; Is further needed
    swap R16 ; Upper nibble to lower nibble
    rcall LcdNibble ; Display lower nibble
    pop R16 ; Restore binary
  LcdNibble:
    andi R16,0x0F ; Clear upper nibble
    subi R16,-'0' ; Add ASCII-0
    cpi R16,'9'+1 ; A to F?
    brcs LcdNibble1 ; No
    subi R16,-7 ; Add 7
  LcdNibble1:
    rjmp LcdChar ; Display char on LCD
  ;
  ; LcdHex4 converts binary in Z to hexadecimal
  LcdHex4:
    mov R16,ZH ; MSB to R16
    rcall LcdHex2 ; Display byte hexadecimal
    mov R16,ZL ; LSB to R16
    rjmp LcdHex2 ; Display byte hexadecimal
  ;
  .endif
;
; *******************************
;  L C D   S U B R O U T I N E S
; *******************************
;
; Wait until LCD busy flag clear
;   needed only if wait mode is off
.if LcdWait == 0
  LcdBusy:
    push R16 ; Save R16
    .if LcdBits == 8
      clr R16 ; Data bus direction to input
      .else
      in R16,pLcdDD ; Read direction bits
      .if Lcd4High == 1
        andi R16,0x0F ; Clear upper nibble
        .else
        andi R16,0xF0 ; Clear lower nibble
        .endif
      .endif
    out pLcdDD,R16 ; Write to direction port
    .if LcdBits == 8
      clr R16 ; All output pins to low
      .else
      in R16,pLcdDO ; Read output port
      .if Lcd4High == 1
        andi R16,0x0F ; Clear upper nibble
        .else
        andi R16,0xF0 ; Clear lower nibble
        .endif
      .endif
    out pLcdDO,R16 ; Clear pull-Ups
    cbi pLcdCRSO,bLcdCRSO ; RS pin to low
    sbi pLcdCRWO,bLcdCRWO ; RW pin to high
  LcdBusyWait:
    rcall LcdIn ; Activate E, read data port, deactivate E
    .if LcdBits == 4
      rcall LcdPulseE ; Dummy for lower nibble
      .endif
    lsl R16 ; Busy flag to carry
    brcs LcdBusyWait ; Flag = 1, wait on
    cbi pLcdCRWO,bLcdCRWO ; RW pin to low
    .if LcdBits == 8
      ldi R16,0xFF ;  Data to output
      .else
      in R16,pLcdDD ; Read direction data port
      .if Lcd4High == 1
        ori R16,0xF0 ; Upper nibble high
        .else
        ori R16,0x0F ; Lower nibble high
        .endif
      .endif
    out pLcdDD,R16 ; Set direction port
    pop R16 ; Restore R16
    ret ; Done
  ;
  ; Read busy flag to R16
  ;   Needed only if wait mode off
  LcdIn:
    cbi pLcdCRSO,bLcdCRSO ; LCD RS pin to low
    sbi pLcdCEO,bLcdCEO ; Set bit bLcdCEO on LCD control port
    nop ; Wait at least one clock cycle
    .if clock>1000000 ; Insert further NOPs for higher clocks
      nop
      .endif
    .if clock>2000000
      nop
      .endif
    .if clock>3000000
      nop
      .endif
    .if clock>3000000
      nop
      .endif
    .if clock>4000000
      nop
      .endif
    .if clock>5000000
      nop
      .endif
    .if clock>6000000
      nop
      .endif
    .if clock>7000000
      nop
      .endif
    .if clock>8000000
      nop
      .endif
    .if clock>9000000
      nop
      .endif
    .if clock>10000000
      nop
      .endif
    .if clock>11000000
      nop
      .endif
    .if clock>12000000
      nop
      .endif
    .if clock>13000000
      nop
      .endif
    .if clock>14000000
      nop
      .endif
    .if clock>15000000
      nop
      .endif
    .if clock>16000000
      nop
      .endif
    .if clock>17000000
      nop
      .endif
    .if clock>18000000
      nop
      .endif
    .if clock>19000000
      nop
      .endif
    in R16,pLcdDI ; Read data bus to R16
    cbi pLcdCEO,bLcdCEO ; Clear bit bLcdCEO
    ret
  .endif
;
; 1 us pulse on LCD E pin
LcdPulseE:
  sbi pLcdCEO,bLcdCEO ; Set bit bLcdCEO in LCD control port
  .if clock>1000000 ; Add further NOPs for higher clocks
    nop
    .endif
  .if clock>2000000
    nop
    .endif
  .if clock>3000000
    nop
    .endif
  .if clock>3000000
    nop
    .endif
  .if clock>4000000
    nop
    .endif
  .if clock>5000000
    nop
    .endif
  .if clock>6000000
    nop
    .endif
  .if clock>7000000
    nop
    .endif
  .if clock>8000000
    nop
    .endif
  .if clock>9000000
    nop
    .endif
  .if clock>10000000
    nop
    .endif
  .if clock>11000000
    nop
    .endif
  .if clock>12000000
    nop
    .endif
  .if clock>13000000
    nop
    .endif
  .if clock>14000000
    nop
    .endif
  .if clock>15000000
    nop
    .endif
  .if clock>16000000
    nop
    .endif
  .if clock>17000000
    nop
    .endif
  .if clock>18000000
    nop
    .endif
  .if clock>19000000
    nop
    .endif
  cbi pLcdCEO,bLcdCEO ; Clear bit bLcdCEO
  ret
;
; Write R16 in 8 bit mode to LCD control
Lcd8Ctrl:
  .if simulation == 1
    push R16
    clr R16
    sts SimDisplayPos,R16
    pop R16
    .endif
  .if LcdBits == 4
    push ZL ; Save ZL
    in ZL,pLcdDO ; Read data output port
    .if Lcd4High == 1
      andi ZL,0x0F ; Clear upper nibble output
      andi R16,0xF0 ; Clear lower nibble R16
      .else
      andi ZL,0xF0 ; Clear lower nibble output
      swap R16 ; Swap upper and lower nibble
      andi R16,0x0F ; Clear upper nibble R16
      .endif
    or R16,ZL ; Combine
    pop ZL
    .endif
  out pLcdDO,R16 ; Data to LCD output port
  cbi pLcdCRSO,bLcdCRSO ; RS bit low
  rjmp LcdPulseE ; Activate E
;
; LcdData: Write data in R16 to LCD
;   R16: Character or data
LcdData:
  .if LcdWait == 0
    rcall LcdBusy ; Wait for busy flag of the LCD
    .endif
  sbi pLcdCRSO,bLcdCRSO ; Set RS bit
  .if LcdBits == 4
    push ZL
    push R16
    in ZL,pLcdDO ; Read data output port
    .if Lcd4High == 1
      andi ZL,0x0F ; Clear upper nibble port
      andi R16,0xF0 ; Clear lower nibble R16
      .else
      andi ZL,0xF0 ; Clear lower nibble port
      swap R16 ; Oberes und unteres Nibble vertauschen
      andi R16,0x0F ; Clear upper nibble R16
      .endif
    or R16,ZL ; Combine
    out pLcdDO,R16
    rcall LcdPulseE ; Activate E
    pop R16 ; Restore R16
    push R16 ; and save again
    in ZL,pLcdDO ; Read data output port
    .if Lcd4High == 1
      andi ZL,0x0F ; Preserve lower nibble of port
      swap R16 ; Upper to lower nibble
      andi R16,0xF0 ; Preserve upper nibble R16
      .else
      andi ZL,0xF0 ; Preserve upper nibble port
      andi R16,0x0F ; Preserve lower nibble R16
      .endif
    or R16,ZL ; Combine
    out pLcdDO,R16 ; To data bus
    rcall LcdPulseE ; Activate E
    pop R16 ; Restore R16
    pop ZL ; Restore ZL
    .else
    out pLcdDO,R16 ; Byte to data bus
    rcall LcdPulseE ; Activate E
    .endif
  .if LcdWait == 1
    rjmp LcdWait40us ; Wait for 40 us
    .endif
  ret
;
; ********************************
;    W A I T   R O U T I N E S
; ********************************
;
; Wait for 50 ms
;   At clock frequencies above 3.1 MHz the maximum
;   counting capability of Z exceeds its limit,
;   therefore the 50 ms calls the 5 ms delay 10
;   times
LcdWait50ms:
  push R16 ; Save R16
  ldi R16,10 ; 10 times 5 ms
LcdWait50ms1:
  rcall LcdWait5ms ; Call 5 ms delay
  dec R16 ; Decrease counter
  brne LcdWait50ms1 ; Repeat
  pop R16 ; Restore R16
  ret
;
LcdWait5ms:
  ; Wait for 5 ms, RCALL 3 clock cycles
  .equ cLcdZ5ms = (5*clock/1000 - 18 + 2) / 4
  push ZH ; Save ZH, + 2 cycles
  push ZL ; Save ZL, + 2 cycles
  ldi ZH,High(cLcdZ5ms) ; + 1 cycle
  ldi ZL,Low(cLcdZ5ms) ; + 1 cycle
  rjmp LcdWaitZ ; + 2 cycles
  ; Total: 11 clock cycles
;
.if LcdWait == 1 ;
  ; Wait routines necessary for wait mode
  ; Wait 1.64 ms
  .equ cLcdZ1640us = (164*(clock/1000)/100 - 18 + 2) / 4
  LcdWait1640us:
  push ZH ; Save ZH
  push ZL ; Save ZL
  ldi ZH,High(cLcdZ1640us)
  ldi ZL,Low(cLcdZ1640us)
  rjmp LcdWaitZ
  ;
  ; Wait 40 us
  .equ cLcdZ40us = (40*clock/1000000 - 18 + 2) / 4
  LcdWait40us:
  push ZH ; Save ZH
  push ZL ; Save ZL
  ldi ZH,High(cLcdZ40us)
  ldi ZL,Low(cLcdZ40us)
  rjmp LcdWaitZ
  .endif
;
; Wait routine for Z cycles
LcdWaitZ: ; 11 clock cycles
  sbiw ZL,1 ; Count down, +2 cycles
  brne LcdWaitZ ; If not zero: go on, + 2 cycles; if zero: 1 cycle
  pop ZL ; Restore ZL, +2 cycles
  pop ZH ; Restore ZH, +2 cycles
  ret ; Back, 4 cycles
  ; Total cycles: 11 + 4 * (Z - 1) + 3 + 8
  ;
  ; Number cycles= 11 + 4 * (Z - 1) + 3 + 8
  ;                11: RCALL, PUSH, PUSH, LDI, LDI, RJMP
  ;                4: 4 cycles per Z count (minus 1)
  ;                3: 3 cycles for last count
  ;                8: POP, RET
  ;              = 4 * Z + 11 - 4 + 3 + 8
  ;              = 4 * Z + 18
  ; Z = (Clock cycles - 18 + 2) / 4
  ;                +2: Rounding up during division by 4
;
; End of include file
;

Page top Main LCD Music Debug

3 The music music_code.inc

Link to this code in asm format

;
; ***********************************
; * Playing music with the ATmega16 *
; * (C)2018 by avr-asm-tutorial.net *
; ***********************************
;
; Convert a music piece to SRAM and start it
;   rmp points to a melody number (0..8)
MusicConvert:
  cpi rmp,cMaxMelody+1 ; rmp inside number of melodies?
  brcs MusicConvert1
  subi rmp,cMaxMelody
  brcc MusicConvert
  subi rmp,-cMaxMelody ; Add number of melodies
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) ; To SRAM start of music
  ldi XL,Low(sMelody)
MusicConvert2:
  lpm rmp,Z+ ; Read note
  cpi rmp,MelodyEnd ; End of melody?
  breq MusicConvertEnd
  rcall ConvertNote ; CTC to Y, Delay to R1:R0
  lpm rmp,Z ; Read duration multiplier
  andi rmp,0x0F ; Lower byte
MusicConvert3:
  tst rmp
  breq MusicConvert6
  lsr R1 ; Divide duration by two
  brne MusicConvert5 ; Not zero, without rounding
  ror R0 ; MSB zero, rotate with rounding
  brcc MusicConvert4 ; A one rotated to carry
  inc R0 ; Round up
  brne MusicConvert4 ; No carry
  inc R1 ; Increase MSB
MusicConvert4:
  dec rmp ; Decrease rotation
  rjmp MusicConvert3
MusicConvert5:
  ror R0 ; MSB larger than 0, rotate without rounding
  dec rmp ; Decrease rotation
  rjmp MusicConvert3
MusicConvert6:
  lpm rmp,Z+ ; Re-read divider
  swap rmp ; Upper nibble to lower nibble
  andi rmp,0x0F ; Isolate adder
  breq MusicConvert8 ; No adding
  push ZH ; Save Z for adding
  push ZL
  mov ZH,R1 ; Copy current count
  mov ZL,R0
MusicConvert7:
  add R0,ZL ; Add once
  adc R1,ZH
  dec rmp ; Decrease Adder
  brne MusicConvert7 ; Add on
  pop ZL ; Restore Z
  pop ZH
MusicConvert8:
  mov rmp,R0 ; Counter at zero?
  or rmp,R1 ; Or MSB
  brne MusicConvert9
  inc R0 ; Minimum one CTC cycle
MusicConvert9:
  st X+,YH ; Write MSB CTC to SRAM
  st X+,YL ; Then LSB CTC to SRAM
  st X+,R1 ; MSB duration to SRAM
  st X+,R0 ; LSB duration to SRAM
  cpi XH,High(sMelodyEnd+4) ; End of buffer reached?
  brcs MusicConvert2 ; No
  cpi XL,Low(sMelodyEnd+4) ; LSB?
  brcs MusicConvert2 ; No
MusicConvertEnd:
  ldi rmp,0xFF ; End of melody
  st X+,rmp
  st X+,rmp
  clr rmp ; Add 0x0000
  st X+,rmp
  st X,rmp
;
; Play music in SRAM (again)
MusicPlay:
  ldi YH,High(sMelody) ; Point Y to SRAM buffer
  ldi YL,Low(sMelody)
  ldi rmp,(1<<TOIE0)|(1<<OCIE1A)|(1<<TOIE2) ; Enable int TC1
  out TIMSK,rmp ; in Timer int mask
  ldi rDurL,1 ; Start timer with one
  clr rDurH
  ret
;
; Converts a note in rmp
;   Returns CTC 16 bit value in Y
;           Duration 16 bit value in R1:R0
ConvertNote:
  push ZH ; Save Z
  push ZL
  ldi ZH,High(2*NotesTimerTable)
  ldi ZL,Low(2*NotesTimerTable)
  mov R0,rmp
  clr R1
  lsl R0 ; Multiply by 4
  rol R1
  lsl R0
  rol R1
  add ZL,R0 ; Add to note table
  adc ZH,R1
  lpm YL,Z+
  lpm YH,Z+
  lpm R0,Z+
  lpm R1,Z
  pop ZL
  pop ZH
  ret
;
; Table of frequencies of music notes
;   For calculating notes from clock
;   fe renamed fee to avoid conflict with FE
;   (Framing Error, UART)!
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
;
; Notes table: yields the CTC value for TC1 and
;   the number of CTC cycles for the duration
;   of one second for all music notes
; Adjusted for a 16 bit timer, clock frequency
;   in Hz and prescaler = 1
; Timer table for the music notes 0 to 70
;   First word:  Compare value for CTC
;   Second word: Duration = number of CTC cycles
;                = 2*frequency of note
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, speaker off, #70
; End
.equ MelodyEnd = 0xFF
;
; Melodies
;   Melodies that the clock can play randomly
;   Maximum 16 melodies (0..15)
;
.equ cMaxMelody = 8 ; Number of melodies
;
; Melody table
;   Table entries point to begin of melodies
;   in flash * 2
;
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
;
; Duration dividers for tones and pauses:
.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, speed = 80 er minute 
  ; G    e r   h    a  r   d    S   c    h    m  i  d   t
  ; --.  . .-. .... .- .-. -..  ... -.-. .... -- .. -.. -
  ; '.' = 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
;

Page top Main LCD Music Debug

4 The routines for debugging debug_code_en.inc

Link to this code in asm format

;
; *********************************
; * Debug codes for dcf77_m16_v4  *
; * (C)2018 by Gerhard Schmidt    *
; *********************************
;
; Debug the conversion of DCF signals to date/time
; Set received DCF signal to 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 ; Debug the conversion routine
DbgMinuteLoop: ; Stop further execution
  rjmp DbgMinuteLoop
;
; Debug the ADC by displaying the raw results
;   from the selected channel
DebugAdc:
  ldi rmp,debugAdcChannel|(1<<REFS0) ; Select channel
  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
;
; Debug the keys attached by displaying
; their current state over and over
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
;
; Speaker to 1.000 Hz
DebugSpeaker:
  sbi pSpkD,bSpkD ; Output pin active
  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 COM1A
  out TCCR1A,rmp
  ldi rmp,(1<<WGM12)|(1<<CS10) ; CTC, prescaler = 1
  out TCCR1B,rmp
DebugSpeaker1:
  rjmp DebugSpeaker1
;
; Init the debugging of DCF signal duration analysis
DebugDcfDur:
  ldi ZH,3
  ldi ZL,12
  call LcdPos
  ldi ZH,High(2*DebugDcfText)
  ldi ZL,Low(2*DebugDcfText)
  jmp LcdText
;
; Display DCF signal duration
DebugDcfDurDispl:
  ldi ZH,3
  ldi ZL,0
  sbic pDcfI,bDcfI
  ldi ZL,6
  rcall LcdPos
  mov R0,rDcfL ; Copy pulses
  mov R1,rDcfH
  lsl R0 ; Multiply by 5
  rol R1
  lsl R0
  rol R1
  add R0,rDcfL
  adc R1,rDcfH
  mov ZH,R1
  mov ZL,R0
  jmp LcdDec5 ; Display pulse duration in ms on LCD
;
; Echo DCF bits
DebugDcfBitEcho:
  ldi ZH,3
  ldi ZL,0
  rcall LcdPos
  mov rmp,rDcfBitCnt ; Display bit number
  rcall LcdHex2
  ldi rmp,':'
  rcall LcdChar
  ldi rmp,' '
  rcall LcdChar
  lds ZL,sDcfBits+7 ; Load newest byte
  ldi ZH,8
Dcf01Ok1:
  ldi rmp,'0' ; Display zero
  lsl ZL
  brcc Dcf01Ok2 ; Display one
  ldi rmp,'1' ; Display one
Dcf01Ok2:
  rcall LcdChar
  dec ZH
  brne Dcf01Ok1
  ret
;
; Debug the DCF signal display the last 8 bits
DebugDcfSig1:
  ldi ZH,3 ; Long version of report on line 4
  ldi ZL,0
  rcall LcdPos
  mov R0,rDcfBitCnt ; Display number of bits
  rcall LcdDec2
  ldi ZH,High(2*LcdErr6) ; Add number of bit text
  ldi ZL,Low(2*LcdErr6)
  jmp LcdText
;
; Debug the DCF signal with long error messages
DebugDcfSig2:
  mov XH,ZH ; Save Z position in flash
  mov XL,ZL
  ldi ZH,3
  ldi ZL,0
  rcall LcdPos
  mov ZH,XH ; Restore report text to Z
  mov ZL,XL
  rjmp LcdText
;
; Display the key state in line 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
;
; Set debugging active display, message on line 3
;
DebugActive:
  ldi ZH,2
  ldi ZL,0
  rcall LcdPos
  ldi ZH,High(2*DebugActiveText)
  ldi ZL,Low(2*DebugActiveText)
  rjmp LcdText
;
DebugActiveText:
  .db "Debugging activated!",0xFE,0xFE
;
DebugDcfText:
  .db "ms hi/lo ",0xFE
;
; Debug the received DCF77 information
;
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
;
; Test blinking of the LCD
Blinktest:
  clr XL
  rcall LcdBlink
Blinktest1:
  ldi rmp,40
DelayA:
  rcall LcdWait50ms
  dec rmp
  brne DelayA
  ldi ZH,High(2*LcdTxtInit1)
  ldi ZL,Low(2*LcdTxtInit1)
  rcall LcdText
  ldi ZH,0
  inc XL
  mov ZL,XL
  rcall LcdPos
  rjmp Blinktest1
;
; Debug the music play
DebugMusic:
  ldi ZH,3 ; Last line
  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
;
; Debug the status of 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'
  rcall LcdChar
  ldi rmp,' '
  rcall LcdChar
  ldi rmp,'S' ; Display stackpointer
  rcall LcdChar
  ldi rmp,'t'
  rcall LcdChar
  cli
  in R0,SPL
  in R1,SPH
  sei
  rjmp LcdHex4
;
; For date/time 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
;
; End of include file
;

Page top Main LCD Music Debug


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