Go Down

Topic: Looking for a better frequency counter library (Read 5317 times) previous topic - next topic


I'm looking for a more advanced frequency counter library, something that doesn't need to halt the program while reading the frequency input.  Thanks!


I've done a couple of frequency counters here:


They aren't exactly libraries, but the ideas there should get you started. You can be doing other stuff while the frequency is counted as the counting is done by the hardware counters/timers.
Please post technical questions on the forum, not by personal message. Thanks!

More info:


have you checked this one - http://interface.khm.de/index.php/lab/experiments/arduino-frequency-counter-library/ -

BTW, can you define more details about your needs?

Is it a freq counter of dicrete events (0..5V) or determine the frequency of an analog signal?
What is the working range ? 
1 - 1000 Hz
1 - 1.000.000 Hz

What is the duty cycle of the pulses?
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)


Basically, I'm working on an environmental controller system.  The humidity sensor is one of those capacitive types that puts out a square wave, I think from 5-10 KHz, with the signal around 3v, 50% duty cycle.  Right now my program is set up to poll sensors and user inputs rapidly, but there's nothing more frustrating than pressing a button and waiting for a second for a cursor to move, for example.  I want the system to poll each sensor once a second and the user inputs at several times that rate, but at the same time, I'd like the program to be constantly reading the frequency of the humidity sensor so that a reading is Just Available when I want it. 

Right now I've got it hooked up to a cheap DS1307-based RTC, so I do have a 1Hz square wave output to work with, and I'm thinking of upgrading to a more precise RTC with a 32KHz output, if that helps.  I can generally get what I'm working on working, eventually, as far as programming goes, but I've got no experience with working close to the bare metal of an ATMega.

I do have flip-flops and and some other basic logic laying about, so if worst comes to worst I guess I could offload most of the frequency counting on hardware.

Coding Badly




I don't know if this will help or not but I wrote this after reading Nick Gammon's articles on counter and interrupts.

In this, I used the 'micros()' function.  This is not very accurate.  It is not as accurate as dedicating a timer to the purpose.  However it is much more simple.  In an effort to compensate for it lack of accuracy I adjust the sample size.  Sort of an auto-scaling.

It is rather simple.  It uses all standard Arduino library functions.  I did use version 1.01 RC2 for my IDE so the code may not compile with older IDEs.  I did comment most of the code, but let me know if you have any question.


Let me post it to save people the trouble of downloading:

