Pages: [1] 2   Go Down
Author Topic: Looking for a better frequency counter library  (Read 2737 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 25
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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!
Logged

Global Moderator
Online Online
Brattain Member
*****
Karma: 506
Posts: 19121
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I've done a couple of frequency counters here:

http://www.gammon.com.au/forum/?id=11504

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.
Logged


Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 224
Posts: 13921
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


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?
Logged

Rob Tillaart

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

0
Offline Offline
Newbie
*
Karma: 0
Posts: 25
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Global Moderator
Dallas
Online Online
Shannon Member
*****
Karma: 210
Posts: 13041
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


http://www.pjrc.com/teensy/td_libs_FreqMeasure.html
http://www.pjrc.com/teensy/td_libs_FreqCount.html
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 25
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks!  These look very handy.
Logged

Offline Offline
Sr. Member
****
Karma: 1
Posts: 322
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

* Simple_Freq_Counter.zip (1.57 KB - downloaded 15 times.)
Logged

Global Moderator
Online Online
Brattain Member
*****
Karma: 506
Posts: 19121
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Code:
/* 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 () {
  Wire.begin();

  lcd.begin(16, 2);
  lcd.clear();
  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_1Hz);
//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() {
  DisplayDateTime();
  delay(500);

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

  if (bSamplesCtl != 1) { // Sampling not complete
    lcd.print('*');
  } 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);
  lcd.print(szBuffer);
}

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;

  iSampleCnt++;
  if (iSampleCnt > iSample_Size) { // Sampling complete
    iSample = iTemp-iPrev;        // Save results
    iPrev = 0; iSampleCnt = 0;    // Clean-up ready for next use.
    bSamplesCtl = 1;           //
  }
}
Logged


Offline Offline
Sr. Member
****
Karma: 1
Posts: 322
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Global Moderator
Online Online
Brattain Member
*****
Karma: 506
Posts: 19121
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
    lcd.print(iFreq); lcd.print("Hz "); lcd.print(iSample); lcd.print(" "); lcd.print(iSample_Size);

or:

Code:
      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.
Logged


Offline Offline
Sr. Member
****
Karma: 1
Posts: 322
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Global Moderator
Online Online
Brattain Member
*****
Karma: 506
Posts: 19121
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


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.
Logged


Global Moderator
Online Online
Brattain Member
*****
Karma: 506
Posts: 19121
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for the comments. I think this will be more accurate:

Code:
// 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

ISR (TIMER1_OVF_vect)
{
  ++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

ISR (TIMER2_COMPA_vect)
{
  // 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)
    overflowCount++;
   
  // 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.begin(115200);      
  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
  delay(200);

}   // 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.
« Last Edit: April 24, 2012, 10:39:27 pm by Nick Gammon » Logged


Offline Offline
Sr. Member
****
Karma: 1
Posts: 322
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Offline Offline
Sr. Member
****
Karma: 1
Posts: 322
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
Quote
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:
   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:
   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.
Logged

Pages: [1] 2   Go Up
Jump to: