Using an external counter ic to determine frequency of square wave?

Hi. I have an Arduino and I tried to use the pulseIn() function (and digitalRead()) and >1000 averaging to get the frequency of a square wave but it is always off by <|0.5kHz| (confirmed using oscilloscope). I cannot get the FreqCount library to work.

Is it feasible to use a counter to time when [editted]4096 clock cycles (depending on the chip used) have occurred and determine the frequency that way? My rational is the arduino only adds two 'delays:' at the beginning during reset and end.

Extra Info:

I think the reason the arduino doesn't work is because of timing inaccuracies in micros() and delays between digitalRead()/pulseIn(). Thats why I thought to delegate this to an external chip.

The FreqCounter library does not work because I am using SPI and I think there are conflicting pins.

One thing to keep in mind is the accuracy of the processor clock. On a typical Arduino it is not very good, usually off by something on the order of a tenth of a percent.

jboyton:
One thing to keep in mind is the accuracy of the processor clock. On a typical Arduino it is not very good, usually off by something on the order of a tenth of a percent.

So I should really not use the arduino for timing measurements at all. Are there chips that spit out frequency via SPI? I looked around but couldn't find them myself.

The FreqCounter library does not work because I am using SPI and I think there are conflicting pins.

No, digital 5 - counter's input isn't part of SPI.
http://interface.khm.de/index.php/lab/interfaces-advanced/arduino-frequency-counter-library/
What is the board?

I've been using an Uno to make precise frequency measurements by coupling it with a GPS. The PPS output of the GPS is a 1Hz pulse with an accuracy of ~10ns. I route that to the input capture pin on the Uno and use the number of timer cycles to determine the current frequency of the processor clock. Then, at the same time, I can count cycles for some other pulse input and know what those cycles mean.

It's a bit convoluted but I didn't have to get out of my chair to do it. I'm sure there are chips that would do what you want.... someone will probably have a good answer for you.

Magician:
No, digital 5 - counter's input isn't part of SPI.
http://interface.khm.de/index.php/lab/interfaces-advanced/arduino-frequency-counter-library/
What is the board?

The documentation says pins 3,9,10, and 11 are unusable with analog write (ss, and mosi). Any other reasons it might not be working though? What happens is there is never any available data.

What is a board? Arduino Uno?

jboyton:
I've been using an Uno to make precise frequency measurements by coupling it with a GPS. The PPS output of the GPS is a 1Hz pulse with an accuracy of ~10ns. I route that to the input capture pin on the Uno and use the number of timer cycles to determine the current frequency of the processor clock. Then, at the same time, I can count cycles for some other pulse input and know what those cycles mean.

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?

But simple solutions are preferred. A chip would be fantastic.

The documentation says pins 3,9,10, and 11 are unusable with analog write (ss, and mosi).

10 and 11 is SPI. you can use 3 & 9.
AnalogWrite is different feature, based on OC - output compare. Freq. counting library works with IC - input capture, pin 5. I can't see how IC could interfere with SPI, except something else in your code,that you didn't post

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.