How to Improve a Frequency Measurement?

Hello everyone!

I'm having some trouble regarding the accuracy of a frequency measurement of a 1 MHz signal. I'm using the FreqCount library from PJRC. I'm using an Arduino Mega.

Right now I have two problems:

  1. I need an accuracy of 10 ppm.

  2. The measure fluctuate a lot if there are temperature variations.

Both problems seems to be related to the 16 MHz resonator. It has a tolerance of 0.5%, which is ~5000 ppm. And the resonator is very sensible to temperature.

After looking here in the forum, searching on Google and talking with my friends, we reached to the possible solutions:

S1) Change the resonator with a better crystal oscillator. But I haven't found a good crystal with the same mounting type of the resonator.

S2) Use an external clock signal in the terminal XTAL. We chose to use Si5351A Clock Generator. But I still don't understand how to change the fuse bits to use an external clock signal of an Arduino Mega/Atmega2560.

S3) Use an external clock signal in a timer clock input terminal. There are only 02 terminals available as timer clock inputs: 38 (Timer 0) and 47 (Timer 5). Right now I use the terminal 47 to measure the frequency. So probably I'm using Timer 5 as a counter and Timer 5 uses the resonator as a time reference. The idea is to insert a good clock source (the same as solution 2) in Timer 0 and set it as the time reference for Timer 5. But I still have no idea on how to do it.

S4) Find or create a device that does the frequency measure and sends the results to my main Arduino. But I didn't find such device. Maybe I should build one using a simple microcontroller...

And here are my questions after all this:

Q1: Are these solutions feasible?

Q2: Are there other possible solutions?

Q3: How do I change the fuse bits to use an external clock signal? Is it possible to do it using Arduino IDE? Do I need to set a bootloader?

Q4: If solution S3 is feasible, how would I do it? I still don't understand very well the FreqCount library. Where should I start learning about it? Are there information in Atmega2560 datasheet that I should know first?

I will try to apply the solution S4 if the others fail.

And that's it. Thank you very much for your time and help!

You can use an external clock source to trigger T0 on pin 38. Set the T/C to operate normally, TCCR0A = 0B00000000, and use external clock TCCR0B = 0B00000110 clock on falling edge or 0B00000111 clock on rising edge.

Choose your crystal for whatever resolution you want, say 10MHz for 100ns, and then TCNT0 will keep track of the ticks.

IMHO, you can't get 10 ppm using any crystal you could find. W/o temperature oven or GPS. Personally, I'd choose GPS unit that has 1 PPS output. Than, feeding your 1 MHz as a clock source T5 (47) you apply gate clock (1 PPS) to ICP5 (48)- > input capture.
You also 'd need couples interrupts running in background

  1. TOIE5- timer5 overflow, so 16-bits timer would overflow approximately 1 000 000 / 65 536 = 15.2 times during 1 second gate interval.
  2. ICIE5 - input capture interrupt.
    Start from here.
    1 ppm seems achievable.

I second the GPS idea. That gives you a clock which is more accurate than the rotation of the Earth itself. Either use the 1 pulse-per-second output to gate your counter (make one reading per second) or use it to tweak the results from the code you already have

DKWatson:
You can use an external clock source to trigger T0 on pin 38. Set the T/C to operate normally, TCCR0A = 0B00000000, and use external clock TCCR0B = 0B00000110 clock on falling edge or 0B00000111 clock on rising edge.

Choose your crystal for whatever resolution you want, say 10MHz for 100ns, and then TCNT0 will keep track of the ticks.

Thanks for the reply, DKWatson. Unfortunatly I'm afraid I don't have yet the knowledge to apply this solution. I'm studying the FreqCount library at the moment. I still have yet to understand how to set to use the external clock source on pin 38.

I imagine that I will have to make the changes of the registers on the library files, is that correct?

MasterT:
IMHO, you can't get 10 ppm using any crystal you could find. W/o temperature oven or GPS. Personally, I'd choose GPS unit that has 1 PPS output. Than, feeding your 1 MHz as a clock source T5 (47) you apply gate clock (1 PPS) to ICP5 (48)- > input capture.
You also 'd need couples interrupts running in background

  1. TOIE5- timer5 overflow, so 16-bits timer would overflow approximately 1 000 000 / 65 536 = 15.2 times during 1 second gate interval.
  2. ICIE5 - input capture interrupt.
    Start from here.
    1 ppm seems achievable.

MorganS:
I second the GPS idea. That gives you a clock which is more accurate than the rotation of the Earth itself. Either use the 1 pulse-per-second output to gate your counter (make one reading per second) or use it to tweak the results from the code you already have

Thanks for the replies, MasterT and MorganS.

The idea to use a clock signal from a GPS is to obtain the most accuracy possible, is that correct? I have not yet used a GPS in my projects. Does it have an output with a clock signal?

You suggested the usage of the pin ICP5 (48). I will study how to use it in the link you sent me.

Thanks!

WKAriyoshi:
Thanks for the replies, MasterT and MorganS.

The idea to use a clock signal from a GPS is to obtain the most accuracy possible, is that correct? I have not yet used a GPS in my projects. Does it have an output with a clock signal?

