012anonymousxyz:
Yeah unfortunately I don't have access to an external accurate timer, although I could buy an external timer instead of a counter. Can you post the code that did this? I can't picture it. What I understand is, instead of using millis() you count the number of cycles for the frequency of interest during once cycle of a frequency you know very accurately?
I don't use millis(). I set up timer 1 to clock in normal mode with a prescaler of 1. That means it increments on every processor cycle, 62.5ns at 16MHz. Then I enable the input capture feature. It uses a specific pin on the processor. On an Uno it's digital pin 8 (pin 14 on the Atmega328 chip itself). When there is a pulse on the input capture pin the current timer count is latched into a register by hardware. There is some latency in this but for my purposes (I just want ppm accuracy) it isn't an issue.
Since the timer rolls over in 4096us (65536*62.5ns) and I'm timing pulses of low frequency I have to also count the rollovers. That's done with an interrupt routine. It's a little tricky managing that and frankly my code sometimes "burps" and displays the wrong number. But it's infrequent and I have no problem ignoring it.
Since the board has only one input capture pin, for the other pulse I'm measuring I simply poll the pin with direct port accesses and use the timer 1 count (and rollover count) to determine the period. It is a little sloppier than using input capture but it works well enough for what I want.
You can read about input capture and timers in general in various places. The datasheet for the Atmega328 is one place. Nick Gammon's page on timers is another; I highly recommend reading that page (and the others on his website).
I have several versions of code that do this, including some that output the results to an SPI connected display. Input capture does not interact with SPI.
Here's a serial output version I used to check the accuracy of a DS1307 RTC (it wasn't very accurate). The code isn't well documented, it may have bugs, I wrote it for myself. Maybe you'll find it useful.
edit: I just found a bug in my code -- I had the wrong sign for the RTC error.
// Check processor clock and DS1307 square wave accuracy against GPS
//
// **** DO NOT STACK THE GPS SHIELD ****
// **** DO NOT STACK THE GPS SHIELD ****
//
// Connect the GPS shield via 5V, GND.
// Connect the PPS signal to the input capture pin as defined below.
// Connect the SQW output of the DS1307 to the pin defined below.
#include <Wire.h>
#include <DS1307.h>
#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)
DS1307 rtc;
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 temp RTC SQW");
Serial.println("Clks/GPSsec error A/D calc clks/GPSsec error\n");
pinMode(SQW_INPUT_PIN, INPUT_PULLUP); // requires a pull-up
pinMode(PPS_INPUT_PIN, INPUT);
Wire.begin();
rtc.enable();
rtc.setSQW(DS1307_SQW_1HZ);
// Set up timer 1 for input capture of PPS signal
TCCR1A = 0; // reset timer1
TCCR1B = 0;
TCNT1 = 0;
TCCR1B = 0x41; // no prescale; capture on rising edge
TIMSK1 = 0x21; // 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 RTC square wave 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, 0);
Serial.print(" ppm, ");
uint16_t tempADC = readTempADC();
Serial.print(tempADC);
Serial.print(", ");
Serial.print((float(tempADC) - 125.0) * 0.1075, 1);
Serial.print(" C, ");
Serial.print(rtcElapsed);
Serial.print(" cks, ");
Serial.print(float(int32_t(elapsed) - int32_t(rtcElapsed))/(float(elapsed)/1000000.0), 0);
Serial.print(" ppm");
Serial.println();
}
}
}
// =========================================================================================
uint16_t readTempADC() {
// Read temperature sensor against 1.1V reference
#if defined(__AVR_ATmega32U4__)
ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0);
ADCSRB = _BV(MUX5); // the MUX5 bit is in the ADCSRB register
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(REFS1) | _BV(MUX5) | _BV(MUX1);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0);
#else
ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3);
#endif
delay(2); // Wait for ADMUX setting to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA,ADSC)); // measuring
return ADC;
}
// ---------------------------------------------------------------------------------------
// readVcc
// ---------------------------------------------------------------------------------------
long readVcc() {
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA,ADSC)); // measuring
int result = ADC;
result = 1125300L / long(result); // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
return result; // Vcc in millivolts
}
//------------------------------------------------------------------------------
I'd be curious to know about a good chip for doing this as well. My method is kind of clunky.