Frequency Counter Library

On the Arduino Playground :: Code Library and Tutorials :: we published a Frequency Counter Library. Would be nice if somebody who is familiar with interrupthandling within libraries have a look over it.

http://interface.khm.de/index.php/labor/experimente/arduino-frequency-counter-library/

regards
martin

Hi Kyle,
you are right, the overflow has to be checked twice in the interrupt. But how can be there glitches at 2 and 4 Mhz ? When i checked for glitches i was looking for the more suspicious frequencies at multiple of 65636 Hz but couldn't find any mayor deviations. maybe it has to do something with the timer1 input circuit. The signal is sampled with the system clock at 16 Mhz. When the duty cycle of the input signal is too small, pulses are dropped.

To minimize any latencies in the timer2 interrupt i would not put the code for checkOverflow() in a function . All the push's, pop's and stack operations in a function call are costing to much time in my opinion. In an other project based on the Bascom compiler i did the interrupt code for further optimisation in inline assembler but doing this in c is rather difficult ( for me). Maybe there is someone in the Forum to show us how that works.

martin

I forgot to indicate: I was testing at 30 ms refresh rate. (30 / (1000 ms)) * 65536 = 1.96 MHz (then again at 3.93 MHz).

Multiples of 65536 Hz would cause the same thing to happen if you are sampling at 1000 ms intervals, though.

For optimization, you're totally right about not using the function call. I just used that for making sure it worked correctly. A couple push/pops every 2 ms isn't too bad, but I suppose it depends on the application :slight_smile:

Hi Kyle,
so is it running smooth now after you made these changes ?
I would like to update the lib .

Hi again,
there is still something what i observed and forgot to say. If you make a Serial.print and afterwards start the counter you get faulty values. This is because the Serial.print emptys the transmit buffer interrupt driven and slows down the timer2 process. To overcome this effect put a delay after the print to allow all characters to be send before you start the counter.
regards
martin

Yes, it's working well for me now. I've modified mine to be "PulseCounter" rather than "FreqCounter", as I'm more curious about how many pulses occur over a short period of time, and not very worried about compensating with delays, etc. (If you're doing capacitive sensing and normalizing the output, the exact frequency isn't really necessary.)

I don't see why there would be an issue with print() slowing down TCNT2? As far as I can tell, print() executes in a tight loop/is blocking. Therefore, loop() isn't called again until print() is done executing.

Maybe you're thinking about the serial RX, which does have an interrupt? But I don't know why it would slow down TCNT2...

I've downloaded this? freqcounterlibrary.zip

copied and entered this code into my Arduino - 0012

#include <FreqCounter.h>

void setup() {
  Serial.begin(57600);                    // connect to the serial port
  Serial.println("Frequency Counter");
}

long int frq;
Void loop() {

 FreqCounter::f_comp= 8;             // Set compensation to 12
 FreqCounter::start(100);            // Start counting with gatetime of 100ms
 while (FreqCounter::f_ready == 0)         // wait until counter ready
 
 frq=FreqCounter::f_freq;            // read result
 Serial.println(frq);                // print result
 delay(20);
}

When I attempt to verify the "frequency counter" code I get :
error: FreqCounter.h: no such file or directory

I've downloaded and unzipped freqcounterlibrary.zip into :Arduino-12 > hardware > libraries. Where it is now quietly residing, but arduino-alpha doesn't seem to know that.

Posted by: Kyle McDonald Posted on: 06.02.2009 at 22:12:18
Yes, it's working well for me now. I've modified mine to be "PulseCounter" rather than "FreqCounter", as I'm more curious about how many pulses occur over a short period of time, and not very worried about compensating with delays, etc. (If you're doing capacitive sensing and normalizing the output, the exact frequency isn't really necessary.)

I would also like to use this or similar code as a pulse counter. Will this code do the trick? Or is there more to it than that?

Pakrat

I've also downloaded and extracted:
freqcounterlib_example.zip
After attempting to verify I get:25: error: FreqCounter.h: no such file or directory In function 'void loop()';

I'm guessing your #include <...> needs to be #include "...". I've never heard an explanation as to carets vs quotes, but find that substituting for quotes generally fixes things.

Here's PulseCounter.h:

#ifndef PulseCounter_h
#define PulseCounter_h

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#include <avr/interrupt.h>
#include <WProgram.h>

namespace PulseCounter {
  extern unsigned long pulses;
  extern volatile boolean ready;
  extern volatile unsigned long overflows;
  extern volatile unsigned int refreshCount;
  extern volatile unsigned int refreshTime;
  void start(int ms);      
  void checkOverflow();
}

