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.
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?
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.
http://www.pjrc.com/teensy/td_libs_FreqMeasure.html
http://www.pjrc.com/teensy/td_libs_FreqCount.html
Thanks! These look very handy.
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)
Let me post it to save people the trouble of downloading:
/* 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; //
}
}
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
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.
lcd.print(iFreq); lcd.print("Hz "); lcd.print(iSample); lcd.print(" "); lcd.print(iSample_Size);
or:
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.
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.
RandallR:
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.
Thanks for the comments. I think this will be more accurate:
// 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.
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.
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
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.
Actually I missed it:
Took: 4294902248 counts. Frequency: 0 Hz. Finish: 1229062193 Start: 1229127241
The sample started after it finished:
1229062193 - Finished time
-1229127241 - Starting time
-65048 - Elapsed time
4294902248 - Unsigned 32 bit int.
RandallR:
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().
Yes it is, but if you know what you are trying to achieve you can choose whether you want the extra accuracy. For example:
// 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
// restart timer 0
TCCR0A = oldTCCR0A;
TCCR0B = oldTCCR0B;
Here we just turn off timer 0 during the frequency count. So the millis figure will be out by the duration of the counting. You may not care about that.
Again to improve accuracy I increased the sample size.
There is a trade-off between the two methods I mentioned on my web page. One measures the period (by measuring the time for exactly one pulse change) and the other measures the frequency (by counting).
The period one is better suited to lower frequencies, because you get a higher-resolution result. Plus it is fast, even for, say, 50 Hz. It would take 1/50th of a second to work out 50 Hz.
The frequency counter is better suited to higher frequencies, because you get a higher count. So for example, to measure 5 MHz you probably want to count, rather than measure the period. But for 50 Hz you probably want to measure the period, rather than count.