![]() |
Applications of AVR Single Chip controllers AT90S, ATtiny, ATmega and ATxmega LC-Oscillator for DCF77-Superhet-Receiver |
![]() |
;
; *********************************
; * LC Oscillator with frequency *
; * regulation via PWM&varicap *
; * (C)2019 avr-asm-tutorial.net *
; *********************************
;
.nolist
.include "tn25def.inc" ; Define device ATtiny25
.list
;
; **********************************
; D E B U G G I N G S W I T C H
; **********************************
;
.equ Yes = 1 ; Set debug on
.equ No = 0 ; Set debug off
;
; Do not compare, blink in 500 ms
.equ debug_blink500ms = No ; Yes = blink
;
; Blink on measuring on analog compare int
; Red and green LED blink very fast if comparer
; int occurs, counting is disabled
.equ debug_blinkaci = No ; Yes = blink
;
; **********************************
; H A R D W A R E
; **********************************
;
; Device: ATtiny25, Package: 8-pin-PDIP_SOIC
; _________
; 1 / |8
; RESET o--|RESET VCC|--o +5V
; Xtal osc o--|CLKI PB2|--o SCK/LED
; PWM out o--|OC1B PB1|--o MISO/AIN1
; 0V o--|GND PB0|--o MOSI/AIN0
; 4 |__________|5
;
; **********************************
; P O R T S A N D P I N S
; **********************************
;
.equ pPwmD = PORTB ; PWM direction port
.equ bPwmD = DDB4 ; PWM direction port pin
.equ pLedO = PORTB ; LED output port
.equ pLedD = DDRB ; LED direction port
.equ bLedO = PORTB2 ; LED output port pin
.equ bLedD = DDB2 ; LED direction port pin
.equ pLedI = PINB ; LED blinking port
.equ bLedI = PINB2 ; LED blinking port pin
;
; **********************************
; A D J U S T A B L E C O N S T
; **********************************
;
; Clock rate of external crystal oscillator
.equ clock=8000000 ; Define clock frequency
.equ cOscFreq = 77500 + 32768 ; Frequency of the LC oscillator, Hz
.equ cOscTol = 5 ; Frequency tolerance +/-, Hz
.equ cMinVolt = 2000 ; Minimum voltage of PWM, mV
;
; Use sheet clock in dcf77_lcosc_tn25.ods for the following
.equ cGateTime = 500 ; Gate time for frequency measurement in ms
.equ cPresc = 256 ; Prescaler (from spreadsheet)
.equ cCtcDiv = 125 ; CtcDivider (from spreadsheet)
;
; **********************************
; F I X & D E R I V. C O N S T
; **********************************
;
; Check clock
.set cClockCorrect = clock==4000000
.set cClockCorrect = cClockCorrect || (clock==4194304)
.set cClockCorrect = cClockCorrect || (clock==4915200)
.set cClockCorrect = cClockCorrect || (clock==5120000)
.set cClockCorrect = cClockCorrect || (clock==6553600)
.set cClockCorrect = cClockCorrect || (clock==7372800)
.set cClockCorrect = cClockCorrect || (clock==8000000)
.set cClockCorrect = cClockCorrect || (clock==16000000)
.if cClockCorrect
.message "Clock is correct"
.else
.error "Incorrect clock setting!"
.endif
;
; Define frequency measurement constants
.equ cTc0Clk = clock / cPresc ; Define prescaler from clock
.equ cDiv = cTc0Clk / cCtcDiv / 2 ; Register divider
.if cDiv > 256
.error "cDiv is too large!"
.endif
.if cCtcDiv == 256
.equ cCtcCmp = 0
.else
.equ cCtcCmp = cCtcDiv-1
.endif
.equ cDelta = cDiv*(cCtcCmp+1)*cPresc ; Clock calculated
.if cDelta != clock
.message "Clock divider has division rest, inaccurate second"
.endif
.equ cMeasFreq = 1000 / cGateTime ; Measuring frequency
.if cMeasFreq < 1
.error "Measuring gate time too long"
.endif
;
; Oscillator constants
.equ cOscLow = cOscFreq - cOscTol ; Smallest tolerable frequency
.equ cOscMax = 2*cOscTol + 1 ; Largest tolerable frequency
;
; Minimum voltage of PWM
.equ cMinPwm = (cMinVolt * 256) / 5000 ; Minimum PWM value
;
; **********************************
; R E G I S T E R S
; **********************************
;
; free: R0 to R14
.def rSreg = R15 ; Save/Restore status port
.def rmp = R16 ; Define multipurpose register
; free: R17 to R29
.def rFlag = R17 ; Flag register
.equ bOver = 0 ; Measuring cycle is over flag
; free: R18
.def rDiv = R19 ; Register divider
.def rFrq0 = R20 ; Measured frequency result, byte 1
.def rFrq1 = R21 ; dto., byte 2
.def rFrq2 = R22 ; dto., byte 3
.def rCnt0 = R23 ; LSB of 24 bit counter
.def rCnt1 = R24 ; HSB, used as 16 bit counter
.def rCnt2 = R25 ; MSB of 16 bit counter
; free: R26 to R31
;
; **********************************
; S R A M
; **********************************
;
; No SRAM used
;
; **********************************
; 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 ; INT0
reti ; PCI0
reti ; OC1A
reti ; OVF1
reti ; OVF0
reti ; ERDY
rjmp AciIsr ; ACI
reti ; ADCC
reti ; OC1B
rjmp Oc0AIsr ; OC0A
reti ; OC0B
reti ; WDT
reti ; USI_START
reti ; USI_OVF
;
; **********************************
; I N T - S E R V I C E R O U T .
; **********************************
;
; Counts changes of the analog comparer
; Occurs every 4.5344 us at 110.268 kHz
AciIsr: ; 7 clocks for int and vector jump
.if debug_blinkaci == Yes
sbi pLedI,bLedI ; Blink LED
reti
.endif
in rSreg,SREG ; Save SREG, +1 = 8
inc rCnt0 ; Count LSB, +1 = 9
brne AciIsr1 ; +1/2 = 10/11
adiw rCnt1,1 ; Count HSB/MSB, +2 = 12
AciIsr1: ; 11/12 clocks
out SREG,rSreg ; Restore SREG, +1 = 12/13
reti ; +4 = 16/17
; Maximum 17 clock cycles
; 17 clock cycles in 4.5344 us = 4 MHz min.
;
; 2 Hz counter interrupt service routine
; counts for 0.5 seconds and reads
; counting result
Oc0AIsr: ; 7 clocks for int and vector jump
in rSreg,SREG ; Save SREG, +1 = 8
dec rDiv ; Decrease divider, +1 = 9
brne Oc0AIsr1 ; Not yet zero, +1/2 = 10/11
sbr rFlag,1<<bOver ; Set bOver flag, +1 = 11
ldi rDiv,cDiv ; Restart cDiv, +1 = 12
mov rFrq0,rCnt0 ; Copy counter, +1 = 13
mov rFrq1,rCnt1 ; +1 = 14
mov rFrq2,rCnt2 ; +1 = 15
clr rCnt0 ; Clear counter, +1 = 16
clr rCnt1 ; +1 = 17
clr rCnt2 ; +1 = 18
Oc0AIsr1: ; 11/18 clock cycles
out SREG,rSreg ; Restore SREG, +1 = 12/19
reti ; +4 = 16/23
;
; **********************************
; 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 I/O ports
sbi pLedD,bLedD ; Turn LED output on
sbi pLedO,bLedO ; Turn red LED on
; Start TC1 as async PWM
sbi pPwmD,bPwmD ; Set OC1B as output
ldi rmp,255 ; Start with the highest PWM stage
out OCR1B,rmp ; in compare port B
ldi rmp,255 ; End value for PWM, 8-Bit PWM
out OCR1C,rmp ; in output compare register C
ldi rmp,(1<<PLLE)|(1<<LSM) ; Enable PLL in low speed mode
out PLLCSR,rmp ; in PLL control register
; Wait for 100 microseconds
; n = 2+4*(z16-1) + 3
; n = 2+4*z16-4+3 = 4*z+1
; 4*z16 = n-1
; z16 = (n-1)/4
; n @ 8MHz = 800
.equ z16 = (clock/10000+2)/4
ldi rCnt2,High(z16) ; Wait for 100 us, MSB
ldi rCnt1,Low(z16) ; dto., LSB
PllWait:
sbiw rCnt1,1 ; Count down
brne PllWait ; Wait further
ldi rmp,(1<<PLLE)|(1<<LSM)|(1<<PCKE) ; and PCK
out PLLCSR,rmp ; in PLL control register
ldi rmp,(1<<PWM1B)|(1<<COM1B1) ; PWM B enabled, High to low
out GTCCR,rmp ; in general timer control register
ldi rmp,(1<<PWM1A)|(1<<CS12) ; PWM A, High/Low, Prescaler=8
out TCCR1,rmp ; in TC1 control register
; Start TC0 as gate timer
ldi rDiv,cDiv ; Start software divider
ldi rmp,cCtcCmp ; Set compare A value
out OCR0A,rmp ; in compare A
ldi rmp,1<<WGM01 ; Set CTC mode 3
out TCCR0A,rmp ; in TC0 control port
clr rmp
.if (cPresc == 1) || (cPresc == 64) || (cPresc == 1024)
sbr rmp,1<<CS00
.endif
.if (cPresc == 8) || (cPresc == 64)
sbr rmp,1<<CS01
.endif
.if (cPresc == 256) || (cPresc == 1024)
sbr rmp,1<<CS02
.endif
out TCCR0B,rmp ; to TC0 control port B
ldi rmp,1<<OCIE0A ; Enable interrupt on compare A
out TIMSK,rmp ; in timer int mask
; Sleep mode idle
ldi rmp,1<<SE ; Sleep enable
out MCUCR,rmp ; in microcontroller control port
; Init analog comparer as frequency input
ldi rmp,(1<<AIN1D)|(1<<AIN0D) ; Disable digital inputs
out DIDR0,rmp ; in analog disable port register
ldi rmp,1<<ACIE ; Enable analog comparator interrupts
out ACSR,rmp ; in analog comparer status register
;
; Enable interrupts
sei ; Enable interrupts
;
; **********************************
; P R O G R A M L O O P
; **********************************
;
Loop:
sleep ; Go to sleep
nop ; Delay on wake-up
sbrc rFlag,bOver ; bOver flag clear?
rcall Measured ; Frequency measurement
rjmp Loop
;
; Frequency measurement complete
Measured:
cbr rFlag,1<<bOver ; Clear flag
.if debug_blink500ms == Yes
sbi pLedI,bLedI
ret
.endif
ldi rmp,Byte1(cOscFreq) ; Byte 1 of cOscLow
sub rFrq0,rmp ; Subtract measured frequency, LSB
ldi rmp,Byte2(cOscFreq) ; Byte 2 of cOscLow
sbc rFrq1,rmp ; dto., HSB
ldi rmp,Byte3(cOscFreq) ; Byte 3 of cOscLow
sbc rFrq2,rmp ; dto., MSB
brcs MeasuredLow ; Frequency too small, increase
ldi rmp,Byte1(cOscMax) ; Byte 1 of upper bound
sub rFrq0,rmp ; Subtract upper bound, LSB
ldi rmp,Byte2(cOscMax) ; Byte 2 of upper bound
sbc rFrq1,rmp ; Subtract upper bound, HSB
ldi rmp,Byte3(cOscMax) ; Byte 3 of upper bound
sbc rFrq2,rmp ; Subtract upper bound, MSB
brcc MeasuredHigh ; Frequency too high, decrease
cbi pLedD,bLedD ; LED off
ret
MeasuredLow:
; Measured frequency too low, increase
in rmp,OCR1B ; Read compare value
inc rmp ; Increase compare value
brne MeasuredSetPwm ; Not the max. value, set PWM
dec rmp ; Decrease again
cbi pLedO,bLedO ; LED to yellow
rjmp MeasuredSetPwm
MeasuredHigh:
; Measured frequency too high, decrease
in rmp,OCR1B ; Read compare value
dec rmp ; Decrease
cpi rmp,cMinPwm ; Smaller than minimum PWM
brcc MeasuredSetPwm ; Not smaller than min., set PWM
inc rmp ; Increase again
sbi pLedO,bLedO ; LED to red
MeasuredSetPwm:
out OCR1B,rmp ; Write new value to TC1 compare B
sbi pLedD,bLedD ; LED pin as output
ret
;
; End of source code
;
Copyright:
.db "(C)2019 by Gerhard Schmidt",0,0
.db "C(2)10 9ybG reahdrS hcimtd",0,0