Author Topic: GPS precise frequency measurements with arduino (AtMega series).  (Read 3880 times)

0 Members and 1 Guest are viewing this topic.

Offline MasterTTopic starter

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Answering a question on another forum, I write an arduino sketch to measure frequency. Let's call it "GPS assisted" to get extra-precision.
 Thought, someone could find it useful as well. Written for AtMega2560, though it would be easy to adapt code to any AtMega uCPU, arduino or similar. One condition apply, both pin Tn and ICPn has to be brought out to header. Arduino UNO, for example has Timer1, Mega2560 - Timer5, etc.
Code: [Select]
/*
  Using Input Capture to extra-precise frequency measurements
  on Arduino Mega2560.

  GPS 1 PPS clock is supplied on pin 48 (ICP5).
  (Ublox NEO-7M unit was tested.)
  External signal is connected to pin 47 (T5).
  Prints the result over Serial Monitor.
  Commands:
    1: activate printout        - "d".
    2: switch to internal clock - "i1".
    3: switch to external clock - "i0".           
  Range 1 Hz <-> 6 MHz, (16 MHz if internal selected).
 
  Released into the public domain.

  Created by Anatoly Kuzmenko, April 2018
  k_anatoly@hotmail.com
*/

           String       in_String         =     "";       
           boolean      end_input         =  false; 
           uint8_t      adres_reg         =      0;         
           uint8_t      debug_osm         =      0;


  volatile int32_t      frequency         =      0;
  volatile uint8_t      tmr_overf         =      0;
  volatile uint8_t      f_capture         =      0; // flag

void setup()
{
  Serial.begin(115200);
  in_String.reserve(200);

  init_tmr5();
}

void loop()
{
  int32_t  tempr = 0;
  char *   pEnd;
 
  if(debug_osm) {
    if(f_capture) {
      Serial.print(F("\n\tFreq: "));
      Serial.print(frequency, DEC);     
      f_capture = 0;
      } 
    }

  serialEvent();

  if( end_input) {
    char cmd = in_String[0];
    in_String[0] = '+';
   
    if( cmd == 'd' ) {
      debug_osm = 1 - debug_osm;
      if(debug_osm) Serial.print(F("\nDebug aktiv."));
      else          Serial.print(F("\nDebug de-aktiv."));
      }

    if( cmd == 'i' ) {
      tempr = strtol( in_String.c_str(), &pEnd, 10);
      Serial.print(F("\n\tInput: "));
      if(tempr) Serial.print(F("internal 16 MHz."));
      else      Serial.print(F("external pin-D5."));
      input_clock(tempr);
      }
                       
    in_String = "";
    end_input= false;
  }
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    in_String += inChar;
    if (inChar == '\n') {
      end_input= true;
    }
  }
}
 
Timer5 Tab:
Code: [Select]
void input_clock(uint8_t inp)

  if(inp) {
    TCCR5B &= ~(1<<CS52); // internal
    TCCR5B &= ~(1<<CS51);
    TCCR5B |=  (1<<CS50);     
    }
  else {
    TCCR5B |= (1<<CS52); // external clock
    TCCR5B |= (1<<CS51);
    TCCR5B |= (1<<CS50);     
    }
}

void init_tmr5(void)

  TCCR5A = 0;
  TCCR5B = 0;

  TCCR5B |= (1<<CS50);  // set prescaler to 16 MHz

  TCCR5B |= (1<<ICNC5); // input noise canceler on
  TCCR5B |= (1<<ICES5); // input capture edge select

  TIMSK5 |= (1<<TOIE5); // Overflow Interrupt Enable
  TIMSK5 |= (1<<ICIE5);   
}

ISR(TIMER5_OVF_vect) {
  tmr_overf++; 
}