Code: [Select]
/* The LCD is usually interfaced via 16 pins which are labelled as shown below:
    LCD Pin
    1. GND - Ground
    2. VDD - 3 - 5V
    3. VO  - Contrast
    4. RS  - Register Select - Command (0) or Character (1)
    5. RW  - Read/Write - Write (0) or Read (1)
    6. E   - Enable - Enable data transmit (0)
    7. DB0 - Data Bit 0
    8. DB1 - Data Bit 1
    9. DB2 - Data Bit 2
   10. DB3 - Data Bit 3
   11. DB4 - Data Bit 4 - used in 4 bit operation
   12. DB5 - Data Bit 5 - used in 4 bit operation
   13. DB6 - Data Bit 6 - used in 4 bit operation
   14. DB7 - Data Bit 7 - used in 4 bit operation
   15. BL1 - Backlight +
   16. BL2 - Backlight -
                         //Connections to Arduino
                         //LCD        Ardunino
                         // 1. GND    N/A
                         // 2. VDD    N/A
                         // 3. VO     N/A  (Tap off a 5K - 10K pot across VCC and Ground)
#define LCD_RS       12  // 4. RS     D12
                         // 5. RW     GND
#define LCD_ENABLE   11  // 6. E      D11
                         // 7. DB0    None
                         // 8. DB1    None
                         // 9. DB2    None
                         //10. DB3    None
#define LCD_DB4       4  //11. DB4    D4
#define LCD_DB5       6  //12. DB5    D6
#define LCD_DB6       7  //13. DB6    D7
#define LCD_DB7       8  //14. DB7    D8
#define LCD_Backlight 9  //15. BL1    Emitter of 2N3904, Collector to VCC, Base to D9 via 10K resistor
                         //16. BL2    GND

#include <Wire.h>
#include <Time.h>
#include <DS1307.h>
#include <LiquidCrystal.h>

LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_DB4, LCD_DB5, LCD_DB6, LCD_DB7);

#define SAMPLE_SIZE  1
static  uint16_t iSample_Size = SAMPLE_SIZE;
volatile uint32_t iSample;
volatile char bSamplesCtl = -1; // -1= Stop, not taking samples  0=Start taking samples  1=Sample are ready

void setup () {

  lcd.begin(16, 2);
  pinMode(LCD_Backlight, OUTPUT); analogWrite(LCD_Backlight, 128); // Set the brightness of the backlight using PWM
  pinMode(2, INPUT_PULLUP);

  attachInterrupt(0, Pin2_ISR, FALLING);

//DS1307::SetSquaresWave(SQW_4kHz);   // 4.096 kHz =  4096 Hz
//DS1307::SetSquaresWave(SQW_8kHz);   // 8.192 kHz =  8192 Hz
  DS1307::SetSquaresWave(SQW_32kHz);  //32.768 kHz = 32768 Hz
  bSamplesCtl = 0; // Start taking timing samples

void loop() {

  lcd.setCursor(0, 1);  lcd.print("                ");
  lcd.setCursor(0, 1);

  if (bSamplesCtl != 1) { // Sampling not complete
  } else {                // we have a set of samples
    uint32_t iFreq;
    if (iSample_Size <= 4000) {
      iFreq = (1000000L * iSample_Size) / iSample;
    } else {
      iFreq = 1000000L / (iSample / iSample_Size);
    lcd.print(iFreq); lcd.print("Hz "); lcd.print(iSample); lcd.print(" "); lcd.print(iSample_Size);
    if (iSample < 1000) { // Less than one millisecond worth of samples
      iSample_Size *= 2; // double the samples
      if (iSample_Size > 4000) iSample_Size = 4000;
    bSamplesCtl = 0; // Take another samples

void DisplayDateTime(void)
  char szBuffer[16];
  strftime(szBuffer, sizeof(szBuffer), "%m/%d %T", DS1307::getTime());
  lcd.setCursor(0, 0);

void Pin2_ISR()
  static uint32_t iPrev = 0;
  static uint16_t iSampleCnt = 0;
  uint32_t iTemp = micros();

  if (bSamplesCtl == -1) return; // -1= Stop, not taking samples
  if (bSamplesCtl == 1)  return; //  1= Sample ready for use.
                                 //  0= Taking samples
  if (iSampleCnt == 0)
    iPrev = iTemp;

  if (iSampleCnt > iSample_Size) { // Sampling complete
    iSample = iTemp-iPrev;        // Save results
    iPrev = 0; iSampleCnt = 0;    // Clean-up ready for next use.
    bSamplesCtl = 1;           //
Please post technical questions on the forum, not by personal message. Thanks!

More info:


I am impressed by your writings and if you have the time I would like to hear your criticisms of my code.
Thank you


The first thing I usually do is hit the auto-format button, because I am not a great fan of multiple instructions on one line, eg.

Code: [Select]
    lcd.print(iFreq); lcd.print("Hz "); lcd.print(iSample); lcd.print(" "); lcd.print(iSample_Size);


Code: [Select]
      if (iSample_Size > 4000) iSample_Size = 4000;

Apart from those minor points the code looks fine to me.

The question is, does it work to your satisfaction? I don't have that LCD so I can't actually test it in operation.

I'm not sure you need to use the RTC to do the timings, after all the Arduino has built-in timers.
Please post technical questions on the forum, not by personal message. Thanks!

More info:


I already had everything on a breadboard.  I was working with clock and scheduler code.  I was looking at time drift and such.  When I needed a frequency source I used the DS1307.  It gave me a chance to check that portion of the library as well as gave me something to measure.
In another thread, I was wondering if you could count the pulses from a DS1307 while the processor was asleep.  I tried every way I could think of to do just that.  It seems that the only counter that will work when asleep is counter 2 and it does not have an external source.
I could get counter 1 to count pulses but only when awake.  But back to this thread, the RTC is used as a frequency source and not a time base.  The 16MHz clock is used as the time base.
As far as working to my satisfaction,  it seems to.  I haven't need to use a frequency counter but I was inspired by your writings to try and duplicate your success.  I was seeing some strange reading from your code every now and then and I think it was happening when there was an overflow interrupt while reading the overflow counter.  In your code, at the beginning of the interrupt handler, the current counter value is saved to local variable.  Several instructions later the overflow counter is shifted 16 bits and the counter is added to it.  This gives the number of 65ns pulsed in the period.  The problem is that in that time, if there is an overflow, the count is off by about 64K.  This gives strange results. 
Granted, it doesn't happen often, but I did see it a time or two.  I loaded your code and fed the 32KHz signal into the Nano.  There was the occasional burp.  I assumed it was due to an overflow while doing the calculation.  There was also the problem of the overflow counter wrapping while sampling.  Again, very rare.  I have the same problem in my code when the micro() counter overflows.

Thank you for providing so much educational material.


Granted, it doesn't happen often, but I did see it a time or two. 

How often? I'm testing with 5 MHz and haven't seen it over a couple of minutes.
Please post technical questions on the forum, not by personal message. Thanks!

More info:


Apr 25, 2012, 05:34 am Last Edit: Apr 25, 2012, 05:39 am by Nick Gammon Reason: 1
Thanks for the comments. I think this will be more accurate:

Code: [Select]
// Timer and Counter example
// Author: Nick Gammon
// Date: 17th January 2012

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

// internal to counting routine
unsigned long overflowCount;
unsigned int timerTicks;
unsigned int timerPeriod;

void startCounting (unsigned int ms)

 counterReady = false;         // time not up yet
 timerPeriod = ms;             // how many 1 mS counts to do
 timerTicks = 0;               // reset interrupt counter
 overflowCount = 0;            // no overflows yet

 // reset Timer 1 and Timer 2
 TCCR1A = 0;            
 TCCR1B = 0;              
 TCCR2A = 0;
 TCCR2B = 0;

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

 // Timer 2 - gives us our 1 mS counting interval
 // 16 MHz clock (62.5 nS per tick) - prescaled by 128
 //  counter increments every 8 uS.
 // So we count 125 of them, giving exactly 1000 uS (1 mS)
 TCCR2A = _BV (WGM21) ;   // CTC mode
 OCR2A  = 124;            // count up to 125  (zero relative!!!!)

 // Timer 2 - interrupt on match (ie. every 1 mS)
 TIMSK2 = _BV (OCIE2A);   // enable Timer2 Interrupt

 TCNT1 = 0;      // Both counters to zero
 TCNT2 = 0;    

 // Reset prescalers
 GTCCR = _BV (PSRASY);        // reset prescaler now
 // start Timer 2
 TCCR2B =  _BV (CS20) | _BV (CS22) ;  // prescaler of 128
 // start Timer 1
 // External clock source on T1 pin (D5). Clock on rising edge.
 TCCR1B =  _BV (CS10) | _BV (CS11) | _BV (CS12);

}  // end of startCounting

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

//  Timer2 Interrupt Service is invoked by hardware Timer 2 every 1ms = 1000 Hz
//  16Mhz / 128 / 125 = 1000 Hz

 // grab counter value before it changes any more
 unsigned int timer1CounterValue;
 timer1CounterValue = TCNT1;  // see datasheet, page 117 (accessing 16-bit registers)

 // see if we have reached timing period
 if (++timerTicks < timerPeriod)
   return;  // not yet

  // if just missed an overflow
  if (TIFR1 & TOV1)
 // end of gate time, measurement ready

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

 TCCR2A = 0;    // stop timer 2
 TCCR2B = 0;    

 TIMSK1 = 0;    // disable Timer1 Interrupt
 TIMSK2 = 0;    // disable Timer2 Interrupt

 // calculate total count
 timerCounts = (overflowCount << 16) + timer1CounterValue;  // each overflow is 65536 more
 counterReady = true;              // set global flag for end count period
}  // end of TIMER2_COMPA_vect

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

} // end of setup

void loop () {

 // stop Timer 0 interrupts from throwing the count out
 byte oldTCCR0A = TCCR0A;
 byte oldTCCR0B = TCCR0B;
 TCCR0A = 0;    // stop timer 0
 TCCR0B = 0;    

 startCounting (500);  // how many mS to count for

 while (!counterReady)
    { }  // loop until count over

 // adjust counts by counting interval to give frequency in Hz
 float frq = (timerCounts *  1000.0) / timerPeriod;

 Serial.print ("Frequency: ");
 Serial.println ((unsigned long) frq);
 // restart timer 0
 TCCR0A = oldTCCR0A;
 TCCR0B = oldTCCR0B;
 // let serial stuff finish

}   // end of loop

I've allowed for the overflow when doing the final count, and also turned off Timer 0. That seems to me to be delivering a more consistent count each time.
Please post technical questions on the forum, not by personal message. Thanks!

More info:


But isn't timer0 is used for delay() and millis() and other Arduino functions.  I am hesitant to use the timers directly because they are used for other Arduino functions like tone() and analogWrite(). 

I am still not sure which functions I loose with I take over a timer/counter.


I did not look as closely at the "Timer and Counter" example as I did at the "Frequency timer" example.

I made a couple very small changes.  For one thing I printed the start and ending times.
When I run that I get the following:

Frequency Counter
Took: 489 counts.   Frequency: 32719 Hz.    Finish: 811   Start: 322
Took: 486 counts.   Frequency: 32921 Hz.    Finish: 1213057881   Start: 1213057395
Took: 486 counts.   Frequency: 32921 Hz.    Finish: 1221092806   Start: 1221092320
Took: 4294902248 counts.   Frequency: 0 Hz.    Finish: 1229062193   Start: 1229127241
Took: 486 counts.   Frequency: 32921 Hz.    Finish: 1237165583   Start: 1237165097
Took: 488 counts.   Frequency: 32786 Hz.    Finish: 1245200505   Start: 1245200017

It appears that the counter is wrapping.  I addressed that by clearing the counter in the "prepareForInterrupts" function.

Another change a made was in the calculation of the frequency.  I avoid, as much as possible, floating point numbers.  There is conversion overhead and lots of rounding.  Of course with integers there can be a lot of truncation, so care must be taken.
Code: [Select]

   uint32_t iElapsedTime = iFinishTime-iStartTime;
   uint32_t iFreq = F_CPU / iElapsedTime;     // F_CPU = CPU Frequency (i.e. 1600000 for 16 MHz)
   lcd.print(iFreq);  lcd.print(" Hz.   ");

I also used the define so the code works for 16 and 20 Mhz.

When trying to get the maximum accuracy I ran the clock without a prescaler.  Since I was measuring the length of the period I wanted to have the time measurement a good as possible.

Again to improve accuracy I increased the sample size.  To avoid truncation errors I multiplied the CPU frequency rather than divide the elapsed time.  So it came out something like
Code: [Select]

   uint32_t iElapsedTime = iFinishTime-iStartTime;
   uint32_t iFreq = (F_CPU * iSampleSize) / iElapsedTime;     // F_CPU = CPU Frequency (i.e. 1600000 for 16 MHz)
   lcd.print(iFreq);  lcd.print(" Hz.   ");

I needed to limit the sample size so as to not overflow the 32 bit integer.
I could then play with the sample size.  I wanted a large sample without taking too much time so after taking a sample I could look at the elapsed time and if it wasn't too long I could increase my samples.

As I have been writing this I have been monitoring a run and have not seen an example of a time problem, so it must be very rare.

The measuring of time period is probably best suited for lower frequencies.

Go Up