;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; Self calibrating TCXO controller ; ; A minimalistic GPSDO for low performance self-calibrating systems ; (then again, we may have differing definitions of 'low performance') ; ; ; ; ; (C) 2010 Kasper Kjeld Pedersen ; ---------------------------------------------------------------------------- ; "THE BEER-WARE LICENSE" (Revision 42): ; Kasper Kjeld Pedersen wrote this file. As long as you retain this notice you ; can do whatever you want with this stuff. If we meet some day, and you think ; this stuff is worth it, you can buy me a beer in return ; ---------------------------------------------------------------------------- ; ; ; Please note that this design has limited range - 2 ppm or so. ; If the oscillator is outside of this range, the PPS will be considered ; invalid, and it will coast on the eeprom-stored DAC values. ; ; While PPS is good and being tracked, it will store the DAC value to eeprom ; every 27 minutes. ; ; ; Pinout (ATTiny13): ; ; _______ ; reset ^ vcc ; 10MHz --> clock serial --------[1k]---> terminal ; PPS-----> pps bpwm ---->lowpass filer---> vco-input ; gnd______apwm ---->lowpass filter---> error meter ; ; ; bpwm is the loop output. Sigma-delta technique is used to give 16 bit resolution. ; It should be passed through a filter that has a time constant of less than 10 ; seconds, and attenuates 39kHz by at least 39000 times. ; 22k 180uF as used in the prototype has tc=4 sec and attenuation=1M ; ; apwm outputs the estimated frequency error. 0%-50%-100% is -50ppb - 0 - +50ppb ; For connection to an analog panel meter. Will center until pps is received. ; A 10ms+ filter is suitable - 50k 1uF would be good for driving a 100uA meter. ; ; The PPS input is rising edge sensitive. ; Minimum PPS low time is 5 us. Minimum PPS high time is 600ns. ; The input does not need to be 1Hz. Any integer frequency up to 100kHz will ; work. Provide an external 500k or less pullup or pulldown if it can be ; left unconnected. ; ; ; The serial output is a RS232 polarity (ie. idle 0V) 38400 bps, 8 data bit, ; no parity, 1 stop bit. The output format is ; ; ; example string: ; C259 02 FF FB ; ++++ DAC: goes from 0000 to FF00. ; ++ PPS timing: Goes from -20 to +20. -20 would be sent at "EC" ; A value of 2 means that the PPS edge arrived 200ns 'late'. ; ++ GoodCount: Set to 00 whenever PPS lock is lost. Then counts ; good pulses, and when 255 good ones are seen, the PLL starts ; using the information. ; ++ FrqErr: From -128 (0x80) to +127 (0x7F) in units of 4E-10. ; The estimated error of the vco based on a tau of 4 minutes. ; When positive, the vco is fast. ; ; ; ; ; Calculating the resulting time constant: ; if the oscillator is 1ppm low, the loop will step the adc SPAN*10/65536 per second. ; Thus, if the vcxo range is 1 ppm, the time constant is 6554 seconds. ; ; ; 6553.6 ; time constant in seconds = ------------------------------------------ ; (vcxo change in ppm for 5V) * more_gain ; ; more_gain is a user programmable figure in the range 1..5. ; ; ; ; ; My test TCXO setup v1: ; gain of 5 on a 2ppm range gives 655 seconds tau. ; the frequency step for 100ns (the PPS error) is thus 100ns/655s = 0.15 ppb = 1.5E-10 ; which is smaller than the freq-err readsout resolution. ; ; My test TCXO setup v2: ; gain of 1 on a 2ppm range gives 54 minute tau. ; the frequency step for 100ns (the PPS error) is thus 100ns/655s = 0.15 ppb = 1.5E-10 ; which is smaller than the freq-err readsout resolution. ; ; ; ;;;;;;;;;;;;;;;;;;;;; SETTINGS ;;;;;;;;;;;;;;;;;;; ;this setting decreases the time constant. #define MOREGAIN_1 ;;#define MOREGAIN_2 ;;#define MOREGAIN_3 ;;#define MOREGAIN_4 ;;#define MOREGAIN_5 ;which way does the VCO swing? POS if a positive control voltage raises frequency #define DFDV_NEG ;;#define DFDV_POS ;this will cause internal pullup on pps when no pps is present. ;Convenient, but can degrade pps-less accuracy slightly if ;the pps pin is also driven low. ;#define PPSAUTOPULL ;this define boosts the loop to 1kHz. without it simulation takes AGES. ;not for use in hardware unless you really know what you are doing. ;#define DEBUG1KHZ .INCLUDE "TN13DEF.INC" .def irqscratch=r1 .def timerM = r2 .def timerH = r3 .def nextL = r4 .def nextM = r5 .def nextH = r6 .def dacL = r7 .def dacH = r8 .def lasterror = r9 .def good_pps_counter = r10 .def deltasigmaacc = r11 .def irqwork = r12 .def frqavgH = r13 .def frqavgL = r14 .def storetimerL = r15 .def storetimerP = r16 v_reset: rjmp start v_int0: nop v_pcint: rjmp target_of_pcint v_ovf0: rjmp ovf0_irq ;v_eerdy: nop ;v_anac: nop ;v_t0a: nop ;v_t0b: nop ;v_wdt: nop ;v_adc: nop start: #ifdef DFDV_POS ldi r24, (1<48 add lasterror,lasterror ; 48->96 add lasterror,r24 ; 96+24= 120 #endif #ifdef MOREGAIN_4 add lasterror,lasterror ; 24->48 add lasterror,lasterror ; 48->96 #endif #ifdef MOREGAIN_3 mov r24,lasterror add lasterror,lasterror ; 24->48 add lasterror,r24 ; 96+24= 120 #endif #ifdef MOREGAIN_2 add lasterror,lasterror ; 24->48 #endif ret #ifdef DEBUG1KHz adjust_1kHz: ;ldi r24,0x70 ;subtract 10M-10000 clocks ;sub nextL,r24 ;ldi r24,0x6F ;sbc nextM,r24 ;ldi r24,0x98 ;sbc nextH,r24 ret #endif ovf0_irq: in irqscratch,SREG mov irqwork,dacH add deltasigmaacc,dacL brcc no_ds_cy inc irqwork no_ds_cy: out OCR0B,irqwork inc timerM brne ovf0ret inc timerH ovf0ret: out SREG,irqscratch reti target_of_pcint: pop irqscratch ;;get rid of pushed program counter pop irqscratch rjmp target_of_pcint_cleaned store_ee: ;compute write address ldi r31,0x1F ;controller address rcall read_ee_byte com r24 andi r24,0x10 ;flip controller add r31,r24 ;0x20 or 0x30 mov r24,dacL rcall store_ee_byte mov r24,dacH rcall store_ee_byte mov r24,r31 ldi r31,0x1F ;rjmp store_ee_byte ; controller flipin eeprom ;fallthrough store_ee_byte: sbic EECR,EEPE rjmp store_ee_byte ldi r30, 0 out EECR, r30 out EEARL,r31 out EEDR,r24 sbi EECR,EEMPE sbi EECR,EEPE inc r31 ret ;scan for 2 identical copies of DAC setting. read_ee: ;compute read address ldi r31,0x1F ;controller address rcall read_ee_byte andi r24,0x10 ;flip controller add r31,r24 ;0x20 or 0x30 rcall read_ee_byte mov dacL,r24 rcall read_ee_byte mov dacH,r24 ret read_ee_byte: sbic EECR,EEPE rjmp read_ee_byte out EEARL,r31 sbi EECR,EERE in r24,EEDR inc r31 ret transmit_hex_byte: push r24 swap r24 rcall transmit_hex_digit pop r24 ;fallthrough ;;;;;;;;;;;;; hex transmit digit;;;;;;;;;;;;;;;;;;;;;;;; transmit_hex_digit: andi r24,0x0F subi r24,-48 cpi r24,58 brcs transmit_byte ;0..9 subi r24,-7 ;65 - 48 -10= 7 ;fallthrough ;;;;;;;;;;;;; softuart transmit ;;;;;;;;;;;;;;;;;;; transmit_byte: rcall wait_bit sbi PORTB,2 ldi r25,9 bitloop: rcall wait_bit sbrs r24,0 sbi PORTB,2 sbrc r24,0 cbi PORTB,2 sec ;stopbit. after 8 iterations the register is all-1 ror r24 dec r25 brne bitloop ldi r24,32 ;this makes inserting spaces cheaper ;;ret ;r24=32,r25=0 on return ;fallthrough. Saves a word, and adds an additional stop bit (8N2), fine for 8N1 receivers. wait_bit: mov r0, timerM wait_internal: cp r0,timerM breq wait_internal ret