ISR(TIMER5_CAPT_vect) {
  static uint16_t last_v = 0;
         uint16_t curr_v = ICR5;

  if((TIFR5 & (1<<TOV5)) && (curr_v < 0x7F)) {
    tmr_overf++;
    TIFR5 |= (1<<TOV5);
    }
         
    frequency = curr_v + tmr_overf *65536UL;
    frequency -= last_v;
    last_v    = curr_v;
    tmr_overf = 0;       
    f_capture = 1;       
}
« Last Edit: April 06, 2018, 02:24:55 pm by MasterT »
 

Offline Kleinstein

  • Super Contributor
  • ***
  • Posts: 14181
  • Country: de
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #1 on: April 05, 2018, 07:41:16 pm »
The code still has a slight bug inside, that might cause some rare glitches: There can be a kind of race condition when the ICP and timer overflow interrupt happen at about the same time. This causes some glitches that should be avoided. There is a way to check if the overflow interrupt should be run before the ICP one or not.
 

Online metrologist

  • Super Contributor
  • ***
  • Posts: 2199
  • Country: 00
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #2 on: April 05, 2018, 09:27:04 pm »
Thanks. I'll study the code and try to figure out how it works, and it may make use in one of my projects.
 

Offline MasterTTopic starter

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #3 on: April 05, 2018, 10:34:10 pm »
The code still has a slight bug inside, that might cause some rare glitches: There can be a kind of race condition when the ICP and timer overflow interrupt happen at about the same time. This causes some glitches that should be avoided. There is a way to check if the overflow interrupt should be run before the ICP one or not.
There is a Vector Interrupts table
Quote
11 0x00A TIMER1 CAPT Timer/Counter1 Capture Event
12 0x00B TIMER1 COMPA Timer/Counter1 Compare Match A
13 0x00C TIMER1 COMPB Timer/Coutner1 Compare Match B
14 0x00D TIMER1 OVF Timer/Counter1 Overflow
CAPT has higher priority than OVF.  Digital number associated with pulse edge arriving time is already "in-printed" when uCPU goes into ISR.  And OVF arriving just after ICP can't change number of overflows, since it has to wait.
« Last Edit: April 05, 2018, 10:42:21 pm by MasterT »
 

Offline NivagSwerdna

  • Super Contributor
  • ***
  • Posts: 2495
  • Country: gb
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #4 on: April 05, 2018, 10:48:12 pm »
The timer ticks at XTAL rate which is only as good as the XTAL and subject to temperature effects etc.

You may benefit from counting ticks between PPS pulses to calibrate the local oscillator?
 

Offline MasterTTopic starter

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #5 on: April 06, 2018, 12:08:21 am »
The timer ticks at XTAL rate which is only as good as the XTAL and subject to temperature effects etc.

You may benefit from counting ticks between PPS pulses to calibrate the local oscillator?
There are two mode, have you missed that? Primary objective is external frequency measurements, when timer is running by T5 input.  Referencing internal 16 MHz is less practical, more like self verification that GPS is present and o'k.,   
 

Offline Kleinstein

  • Super Contributor
  • ***
  • Posts: 14181
  • Country: de
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #6 on: April 06, 2018, 10:09:27 am »
The code still has a slight bug inside, that might cause some rare glitches: There can be a kind of race condition when the ICP and timer overflow interrupt happen at about the same time. This causes some glitches that should be avoided. There is a way to check if the overflow interrupt should be run before the ICP one or not.
There is a Vector Interrupts table
Quote
11 0x00A TIMER1 CAPT Timer/Counter1 Capture Event
12 0x00B TIMER1 COMPA Timer/Counter1 Compare Match A
13 0x00C TIMER1 COMPB Timer/Coutner1 Compare Match B
14 0x00D TIMER1 OVF Timer/Counter1 Overflow
CAPT has higher priority than OVF.  Digital number associated with pulse edge arriving time is already "in-printed" when uCPU goes into ISR.  And OVF arriving just after ICP can't change number of overflows, since it has to wait.
There is an interrupt priority, however this only acts to decide that if two interrupts are waiting for execution which one is served first. However in the main program there are essentially always instructions that need more than one cycle. So it can still happen that OVF interrupt happens first but CAPT interrupt is serviced first, despite of coming 1 or 2 cycles later, but still during the same instruction.  This is so tricky that even the 1. st version of the AVR simulator in AVRstudio got it wrong, the later version got it right. 

 So it needs an extra test in the capture ISR to check if there is a pending OVF interrupt that should have come first. This is the case if the OVF interrupt flag is set and the ICP value is small (e.g. < 100). The limit value for small is not critical at all. 
 

