![]() |
AVR-Anwendungen DCF77-Uhr mit ATtiny24 Assembler-Quellcode für die DCF77-Uhr |
![]() |
;
; ***********************************
; * DCF77 receiver ATtiny24 V3 *
; * with external crystal 32768 Hz *
; * (C)2019 by avr-asm-tutorial.net *
; ***********************************
;
.nolist
.include "tn24adef.inc" ; Define device ATtiny24A
.list
;
; **********************************
; D E B U G S W I T C H E S
; **********************************
;
.equ Yes = 1
.equ No = 0
;
.equ SkipLcd = No ; Skip all LCD routines
;
; Test the DCF77 pulse calculation routine
.equ DebugDcfPulse = No ; Debug the DCF pulse code
;
; Test the DCF77 conversion routine
.equ DebugDcfCode = No ; Debug the DCF code
;
; Test the INT0 pulse durations
; measures pulse durations and displays TC1 ticks
.equ DebugPulseDur = No ; Debug pulse durations
;
; **********************************
; H A R D W A R E
; **********************************
; ;
; Device: ATtiny24A, Package: 14-pin-PDIP_SOIC
;
; _________
; 1 / |14
; + 5V o--|VCC GND|--o 0 V
; XTAL1 o--|PB0 PA0|--o J1-UTC
; XTAL2 o--|PB1 PA1|--o LCD-RS
; RESET o--|RESET PA2|--o LCD-R/W
; DCF77 o--|PB2 PA3|--o LCD-E
; LCD-D7 o--|PA7 PA4|--o LCD-D4
; LCD-D6 o--|PA6 PA5|--o LCD-D5
; 7 |__________|8
;
; **********************************
; P O R T S A N D P I N S
; **********************************
;
; Jumper 1 ports and pins
.equ pJ1O = PORTA ; Jumper 1 output port
.equ bJ1O = PORTA0 ; Jumper 1 output portpin
.equ pJ1D = DDRA ; Jumper 1 direction port
.equ bJ1D = DDA0 ; Jumper 1 direction portpin
.equ pJ1I = PINA ; Jumper 1 input port
.equ bJ1I = PINA0 ; Jumper 1 input portpin
;
; DCF77 ports and pins
.equ pDcfO = PORTB ; DCF77 output port
.equ bDcfO = PORTB2 ; DCF77 output portpin
.equ pDcfD = DDRB ; DCF77 direction port
.equ bDcfD = DDB2 ; DCF77 direction portpin
.equ pDcfI = PINB ; DCF77 input port
.equ bDcfI = PINB2 ; DCF77 input portpin
;
; (Ports of the LCD see LCD configuration section)
;
; **********************************
; A D J U S T A B L E C O N S T
; **********************************
;
.equ crystal = 32768 ; Crystal frequency
;
; DCF77 signal durations, all in ms
.equ MySet = Yes ; Use individually measured set
.if MySet == No
; The standard set
.equ Spur = 5 ; Spurious signal
.equ Zero = 100 ; Binary zero
.equ One = 200 ; Binary one
.equ Pause = 1000-(Zero+One)/2 ; Pause between bit and next second pulse
.else
; The individual set
.equ Spur = 10 ; Longer spurious signal
.equ Zero = 130 ; Longer zero
.equ One = 225 ; Longer one
.equ Pause = 825 ; Shorter pause
.endif
.equ Minute = 2000-(Zero+One)/2 ; Minute pulse
.equ TimeOut = 5000 ; Signal time out
;
; DCF77 signal tolerance
.equ cDcfTol = 25 ; Tolerance in percent
;
; Monitor incoming DCF77 signals
; Select only one of those as they write on the
; same space on the LCD
.equ cDcfMoniBits = No ; Monitor the incoming bits
.equ cDcfMoniBitCount = No ; Monitor the bit count
;
; Date format selection
.equ cDateFormat = 0 ; 0=DE, 1=EN
;
; Delay between init display and mask display
.equ cTxtDelay = 100 ; Delay start-up, multiples of 50 ms
;
; **********************************
; F I X & D E R I V. C O N S T
; **********************************
;
; TC0 as seconds pulse generator
.equ cTc0Presc = 64 ; Prescaler value
.equ cTc0Div = 256 ; Overflow
.equ cDivSec = crystal / cTc0Presc / cTc0Div
;
; TC1 as DCF77 signal duration counter
.equ cTc1Presc = 64 ; Prescaler value
.equ cTc1Count = crystal / cTc1Presc ; Counter frequency
.equ cTc1T = 1000000 / cTc1Count ; Time per Tick in us
; Default is: 1,953 us/tick
;
; DCF77 signal durations as TC1 counts, with tolerances
.equ cSpur=(1000*Spur)/cTc1T + 1 ; Spurious signal duration count
.equ cZeroMin=((Zero-Zero*cDcfTol/100)*1000)/cTc1T ; Minimum zero
.equ cZeroMax=((Zero+Zero*cDcfTol/100)*1000)/cTc1T + 1 ; Maximum zero
.equ cOneMin=((One-One*cDcfTol/100)*1000)/cTc1T ; Minimum one
.equ cOneMax=((One+One*cDcfTol/100)*1000)/cTc1T+1 ; Maximum one
.equ cPauseMin=((Pause-Pause*cDcfTol/100)*1000)/cTc1T ; Minimum pause
.equ cPauseMax=((Pause+Pause*cDcfTol/100)*1000)/cTc1T+1 ; Maximum pause
.equ cMinuteMin=((Minute-Minute*cDcfTol/100)*1000)/cTc1T ; Minimum minute
.equ cMinuteMax=((Minute+Minute*cDcfTol/100)*1000)/cTc1T+1 ; Maximum minute
.equ cTimeOut=(1000*TimeOut)/cTc1T ; Timeout value
;
; Error checking
.if cZeroMin <= cSpur
.error "Zero min less or equal spurious!"
.endif
.if cOneMin <= cZeroMax
.error "One min less or equal zero max!"
.endif
.if cPauseMin <= cOneMax
.error "Pause min less or equal one max!"
.endif
.if cMinuteMin <= cPauseMax
.error "Minute min less or equal pause max!"
.endif
.if cTimeOut <= cMinuteMax
.error "Time out less or equal minute max!"
.endif
;
; ***********************************
; L C D C O N F I G U R A T I O N
; ***********************************
;
; Specific parameter set of properties/definitions
.equ clock = crystal ; Clock frequency of controller in Hz
; LCD size:
.equ LcdLines = 2 ; Number of lines (1, 2, 4)
.equ LcdCols = 16 ; 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 = PORTA ; Control E output port
.equ bLcdCEO = PORTA3 ; Control E output portpin
.equ pLcdCED = DDRA ; Control E direction port
.equ bLcdCED = DDA3 ; Control E direction portpin
.equ pLcdCRSO = PORTA ; Control RS output port
.equ bLcdCRSO = PORTA1 ; Control RS output portpin
.equ pLcdCRSD = DDRA ; Control RS direction port
.equ bLcdCRSD = DDA1 ; Control RS direction portpin
; If LcdWait = 0:
.equ pLcdDI = PINA ; Data input port
.equ pLcdCRWO = PORTA ; Control RW output port
.equ bLcdCRWO = PORTA2 ; Control RW output portpin
.equ pLcdCRWD = DDRA ; Control RW direction port
.equ bLcdCRWD = DDA2 ; 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
;
; **********************************
; R E G I S T E R S
; **********************************
;
.def rDcf0 = R0 ; DCF bits shift register 0
.def rDcf1 = R1 ; dto., 1
.def rDcf2 = R2 ; dto., 2
.def rDcf3 = R3 ; dto., 3
.def rDcf4 = R4 ; dto., 4
.def rDcf5 = R5 ; dto., 5
.def rDcf6 = R6 ; dto., 6
.def rDcf7 = R7 ; dto., 7
; free: R8
.def rDcfBits = R9 ; DCF bits counter
.def rDcfErr = R10 ; DCF signal error
.def rTc1L = R11 ; TC1 count, LSB
.def rTc1H = R12 ; dto., MSB
.def rDcfL = R13 ; DCF duration, LSB
.def rDcfH = R14 ; dto., MSB
.def rSreg = R15 ; Save/Restore status port
.def rmp = R16 ; Define multipurpose register
.def rimp = R17 ; Multipurpose inside ints
.def rFlag = R18 ; Flag register
.equ bSec = 0 ; Seconds flag
.equ bDcf = 1 ; DCF77 pulse flag
.equ bDcfTO = 2 ; DCF77 missing signal
.equ bDcfErr = 3 ; DCF77 error flag
.equ bDcfOk = 4 ; DCF77 conversion ok
.equ bUtcChg = 4 ; UTC change
.equ bUtc = 5 ; UTC flag
.def rDivSec = R19 ; Seconds divider
; free: R20 to R25
; used: R27:R26 = X as pointer outside ints
; used: R29:R28 = Y as output pointer outside ints
; used: R31:R30 = Z for diverse purposes outside ints
;
; **********************************
; S R A M
; **********************************
;
.dseg
.org SRAM_START
sMet: ; MET = ss_mm_hh_wd_DD_MM_YY_ST (ST=Summer time)
.byte 8 ; 8 bytes for base time (middle european time)
sUtc: ; UTC = hh_wd_DD_MM_YY
.byte 5 ; 5 bytes for universial time coordinated
sDcf: ; DCF77 time = mm_hh_wd_DD_MM_YY
.byte 7 ; 7 bytes for received DCF77 time
; Displacements relative to sMet:
.equ dSec = 0
.equ dMin = 1
.equ dHour = 2
.equ dWd = 3
.equ dDay = 4
.equ dMonth = 5
.equ dYear = 6
.equ dSummer = 7
.equ dUtcHour = 8
.equ dUtcWd = 9
.equ dUtcDay = 10
.equ dUtcMonth = 11
.equ dUtcYear = 12
.equ dDcfMin = 13
.equ dDcfHour = 14
.equ dDcfWd = 15
.equ dDcfDay = 16
.equ dDcfMonth = 17
.equ dDcfYear = 18
.equ dDcfSummer = 19
;
; **********************************
; D C F E R R O R C O D E S
; **********************************
;
; Error codes that occur on the end of line 1 of the LCD
; E0: Time-out on the DCF input pin
; E1: Signal too short
; E2: Signal between zero and one
; E3: Signal between one and pause
; E4: Signal between a pause and a minute
; E5: Longer than minute
; (E6): Not 59 bits, displays number of bits decimal
; instead of error code
; E7: Parity minutes odd
; E8: Minute ones larger than 9
; E9: Minutes larger than 60
; EA: Hours parity odd
; EB: Hour ones larger than 9
; EC: Hours larger than 23
; ED: Date parity odd
; EE: Day ones larger than nine
; EF: Day larger than 31
; EG: Weekday is zero
; EH: Month ones larger than 9
; EI: Month ones larger than 12
; EJ: Year ones larger than ten
; EK: Year larger than 99
; EL: First bit is not zero
; EM: Date/Time start bit not a one
;
; **********************************
; C O D E
; **********************************
;
.cseg
.org 000000
;
; **********************************
; R E S E T & I N T - V E C T O R S
; **********************************
rjmp Main ; Reset vector
rjmp Int0Isr ; EXT_INT0
rjmp Pci0Isr ; PCI0
reti ; PCI1
reti ; WATCHDOG
reti ; ICP1
rjmp Oc1AIsr ; OC1A
reti ; OC1B
reti ; OVF1
reti ; OC0A
reti ; OC0B
rjmp Ovf0Isr ; OVF0
reti ; ACI
reti ; ADCC
reti ; ERDY
reti ; USI_STR
reti ; USI_OVF
;
; **********************************
; I N T - S E R V I C E R O U T .
; **********************************
;
; INT0 interrupt service routine
; called by: DCF77 input pin on level change
; read TC1 count and
; if spurious length: continue
; if not: copy counter, set bDcf flag, clear counter
Int0Isr: ; 7 clock cycles for int and vector jump
in rSreg,SREG ; Save SREG, +1=8
in rTc1L,TCNT1L ; Read LSB counter, +1=9
in rTc1H,TCNT1H ; dto., MSB, +1=10
ldi rimp,Low(cSpur) ; LSB of spurious count, +1=11
cp rTc1L,rimp ; Count smaller spurious? +1=12
ldi rimp,High(cSpur) ; MSB of spurious count, +1=13
cpc rTc1H,rimp ; Count smaller spurious, +1=14
brcs Int0Isr1 ; Spurious, ignore, +1/2=15/16
clr rimp ; Restart counter, +1=16
out TCNT1H,rimp ; Clear TC1 MSB, +1=17
out TCNT1L,rimp ; dto., LSB, +1=18
mov rDcfL,rTc1L ; Copy count, LSB, +1=19
mov rDcfH,rTc1H ; dto., MSB, +1=20
sbr rFlag,1<<bDcf ; Set DCF flag, +1=21
Int0Isr1: ; 16/21 clock cycles
out SREG,rSreg ; Restore SREG, +1=17/22
reti ; +4=21/26
; 21 or 26 clock cycles = 0.64 ms resp. 0.79 ms
; Restart counter delay = 18 clock cycles = 0.55 ms
;
; Jumper input has changed
Pci0Isr:
in rSreg,SREG ; Save SREG
sbr rFlag,1<<bUtcChg ; Set change flag
out SREG,rSreg ; Restore flag
reti
;
; OC1A interrupt service routine
; called by overflow of the DCF77 duration counter
; set bDcfTO flag
Oc1AIsr:
in rSreg,SREG ; Save SREG
sbr rFlag,1<<bDcfTO ; Set DCF signal timeout flag
out SREG,rSreg ; Restore SREG
reti
;
; TC0 overflow interrupt service routine
; called by CTC event every 200 ms
; downcount the second divider
; if zero: set seconds flag and restart second divider
Ovf0Isr: ; 7 clock cycles int plus vector jump
in rSreg,SREG ; Save SREG, +1=8
dec rDivSec ; Count divider down, +1=9
brne Ovf0Isr1 ; Not at zero, +1/2=10/11
sbr rFlag,1<<bSec ; Set seconds flag, +1=11
ldi rDivSec,cDivSec ; Restart divider, +1=12
Ovf0Isr1: ; 11/12 clock cycles
out SREG,rSreg ; Restore SREG, +1= 12/13
reti ; +4=16/17 clock cycles
;
; **********************************
; M A I N P R O G R A M I N I T
; **********************************
;
Main:
ldi rmp,Low(RAMEND)
out SPL,rmp ; Init LSB stack pointer
.ifdef SPH ; For the ATtiny84
ldi rmp,High(RAMEND) ; Set MSB of stack
out SPH,rmp
.endif
ldi YH,High(sMet)
ldi YL,Low(sMet)
;
; **********************************
; D E B U G R O U T I N E S
; **********************************
;
; Test the DCF77 pulse analysis routine
.if DebugDcfPulse == Yes
.equ DebugDcfPulseTestTime = 850 ; Time to test in ms
.equ cDebugDcfPulseTest = DebugDcfPulseTestTime*1000/cTc1T ; Convert to TC1 counts
sbr rFlag,1<<bDcfErr ; Set or clear the DCF error flag
ldi ZH,High(cDebugDcfPulseTest) ; Simulate Testtime
ldi ZL,Low(cDebugDcfPulseTest)
mov rDcfH,ZH
mov rDcfL,ZL
.if (cDebugDcfPulseTest>=cMinuteMin)&&(cDebugDcfPulseTest<cMinuteMax)
rjmp DebugDcfPulse1
; Minute complete, copy DCF77 time pattern
; (Use the OpenOffice calculation sheet "dcf77_pattern" to generate)
; DCF77 code for 31.12.2099, 23:59, bytes 0 to 7
DcfTestTable: .db 0,0,64,102,141,99,202,76
DebugDcfPulse1:
ldi rmp,59 ; Simulate 59 bits received
mov rDcfBits,rmp
ldi ZH,High(2*DcfTestTable) ; Point to table
ldi ZL,Low(2*DcfTestTable)
clr XH ; Point to R0
clr XL
DebugDcfPulse2:
lpm rmp,Z+ ; Read byte
st X+,rmp ; Write byte to register
cpi XL,8 ; All 7 written?
brne DebugDcfPulse2 ; No, continue
.else
clr rDcfBits
.endif
rcall DcfP ; Check pulse
DebugDcfPulseLoop:
rjmp DebugDcfPulseLoop
.endif
;
; Test the DCF77 conversion routine
.if DebugDcfCode == Yes
ldi ZH,High(2*DcfCodeTable) ; Point to table
ldi ZL,Low(2*DcfCodeTable)
clr XH ; Point to R0
clr XL
DebugDcfCodeCopy:
lpm rmp,Z+ ; Read byte
st X+,rmp ; Write byte to register
cpi XL,8 ; All 7 written?
brne DebugDcfCodeCopy ; No, continue
rcall DcfMinute ; Convert DCF bits to date/time
DebugDcfCodeLoop:
rjmp DebugDcfCodeLoop
; (Use the OpenOffice calculation sheet "dcf77_pattern" to generate)
; DCF77 code for 31.12.2099, 23:59, bytes 0 to 7
DcfCodeTable: .db 0,0,64,102,141,99,202,76
.endif
;
; **********************************
; N O R M A L I N I T
; **********************************
; Init jumper port
cbi pJ1D,bJ1D ; Set jumper as input pin
sbi pJ1O,bJ1O ; Enable pull-up
; Init DCF port
cbi pDcfD,bDcfD ; Set DCF input as input pin
cbi pDcfO,bDcfO ; Disable pull-up
; Init LCD
.if SkipLcd == No
rcall LcdInit ; Initiate the LCD
.endif
clr ZH ; Back to line 1 column 1
clr ZL
.if SkipLcd == No
rcall LcdPos ; Set position on the LCD
.endif
ldi ZH,HIGH(2*InitText) ; Display init text on LCD
ldi ZL,LOW(2*InitText)
.if SkipLcd == No
rcall LcdText
ldi rmp,cTxtDelay ; Delay
Delay1:
rcall LcdWait50ms ; Wait for 50 ms
dec rmp ; Count down
brne Delay1 ; Continue
clr ZH ; LCD to line 1 column 1
clr ZL
rcall LcdPos
ldi ZH,High(2*ClockMask)
ldi ZL,Low(2*ClockMask)
rcall LcdText ; Write mask to LCD
.endif
; Init TC0 as seconds clock
ldi rDivSec,cDivSec ; Start seconds divider
ldi rmp,0 ; Normal counting mode
out TCCR0A,rmp ; in Timer control port A
ldi rmp,(1<<CS01)|(1<<CS00) ; Prescaler = 64
out TCCR0B,rmp
; Init TC1 as DCF77 signal duration counter
ldi rmp,High(cTimeOut) ; Time out DCF77 signal input
out OCR1AH,rmp
ldi rmp,Low(cTimeOut)
out OCR1AL,rmp
ldi rmp,0 ; CTC on OCR0A mode
out TCCR1A,rmp
ldi rmp,(1<<WGM12)|(1<<CS11)|(1<<CS10) ; CTC-A, Presc=64
out TCCR1B,rmp
; Init date/time in SRAM
.if DebugPulseDur == No
rcall InitDT
rcall UpDateUtc
sbrc rFlag,bUtc
rcall Conv2Utc
rcall DispAll
.endif
; Interrupt enables
ldi rmp,1<<TOIE0 ; TC0 overflow interrupt
out TIMSK0,rmp ; to timer 0 int mask
ldi rmp,1<<OCIE1A ; Compare match A interrupt
out TIMSK1,rmp ; to timer 1 int mask
; Sleep mode and external interrupts
ldi rmp,1<<PCINT0 ; Enable PCINT on PA0
out PCMSK0,rmp
ldi rmp,(1<<SE)|(1<<ISC00) ; Sleep mode idle, INT0 any changes
out MCUCR,rmp
ldi rmp,(1<<INT0)|(1<<PCIE0) ; Enable interrupts
out GIMSK,rmp ; in general interrupt mask
sei ; Enable interrupts
;
; **********************************
; P R O G R A M L O O P
; **********************************
;
Loop:
sleep ; Go to sleep
nop ; Wake up dummy, +1=1
sbrc rFlag,bDcf ; DCF signal flag clear? +1/2=2/3
rcall DcfP ; No, evaluate DCF pulse, +3=5 + 14.65 ms max.
sbrc rFlag,bSec ; Seconds flag clear? +1/2=3/4 wo DcfP, 6/7 + 14.65 ms max
rcall Seconds ; No, increase seconds, +3=7
sbrc rFlag,bUtcChg ; Jumper input unchanged?
rcall UtcChanged ; UTC jumper has changed
; Repeat if lengthy routines in between
sbrc rFlag,bDcf ; DCF signal flag clear?
rcall DcfP ; No, evaluate DCF pulse
sbrc rFlag,bSec ; Seconds flag clear?
rcall Seconds ; No, increase seconds
sbrc rFlag,bDcfTO ; DCF signal Timeout clear?
rcall DcfTimeOut ; Display time out
rjmp loop
;
; **********************************
; H A N D L I N G R O U T I N E S
; **********************************
;
; Seconds flag
Seconds:
cbr rFlag,1<<bSec ; Clear flag
.if DebugPulseDur == Yes
ret
.endif
ldd rmp,Y+dSec ; Read seconds
inc rmp
std Y+dSec,rmp ; Write seconds
cpi rmp,60 ; End of minute?
brcc Minutes ; Yes
rjmp DisplSc ; Display seconds
Minutes:
clr rmp ; Seconds restart
std Y+dSec,rmp
ldd rmp,Y+dMin ; Read minutes
inc rmp
std Y+dMin,rmp ; Write minutes
cpi rmp,60 ; End of hour?
brcc Hours
rjmp DisplMi ; Display minutes
Hours:
clr rmp ; Minutes restart
std Y+dMin,rmp
ldd rmp,Y+dHour ; Read hour
inc rmp
std Y+dHour,rmp ; Write hours
cpi rmp,24 ; End of day?
brcc Day
rjmp DisplHr ; Display hour
Day:
clr rmp ; Restart hours
std Y+dHour,rmp
ldd rmp,Y+dWd ; Weekday
inc rmp
cpi rmp,7 ; End of week?
brcs Day1
ldi rmp,0
Day1:
std Y+dWd,rmp
ldd rmp,Y+dDay
inc rmp ; Next day
std Y+dDay,rmp
ldd ZH,Y+dMonth
cpi ZH,2 ; February
brne Day2 ; Not February
ldd ZH,Y+dYear ; Read year
andi ZH,0x03 ; Leap year?
ldi ZL,29 ; February has 28 days
brne Day4 ; No leap year
ldi ZL,30 ; February has 29 days
rjmp Day4 ; Compare day with ZL
Day2:
cpi ZH,8 ; Month larger than August?
brcs Day3 ; No, don't reverse number of days
dec ZH ; Reverse number of days
Day3:
ldi ZL,31 ; Month has 30 days
sbrc ZH,0 ; Bit 0 of month
inc ZL
Day4:
cp rmp,ZL ; Compare day with number of days
brcc Month ; Month has ended
rjmp DisplDy ; Display day
Month:
ldi rmp,1 ; Restart days
std Y+dDay,rmp
ldd rmp,Y+dMonth ; Read month
inc rmp
std Y+dMonth,rmp ; Write month
cpi rmp,13
brcc Year
rjmp DisplMo ; Display month
Year:
ldi rmp,1 ; Restart month
std Y+dMonth,rmp
ldd rmp,Y+dYear ; Read year
inc rmp
std Y+dYear,rmp ; Write year
cpi rmp,100 ; End of 100 years?
brcc Year100 ; Yes
rjmp DisplYr ; Display year
Year100:
clr rmp ; Restart Year
std Y+dYear,rmp
rjmp DisplYr
;
; **********************************
; D E F A U L T D A T E / T I M E
; **********************************
;
; Init date and time
InitDT:
ldi ZH,High(2*InitDTTable)
ldi ZL,Low(2*InitDTTable)
InitDTZ:
ldi XH,High(sMet) ; Pointer to SRAM
ldi XL,Low(sMet)
InitDT1:
lpm rmp,Z+ ; Read byte from flash
st X+,rmp ; Write to SRAM
cpi ZL,Low(2*InitDTTableEnd)
brne InitDT1 ; Continue copying
ret
;
InitDTTable:
; sMet: ; MET = ss_mm_hh_wd_DD_MM_YY_ST
; sUtc: ; UTC = hh_wd_DD_MM_YY
; sDcf: ; DCF77 time = mm_hh_wd_DD_MM_YY
.db 0,0,20,6,31,3,19,1,18,6,31,3,19,0,0,0,0,0,0,0
InitDTTableEnd:
;
; The UTC jumper has changed
UtcChanged:
cbr rFlag,1<<bUtcChg ; Clear change flag
rcall UpDateUtc ; Get UTC bit
sbrc rFlag,bUtc ; UTC flag clear?
rcall Conv2Utc ; Convert date/time to UTC
rjmp DispAll ; Display all
;
; *************************************
; D A T E / T I M E R O U T I N E S
; *************************************
;
; Update the UTC flag
UpdateUtc:
cbr rFlag,1<<bUtc ; Clear UTC flag
sbis pJ1I,bJ1I ; UTC jumper input high?
sbr rFlag,1<<bUtc
ret
;
; Display all
DispAll:
ldi ZH,0 ; Set mode position
ldi ZL,DisplMode
rcall LcdPos
ldi ZH,High(2*ModesText) ; Point to modes
ldi ZL,Low(2*ModesText)
sbrs rFlag,bUtc ; UTC mode?
rjmp DispAll1
adiw ZL,8 ; Point to UTC
rjmp DispAll2
DispAll1:
ldd rmp,Y+dSummer ; Summer time?
cpi rmp,1 ; Summer time = 1?
brne DispAll2 ; Not summer time
adiw ZL,4 ; Point to summer time
DispAll2:
lpm rmp,Z+ ; Read first char
rcall LcdChar ; Write to LCD
lpm rmp,Z+ ; Read second char
rcall LcdChar ; Write to LCD
lpm rmp,Z+ ; Read third char
rcall LcdChar ; Write to LCD
lpm rmp,Z+ ; Read fourth char
rcall LcdChar ; Write to LCD
; Display year
DisplYr:
ldi ZH,1 ; Set position
ldi ZL,DisplYear
rcall LcdPos
ldd rmp,Y+dYear ; Read year
sbrc rFlag,bUtc ; MET year?
ldd rmp,Y+dUtcYear ; Read UTC year
rcall LcdDec2 ; Display year
DisplMo:
ldi ZH,1 ; Set position
ldi ZL,DisplMonth
rcall LcdPos
ldd rmp,Y+dMonth ; Read year
sbrc rFlag,bUtc ; MET year?
ldd rmp,Y+dUtcMonth ; Read UTC month
rcall LcdDec2 ; Display month
DisplDy:
ldi ZH,1 ; Set position
ldi ZL,DisplDay
rcall LcdPos
ldd rmp,Y+dDay ; Read day
sbrc rFlag,bUtc ; MET day?
ldd rmp,Y+dUtcDay ; Read UTC day
rcall LcdDec2 ; Display day
ldi ZH,1 ; Set position
ldi ZL,DisplWd
rcall LcdPos
ldi ZH,High(2*Weekday)
ldi ZL,Low(2*Weekday)
ldd rmp,Y+dWd ; Read weekday
sbrc rFlag,bUtc ; Not UTC?
ldd rmp,Y+dUtcWd ; Read UTC weekday
lsl rmp ; Multiply by 2
add ZL,rmp ; Add weekday, LSB
ldi rmp,0
adc ZH,rmp ; MSB
lpm rmp,Z+ ; Read first char
rcall LcdChar ; char to LCD
lpm rmp,Z ; Read second char
rcall LcdChar ; second char to LCD
DisplHr:
ldi ZH,0 ; Position hours
ldi ZL,DisplHour
rcall LcdPos
ldd rmp,Y+dHour ; Read hours
sbrc rFlag,bUtc ; UTC flag not set?
ldd rmp,Y+dUtcHour ; Read UTC hour
rcall LcdDec2 ; Display hours
DisplMi:
ldi ZH,0 ; Position minutes
ldi ZL,DisplMin
rcall LcdPos
ldd rmp,Y+dMin ; Read minutes
rcall LcdDec2 ; Display minutes
DisplSc:
ldi ZH,0 ; Position seconds, +1=1
ldi ZL,DisplSec ; +1=2
rcall LcdPos ; +3+109=114
ldd rmp,Y+dSec ; Read seconds, +2=116
rjmp LcdDec2 ; Display seconds, +2+226=344 = 10.5 ms
;
; Weekdays
Weekday:
.if cDateFormat == 0
.db "MoDiMiDoFrSaSo"
.else
.db "MoTuWdThFrSaSu"
.endif
;
ModesText:
.if cDateFormat == 0
.db "MEZ "
.db "MESZ"
.db "UTC "
.else
.db "CET "
.db "CEST"
.db "UTC "
.endif
;
; Convert base time to UTC
; Requires 13..55 clock cycles = 0.40..1.68 ms
Conv2Utc:
ldd rmp,Y+dSummer ; Read summer time byte, +2=2
inc rmp ; +1=3
ldd ZL,Y+dHour ; Read hour, +2=5
sub ZL,rmp ; Subtract 1/2, +1=6
std Y+dUtcHour,ZL ; and store in UTC, +2=8
brcs Conv2Utc1 ; If a carry occurred, +1/2=9/10
Conv2Utc0:
ret ; No carry, +4=13/27
Conv2Utc1:
subi ZL,-24 ; Add a day, +1=11
std Y+dUtcHour,ZL ; +2=13
ldd rmp,Y+dWd ; Read weekday, +2=15
subi rmp,1 ; +1=16
brcc Conv2Utc2 ; Not a monday, +1/2=17/18
ldi rmp,6 ; Monday, to sunday, +1=18
Conv2Utc2:
std Y+dUtcWd,rmp ; Store weekday, +2=20
ldd rmp,Y+dDay ; Read day, +2=22
subi rmp,1 ; Previous day, +1=23
std Y+dUtcDay,rmp ; Store day, +2=25
brne Conv2Utc0 ; Not day zero, +1/2=26/27
ldd ZL,Y+dMonth ; Read month, +2=28
subi ZL,1 ; Previous month, +1=29
std Y+dUtcMonth,ZL ; Write month, +2=31
brne Conv2Utc3 ; Not zero, +1/2=32/33
ldi ZL,12 ; Month of last year, +1=33
std Y+dUtcMonth,ZL ; Write month, +2=35
ldd ZH,Y+dYear ; Read year, +2=37
subi ZH,1 ; Previous year, +1=38
std Y+dUtcYear,ZH ; Write year, +2=40
Conv2Utc3: ; 33/40 clock cycles
; Previous month, rmp=UTC-day, ZL=UTC-Month
cpi ZL,2 ; February? +1=34/41
brne Conv2Utc4 ; Not February, +1/2=35/36/42/43
ldd ZH,Y+dUtcYear ; Read UTC-Year, +2=37/38
andi ZH,0x03 ; Leap year, +1=38/39
ldi rmp,28 ; Not a leap year, +1=39/40
brne Conv2Utc6 ; No, +1/2=40/41/42
ldi rmp,29 ; Leap year, +1=41/42
rjmp Conv2Utc6 ; +2=43/44
Conv2Utc4: ; 36/43 clock cycles
ldi rmp,30 ; Month with 30 days, +1=37/44
cpi ZL,8 ; August ++?, +1=38/45
brcs Conv2Utc5 ; +1/2=39/40/46/47
dec ZL ; +1=40/47
Conv2Utc5: ; 40/47
sbrc ZL,1 ; Month uneven? +1/2=41/42/48/49
ldi rmp,31 ; Yes, 31 days,+1=42/49
Conv2Utc6: ; 42/43/44/49
std Y+dUtcDay,rmp ; Write day, +2=44/45/46/51
ret ; +4=48/49/50/55
;
; Handle DCF pulse
; Requires
; 0.89 ms for a Zero
; 1.25 ms for a One
; 1.34 ms for a Pause following an error, or
; 14.55 ms for a usual Pause
; 1.71 ms for minute pulse without date/time conversion
; 14.65 ms for minute pulse date/time conversion
; 27.86 ms for minute pulse plus date/time conv plus message
.equ cSecDly = (2786*256)/50000
DcfP:
.if DebugPulseDur == Yes
; Displays counts of INT0 duration
ldi ZH,0 ; Line on LCD
sbic pDcfI,bDcfI ; Input pin low?
ldi ZH,1 ; No, high
ldi ZL,DisplPulseDur ; Column position
rcall LcdPos ; Set Position
mov ZH,rDcfH ; Copy MSB count
mov ZL,rDcfL ; dto., LSB
rjmp LcdDec5 ; Display count decimal 5 digits
.else
cbr rFlag,1<<bDcf ; Clear flag
clr rDcfErr ; DCF error to zero
inc rDcfErr ; dto., to one
ldi rmp,Low(cZeroMin) ; Signal a zero? LSB
cp rDcfL,rmp ; Compare LSB
ldi rmp,High(cZeroMin) ; dto., MSB
cpc rDcfH,rmp ; Compare MSB
brcs DcfE ; Error 1: signal too short
ldi rmp,Low(cZeroMax) ; Signal a zero, LSB
cp rDcfL,rmp ; Compare LSB
ldi rmp,High(cZeroMax) ; dto., MSB
cpc rDcfH,rmp ; Compare MSB
brcc DcfP1 ; Not a zero
clc ; Clear carry, shift 0 into registers
rjmp ShiftBit ; Shift into
DcfP1:
inc rDcfErr ; Next error number
ldi rmp,Low(cOneMin) ; Signal a one? LSB
cp rDcfL,rmp ; Compare LSB
ldi rmp,High(cOneMin) ; dto., MSB
cpc rDcfH,rmp ; Compare MSB
brcs DcfE ; Error 2: Signal between zero and one
ldi rmp,Low(cOneMax) ; Signal a one, LSB
cp rDcfL,rmp ; Compare LSB
ldi rmp,High(cOneMax) ; dto., MSB
cpc rDcfH,rmp ; Compare MSB
brcc DcfP2 ; Not a one
sec ; Set carry one
rjmp ShiftBit ; Shift into
DcfP2:
inc rDcfErr ; Next error number
ldi rmp,Low(cPauseMin) ; Signal a pause? LSB
cp rDcfL,rmp ; Compare LSB
ldi rmp,High(cPauseMin) ; dto., MSB
cpc rDcfH,rmp ; Compare MSB
brcs DcfE ; Error 3: Signal between one and pause
ldi rmp,Low(cPauseMax) ; Signal a pause, LSB
cp rDcfL,rmp ; Compare LSB
ldi rmp,High(cPauseMax) ; dto., MSB
cpc rDcfH,rmp ; Compare MSB
brcc DcfP3 ; Not a pause
sbrc rFlag,bDcfOk ; DCF not ok?
ret ; DCF ok, do not clear DCF space
sbrs rFlag,bDcfErr ; Error condition on LCD?
rjmp DcfClr ; Clear the error field
cbr rFlag,1<<bDcfErr ; Clear error flag
ret ; A correct pause
DcfP3:
inc rDcfErr ; Next error number
ldi rmp,Low(cMinuteMin) ; Signal a minute? LSB
cp rDcfL,rmp ; Compare LSB
ldi rmp,High(cMinuteMin) ; dto., MSB
cpc rDcfH,rmp ; Compare MSB
brcs DcfE ; Error 4: Signal between a pause and a minute
ldi rmp,Low(cMinuteMax) ; Signal a one, LSB
cp rDcfL,rmp ; Compare LSB
ldi rmp,High(cMinuteMax) ; dto., MSB
cpc rDcfH,rmp ; Compare MSB
brcc DcfP4 ; Not a minute
inc rDcfErr ; Next error number
inc rDcfErr ; Next error number
ldi rmp,59 ; 59 bits received?
cp rmp,rDcfBits ; Compare
breq DcfMinute ; Correct, convert DCF bits
ldi ZH,0 ; Lcd to DCF position
ldi ZL,DisplDcf
rcall LcdPos
mov rmp,rDcfBits ; Display number of bits
clr rDcfBits
rjmp LcdDec3 ; Convert DCF bits to date/time
DcfP4:
inc rDcfErr ; Error 5: Longer than minute
.endif
;
; An error occurred during DCF signal checking
DcfE:
ldi ZH,0 ; Position to DCF output
ldi ZL,DisplDcf
rcall LcdPos
ldi rmp,' ' ; A blank
rcall LcdChar
ldi rmp,'E' ; E character
rcall LcdChar
ldi rmp,'0' ; ASCII Null
add rmp,rDcfErr ; Add error number
rcall LcdChar ; Display error character
sbr rFlag,1<<bDcfErr ; Set error flag
cbr rFlag,1<<bDcfOk ; Clear ok flag
ret
;
; Minute complete, convert DCF bits to date/time
; Requires 26.1 milliseconds
.if DebugPulseDur == No
DcfMinute:
; Check minutes
clr rDcfBits ; Restart DCF bit collection
ldi rmp,7 ; Error number 7
mov rDcfErr,rmp
mov ZL,rDcf3 ; Copy DCF bits minutes
mov ZH,rDcf4
lsr ZH ; Shift by two bits right
ror ZL
lsr ZH
ror ZL
rcall Parity ; Check parity minutes
brne DcfE ; Error 7: Parity minutes odd
inc rDcfErr ; Next error number
mov ZL,rDcf3 ; Minute ones
lsr ZL ; Shift two bits right
lsr ZL
andi ZL,0x0F ; Isolate ones
cpi ZL,10 ; Larger than ten?
brcc DcfE ; Error 8: Minute ones larger than 9
inc rDcfErr ; Next error number
mov ZH,rDcf4 ; Minute tens
sbrc ZH,0 ; Fourties
subi ZL,-40 ; Add fourty
mov ZH,rDcf3 ; 20s and 10s
sbrc ZH,7 ; Twenties
subi ZL,-20 ; Add twenty
sbrc ZH,6 ; Tens
subi ZL,-10 ; Add ten
cpi ZL,60 ; Larger than 59?
brcc DcfE ; Error 9: Minutes larger than 60
std Y+dDcfMin,ZL ; Save DCF minutes
ldi rmp,'A'-10-'0' ; Error number to characters
mov rDcfErr,rmp
mov ZL,rDcf4 ; Read hours
mov ZH,rDcf5 ; and their parity bit
lsr ZH ; Shift parity bit into hours
ror ZL
lsr ZL ; Shift hours right
mov ZH,ZL ; Copy to ZL
rcall Parity
breq DcfHours ; Parity hours ok
rjmp DcfE ; Error A: Hours parity odd
DcfHours:
inc rDcfErr ; Next error number
mov ZL,ZH ; Copy hours to ZL
andi ZL,0x0F ; Isolate ones
cpi ZL,10 ; Larger than 9?
brcs DcfHours1 ; No, ok
rjmp DcfE ; Error B: Hour ones larger than 9
DcfHours1:
inc rDcfErr ; Next error number
sbrc ZH,5 ; Bit 7 in rDcf4
subi ZL,-20 ; Add twenty
sbrc ZH,4 ; Bit 6 in rDcf4
subi ZL,-10 ; Add ten
cpi ZL,24 ; Hours larger than 24?
brcs DcfHours2 ; Hours ok
rjmp DcfE ; Error C: Hours larger than 23
DcfHours2:
std Y+dDcfHour,ZL ; Save DCF hours
inc rDcfErr ; Next error number
mov ZL,rDcf5 ; Copy date byte 0
mov ZH,rDcf6 ; Copy date byte 1
mov rmp,rDcf7 ; Copy date byte 2
lsr rmp ; Shift one bit right
ror ZH
ror ZL
rcall Parity ; Check parity byte 0
mov ZL,ZH
rcall Parity1 ; Check parity byte 1
mov ZL,rDcf7
lsr ZL
rcall Parity1
breq DcfDay
rjmp DcfE ; Error D: Date parity odd
DcfDay:
inc rDcfErr ; Next error number
mov ZL,rDcf5 ; Read day
lsr ZL ; One bit right
andi ZL,0x0F ; Isolate ones
cpi ZL,10 ; Larger than nine?
brcs DcfDay1 ; Day ones fine
rjmp DcfE ; Error E: Day ones larger than nine
DcfDay1:
inc rDcfErr ; Next error number
mov ZH,rDcf5 ; Read tens
sbrc ZH,6 ; Twenties
subi ZL,-20 ; Add twenty
sbrc ZH,5 ; Tens
subi ZL,-10 ; Add ten
cpi ZL,32 ; More than 32 days?
brcs DcfDay2
rjmp DcfE ; Error F: Day larger than 31
DcfDay2:
std Y+dDcfDay,ZL ; Save DCF day
inc rDcfErr ; Next error number
mov ZL,rDcf5 ; Read weekday
mov ZH,rDcf6
lsl ZL
rol ZH
andi ZH,0x07
brne DcfWeekday
rjmp DcfE ; Error G: Weekday is zero
DcfWeekday:
dec ZH ; Weekdays 1 to 7 to 0 to 6
std Y+dDcfWd,ZH ; Save weekday
inc rDcfErr ; Next error number
mov ZH,rDcf6 ; Read month
mov ZL,rDcf6
lsr ZL
lsr ZL
andi ZL,0x0F ; Isolate ones
cpi ZL,10 ; Ones larger than 9?
brcs DcfMonth
rjmp DcfE ; Error I: Month ones larger than 9
DcfMonth:
inc rDcfErr ; Next error number
sbrc ZH,6 ; Tens
subi ZL,-10 ; Add ten
cpi ZL,13 ; Month larger than 12?
brcs DcfMonth1
rjmp DcfE ; Error H: Month larger than 12
DcfMonth1:
std Y+dDcfMonth,ZL ; Save DCF month
inc rDcfErr ; Next error number
mov ZL,rDcf6 ; Copy year
mov ZH,rDcf7
lsl ZL
rol ZH
mov ZL,ZH
andi ZL,0x0F ; Isolate ones
cpi ZL,10 ; Ones larger than 10?
brcs DcfYear
rjmp DcfE ; Error J: Year ones larger than ten
DcfYear:
inc rDcfErr ; Next error number
sbrc ZH,7 ; Eighty
subi ZL,-80
sbrc ZH,6 ; Fourty
subi ZL,-40
sbrc ZH,5 ; Twenty
subi ZL,-20
sbrc ZH,4 ; Ten
subi ZL,-10
cpi ZL,100 ; Year larger than 99?
brcs DcfYear1
rjmp DcfE ; Error K: Year larger than 99
DcfYear1:
std Y+dDcfYear,ZL ; Save DCF year
clr rmp ; Winter time
mov ZL,rDcf2
sbrc ZL,6 ; Bit 6 is MESZ bit
inc rmp
std Y+dDcfSummer,rmp ; Save DCF summer time
inc rDcfErr ; Next error number
mov ZL,rDcf0 ; Read first bit
sbrc ZL,5 ; Bit 5 is first DCF bit
rjmp DcfE ; Error L: First bit is not zero
inc rDcfErr ; Next error number
mov ZL,rDcf3 ; Read start bit date/time
sbrs ZL,1 ; Bit 1 is start bit, one?
rjmp DcfE ; Error M: Date/Time start bit not a one
; DCF date/time complete, copy over date/time
ldi ZH,High(sDcf) ; Point Z to DCF in SRAM
ldi ZL,Low(sDcf)
ldi XH,High(sMet+1) ; Point Z to MET-minutes in SRAM
ldi XL,Low(sMet+1)
DcfCopy:
ld rmp,Z+ ; Copy byte
st X+,rmp
cpi ZL,Low(sDcf+8) ; End of DCF time
brne DcfCopy
rcall DcfOkMsg ; Display ok message
clr rmp ; Clear seconds
st Y,rmp
cbr rFlag,1<<bSec ; Clear seconds bit
ldi rDivSec,cDivSec ; Restart seconds divider
ldi rmp,cSecDly ; Set seconds counter
out TCNT0,rmp ; Set execution delay time
sbr rFlag,(1<<bUtcChg)|(1<<bDcfOk) ; Force update of display
sbrc rFlag,bDcf ; A new DCF signal to process?
rjmp DcfP ; Yes, start new
ret
;
; Check parity of byte in ZL
; Returns zero flag clear if even
Parity:
clr rmp
Parity1:
lsr ZL ; Shift lowest bit to carry
breq Parity2 ; No ones left
brcc Parity1 ; Bit not one
inc rmp ; Count ones
rjmp Parity1
Parity2:
brcc Parity3 ; Last bit a zero?
inc rmp ; Count last bit
Parity3:
andi rmp,0x01 ; Test lowest bit even
ret
;
; Shift bit in carry to DCF bit shift registers
ShiftBit:
ror rDcf7 ; Rotate into 7
ror rDcf6 ; dto., into 6
ror rDcf5 ; dto., into 5
ror rDcf4 ; dto., into 4
ror rDcf3 ; dto., into 3
ror rDcf2 ; dto., into 2
ror rDcf1 ; dto., into 1
ror rDcf0 ; dto., into 0
inc rDcfBits ; Count bits
.if (cDcfMoniBits == 1)||(cDcfMoniBitCount == 1)
ldi ZH,0 ; Set display position
ldi ZL,DisplDcf ; to DCF position on LCD
rcall LcdPos ; Set position
.endif
.if cDcfMoniBits == 1
; Monitor bits binary
mov ZL,rDcf7 ; Read the last eight bits
ldi rmp,'0' ; A zero
sbrc ZL,7 ; Last incoming bit
ldi rmp,'1' ; A one
rcall LcdChar ; Write char to LCD
ldi rmp,'0' ; A zero
sbrc ZL,6 ; Pre-last bit
ldi rmp,'1' ; A one
rcall LcdChar ; Display the last
ldi rmp,'0'
sbrc ZL,5 ; Pre-pre-last bit
ldi rmp,'1' ; A one
rjmp LcdChar ; Display the third bit
.endif
; Monitor the incoming bits
.if cDcfMoniBitCount == 1
; Monitor the bit count
mov rmp,rDcfBits ; Copy bit count
rjmp LcdDec3 ; Display the bit count
.endif
ret
.endif
;
; DCF signal timeout
DcfTimeOut:
cbr rFlag,1<<bDcfTO ; Clear flag
clr rDcfErr
rjmp DcfE ; Error 0: Time-out on the DCF input pin
;
; Clear error field on LCD
DcfClr:
ldi ZH,High(2*DcfClearTxt)
ldi ZL,Low(2*DcfClearTxt)
rjmp DcfTxt
;
; DCF ok message
; requires 433 clock cycles = 13.21 ms
DcfOkMsg:
ldi ZH,High(2*DcfOkTxt) ; +1=1
ldi ZL,Low(2*DcfOkTxt) ; +1=2
DcfTxt:
push ZH ; Save text position, +2=4
push ZL ; +2=6
ldi ZH,0 ; +1=7
ldi ZL,DisplDcf ; +1=8
rcall LcdPos ; +3+109=120
pop ZL ; +2=122
pop ZH ; +2=124
lpm rmp,Z+ ; +2=126
rcall LcdChar ; +3+99=228
lpm rmp,Z+ ; +2=230
rcall LcdChar ; +3+99=332
lpm rmp,Z ; +2=334
rjmp LcdChar ; +2+99=443
;
DcfClearTxt:
.db " " ; Clear error message
DcfOkTxt:
.db " ok " ; DCF ok message
;
; **********************************
; L C D R O U T I N E S
; **********************************
;
; LCD text masks, 16 chars per line
InitText:
.db " DCF77 clock V3 ",0x0D,0xFF
.db " (C)2019 DG4FAC ",0xFE,0xFF
ClockMask:
.if DebugPulseDur == Yes
.db "Pulse lo = 12345",0x0D,0xFF
.db " hi = 12345",0xFE,0xFF
; 0123456789012345
.else
.if cDateFormat == 0
.db "hh:mm:ss utc dcf",0x0D,0xFF
.db " wd, DD.MM.20YY ",0xFE,0xFF
.else
.db "hh:mm:ss utc dcf",0x0D,0xFF
.db " wd, MM/DD/20YY ",0xFE,0xFF
; 0123456789012345
.endif
.endif
;
; Display positions, line 1
.equ DisplSec = 6
.equ DisplMin = 3
.equ DisplHour = 0
.equ DisplMode = 9
.equ DisplDcf = 13
; Display positions, line 2
.equ DisplWd = 1
.if cDateFormat == 0
.equ DisplDay = 5
.equ DisplMonth = 8
.else
.equ DisplDay = 8
.equ DisplMonth = 5
.endif
.equ DisplYear = 13
.if DebugPulseDur == Yes
.equ DisplPulseDur = 11
.endif
;
; LCD include routines
.include "lcd.inc"
;
; End of source code