software PLL to generate 3.2 Khz sampling freq locked with 1pps from GPS in arduino

I want to design a phase locked loop using Arduino. To generate 3.2 Khz sampling freqency locked with 1 PPS (from GPS), for my data aquisition module,

the sampling clock generated without using PLL get drifted over time. which leads to wrong sample collection. if sampling clock is generated using PLL locked with a reference freqency (1 PPS) :: data aquisition modules at different location will be able to collect sample at same instant.


You can make a phase-locked loop, literally.

You need two interrupts:

  1. First will fire on the front of a pulse at 1 PPS input.
  2. Second will be called 3200 times per second, each time when timer wraps around.

First, setup the timer cycle length to 5000 CPU cycles.

The timer interrupt is very simple just increases some counter variable, and when it reaches 3200, resets it to zero.

The one-second pulse interrupt acts as follows:

  • If the counter variable value is zero - that means timer works at the exact frequency - do nothing
  • If the counter variable value greater than 0 but less than 1600 (i.e. timer works faster than required) - increase the timer period by 1
  • If the counter variable value 1600 or greater (the once-per-second interrupt happened faster than the counter reached 3200) decrease the timer period by 1

For the first start, set the counter variable to 0, timer value to 2500 (half of the period), timer period to 5000, and wait until the first pulse appears, then start the timer.

The code example maybe like this (assuming usage of Arduino UNO, and 1 PPS connected to INT0 input, triggered by a rising edge):

volatile uint16_t counter = 0; // counter variable

ISR(TIMER1_COMPA_vect) { // Timer interrupt
  if (++counter >= 3200) counter = 0;
}

// edited: INT0_vect
ISR(INT0_vect) { // External Interrupt Request 0
  if ((TCCR1B & ((1 << CS12) | (1 << CS11) | (1 << CS10))) == 0) {
    // if timer is not running (the first pulse)
    TCCR1B = (1 << WGM12) | (1 << CS10); // Start in CTC mode, 1:1 prescaler
  } else { // Once-per-second pulse
    uint16_t cnt = counter;
    if (cnt >= 1600) { 
      uint16_t ocr_val = OCR1A - 1; // edited: uint16_t
      OCR1A = ocr_val;
      if (TCNT1 >= ocr_val) { // for the case, when OCR1A was changed below the current value of TCNT1
        TCNT1 = 0;
      }
    } else if (cnt > 0) {
      OCR1A++;
    }
  }
}


// initialization code
TCCR1A = 0; // for now timer is disabled
TCCR1B = 0;
OCR1A = 4999; // 5000 timer period
TCNT1 = 2500; // starts from the middle of the first period
TIMSK1 = (1 << OCIE1A); // allow the output compare 1 interrupt


EICRA = (1 << ISC01) | (1 << ISC00); // The rising edge of INT0 generates an interrupt request
EIMSK = (1 << INT0); // Allow int0 interruput

sei(); // Enable interrupts.

If you want the output signal to be generated at OC1B pin, you may initialize the timer like this:

TCCR1A = (1 << COM1B1) | (1 << WGM11) | (1 << WGM10); // edited: COM1B1
OCR1B = 2499; // PWM with pulse length half of the period 

DDRB |= (1 << DDB2); // edited: port initialization

and in the INT0 interrupt start the timer like this:

TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10); // Start in FastPWM - CTC mode, 1:1 prescaler

the output should appear at PB2/OC1B pin = Arduino UNO pin 10