[time-nuts] First success with very simple, very low cost GPSDO, under $8

Chris Albertson albertson.chris at gmail.com
Wed Apr 9 03:05:27 UTC 2014

I just had some success with a new GPSDO based very much on Lars Walenius'

I cut his design and his software down to make it even more simple and cost
less.  The controller is now well under $8.  The software is also much
simpler and easy to read even if you are not a "software guy".

Lars' design and software will out perform mine.  There is no question on
that.  But my goal was to first build a VERY low cost and more importantly
easy to understand and replicate GPSDO.  In this first version of the GPSDO
I did not implement the TIC.  I only count how many cycles occur from the
local oscillator between PPS pulses and then use a PI control to adjust the

Performance.  I am open to suggestions on how I can characterize the
performance.  This project is being designed for a beginner who would not
have exotic test equipment.  I would call the performance "not as bad as
you might guess".  I have had the output of this GSPDP and the 10MHz signal
from a Trimble Thunderbolt both up on my Tek 365 100MHz dual channel scope
for about 26 hours and they seem to more or less keep in phase.   The GPSDO
waveform drifts one way then the other.  I can see the short term stability
is good only to about the E-9 level but of course being locked to a GPS it
has very good long term stability.  And this with about $8 worth of parts
and build in about one hour. (Not counting the power supply (a plug-in wall
wort type), OCXO and GPS)  Also the controller software is intentionally
simplified.  The E-9 is an estimate.  I'm looking for a good way to measure
it with simple tools.

Plans:  I will add features and sophistication one step at a time and I
will try to document the process.  Even as I add more my goal is still (1)
an understandable design that anyone can build with no special tools or
equipment and (2) cost.  With equal importance to each.   The next step is
too add some refinements to the software, different modes and so one.  But
this stripped down version will be the "introduction" for a tutorial on
GPSDO design.

Goals:  I want to document this as a design others can build.  Even if they
are new to all of this.  I also want to keep the software open and easy to
modify in the hope that others will actually modify it and post their mods
here for kind of peer review

Parts list:  74HC390, Aruindo "pro mini",  two small electrolytic caps and
three resisters.  That's it.
The Mini is $3.71 with free shipping the 74hc390 was $1.  Other parts where
in the junk box.

A software listing is attached.  I used some of Lars' code but added a few
ideas of my own and removed 80% of the functionality in preference to
clarity.  I'm not 100% done.  I know I can make if more readable.

Chris Albertson
Redondo Beach, California
-------------- next part --------------

#define PWMHIGHPIN     3   // high PWM-DAC pin (connected to 39k)
#define PWMLOWPIN     11   // low PWM-DAC pin (connected to 10M)

volatile boolean pps_interrupt = false;

volatile unsigned long timer1CountNew = 0;
volatile unsigned long timer1CountOld = 0;
volatile unsigned long overflowCount  = 0; // counter for timer1 overflows
volatile unsigned long overflowsNew   = 0;
unsigned long          overflowsOld   = 0;

unsigned int dacOutCount;

// PPS interrupt routine.
// This code executes once per second at the raaising edage of each PSS
void pps() {
  timer1CountNew = TCNT1;   // get Timer1 counter value
  overflowsNew = overflowCount;
  pps_interrupt = true;

// Timer1 overflow Interrrupt Service Routine
ISR (TIMER1_OVF_vect) {
  overflowCount++;               // count number of Counter1 overflows

// Setup Timer
// This is hard to understand without looking at the AVR data sheet.
// After this setup the result is...
//     Timer1 is a 16-bit counter on pin-D5 that interrupts on overflow
void setupTimer() {

  overflowCount = 0;  // no overflows yet (move to variables?)
  // stop Timer 0 so that timer1 not misses counts due to timer0 interrupts
  // note: millis() delay()) etc will not work!
  TCCR0A = 0;
  TCCR0B = 0;
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  // Timer 1 - counts events on pin D5
  // 5MHz external clock source on T1 pin (D5). Clock on rising edge.
  TIMSK1 = _BV (TOIE1);   // interrupt on Timer 1 overflow
  TCNT1 = 0;      //counter 1 to zero
  TCCR1B =  _BV (CS10) | _BV (CS11) | _BV (CS12); // start Timer 1

// The DAC is actually two smaller DAC with ther output summed.
// We need to set each DAC
void dacOut(unsigned int dacValueOut) {

  analogWrite(PWMHIGHPIN, highByte(dacValueOut));
  analogWrite(PWMLOWPIN,  lowByte (dacValueOut));

void setup() {

  // connect the PPS handler to the PPS pin
  attachInterrupt(0, pps, RISING);

  //Set up Timer1

  // Sert the DAC to a default value.
  // Any random value is oK
  dacOutCount = 32000;

long I = 0;

void loop() {

  if (pps_interrupt) {

    long error = 0;

    long countsPPS;
    int  overflowsPPS;
    long timer1CountPPS;

    // The number of Timer1 overflows between this and the last PPS
    overflowsPPS = overflowsNew - overflowsOld;
    overflowsOld = overflowsNew;

    // The number of Timer1 counts between the last overflow and this PPS
    timer1CountPPS = timer1CountNew - timer1CountOld;
    timer1CountOld = timer1CountNew;

    // The total number of counts between this and the last PPS  
    countsPPS = (overflowsPPS  * 65536 ) + timer1CountPPS;

    // "error" is the different between what we whant and what we got
    // we limit the error so as to not make changes to fast
    error = 5000000 - countsPPS;
    if(error > 100)  error =  100;
    if(error < -100) error = -100;

    // Inbtegrate the error, over time I will average to zero
    I += error;

    // Limit I to prevent "wind up" of PI controller 
    if(I > 25)  I =  25;
    if(I < -25) I = -25;

    // This is the classic "PI" controller function
    dacOutCount += (error * 5) + I/2;

    // Limit DAC to reasonable values
    if(dacOutCount < 1000)  dacOutCount = 1000;
    if(dacOutCount > 60000) dacOutCount = 60000;

    // Send the new value tot  he DAC

    pps_interrupt = false; 

More information about the Time-nuts_lists.febo.com mailing list