Visserslatijn

5 mei 2016

GPS disciplined OCXO

Filed under: Geen categorie — visserslatijn @ 21:17

Een hele tijd geleden heb ik eens mee gedaan met zo’n crowdfunding actie voor een GPS dingetje (NavSpark GL). Daar heb ik eigenlijk nooit iets mee gedaan. Nou was ik een poosje bezig met een OCXO die ik heb gecalibreerd met mijn Rubidium standaard, wat overigens heel goed werkt.

In die tijd kwam ik zo nu en dan zelfbouw projecten tegen die de OCXO continu afstemden met behulp van een GPS. Dat wilde ik ook eens proberen. Ik heb het ding weer opgezocht en aangesloten. Daar komt een hele mooie puls uit van precies 1Hz. Heel precies volgens de docs, maximale jitter iets van 10nS.

navspark_gl

Behalve die 1puls per seconde (1PPS) komt er natuurlijk ook GPS data uit in de vorm van een 115200 baud stream van NMEA strings. Daar doe ik nog weinig mee, ik gebruik het om de tijd in UTC, de datum, het aantal zichtbare satellieten op een scherm te zetten. Ik was eerst van plan om van die data het aantal satellieten te gebruiken als signaal dat je de output van de oscillator mag vertrouwen. Dat bleek later niet nodig. De NMEA data wordt dus niet echt gebruikt. Al heb ik er wel een heel nauwkeurige klok erbij.

De OCXO die ik heb liggen levert een merkwaardige frequentie van 38,880,000 Hz. Dat is volgens Wikipeadia een frequentie die door de telecommunicatie maatschappijen werd gebruikt voor hun onderlinge communicatie. Niet interessant genoeg om daar veel tijd aan te besteden. En omdat het zo’n “onhandige”  frequentie is zijn ze lekker goedkoop via ebay te koop.

ocxo

Pas laat kwam ik er achter dat je deze dingen over een erg groot bereik kunt tunen, hoeveel precies weet ik niet meer maar het zijn honderden Herz boven en onder die 38.88MHz. Waarom zoveel is me een raadsel en het leverde me ook problemen op, waarover later meer. Je stemt de oscillator af met een regelspanning op de EFC ingang.

De 1PPS puls gaat een 74HC390 (dual decade counter) in die ik zo gebruik dat er een signaal van 8 seconden laag en 2 seconden hoog uit komt.

Dat signaal is de CLK-enable van een teller die als ingangsignaal  de OCXO heeft en dus 8 seconden lang 38,880,000 pulsen telt. Dat zijn er 311,040,000.

De microcontroller is een ATMEGA8 die niet in staat is om 38MHz signalen te verwerken, daarnaast wilde ik de counters van de controller helemaal niet gebruiken omdat het in en uitschakelen altijd gesynchroniseerd wordt met de clock van de microcontroller, ook als de counter een externe clock heeft. Dat zou al snel enkele microseconden fout opleveren.

De counter is dus ook in hardware uitgevoerd. Eerst wilde ik een enkele 74HC590 (8 bit binairy counter) gebruiken. Dat klinkt op het eerste gezicht wat vreemd, die kan immers maar van 0 tot 255 tellen en er komen meer dan 300 miljoen pulsen voorbij. Maar het zijn er altijd ongeveer evenveel. Zouden het er precies 311,040,000 zijn dan is de teller 1,215,000 keer rond gegaan en staat deze dus op 0. Als het er een paar meer waren komt de teller iets hoger te staan en waren het iets minder dan staat de teller nog voor de 255. Met een signed integer van 8 bits krijg je dan mooi een getal dat het tekort of overschot aan pulsen voorstelt.

Helaas werkte dat niet goed. En dat komt doordat de OCXO, zoals ik al eerder zei, over een groot bereik getuned kan worden. De afwijking van gewenste aantal pulsen kan makkelijk meer dan plus of min 128 worden. De oplossing was dus twee tellers te gebruiken, een extra investering van 25 eurocent. Dat is geen probleem, ik maak tenslotte maar een (1) apparaat, geen hele serie. Maar dit leverde een ander, groter, probleem voor me op. Zie hier het schema:

20160505_174118

De  1PPS die door tien gedeeld wordt en een duty cycle van 80% heeft enabled de clock van de tellers en gaat ook naar de INT0 ingang van de ATMEGA8. Zodra de tellers stil staan nadat ze 8 seconden hebben geteld wordt er een interrupt gegenereerd en zijn er 2 seconden de tijd om de data te verwerken. De tellers zetten de data op de uitgangen met behulp van RCK en kunnen daarna gereset worden. Dan leest de microcontroller de tellerstanden en gaat daarmee aan het werk. Nu was het plan om met een enkele 8 bit counter te werken, er bleven dan voldoende GPIO poorten over voor andere dingen, zoals een LCD schermpje. Dat ging dus niet door, alle GPIO poorten zijn in gebruik. Er moet zelfs gebruik gemaakt worden van de interne 8MHz rc oscillator, zo weinig poorten waren er over. Eigenlijk zou ik over moeten stappen op een andere controller… Maar goed, nu mag de tellerstand wel 32000 te hoog of te laag zijn. Zo groot is het tunings bereik van de OCXO niet. Jammer is wel dat als de juiste frequentie bereikt is de teller niet mooi op nul uitkomt maar op 6144.