Offline MasterTTopic starter

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #7 on: April 06, 2018, 02:20:47 pm »
Yea, Kleinstein you are absolutely right. I run a code at much faster speed to accelerate interrupt collision rate, and here is what I get:
Quote
   0   800         9948004
   1   799         12834639
   2   -64737   167
   3   66336      129
   4   -64736   115
   5   66335      153
Instead of PPS I applied 20 kHz, and counting internal 16 MHz clock, 800 is correct value for frequency.  But accidentally there are wrong numbers, last digit in each line is total number of events. So, 167+129+115+153= 564 out of 9948004+12834639 = 22782643, probability about 564 / 22782643 * 100% = 0.002475569 %. Quite low, but not zero.

 I put couple line to fix an issue, and now it's working w/o single error. Have to update a code on initial post.
Thanks, for advising. 
 

Offline Kleinstein

  • Super Contributor
  • ***
  • Posts: 14181
  • Country: de
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #8 on: April 06, 2018, 06:12:10 pm »
Doing direct counting (via T5) and measuring the 1 PPS signal via ICP is not the way to get the best accuracy. Especially with low frequencies for the external signal the resolution is rather limited (e.g. 1 Hz). The better way usually would be to use reciprocal counting (e.g. use ICP to measure the time for 1 period or more periods) as well. However the direct capture method works only well for not so high frequencies. Depending on the code, the limit would be at around 50-500 kHz (maybe 1 MHz in ASM). So higher frequencies would still need direct counting or a prescaler (e.g. divide by 256). 

The next better stage could than be looking at not only signal transition, but more of them. However here the limited processing power of the AVR can become an issue (e.g. limit the frequency range), especially in C with only 32 Bit and 64 Bit resolution to choose from.

The check of the Xtal clock does not need to be done that often. Even a simple Xtal is good for 0.1 ppm over quite some time. So one could even use the counter to either do a measurement or check the 1 PPS signal.
 

Online metrologist

  • Super Contributor
  • ***
  • Posts: 2199
  • Country: 00
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #9 on: April 06, 2018, 07:34:01 pm »
One idea I had was to count how many processor cycles between pps, and then do longer term averaging and slowly slew the tuning voltage of the clock oscillator, a 10 MHz OCXO clocking the micro (I would still program at base 16M and change osc after - absolute timing would not be important and could otherwise be inferred from the known clock).

You would count 10M + or - x. X goes into some decay function to normalize that out.

I try to convert this code to Uno. Do I just need to change timer5 to timer1? It compiles, but repeatedly runs setup as I put in a Serial.println("init"); in there and it loops that continuously (no other change to main).

Code: [Select]
void input_clock(uint8_t inp)

  if(inp) {
    TCCR1B &= ~(1<<CS12); // internal
    TCCR1B &= ~(1<<CS11);
    TCCR1B |=  (1<<CS10);     
    }
  else {
    TCCR1B |= (1<<CS12); // external clock
    TCCR1B |= (1<<CS11);
    TCCR1B |= (1<<CS10);     
    }
}

void init_tmr5(void)

  TCCR1A = 0;
  TCCR1B = 0;

  TCCR1B |= (1<<CS10);  // set prescaler to 16 MHz

  TCCR1B |= (1<<ICNC1); // input noise canceler on
  TCCR1B |= (1<<ICES1); // input capture edge select

  TIMSK1 |= (1<<TOIE1); // Overflow Interrupt Enable
  TIMSK1 |= (1<<ICIE1);   
}

ISR(TIMER5_OVF_vect) {
  tmr_overf++; 
}

