Arduino quartz crystal accuracy for clock

I'm wondering did anybody test Arduino quartz crystals for accuracy. I cannot find anything there. Except for suggestions to use RTC, but see bellow.

What I can find is that 16Mhz quartz is usually 50ppm, can be better, can be worse. But that seems to be over whole 0-100C temp range, which would be actually better than typical 10ppm 32khz watch crystal.
I can't find any temp dependency curves for 16mhz crystal unfortunately. I guess I will have to try.
Has anybody done any tests?

Now for all crying use RTC, please don't :grin:. yes I know I can do that. But I fail to see your point. I want to test using Arduino crystal. Besides if you cry use RTC, you make all this forum redundant, because 99.999% stuff discussed here can be done way better by buying off the shelf stuff. Arduino is not supposed to be about that.

It may be instructive to check the 'related topics' at the bottom of this thread.

It is possible to replace the crystal with a more accurate one.

Another approach is to measure the actual clock frequency in Hz of the Arduino using the 1PPS output of a GPS unit. Even as a function of temperature, if you like.

This excellent tutorial, specifically the section on how to use the input capture unit of Timer1 on the Arduino Uno R3 (or Pro Mini, Classic Nano, etc.) gives the basic idea.

This code actually measures the crystal clock frequency in Hz, after connecting the ICP input D8 to the 1PPS output of a handy GPS module, that has a satellite fix.

// Frequency timer using input capture unit
// Author: Nick Gammon
// Date: 31 August 2013
// added averaging JR 2015
// Input: GPS 1PPS capture signal on Pin D8

volatile boolean first;
volatile boolean triggered;
volatile unsigned int overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;

// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect)
{
  overflowCount++;
}  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
{
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;

  // if just missed an overflow
  if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 0x7FFF)
    overflowCopy++;

  // wait until we noticed last one
  if (triggered)
    return;

  if (first)
  {
    startTime = (overflowCopy << 16) + timer1CounterValue;
    first = false;
    return;
  }

  finishTime = (overflowCopy << 16) + timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now
}  // end of TIMER1_CAPT_vect

void prepareForInterrupts ()
{
  noInterrupts ();  // protected code
  first = true;
  triggered = false;  // re-arm for next time
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;

  TIFR1 = bit (ICF1) | bit (TOV1);  // clear flags so we don't get a bogus interrupt
  TCNT1 = 0;          // Counter to zero
  overflowCount = 0;  // Therefore no overflows yet

  // Timer 1 - counts clock pulses
  TIMSK1 = bit (TOIE1) | bit (ICIE1);   // interrupt on Timer 1 overflow and input capture
  // start Timer 1, no prescaler
  TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select (rising on D8)
  interrupts ();
}  // end of prepareForInterrupts


void setup ()
{
  Serial.begin(115200);
  Serial.println("Frequency Counter");

  pinMode(8, INPUT_PULLUP);
  pinMode(7, OUTPUT);
  digitalWrite(7, LOW);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);


  // set up for interrupts
  prepareForInterrupts ();
} // end of setup

void loop ()
{
  static unsigned long average = 0;
  static int n = 0;
  // wait till we have a reading
  if (!triggered)
    return;

  PINB |= (1 << 5); //blink LED

  // period is clock cycles in one second
  unsigned long elapsedTime = finishTime - startTime;

  Serial.println (elapsedTime);
  average += elapsedTime;
  n++;
  if (n == 10) {
    Serial.print("System clock count, average of ten: ");
    Serial.println(average / 10);
    n = 0;
    average = 0;
  }


  // so we can read it
  delay (500);

  prepareForInterrupts ();
}   // end of loop

Yet another option: buy an Arduino Uno clone with a more accurate crystal. Many of the $4 Pro Mini clones on eBay have a 16 MHz crystal, not a resonator.

The Arduino Uno's 16MHz crystal is for the USB serial conversion. The uC itself is using a 5000ppm ceramic resonator.

1 Like

Yes, sorry, I forgot to mention, I will use some Chinese pro mini thing. It already has quartz crystal of unknown quality.
I think you can measure the frequency by just making a clock by using regular millis function and then checking the drift after 24 hours.

Then I could software calibrate it. This should make quite accurate clock subject to temperature coefficient of the quartz (can't find any data on this).

Sure, that would be another approach, taking days rather than minutes using the GPS as a time base accurate to around a nanosecond or better.

To determine the temperature dependence, put the setup in a temperature controlled environment and test away.

Hello miegapele

Look here and read the ‘Notes and warnings’ section for more details:

https://docs.arduino.cc/language-reference/en/functions/time/millis/

Yes, I need to care for overflow and not use timer0. Ut it's easy to do. For example like this

int timeMillis =0;
int timeSeconds=0;
int timeMinutes=0;
int timeHours =0;

while (true){
    currentTime =millis();
    timediff = currentTime - previousTime;
    previousTime = currentTime;

    timeMillis+= timediff;
    if (timeMillis >=1000){
        timeSeconds += timeMillis/1000;
        timeMillis %= 1000;
     }
     /// Same for minutes and so on...
}

What is the part number of the crystal?
If the manufacturer does not supply temperature data then it is probably very very poor.

All the data sheets for 16Mhz crystals I can find has only initial tolerance and frequency stability as ppm over all temperature range. Fancy ones even has one number at all like here https://ecsxtal.com/store/pdf/ecs-2325-2333.pdf

I can't find any temperature graphs similar to those for 32Khz crystals

See second sentence of post #6.

You won't.
If you need a temperature stabilized xtal then buy a TCXO.

That's rather odd reasoning. Then why these do exists for 32khz?

Cause 32768 happens to be a power of 2. So with a series of counters you can derive a 1 sec pulse...
Very handy for watches...
So these crystals need to be very accurate and reasonably cheap as they are mass produced.
There are almost 100000 seconds in a day, so even with an accuracy of 0.001% you are still off by a second per day... after 2 months it will be a full minute... after a year 12 minutes...

Timer2 is designed to be able to have a 32 kHz watch crystal hoked up to it. The ATmega328P even has a special low power mode (I think it's called Power Save) that turns off the main oscillator but leaves Timer2 running. If you need RTC accuracy you can use that in place of a full RTC module.

Data on Crystals is out there , giving all the data you need, for example

They are 32khz for easy division. But also because less frequency means less power.
Accuracy is probably better on higher frequencies, that's why I think bulova made watch with 216khz crystal.
Ant tempco is bad see for example this datasheet https://www.mouser.com/datasheet/2/137/FC3215AN_en-2952664.pdf
It's more than 100ppm over 0-75C which is worse than typical 16Mhz crystal

Did you read my first post?. You show the datasheet with 50ppm tempco over temperature, which I wrote I can find. And I do not see any temperature graphs as for example for this 32khz crystal. https://www.mouser.com/datasheet/2/137/FC3215AN_en-2952664.pdf

That is simply because neither the manufacturers nor typical large volume customers consider that information to be important.

1 Like