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.
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.
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
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...
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 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).
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.
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?
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...
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.
You could use PulseCounter.h and remove the Timer2 ISR, instead reading and clearing TCNT1 and PulseCounter::overflows manually, asynchronously, now and then.