Het afstemmen van de OCXO gaat zoals gezegt met een spanning tussen de 0 en 5 volt, na meten bleek de oscillator zo rond de 1.25v op de juiste frequentie te staan. Dus met een regelbare spanning tussen de 0 en 2.5v is deze mooi af te stemmen. Hiervoor wordt TIMER1 gebruikt die een PWM signaal maakt  in 32768 stapjes, dat leek me wel nauwkeurig genoeg, stapjes van 76 uV. En de PWM frequentie is zo ook nog hoog genoeg (244 Hz) De 47u condensator is later vervangen door eentje van 220u wat een lowpass filter met een cutoff van 0.05Hz geeft. Dit gaat direct de OCXO in, die is hoogohmig genoeg.

Hieronder het programma voor de aansturing van de OCXO,  in een volgende post de uitleg.

#define F_CPU 8000000UL
#define UART_BAUD_RATE 9600
#define CLR_74HC590 PB0
#define RCK_74HC590 PB2
#define REMAINDER 6144
#define PWM_START 0x41cd
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include <stdlib.h>
#include "uart.h"

volatile int16_t old_pwm[8];

int main(void)
{
 OSCCAL = 0x9c;
 DDRB = 0x07; //PB0,1,2 output
 DDRC = 0x00; //all input
 DDRD = 0x00; //all input (uart does its own init)

 TCCR1A |= (1 << COM1A1) | (1 << COM1A0); //SET OC1A on Compare Match, CLEAR OC1A at BOTTOM
 TCCR1A |= (1 << WGM11); //fast pwm, top = ICR1
 TCCR1B |= (1 << WGM13) | (1 << WGM12); //fast pwm
 TCCR1B |= (1 << CS10); //prescaler = 1
 ICR1 = 0x7FFF;
 OCR1A = PWM_START; //pwm starting value
 
 MCUCR |= (1 << ISC01) | (1 << ISC00); //INT0 on rising edge
 GIMSK |= (1 << INT0); //interrupt INT0 enabled
 
 uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) );
 uart_flush();
 
 for (uint8_t x=0; x<8; x++) //fill array with wanted settings
 {
   old_pwm[x] = PWM_START;
 }
 
 sei();
 
 while (1)
 {
   //nothing, all work is done in the interrupt routine
 }
}

ISR(INT0_vect) //once every 10 seconds, after 8 seconds of counting
{ //we have two seconds here, so no hurry
 uint16_t stepsize = 32;
 uint8_t freq_low, freq_high;
 int16_t frequency = REMAINDER;
 static uint32_t pwm_value = PWM_START;
 static uint8_t x = 0;
 uint8_t y = 0;
 char buffer[7];

 PORTB |= (1 << RCK_74HC590);
 _delay_ms(1);
 PORTB &= ~(1 << RCK_74HC590);
 _delay_ms(1);
 PORTB &= ~(1 << CLR_74HC590);
 _delay_ms(1);
 PORTB |= (1 << CLR_74HC590);
 _delay_ms(1);
 
 freq_low = (PINC & 0x3F) | (PIND & 0xC0);
 freq_high = ((PINB & 0xF8) >> 3) | ((PIND & 0x38) << 2);
 frequency = (freq_high << 8) | freq_low;
 
 if ((frequency - REMAINDER) > 2)
 {
 if ((frequency - REMAINDER) >= 15) stepsize = 256;
 if ((frequency - REMAINDER) < 15) stepsize = 16;
 if ((frequency - REMAINDER) < 10) stepsize = 4;
 if ((frequency - REMAINDER) < 5) stepsize = 1;
 if ((frequency - REMAINDER) < 3) stepsize = 0;
 pwm_value += stepsize;
 }
 
 if ((REMAINDER - frequency) > 2)
 {
 if ((REMAINDER - frequency) >= 15) stepsize = 256;
 if ((REMAINDER - frequency) < 15) stepsize = 16;
 if ((REMAINDER - frequency) < 10) stepsize = 4;
 if ((REMAINDER - frequency) < 5) stepsize = 1;
 if ((REMAINDER - frequency) < 3) stepsize = 0;
 pwm_value -= stepsize;
 }
 
 uart_puts("FRQ");
 ltoa(frequency, buffer, 10);
 uart_puts(buffer);
 uart_puts("\n\r");
 
 uart_puts("PWM");
 ltoa(pwm_value, buffer, 16);
 uart_puts(buffer);
 uart_puts("\n\r");
 
 old_pwm[x] = pwm_value;
 if (x < 7) x++;
 else x = 0;
 
 pwm_value = 0;
 for (y=0; y<8; y++)
 {
   pwm_value += old_pwm[y];
 }
 pwm_value = pwm_value / 8;
 
 OCR1A = (uint16_t) pwm_value;
 
 uart_flush();
}

 

 

Blog op WordPress.com.