ISR(TIMER5_CAPT_vect) {
  static uint16_t last_v = 0;
         uint16_t curr_v = ICR1;

  if((TIFR1 & (1<<TOV1)) && (curr_v < 0x7F)) {
    tmr_overf++;
    TIFR1 |= (1<<TOV1);
    }
         
    frequency = curr_v + tmr_overf *65536UL;
    frequency -= last_v;
    last_v    = curr_v;
    tmr_overf = 0;       
    f_capture = 1;       
}
 

Offline MasterTTopic starter

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #10 on: April 06, 2018, 08:33:27 pm »
One idea I had was to count how many processor cycles between pps, and then do longer term averaging and slowly slew the tuning voltage of the clock oscillator, a 10 MHz OCXO clocking the micro (I would still program at base 16M and change osc after - absolute timing would not be important and could otherwise be inferred from the known clock).
There is easier way to sync uCPU to GPS is to buy a GPS unit that could output 10 MHz directly , so you don't have to tune anything. Ublox NEO-7M supports that, timing adjustment range is from 1Hz to 10 MHz.

 You missed to change interrupts names:
Code: [Select]

ISR(TIMER1_OVF_vect) {

ISR(TIMER1_CAPT_vect) {
I tested it on UNO, all should works
 

Offline NivagSwerdna

  • Super Contributor
  • ***
  • Posts: 2495
  • Country: gb
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #11 on: April 06, 2018, 08:44:46 pm »
One idea I had was to count how many processor cycles between pps
Indeed. You don't need to change the local clock just calculate the correction.  Due to temperature changes the local oscillator will vary... could always put it in a temperature controlled box... or maybe use an OXCO... looks like we are heading towards a GPSDO  :)
 

Offline MasterTTopic starter

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #12 on: April 06, 2018, 08:49:00 pm »
The check of the Xtal clock does not need to be done that often. Even a simple Xtal is good for 0.1 ppm over quite some time. So one could even use the counter to either do a measurement or check the 1 PPS signal.
Some arduino boards use a ceramic, for example Mega2560 where this code is running:
Quote
Freq: 15992503

Its' 16M - 15992503 / 16M = 0.000468562, 468 ppm off. And it's drifted, +- 10 Hz in a minute, and +-300 Hz on hours scale, more when warming up.
 

Online metrologist

  • Super Contributor
  • ***
  • Posts: 2199
  • Country: 00
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #13 on: April 06, 2018, 09:23:48 pm »
One idea I had was to count how many processor cycles between pps
Indeed. You don't need to change the local clock just calculate the correction.  Due to temperature changes the local oscillator will vary... could always put it in a temperature controlled box... or maybe use an OXCO... looks like we are heading towards a GPSDO  :)

I'm not sure how that's accomplished, since what MasterT mentioned, the LO is far too drifty. Also, the Uno cannot sample very high frequency, so there would need to be much division and loss of resolution as a result.

That was the application. I already bought a couple of the Trimble GPSDO rebuilds, but need more to verify - I actually just want to build one.
 

Offline iMo

  • Super Contributor
  • ***
  • Posts: 4766
  • Country: nr
  • It's important to try new things..
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #14 on: April 06, 2018, 10:32:13 pm »
Mind the NEO-7M datasheet lists
Quote
Accuracy of time pulse signal
RMS 30ns
99% 60ns
 

Offline MasterTTopic starter

  • Frequent Contributor
  • **
  • Posts: 785
  • Country: ca
Re: GPS precise frequency measurements with arduino (AtMega series).
« Reply #15 on: April 12, 2018, 01:42:52 am »
I add a TFT display with touch panel, and here is the current project pictures:




 So far I have created trend & histogram modes. On trend view you may see a drift of internal arduino Mega ceramic resonator, almost 70 Hz in less than 2 minutes. Screen size  is 5 minutes overall, 300 pixels/seconds. 
« Last Edit: April 12, 2018, 01:45:27 am by MasterT »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf