Frequency counting with internal timers

Someone mentioned recently about a frequency counting technique using the internal timers on the Atmega328. Try as I might, I couldn't find the post relating to that, although this might have been it:


However, I wanted to understand exactly how it all worked, hence, my own attempts at a frequency counter:

This uses Timer 1 to count pulses, and Timer 2 to find exactly 1 mS intervals. So for example, you could count the number of pulses over 1000 mS (one second) to find the frequency in Hz.

The accuracy is pretty good, I am measuring 0.02% error with an input of 5 MHz.

For those frequencies, wouldn't input capture be a good option using only 1 timer?

You mean, with micros() or millis() ? That would certainly be an option too.

It was partly an educational exercise, to demonstrate measuring exact intervals as well as counting stuff.

I think though you would sacrifice some accuracy using the normal micros() or millis() timers. Since you don’t have access to the interrupts they generate (which in any case are not exactly at 1 mS intervals) you would have to loop waiting for time to be up, and the overhead of noticing that the time was up, and then stopping the clock and getting a reading, would, I think, introduce an inaccuracy.

Test here:

// these are checked for in the main program
volatile unsigned long timerCounts;

// internal to counting routine
unsigned long overflowCount;

  ++overflowCount;               // count number of Counter1 overflows  
}  // end of TIMER1_OVF_vect

void setup () {
  Serial.println("Frequency Counter");
} // end of setup

void loop () {
    // reset Timer 1
  TCCR1A = 0;             
  TCCR1B = 0;              

  // Timer 1 - counts events on pin D5
  TIMSK1 = _BV (TOIE1);   // interrupt on Timer 1 overflow

  TCNT1 = 0;      // counters to zero
  overflowCount = 0;

  // start Timer 1
  // External clock source on T1 pin (D5). Clock on rising edge.
  TCCR1B =  _BV (CS10) | _BV (CS11) | _BV (CS12);
  // time for a second 
  unsigned long start = micros ();
  while (micros () - start < 1000000UL)
    { }
  unsigned long finish = micros ();
  // end of gate time, measurement ready

  TCCR1A = 0;    // stop timer 1
  TCCR1B = 0;    

  unsigned int timer1CounterValue;
  timer1CounterValue = TCNT1;  // see datasheet, page 117 (accessing 16-bit registers)

  TIMSK1 = 0;    // disable Timer1 Interrupt
  timerCounts = (overflowCount << 16) + timer1CounterValue;  // each overflow is 65536 more
  // adjust counts by counting interval to give frequency in Hz
  float frq = (timerCounts *  1000000.0) / (finish - start);

  Serial.print ("Frequency: ");
  Serial.println ((unsigned long) frq);
  // let serial stuff finish

}   // end of loop

That only used one timer, and seemed to work OK, but with perhaps slightly less accuracy (0.2% error).

Have you seen Paul Stoffregen’s library…

Looks like my Google-Fu wasn't as good as it could have been. Thanks for the link.

But still, learning how to do it myself is always useful, because then I can adapt the concepts to other things. :slight_smile:

I doubt that was a problem. Several months ago I spent about 30 minutes each day for a week collecting information about frequency counting on Arduinos and never found that library. I stumbled across it by accident looking for some Teensy information.

Thanks for the link.

You are welcome. I hope you get something useful from it.

But still, learning how to do it myself is always useful, because then I can adapt the concepts to other things. :slight_smile:

I agree!