#endif

And PulseCounter.cpp:

#include "PulseCounter.h"

unsigned long PulseCounter::pulses;
volatile boolean PulseCounter::ready;
volatile unsigned long PulseCounter::overflows;
volatile unsigned int PulseCounter::refreshCount;
volatile unsigned int PulseCounter::refreshTime;

void PulseCounter::start(int ms) {

#if defined (__AVR_ATmega168__) 
  refreshTime = ms / 2;
  
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  sbi (TCCR1B, CS10);
  sbi (TCCR1B, CS11); 
  sbi (TCCR1B, CS12);
  
  TCCR2A = 0;
  TCCR2B = 0;
  cbi (TCCR2B, CS20);
  sbi (TCCR2B, CS21);
  sbi (TCCR2B, CS22);

  cbi (TCCR2A, WGM20);  
  sbi (TCCR2A, WGM21);
  cbi (TCCR2B, WGM22);
  OCR2A = 124; // 16 MHz / 256 / 125 = 500 Hz

  ready = false;
  overflows = 0;
  refreshCount = 0;
  sbi (GTCCR, PSRASY);
  TCNT2 = 0;
  TCNT1 = 0;

  cbi (TIMSK0, TOIE0);
  sbi (TIMSK2, OCIE2A);

  TCCR1B = TCCR1B | 7;

#endif
}

void PulseCounter::checkOverflow() {  
  if (TIFR1 & 1) {
    PulseCounter::overflows++;
    sbi(TIFR1, TOV1);
  }
}

ISR(TIMER2_COMPA_vect) {
  if (PulseCounter::refreshCount >= PulseCounter::refreshTime) {               
    TCCR1B = TCCR1B & ~7;
    cbi (TIMSK2, OCIE2A);
    sbi (TIMSK0, TOIE0);
    PulseCounter::checkOverflow();
    PulseCounter::pulses = (PulseCounter::overflows << 16) | TCNT1;
    PulseCounter::ready = true;
  }
  PulseCounter::checkOverflow();
  PulseCounter::refreshCount++;
}

Something like a "paraphrase" of the original FrequencyCounter library.

Here's a new version of the PulseCounter code. Instead of using the overflow flag, it uses the overflow interrupt to keep track of overflows, allowing slower sample rates at high frequencies.

Also, multiple sbi/cbi calls are collapsed into single assigns. I don't know if the compiler optimizes those things away anyway or not, but hey.

The model here is different, also. Instead of waiting for the counter to be ready, it runs asynchronously from the loop(). When you say PulseCounter::start(), you pass a function that is called whenever a pulse count is reported. Instead of doing one-shot pulse counting, it runs until you tell it to stop (e.g.: saying PulseCounter::stop() at the end of your function that reads the pulse count values).

PulseCounter.h:

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#include <avr/interrupt.h>
#include <WProgram.h>

#define enableExternalCounter1 TCCR1B = B111;
#define disableCounter1 TCCR1B &= ~B111;

#define enableTimer2Interrupts sbi(TIMSK2, OCIE2A)
#define disableTimer2Interrupts cbi(TIMSK2, OCIE2A)

namespace PulseCounter {
  void (*pulsesCounted)(unsigned long);
  volatile unsigned long overflows;
  volatile unsigned int refreshCount;
  volatile unsigned int refreshTime;
  void resetCounters() {
  #if defined (__AVR_ATmega168__)  
    TCCR1A = 0;
    
    TCCR2A = B10;
    TCCR2B = B110;
    
    OCR2A = 124; // 16 MHz / 256 / 125 = 500 Hz
  
    overflows = 0;
    refreshCount = 0;
    
    sbi (GTCCR, PSRASY);
    
    TCNT2 = 0;
    TCNT1 = 0;
  
    enableTimer2Interrupts;  
    enableExternalCounter1;
  #endif
  }
  void start(int ms, void (*pulsesCounted)(unsigned long)) {
    PulseCounter::pulsesCounted = pulsesCounted;
    refreshTime = ms / 2;
    cbi(TIMSK0, TOIE0); // disable timer 0 overflow interrupts
    sbi(TIMSK1, TOIE0); // enable timer 1 overflow interrupts
    resetCounters();
  }
  void stop() {
    sbi(TIMSK0, TOIE0); // re-enable timer 0 overlflow interrupts
  }
}

