Roger, I don't know what the best solution is. Maybe a frequency counter is the way to go. How accurate is a typical frequency counter?
I don't have a counter or an oscilloscope, just an Uno and a GPS. I wanted to be able to measure the accuracy of real-time clocks as they varied with temperature so that I could write software to compensate for the error. I wrote a sketch to do this and it appears to be able to measure an interval to an accuracy of better than 1us.
Here's output from the sketch. The first value is the number of Uno processor clocks in one GPS second. The second number is the error in parts per million (or microseconds since it is a 1 second period) for the Uno resonator. The third value is the number of Uno clocks per RTC period. And the last value is the error in the RTC as compared to the GPS time. As you can see, the Uno runs about 0.1% slow. The RTC is much better, using a 32.768kHz crystal and carefully chosen capacitors. It is only about 3ppm slow at 24°C.
Hello
Resonator 1Hz pulse
cks/GPSsec error cks/GPSsec error
15982844 cks, -1072.3 ppm, 15982894 cks, -3.1 ppm
15982839 cks, -1072.6 ppm, 15982890 cks, -3.2 ppm
15982839 cks, -1072.6 ppm, 15982891 cks, -3.3 ppm
15982839 cks, -1072.6 ppm, 15982890 cks, -3.2 ppm
15982838 cks, -1072.6 ppm, 15982891 cks, -3.3 ppm
15982839 cks, -1072.6 ppm, 15982890 cks, -3.2 ppm
15982837 cks, -1072.7 ppm, 15982888 cks, -3.2 ppm
15982837 cks, -1072.7 ppm, 15982890 cks, -3.3 ppm
15982838 cks, -1072.6 ppm, 15982888 cks, -3.1 ppm
15982838 cks, -1072.6 ppm, 15982890 cks, -3.3 ppm
15982838 cks, -1072.6 ppm, 15982888 cks, -3.1 ppm
15982837 cks, -1072.7 ppm, 15982890 cks, -3.3 ppm
15982837 cks, -1072.7 ppm, 15982887 cks, -3.1 ppm
15982837 cks, -1072.7 ppm, 15982890 cks, -3.3 ppm
15982837 cks, -1072.7 ppm, 15982888 cks, -3.2 ppm
15982838 cks, -1072.6 ppm, 15982892 cks, -3.4 ppm
15982838 cks, -1072.6 ppm, 15982887 cks, -3.1 ppm
15982837 cks, -1072.7 ppm, 15982890 cks, -3.3 ppm
15982837 cks, -1072.7 ppm, 15982887 cks, -3.1 ppm
15982837 cks, -1072.7 ppm, 15982891 cks, -3.4 ppm
15982837 cks, -1072.7 ppm, 15982887 cks, -3.1 ppm
15982836 cks, -1072.8 ppm, 15982889 cks, -3.3 ppm
15982837 cks, -1072.7 ppm, 15982887 cks, -3.1 ppm
15982837 cks, -1072.7 ppm, 15982890 cks, -3.3 ppm
15982836 cks, -1072.8 ppm, 15982887 cks, -3.2 ppm
15982837 cks, -1072.7 ppm, 15982888 cks, -3.2 ppm
15982836 cks, -1072.8 ppm, 15982887 cks, -3.2 ppm
15982836 cks, -1072.8 ppm, 15982888 cks, -3.3 ppm
15982836 cks, -1072.8 ppm, 15982887 cks, -3.2 ppm
15982837 cks, -1072.7 ppm, 15982889 cks, -3.3 ppm
15982836 cks, -1072.8 ppm, 15982890 cks, -3.4 ppm
15982836 cks, -1072.8 ppm, 15982885 cks, -3.1 ppm
15982835 cks, -1072.8 ppm, 15982889 cks, -3.4 ppm
15982836 cks, -1072.8 ppm, 15982888 cks, -3.3 ppm
15982837 cks, -1072.7 ppm, 15982887 cks, -3.1 ppm
15982836 cks, -1072.8 ppm, 15982888 cks, -3.3 ppm
15982837 cks, -1072.7 ppm, 15982888 cks, -3.2 ppm
15982837 cks, -1072.7 ppm, 15982890 cks, -3.3 ppm
15982836 cks, -1072.8 ppm, 15982887 cks, -3.2 ppm
15982838 cks, -1072.6 ppm, 15982891 cks, -3.3 ppm
15982838 cks, -1072.6 ppm, 15982889 cks, -3.2 ppm
15982839 cks, -1072.6 ppm, 15982890 cks, -3.2 ppm
15982838 cks, -1072.6 ppm, 15982893 cks, -3.4 ppm
15982839 cks, -1072.6 ppm, 15982888 cks, -3.1 ppm
15982838 cks, -1072.6 ppm, 15982891 cks, -3.3 ppm
15982838 cks, -1072.6 ppm, 15982890 cks, -3.3 ppm
15982837 cks, -1072.7 ppm, 15982888 cks, -3.2 ppm
15982838 cks, -1072.6 ppm, 15982890 cks, -3.3 ppm
15982836 cks, -1072.8 ppm, 15982887 cks, -3.2 ppm
15982835 cks, -1072.8 ppm, 15982887 cks, -3.3 ppm
15982836 cks, -1072.8 ppm, 15982885 cks, -3.1 ppm
The sketch isn't perfect. Because it doesn't deal with timer overflows in a robust manner there is an occasional misreport. But since I use it to manually monitor/measure the error I don't care. It could be made more robust. It could also be made to measure over 2 seconds. Here it is, for what it's worth:
// Check processor clock and 1Hz pulse (from any source) accuracy against GPS
//
// Connect the GPS shield via 5V, GND.
// Connect the PPS signal to the input capture pin as defined below.
// Connect the 1Hz pulse output (don't forget a ground wire) to the pin defined below.
#define PPS_INPUT_PIN 8 // 328 pin 14 (input capture)
#define SQW_INPUT_PIN 2
// Pin operation macros
#define INPUT_PORT(pin) (pin < 8 ? PIND : (pin < 14 ? PINB : PINC))
#define OUTPUT_PORT(pin) (pin < 8 ? PORTD : (pin < 14 ? PORTB : PORTC))
#define PIN_MASK(pin) (pin < 8 ? 1<<pin : (pin < 14 ? 1<<pin-8 : 1<<pin-14))
#define SET_PIN(pin, level) (level == LOW ? (OUTPUT_PORT(pin) &= ~PIN_MASK(pin)) : (OUTPUT_PORT(pin) |= PIN_MASK(pin)))
#define PIN_IS_LOW(pin) ((INPUT_PORT(pin) & PIN_MASK(pin)) == 0)
#define PIN_IS_HIGH(pin) ((INPUT_PORT(pin) & PIN_MASK(pin)) != 0)
volatile uint16_t captureTicks = 0;
volatile uint16_t captureRolls = 0;
volatile uint16_t rollovers = 0;
volatile bool captureFlag = false;
uint32_t prevCaptureTime = 0;
bool first = true;
// =======================================================================================
ISR (TIMER1_CAPT_vect)
{
captureTicks = ICR1;
captureRolls = rollovers;
captureFlag = true;
}
ISR (TIMER1_OVF_vect)
{
rollovers++;
}
// =========================================================================================
void setup()
{
Serial.begin(115200);
Serial.println("\nHello\n");
Serial.println("Resonator 1Hz pulse");
Serial.println("cks/GPSsec error cks/GPSsec error\n");
pinMode(SQW_INPUT_PIN, INPUT_PULLUP); // requires a pull-up for DS1307
pinMode(PPS_INPUT_PIN, INPUT);
// Set up timer 1 for input capture of PPS signal
TCCR1A = 0; // reset timer1
TCCR1B = 0;
TCNT1 = 0;
TCCR1B = bit(ICES1) | bit(CS10); // prescale=1; capture on rising edge
TIMSK1 = bit(ICIE1) | bit(TOIE1); // enable capture interrupt; enable overflow interrupt
}
// =========================================================================================
uint8_t cnt = 0;
uint16_t prevRtcT0, prevRtcRolls;
void loop()
{
uint16_t mvTemp;
float vcc;
uint16_t rtcT0, rtcRolls;
uint32_t rtcElapsed;
if (PIN_IS_HIGH(SQW_INPUT_PIN)) { // if pulse output is high
while (PIN_IS_HIGH(SQW_INPUT_PIN)) {} // wait for it to go low
}
TIMSK0 = 0; // disable timer 0 overflow interrupts
while (PIN_IS_LOW(SQW_INPUT_PIN)) {} // now wait for it to go high
rtcT0 = TCNT1; // get a timestamp
rtcRolls = rollovers;
TIMSK0 = 1; // reenable timer 0 interrupts
rtcElapsed = uint32_t(int32_t(rtcT0) - int32_t(prevRtcT0) + (uint32_t(rtcRolls - prevRtcRolls) << 16));
prevRtcT0 = rtcT0;
prevRtcRolls = rtcRolls;
if (captureFlag) {
uint16_t t = captureTicks;
uint16_t r = captureRolls;
captureFlag = false;
uint32_t captureTime = (uint32_t(r) << 16) + uint32_t(t);
uint32_t elapsed = captureTime - prevCaptureTime;
prevCaptureTime = captureTime;
if (first) {
first = false; // discard first calculation since previous values were invalid
} else {
Serial.print(elapsed);
Serial.print(" cks, ");
Serial.print(float(elapsed)/16.0 - 1000000.0, 1);
Serial.print(" ppm, ");
Serial.print(rtcElapsed);
Serial.print(" cks, ");
Serial.print(float(int32_t(elapsed) - int32_t(rtcElapsed))/(float(elapsed)/1000000.0), 1);
Serial.print(" ppm");
Serial.println();
}
}
}
// =========================================================================================