![]() |
AVR applications Analysis for DCF77 receiver signals Assembler software for analyzing |
![]() |
;
; *********************************
; * Test an attached DCF77 module *
; * with a tn24_lcd module *
; * (C)2019 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 ; Enable switch
.equ No = 0 ; Disable switch
;
; Skip all LCD operations for simulation
.equ Debug_SkipLcd = No
;
; Simulate the DCF duration categories
.equ Debug_DcfCat = No
.equ Debug_DcfCatD = 200 ; Set the DCF duration
;
; **********************************
; H A R D W A R E
; **********************************
;
; Device: ATtiny24A, Package: 14-pin-PDIP_SOIC
; _________
; 1 / |14
; +5V o--|VCC GND|--o 0V
; LCD-RS o--|PB0 PA0|--o DCF
; LCD-RW o--|PB1 PA1|--o NC
; RESET o--|RESET PA2|--o NC
; LCD-E o--|PB2 PA3|--o Key
; 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
; **********************************
;
.equ pDcfKeyO = PORTA ; Define the output port
.equ pDcfKeyD = DDRA ; Define the direction port
.equ pDcfKeyI = PINA ; Define the input port
.equ bDcfO = PORTA0 ; Define the DCF output port
.equ bDcfI = PINA0 ; Define the DCF input port
.equ bKeyO = PORTA3 ; Define the key pins
.equ bKeyI = PINA3 ; Define the key input port
; (LCD pins are defined in Adjustable Constants section)
;
; **********************************
; A D J U S T A B L E C O N S T
; **********************************
;
; Delay between initial LCD message and measuring start
.equ tDelay = 5 ; Number of seconds delay
;
; Set pull up resistor on DCF input
.equ DcfPullUp = No ; Yes or no
;
; Time settings for the DCF77 input signals
.equ cDcfTol = 20 ; Tolerance of all times in %
.equ tDcfSp = 10 ; Spurious signals, in ms
.equ tDcf0 = 100 ; Signal duration for a 0, in ms
.equ tDcf1 = 200 ; Signal duration for a 1, in ms
.equ tDcfP = 850 ; Signal duration for a pause, in ms
.equ tDcfM = 1850 ; Signal duration for a missing second pulse, in ms
.equ tDcfTO = 2500 ; DCF signal missing time-out, in ms
;
; Parameter set of properties/definitions for lcd
; (Needed by LCD include)
.equ clock = 1000000 ; Clock frequency of controller in Hz
; 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 = 4 ; Bus size (4 or 8)
; If 4 bit bus:
.equ Lcd4High = 1 ; Bus nibble (1=Upper, 0=Lower)
; LCD wait/busy mode
.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 = PORTB2 ; Control E output portpin
.equ pLcdCED = DDRB ; Control E direction port
.equ bLcdCED = DDB2 ; Control E direction portpin
.equ pLcdCRSO = PORTB ; Control RS output port
.equ bLcdCRSO = PORTB0 ; Control RS output portpin
.equ pLcdCRSD = DDRB ; Control RS direction port
.equ bLcdCRSD = DDB0 ; Control RS direction portpin
; If LcdWait = 0:
.equ pLcdDI = PINA ; Data input port
.equ pLcdCRWO = PORTB ; Control RW output port
.equ bLcdCRWO = PORTB1 ; Control RW output portpin
.equ pLcdCRWD = DDRB ; Control RW direction port
.equ bLcdCRWD = DDB1 ; 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
;
; **********************************
; F I X & D E R I V. C O N S T
; **********************************
;
; Converting DCF times to TC1 clock cycles with tolerances
.equ ckHz = (clock+500)/1000 ; Clock frequency in kHz
.equ cDcfSp = (tDcfSp*ckHz+512)/1024 ; (0..3)
.equ cDcf0Min = ((tDcf0-cDcfTol*tDcf0/100)*ckHz+512)/1024 ; (4..7)
.equ cDcf0Max1 = ((tDcf0+cDcfTol*tDcf0/100)*ckHz+512)/1024+1 ; (8..11)
.equ cDcf1Min = ((tDcf1-cDcfTol*tDcf1/100)*ckHz+512)/1024 ; (12..15)
.equ cDcf1Max1 = ((tDcf1+cDcfTol*tDcf1/100)*ckHz+512)/1024+1 ; (16..19)
.equ cDcfPMin = ((tDcfP-cDcfTol*tDcfP/100)*ckHz+512)/1024 ; (20..23)
.equ cDcfPMax1 = ((tDcfP+cDcfTol*tDcfP/100)*ckHz+512)/1024+1 ; (24..27)
.equ cDcfMMin = ((tDcfM-cDcfTol*tDcfM/100)*ckHz+512)/1024 ; (28..31)
.equ cDcfMMax1 = ((tDcfM+cDcfTol*tDcfM/100)*ckHz+512)/1024+1 ; (32..35)
.equ cDcfTO = (tDcfTO*ckHz+512)/1024+1 ; (36..39)
;
; Checking of constants
.if cDcfTol == 0
.error "Tolerance cDcfTol cannot be zero!"
.endif
.if (cDcf0Min <= cDcfSp)
.error "Minimum for zero less than spurious, reduce tolerance"
.endif
.if (cDcf0Max1 >= cDcf1Min)
.error "Zero maximum overlaps One minimum, reduce tolerance"
.endif
.if (cDcf1Max1 >= cDcfPMin)
.error "One maximum overlaps Pause minimum, reduce tolerance"
.endif
.if (cDcfPMax1 >= cDcfMMin)
.error "Pause maximum overlaps Minute minimum, reduce tolerance"
.endif
;
; **********************************
; R E G I S T E R S
; **********************************
;
.def rBinL = R0 ; Binary LSB
.def rBinH = R1 ; dto., MSB
; free: R2 to R14
.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 bDcf = 0 ; DCF flag
.equ bKey = 1 ; Key input flag
.def rInput = R19 ; Input register
.def rDcfDurL = R20 ; DCF duration, LSB
.def rDcfDurH = R21 ; dto., MSB
; free: R22 to R25
; used: R27:R26 = X as counter pointer
; used: R29:R28 = Y for decimal conversion
; used: R31:R30 = Z for various purposes
;
; **********************************
; S R A M
; **********************************
;
.dseg
.org SRAM_START
sCounters:
.byte 8 ; Spurious count
; 2 bytes description
; 2 bytes counter
; 4 bytes adder
.byte 8 ; Shorter than zero
.byte 8 ; Correct zero
.byte 8 ; Shorter than one
.byte 8 ; Correct one
.byte 8 ; Shorter than pause
.byte 8 ; Correct pause
.byte 8 ; Shorter than minute
.byte 8 ; Correct minute
.byte 8 ; Longer than minute
sCountersEnd:
;
; **********************************
; 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
reti ; EXT_INT0
rjmp Pcint0Isr ; PCI0
reti ; PCI1
reti ; WATCHDOG
reti ; ICP1
reti ; OC1A
reti ; OC1B
reti ; OVF1
reti ; OC0A
reti ; OC0B
reti ; 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 .
; **********************************
;
; PCINT0 interrupt service routine
PcInt0Isr:
in rSreg,SREG ; Save SREG
in rimp,pDcfKeyI ; Read current pins
eor rimp,rInput ; Check pins that changed
sbrc rimp,bKeyI ; Check key pin changed
rjmp PcInt0Isr1 ; No, skip
sbis pDcfKeyI,bKeyI ; Check key pin is high
sbr rFlag,1<<bKey ; No, set key low flag
PcInt0Isr1:
sbrs rimp,bDcfI ; Check if DCF input changed
rjmp PcInt0Isr2 ; No, skip
sbr rFlag,1<<bDcf ; Set DCF flag
in rDcfDurL,TCNT1L ; Read counter, LSB first
in rDcfDurH,TCNT1H ; dto., MSB next
clr rimp ; Restart counter
out TCNT1H,rimp ; MSB first
out TCNT1L,rimp ; LSB next
PcInt0Isr2:
in rInput,pDcfKeyI ; Read input port
out SREG,rSreg ; Restore SREG
reti
;
; **********************************
; 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
; Init ports
sbi pDcfKeyO,bKeyO ; Set pull up on key pin
.if DcfPullup == Yes
sbi pDcfKeyO,bDcfO ; Set pull up on DCF pin
.endif
.if Debug_SkipLcd == No
; Init the LCD
rcall LcdInit ; Init the LCD
ldi ZH,High(2*Text_Intro) ; Intro text
ldi ZL,Low(2*Text_Intro)
rcall LcdText ; Display intro text
; Delay the execution a little bit
ldi rmp,3*tDelay ; tDelay seconds
Main1:
sbiw ZL,1 ; 2 cycles
brne Main1 ; 2 cycles for branching, 1 at the end
dec rmp ; 1 cycle
brne Main1 ; 2 cycles for branching, 1 at the end
.endif
; Complete time for the loop:
; Inner loop: cycles = 65535 * 4 + 3
; Plus outer loop: cycles = (Inner loop + 3) * (rmp -1) + Innerloop + 2
; = (rmp-1)*(Inner+3) + Inner + 2
; = rmp*Inner + 3*rmp - Inner - 3 + Inner + 2
; = rmp*65535*4 + 3 + 3*rmp - 65535*4 - 12 - 3 + 65535*4 + 3 + 2
; = rmp*262140 + 3*rmp + 3 - 12 - 3 + 3 + 2
; = rmp*(262140+3) - 7
; For one second:
; rmp = (1000000+7)/262143 = 3
;
; Init TC1 for counting signal durations
ldi rmp,High(cDcfTO) ; Set time-out for DCF signals, MSB
out OCR1AH,rmp ; in MSB compare A first
ldi rmp,Low(cDcfTO) ; dto., LSB
out OCR1AL,rmp ; in LSB compare next
clr rmp ; Mode TC1 setting
out TCCR1A,rmp ; in mode control A
ldi rmp,(1<<WGM12)|(1<<CS12)|(1<<CS10) ; CTC, prescaler=1024
out TCCR1B,rmp ; in mode control B
; Restart all counters, set standard text, set LCD to standard frame
rcall ClearOnly ; Restart counting
.if Debug_DcfCat == Yes
ldi rDcfDurH,High(Debug_DcfCatD)
ldi rDcfDurL,Low(Debug_DcfCatD)
rcall DcfSig
endless: rjmp endless
.endif
; Init ports
; endless1: rjmp endless1
; Only use this if your module is compatible with
; the pull-up switched on (mot necessary if you
; use an NPN driver stage)
; sbi pDcfKeyO,bDcfO
; Init PCINTs on the DCF and Key inputs
ldi rmp,(1<<bDcfO)|(1<<bKeyO) ; Enable DCF and key ints
out PCMSK0,rmp
ldi rmp,1<<PCIE0 ; Enable PCINT0 interrupts
out GIMSK,rmp ; in General Interrupt Mask
; Sleep and interrupts
ldi rmp,1<<SE ; Sleep mode idle
out MCUCR,rmp ; in Microcontroller Universal Control Port
sei ; Enable interrupts
;
; **********************************
; P R O G R A M L O O P
; **********************************
;
Loop:
sleep ; Go to sleep
nop ; Wake up dummy
sbrc rFlag,bDcf ; DCF flag clear?
rcall DcfSig ; Count DCF signal
sbrc rFlag,bKey ; Key flag clear?
rcall ClearAll ; No, clear all counters
rjmp loop
;
; A DCF signal came in
DcfSig:
cbr rFlag,1<<bDcf ; Clear the flag
ldi XH,High(sCounters+2) ; Point X to sCounters
ldi XL,Low(sCounters+2)
ldi ZH,High(2*DcfDurTab) ; Point Z to duration table
ldi ZL,Low(2*DcfDurTab)
lpm rmp,Z+ ; Read table value, LSB
cp rDcfDurL,rmp ; Compare LSB duration
lpm rmp,Z+ ; Read table value, MSB
cpc rDcfDurH,rmp ; dto., MSB with carry
brcs DcfSigCnt ; Increase count
DcfSig1:
adiw XL,8 ; Next counter
cpi XL,Low(sCountersEnd) ; End of valid counters?
breq DcfSigEnd ; Yes, up-count last
lpm rmp,Z+ ; Read LSB from table
cp rDcfDurL,rmp ; Compare LSB
lpm rmp,Z+ ; Read MSB from table
cpc rDcfDurH,rmp ; Compare MSB and Carry
brcs DcfSigCnt ; Shorter than minimum
adiw XL,8 ; Next counter
cpi XL,Low(sCountersEnd) ; End of valid counters?
breq DcfSigEnd ; Yes, up-count last counter
lpm rmp,Z+ ; Read LSB from table
cp rDcfDurL,rmp ; Compare LSB
lpm rmp,Z+ ; Read MSB from table
cpc rDcfDurH,rmp ; Compare MSB and Carry
brcs DcfSigCnt ; Shorter than maximum plus one, count
rjmp DcfSig1 ; Next compare
DcfSigEnd:
;
; Count the signal counts one up
DcfSigCnt:
ld rmp,X ; Read LSB from SRAM
inc rmp ; Count LSB one up
st X+,rmp ; Save LSB
brne DcfSigCnt1 ; No overflow
ld rmp,X ; Read MSB from SRAM
inc rmp ; Incrase MSB
st X,rmp ; Store MSB
DcfSigCnt1:
adiw XL,1 ; Point to adder byte 1
ld rmp,X
add rmp,rDcfDurL
st X+,rmp
ld rmp,X
adc rmp,rDcfDurH
st X+,rmp
ldi rDcfDurL,0
ld rmp,X
adc rmp,rDcfDurL
st X+,rmp
ld rmp,X
adc rmp,rDcfDurL
st X+,rmp
sbiw XL,6 ; Point back to counter
; Position on LCD
mov ZL,XL ; Copy SRAM position to Z
mov ZH,XH
andi ZL,0xFC ; Clear bit 1 and 0
ldi rmp,Low(sCounters)
sub ZL,rmp
ldi rmp,High(sCounters)
sbc ZH,rmp
ldi ZH,1 ; Line 2 of the LCD
lsr ZL ; Divide counter by two
cpi ZL,20 ; End of line 2?
brcs DcfSigCnt2
subi ZH,-2 ; Add two to line number
subi ZL,20 ; Subtract one line length
DcfSigCnt2:
rcall LcdPos ; Set LCD position
ld rBinL,X+ ; Read counter value to binary
ld rBinH,X
ldi XH,High(10000) ; Check out of decimal range
ldi XL,Low(10000)
cp rBinL,XL
cpc rBinH,XH
brcc DcfSigOvf
set ; Set the T flag
ldi XH,High(1000) ; Count the thousands
ldi XL,Low(1000)
rcall DecDigit ; Calculate and write the decimal digit
ldi XH,High(100)
ldi XL,Low(100)
rcall DecDigit
ldi XH,High(10)
ldi XL,Low(10)
rcall DecDigit
ldi rmp,'0'
add rmp,rBinL ; The last decimal digit
rjmp LcdChar
DcfSigOvf:
ldi rmp,'9' ; Write 9999 to LCD
.if Debug_SkipLcd == No
rcall LcdChar
ldi rmp,'9'
rcall LcdChar
ldi rmp,'9'
rcall LcdChar
ldi rmp,'9'
rjmp LcdChar
.else
ret
.endif
;
; Count the decimal digit and write it to LCD
; Binary in rBinH:rBinL
; Decimal in X
; T flag enables leading zero suppression
DecDigit:
ldi rmp,0xFF
DecDigit1:
inc rmp
sub rBinL,XL ; Subtract decimal from binary
sbc rBinH,XH
brcc DecDigit1
add rBinL,XL ; Undo last subtraction
adc rBinH,XH
brtc DecDigit3 ; Do not suppress leading zeroes
tst rmp ; Zero?
brne DecDigit2 ; Not zero, stop suppression
ldi rmp,' ' ; Display a blank
.if Debug_SkipLcd == No
rjmp LcdChar
.else
ret
.endif
DecDigit2:
clt ; Stop suppression
DecDigit3:
subi rmp,-'0' ; Add ASCII zero
.if Debug_SkipLcd == No
rjmp LcdChar
.else
ret
.endif
;
; Table with the DCF77 counter values
DcfDurTab:
.dw cDcfSp ; Spurious signal, 0..3
.dw cDcf0Min, cDcf0Max1 ; 4..11
.dw cDcf1Min, cDcf1Max1 ; 12..19
.dw cDcfPMin, cDcfPMax1 ; 20..27
.dw cDcfMMin, cDcfMMax1 ; 28..35
DcfDurTabEnd:
;
; Add strings to SRAM
Str2Sram:
ldi XH,High(sCounters)
ldi XL,Low(sCounters)
ldi ZH,High(2*StrTab)
ldi ZL,Low(2*StrTab)
Str2Sram1:
lpm rmp,Z+
st X+,rmp
lpm rmp,Z+
st X+,rmp
adiw XL,6
cpi XH,High(sCountersEnd)
brne Str2Sram1
cpi XL,Low(sCountersEnd)
brne Str2Sram1
ret
;
; Table with the text strings in SRAM
StrTab:
.db "Sp<0 0<1 1<P P<M M>M"
;
; The clear all key has been pressed
ClearAll:
cbr rFlag,1<<bKey ; Clear the key flag
; Write SRAM content to EEPROM
clr ZL ; EEPROM address, LSB
clr ZH ; dto., MSB
ldi XH,High(sCounters) ; X points to Sram counters, MSB
ldi XL,Low(sCounters) ; dto., LSB
Copy2Eep:
; Wait for EEPROM programming clear
sbic EECR,EEPE ; Wait for EEPE bit clear
rjmp Copy2Eep ; Not yest
ldi rmp,(0<<EEPM1)|(0<<EEPM0) ; Atomic Write mode
out EECR,rmp ; to EEPROM control
out EEARH,ZH ; Set write address, MSB
out EEARL,ZL ; dto., LSB
adiw ZL,1 ; Next address
ld rmp,X+ ; Read Sram byte
out EEDR,rmp ; to data register
cli ; Do not interrupt
sbi EECR, EEMPE ; Write program mode enable
sbi EECR, EEPE ; Write program enable
sei ; Interrupts allowed again
cpi XL,Low(sCountersEnd) ; End of the Sram?
brne Copy2Eep ; No, continue
Copy2EepEnd:
sbic EECR,EEPE ; Wait for the last EEPE bit clear
rjmp Copy2EepEnd ; Not yest
; Clear the SRAM counters
ClearOnly:
ldi XH,High(sCounters) ; Point X to counters, MSB
ldi XL,Low(sCounters) ; dto., LSB
clr rmp ; Zero to all registers
ClearAll1:
st X+,rmp ; Clear counter
cpi XL,Low(sCountersEnd) ; All counters cleared?
brne ClearAll1 ; Still counters to be cleared
rcall Str2Sram ; Add strings to SRAM
.if Debug_SkipLcd == No
ldi ZH,0
ldi ZL,0
rcall LcdPos
ldi ZH,High(2*Text_Template) ; Z to text table
ldi ZL,Low(2*Text_Template)
rcall LcdText ; Display the complete text
.endif
WaitKey:
; Wait for key release
ldi ZH,High(16666) ; For 100 ms delay after last key event
ldi ZL,Low(16666) ; (See below for the formula)
WaitKey1:
sbis pDcfKeyI,bKeyI ; Read key input pin, +2 = 2
rjmp WaitKey ; Key input low, restart toggle counter
sbiw ZL,1 ; Count toggle counter down, +2 = 4
brne WaitKey1 ; Wait o, +1/2 = 5/6
; Time required:
; clocks = 6 * (Z - 1) + 5 = 6 * Z - 1
; Z = (clocks + 1) / 6
; clocks = 100,000 for 100 ms
; Z = 8334
clr rFlag ; Clear the flag
clr rmp
out TCNT1H,rmp ; Clear the signal counter
out TCNT1L,rmp
ret
;
; *****************************************
; T E X T T E M P L A T E S
; *****************************************
;
; Initial text, 20 chars per line
Text_Intro:
.db " DCF77 signal check ",0x0D,0xFF
.db " tn24_lcd module ",0x0D,0xFF
.db " ",0x0D,0xFF
.db " (C)2019 DG4FAC ",0xFE,0xFF
; 01234567890123456789
;
Text_Template:
.db " Sp <0 0 <1 1",0x0D,0xFF
.db " 0 0 0 0 0",0x0D,0xFF
.db " <P P <M M >M",0x0D,0xFF
.db " 0 0 0 0 0",0xFE,0xFF
; 01234567890123456789
;
; LCD routines include
.include "lcd.inc"
;
; End of source code