ISR(TIMER2_COMPA_vect) {
  if (PulseCounter::refreshCount >= PulseCounter::refreshTime) {
    disableTimer2Interrupts;               
    disableCounter1;
    unsigned long pulses = (PulseCounter::overflows << 16) | TCNT1;
    PulseCounter::pulsesCounted(pulses);
    PulseCounter::resetCounters();
  } else {
    PulseCounter::refreshCount++;
  }
}

ISR(TIMER1_OVF_vect) {
  PulseCounter::overflows++;
}

PulseCounterExample:

#include "PulseCounter.h"

void setup() {
  Serial.begin(115200);
  PulseCounter::start(30, &cycle);
}

void loop() {
}

void cycle(unsigned long pulses) {
  Serial.println(pulses);
}

@Kyle:

When in brackets(<>), an include file must be in the include paths, but not in the current directory. If you use quotes (""), it searches the current directory too.

Hope that helps :slight_smile:

Thanks Tom :slight_smile: No wonder that always "fixed" things!

Does the FreqCounter.h library work with the Arduino MEGA? I tried, and it compiles alright, but I'm not sure which pin to use. Any chance the controller pin that it required isn't connected to a header?

It should be pin pd6(T1) it looks like it is not connected to the header.
martin

The only external timer input pin connected on the Mega is T5. Here is a modifed version of the original library that should support the Mega using pin 47 as the input. I don't have my Mega board with me so its untested.