You suggested the usage of the pin ICP5 (48). I will study how to use it in the link you sent me.

Thanks!

There are many types GPS receiver, look for unit with PPS (Pulse-Per-Second) pin. For example:
https://www.robotshop.com/en/uart-neo-7m-c-gps-module.html

Interesting, I was not able to find any "ready to use" code over i-net. So, I decided to write my own, luckily I have Ublox NEO-7M unit laying around, to play with.

/*
  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;
    }
  }
}

And timer5 tab:

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;
         
    frequency = curr_v + tmr_overf *65536UL; 
    frequency -= last_v;
    last_v    = curr_v;
    tmr_overf = 0;        
    f_capture = 1;       
}

Example, measuring Mega2560 internal clock:

Freq: 15992847
Freq: 15992845
Freq: 15992846
Freq: 15992845
Freq: 15992844
Debug de-aktiv.

Enjoy!

Gps_counter_public_1a.zip (1.88 KB)

MasterT, thank you very much for the code. A code helps me a lot to understand.

From your code, I can see that you are setting pin 47 (T5) as the input to measure the CLK and pin 48 (ICP5) as the CLK reference for the counter. That will be very useful since I won't need to set anything on Timer 0.

I'm doing my best, but I still haven't fully understood your code. I'm a newbie using Timers.

I tried running your code, but it's not showing anything on my serial monitor. I used an input of 1 MHz on both pins 47 and 48. Do you have any idea why? I tried sending the commands "d", "i1" and "i0", but it didn't answer.

I did some simple tests using your code in mine. It is showing a fixed value of 97450 Hz. What is the frequency of the signal you inserted on pin 48 (from the GPS)?

One thing I found strange using this last code is that when I removed the signals on pins 47 and 48, it would still show the same value (and not 0 Hz).

I will do some other tests here. Thanks!

Pin 48 expects to see PPS.
PPS = 1 Hz. If you apply 1 MHz to it, arduino would stuck , because it could not process interrupts at 1 million per second.
Do your home work man, read more about frequency measurement in general.

Hello MasterT. Thanks for your reply.

Your solution is very interesting. I am very interested on using only Timer 5. Before that, I was trying to modify a code from Nick Gammon from here: Gammon Forum : Electronics : Microprocessors : Timers and counters

In his example, he uses Timer 2 (for time reference) and Timer 5 (for counting). I am trying to substitute Timer 2 with Timer 0, since the last one has an external clock source pin. However, I wasn't able yet to do so.

Returning to your solution, in Atmega2560 datasheet I was reading about "Input Capture Unit". If I understood correctly, every time the GPS sends a clock pulse (1 Hz), it triggers the interruption ISR(TIMER5_CAPT_vect). This interruption calculates the frequency from the signal.

The calculation of the frequency depends on three variables:

  1. ICR5 - The Input Capture is updated with the counter (TCNTn) value each time an event occurs on the ICPn pin (or optionally on the Analog Comparator output for Timer/Counter1). So it's basically the value of the register TCNT0, that depends on the signal from the pin 47.

  2. tmr_overf - This value is incremented after each interruption ISR(TIMER5_OVF_vect). This interruption occurs when the value of the register TCNT0 overflows. The register TCNT0 depends on the signal from the pin 47.

  3. 65536UL - What is this?

Did I understand correctly?

WKAriyoshi:
The calculation of the frequency depends on three variables:

  1. ICR5 - The Input Capture is updated with the counter (TCNTn) value each time an event occurs on the ICPn pin (or optionally on the Analog Comparator output for Timer/Counter1). So it's basically the value of the register TCNT0, that depends on the signal from the pin 47.

  2. tmr_overf - This value is incremented after each interruption ISR(TIMER5_OVF_vect). This interruption occurs when the value of the register TCNT0 overflows. The register TCNT0 depends on the signal from the pin 47.

  3. 65536UL - What is this?

Did I understand correctly?

  1. Since you are talking about Timer5, should be TCNT5 value.
  2. Again, TCNT5 overflows.
  3. 65536 is 2^16 -> maximum value for 16-bits counter. TCNTn is a counter, it's increasing by each pulse at T5 (47) pin.

Thanks for your reply, MasterT.

I took a while to answer here because I was trying to apply the solution S1.

S1) Change the resonator with a better crystal oscillator. But I haven't found a good crystal with the same mounting type of the resonator.

I didn't find a crystal with the same mounting type, but I used some small wires to do the connections. I removed the resonator, the resistor R1 1M and mounted the crystal. It actually improved a lot the measures.

The crystal I used was this: TSX-3225 16.0000MF18X-AC3 EPSON | Crystals, Oscillators, Resonators | DigiKey

I did some tests and it seems that my problem is solved. The measures are much more stable and vary very little with the temperature. For a measure of 1 MHz, between 20 ºC and 40 ºC, the results didn't vary more than 5 Hz, which is lower than the 10 ppm that I need.

For now I will stay with this solution. If I need to apply the other solutions, I will write here again.

Thank you very much!