/*
  FreqCounter.h - 
  Using Counter1 for counting Frequency on T1 / PD5 / digitalPin 5 
  Uses Counter5 on the Mega digitalPin 47

  Using Timer2 for Gatetime generation

  Martin Nawrath KHM LAB3
  Kunsthochschule für Medien Köln
  Academy of Media Arts
  http://www.khm.de
  http://interface.khm.de/index.php/labor/experimente/
  
  History:
        Dec/08 - V0.0 
    May/19  modified by mem to support Mega usting T5 /PL2 on digitalPin 47


  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/



#include <FreqCounter.h>


unsigned long FreqCounter::f_freq;

volatile unsigned char FreqCounter::f_ready;
volatile unsigned char FreqCounter::f_mlt;
volatile unsigned int FreqCounter::f_tics;
volatile unsigned int FreqCounter::f_period;
volatile unsigned int FreqCounter::f_comp;

// 16 bit timer defines added by mem to enable redifining the timer used
#if defined(__AVR_ATmega1280__)
#define TCCRnA TCCR5A
#define TCCRnB TCCR5B
#define TCNTn  TCNT5
#elif defined (__AVR_ATmega168__) 
#define TCCRnA TCCR1A
#define TCCRnB TCCR15B
#define TCNTn  TCNT15
#endif
 

void FreqCounter::start(int ms) {

  f_period=ms/2;
  if (f_comp ==0) f_comp=1;
      
      // hardware counter setup ( refer atmega168.pdf chapter 16-bit counter1)
  TCCRnA=0;                 // reset timer/countern control register A
  TCCRnB=0;                    // reset timer/countern control register A
  TCNTn=0;                       // counter value = 0
  // set timer/counter1 hardware as counter , counts events on pin Tn ( arduino pin 5 on 168,47 on Mega )
  // normal mode, wgm10 .. wgm13 = 0
  sbi (TCCRnB ,CS10);       // External clock source on Tn pin. Clock on rising edge.
  sbi (TCCRnB ,CS11); 
  sbi (TCCRnB ,CS12);


  // timer2 setup / is used for frequency measurement gatetime generation
  // timer 2 presaler set to 256 / timer 2 clock = 16Mhz / 256 = 62500 Hz
  TCCR2A=0;
  TCCR2B=0;
  cbi (TCCR2B ,CS20);
  sbi (TCCR2B ,CS21);
  sbi (TCCR2B ,CS22);

  //set timer2 to CTC Mode
  cbi (TCCR2A ,WGM20);  
  sbi (TCCR2A ,WGM21);
  cbi (TCCR2B ,WGM22);
  OCR2A = 124;   


  f_ready=0;                      // reset period measure flag
  f_tics=0;                 // reset interrupt counter
  sbi (GTCCR,PSRASY);       // reset presacler counting
  TCNT2=0;                  // timer2=0
  TCNTn=0;                  // Countern = 0

  cbi (TIMSK0,TOIE0);       // disable Timer0  //disable  millis and delay
  sbi (TIMSK2,OCIE2A);      // enable Timer2 Interrupt

  TCCRnB = TCCRnB | 7;      //  Counter Clock source = pin Tn , start counting now

}



//******************************************************************
//  Timer2 Interrupt Service is invoked by hardware Timer2 every 2ms = 500 Hz
//  16Mhz / 256 / 125 = 500 Hz
//  here the gatetime generation for freq. measurement takes place: 

ISR(TIMER2_COMPA_vect) {
                                                            // multiple 2ms = gate time = 100 ms
if (FreqCounter::f_tics >= FreqCounter::f_period) {               
                                              // end of gate time, measurement ready

                                                               // GateCalibration Value, set to zero error with reference frequency counter
    delayMicroseconds(FreqCounter::f_comp); // 0.01=1/ 0.1=12 / 1=120 sec 
    TCCR1B = TCCR1B & ~7;                     // Gate Off  / Counter T1 stopped 
    cbi (TIMSK2,OCIE2A);                      // disable Timer2 Interrupt
    sbi (TIMSK0,TOIE0);                       // enable Timer0 again // millis and delay
    FreqCounter::f_ready=1;             // set global flag for end count period
    
                                              // calculate now frequeny value
    FreqCounter::f_freq=0x10000 * FreqCounter::f_mlt;  // mult #overflows by 65636
    FreqCounter::f_freq += TCNT1;            // add counter1 value
    FreqCounter::f_mlt=0;

  }
  FreqCounter::f_tics++;                  // count number of interrupt events
  if (TIFR1 & 1) {                            // if Timer/Counter 1 overflow flag
    FreqCounter::f_mlt++;               // count number of Counter1 overflows
    sbi(TIFR1,TOV1);                          // clear Timer/Counter 1 overflow flag
  }
  // PORTB = PORTB ^ 32;                          // int activity test
}

Thanks for the quick responses. mem, I've replaced the FreqCounter.cpp with the one you just posted above. I've tested it with a 1kHz and a 10kHz freqency but nothing is printed to the serial. The sketch I used to test it is the same that pakrat posted on Reply #6. Also, I have the 0015 compiler.

And just to give me peace of mind that my pin 47 wasn't faulty, I tested it with this:

volatile long count;
void setup()
{
  pinMode(47, OUTPUT);      // set digital pin 47 as output
  Serial.begin(57600);
  attachInterrupt(0,testPin,RISING);  // digital pin 2
}
void loop()
{
  digitalWrite(47, HIGH);      // set pin 47 high
  delay(50);                        // wait 50ms
  digitalWrite(47, LOW);       // set pin 47 low
  delay(50);                        // wait 50ms
  Serial.println(count);         // check if it works
}
void testPin(){count++;}

I missed some changes needed in the ISR, try this:

/*
  FreqCounter.h -
  Using Counter1 for counting Frequency on T1 / PD5 / digitalPin 5
  Uses Counter5 on the Mega digitalPin 47

  Using Timer2 for Gatetime generation

  Martin Nawrath KHM LAB3
  Kunsthochschule für Medien Köln
  Academy of Media Arts
  http://www.khm.de
  http://interface.khm.de/index.php/labor/experimente/
  
  History:
    Dec/08 - V0.0
    May/20  modified by mem to support Mega usting T5 /PL2 on digitalPin 47


  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/



#include <FreqCounter.h>


unsigned long FreqCounter::f_freq;

volatile unsigned char FreqCounter::f_ready;
volatile unsigned char FreqCounter::f_mlt;
volatile unsigned int FreqCounter::f_tics;
volatile unsigned int FreqCounter::f_period;
volatile unsigned int FreqCounter::f_comp;

// 16 bit timer defines added by mem to enable redifining the timer used
#if defined(__AVR_ATmega1280__)
#define TCCRnA TCCR5A
#define TCCRnB TCCR5B
#define TCNTn  TCNT5
#define TIFRn  TIFR5
#define TOVn   TOV5
#elif defined (__AVR_ATmega168__)
#define TCCRnA TCCR1A
#define TCCRnB TCCR1B
#define TCNTn  TCNT1
#define TIFRn  TIFR1
#define TOVn   TOV1
#endif


void FreqCounter::start(int ms) {

  f_period=ms/2;
  if (f_comp ==0) f_comp=1;

      // hardware counter setup ( refer atmega168.pdf chapter 16-bit counter1)
  TCCRnA=0;                 // reset timer/countern control register A
  TCCRnB=0;                    // reset timer/countern control register A
  TCNTn=0;                       // counter value = 0
  // set timer/counter1 hardware as counter , counts events on pin Tn ( arduino pin 5 on 168,47 on Mega )
  // normal mode, wgm10 .. wgm13 = 0
  sbi (TCCRnB ,CS10);       // External clock source on Tn pin. Clock on rising edge.
  sbi (TCCRnB ,CS11);
  sbi (TCCRnB ,CS12);


  // timer2 setup / is used for frequency measurement gatetime generation
  // timer 2 presaler set to 256 / timer 2 clock = 16Mhz / 256 = 62500 Hz
  TCCR2A=0;
  TCCR2B=0;
  cbi (TCCR2B ,CS20);
  sbi (TCCR2B ,CS21);
  sbi (TCCR2B ,CS22);

  //set timer2 to CTC Mode
  cbi (TCCR2A ,WGM20);  
  sbi (TCCR2A ,WGM21);
  cbi (TCCR2B ,WGM22);
  OCR2A = 124;  


  f_ready=0;                      // reset period measure flag
  f_tics=0;                 // reset interrupt counter
  sbi (GTCCR,PSRASY);       // reset presacler counting
  TCNT2=0;                  // timer2=0
  TCNTn=0;                  // Countern = 0

  cbi (TIMSK0,TOIE0);       // disable Timer0  //disable  millis and delay
  sbi (TIMSK2,OCIE2A);      // enable Timer2 Interrupt

  TCCRnB = TCCRnB | 7;      //  Counter Clock source = pin Tn , start counting now

}



//******************************************************************
//  Timer2 Interrupt Service is invoked by hardware Timer2 every 2ms = 500 Hz
//  16Mhz / 256 / 125 = 500 Hz
//  here the gatetime generation for freq. measurement takes place:

ISR(TIMER2_COMPA_vect) {
                                                            // multiple 2ms = gate time = 100 ms
if (FreqCounter::f_tics >= FreqCounter::f_period) {        
                                              // end of gate time, measurement ready

                                                               // GateCalibration Value, set to zero error with reference frequency counter
    delayMicroseconds(FreqCounter::f_comp); // 0.01=1/ 0.1=12 / 1=120 sec
    TCCRnB = TCCRnB & ~7;                     // Gate Off  / Counter Tn stopped
    cbi (TIMSK2,OCIE2A);                      // disable Timer2 Interrupt
    sbi (TIMSK0,TOIE0);                       // enable Timer0 again // millis and delay
    FreqCounter::f_ready=1;             // set global flag for end count period
    
                                              // calculate now frequeny value
    FreqCounter::f_freq=0x10000 * FreqCounter::f_mlt;  // mult #overflows by 65636
    FreqCounter::f_freq += TCNTn;            // add countern value
    FreqCounter::f_mlt=0;

  }
  FreqCounter::f_tics++;                  // count number of interrupt events
  if (TIFRn & 1) {                            // if Timer/Counter n overflow flag
    FreqCounter::f_mlt++;               // count number of Countern overflows
    sbi(TIFRn,TOVn);                          // clear Timer/Counter n overflow flag
  }
  // PORTB = PORTB ^ 32;                          // int activity test
}

Thanks mem! I've tried it from 1kHz to 1MHz and it seems to work just fine now.

A side note to any readers: If you have already downloaded the FreqCounter library from the website and compiled it, you have to delete the "O" file for it to recompile. I found that out the hard way...

The file is C:\Arduino\hardware\libraries\FreqCounter\FreqCounter.o

I'm looking to implement a hardware counter in my project. I am controlling a DC motor via PWM with Timer2. I would like to use a hardware counter to count pulses coming off of the encoder wheel. At the moment I'm using an ISR to manually count each pulse, but I'd rather this counting be done in the automatically in the background by the ATMega hardware. Is this possible?

I'm not a very sophisticated programmer and just managed to gain some basic understanding of the hardware timers and implementing PWM control while looking over the ATMega datasheet and other examples.

Any idea if I could adapt some of the ideas in the frequency counter library to accomplish my needs? I see that it appears to be using Timer2 already...

Thanks!

Dennis

I've taken a little closer look at the library and it appears to me that the usage of Timer2 is merely for frequency calculation. Thus I should be able to use the first part to set up just the counting (I don't need frequency measurement). Correct?

Now I just need to figure out the mechanics of implementing just the portion I need. I've never written nor re-written a library before. I'll start digging in but please let me know if you have any pointers.

Once I get this all figured out, I should have a very solid DC motor control system for folks to leverage. I've seen bits and pieces out there but not the whole package.

Thanks!

Dennis

You could use PulseCounter.h and remove the Timer2 ISR, instead reading and clearing TCNT1 and PulseCounter::overflows manually, asynchronously, now and then.