- 1. Arduino frequency counter intro
- 2. FreqPeriodCounter variables
- 3. Simple example
- 4. Counting multiple frequencies simultaneously
- 5. Examples with the frequency generated by the Arduino
- 6. Frequency in decimals
- 7. Frequency counter library
- 8. Multiple frequency inputs
- 9. Frequency counter unit test
- 10. High frequency measurement with the Arduino
1. Arduino frequency counter intro
Here is a frequency counter for the Arduino, it is used for my solar bike pedelec legalisation device, the e-bike Watt meter and the scale interface.
FreqPeriodCounter(byte pin, unsigned long (*timeFunctionPtr)(), unsigned debounceTime);
Download the FreqPeriodCounter library here. Unzip the library and place it into the Arduino library folder like this: \libraries\FreqPeriodCounter.
- The frequency counter can be used in two ways:
Interrupt triggered by the input signal.
Polled regularly in a loop. - The maximum frequency with polling is approximately 25kHz.
- The measurement can be done in milli seconds or micro seconds. The constructor has a function as argument, here we pass the required function: millis or micros.
- We can take a debounce time of about 10ms if the frequency comes from a mechanically switch.
2. FreqPeriodCounter variables
- ready() If an entire period is measured, ready is true
- elapsedTime Use this to detect if there is no frequency signal: (elapsedTime > timeOut)
- period
- hertz()
- pulseWidth
- pulseWidthLow
- level
3. Simple example
We use the interrupt here. The period time is measured in μs, the frequency is displayed in Hz.
|
#include <FreqPeriodCounter.h>
const byte counterPin = 3;
const byte counterInterrupt = 1; // = pin 3
FreqPeriodCounter counter(counterPin, micros, 0);
void setup(void)
{ Serial.begin(9600);
attachInterrupt(counterInterrupt, counterISR, CHANGE);
}
void loop(void)
{ if(counter.ready()) Serial.println(counter.hertz());
}
void counterISR()
{ counter.poll();
}
|
4. Counting multiple frequencies simultaneously
|
#include <FreqPeriodCounter.h>
FreqPeriodCounter counter1(3, micros, 0);
FreqPeriodCounter counter2(4, micros, 0);
FreqPeriodCounter counter3(5, micros, 0);
void setup(void)
{
}
void loop(void)
{ counter1.poll();
counter2.poll();
counter3.poll();
float f1 = (float)1 / counter1.period;
float f2 = (float)1 / counter2.period;
float f3 = (float)1 / counter3.period;
}
|
5. Examples with the frequency generated by the Arduino
You don't need a separate function generator to test the FreqPeriodCounter. We use the Arduino itself to generate a frequency signal, just connect d3 to d9.
5.1. Frequency counter using interrupt
|
#include <FreqPeriodCounter.h>
#include <Albert.h>
#include <Streaming.h>
#include <TimerOne.h>
/* Note: connect pin 3 to pin 9 */
const byte counterPin = 3; // connect pin 3 to pin 9
const byte counterInterrupt = 1; // = pin 3
const byte PWMpin = 9; // PWM only pin 9 or 10
FreqPeriodCounter counter(counterPin, micros, 0);
void setup(void)
{ Serial.begin(9600);
pinMode(PWMpin, OUTPUT);
Timer1.initialize();
Timer1.pwm(PWMpin, 300, 20000); // duty cycle [10 bit], period [us] <8388480
attachInterrupt(counterInterrupt, counterISR, CHANGE);
}
void loop(void)
{ if(!counter.ready()) Serial << "\nwaiting";
else Serial << endl << counter.level, counter.period, counter.hertz(), counter.pulseWidth, counter.pulseWidthLow;
}
void counterISR()
{ counter.poll();
}
|
5.2. Frequency counter using polling
|
#include <FreqPeriodCounter.h>
#include <Albert.h>
#include <Streaming.h>
#include <TimerOne.h>
/* Note: connect pin 3 to pin 9 */
const byte counterPin = 3; // connect pin 3 to pin 9
const byte counterInterrupt = 1; // = pin 3
const byte PWMpin = 9; // PWM only pin 9 or 10
FreqPeriodCounter counter(counterPin, micros, 0);
void setup(void)
{ Serial.begin(9600);
pinMode(PWMpin, OUTPUT);
Timer1.initialize();
Timer1.pwm(PWMpin, 300, 20000); // duty cycle [10 bit], period [us] <8388480
}
void loop(void)
{ if(counter.poll()) Serial << endl << counter.level, counter.period, counter.hertz(), counter.pulseWidth, counter.pulseWidthLow;
if(counter.elapsedTime > 50000) Serial << "No signal\n";
}
|
6. Frequency in decimals
To measure the frequency more accurate than 1Hz, we can use floating points, e.g. 899.543 Hz. However, this will take a lot of memory because the floating point library will be used. Therefore, the frequency is measured in integer with a multiplying factor. The precision has to be specified, for example 100:
unsigned long centiHz = counter.hertz(100);
This increases the accuracy:
counter.herz() is 16, counter.herz(100) is 1623.
In order to make use of floating points, do the following:
float hz = 1000/counter.period; // period in ms
7. Frequency counter library
cpp file
|
/* FreqPeriodCounter
* Version 22-4-2012
* Copyright (C) 2011 Albert van Dalen http://www.avdweb.nl
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses
Release Notes
17-12-2011 timeFunctionPtr to select millis or micros instead of bool variable
17-12-2011 New function ready()
22-04-2012 poll() counts all transients instead of low-high transients
<------------- period ------------>
pulseWidth
_____________ ______________
|| || || ||
|| || pulseWidthLow || || |
______________|| ||__________________|| ||______________|
transientCount 1 2 3 4 2
transientTime ^ ^ ^ ^
level 1 0 1 0
debounceTime <--> <--> <--> <-->
<- elapsedTime ->
*/
#include "FreqPeriodCounter.h"
FreqPeriodCounter::FreqPeriodCounter(byte pin, unsigned long (*timeFunctionPtr)(), unsigned debounceTime):
time(0), transientTime(0), elapsedTime(0), pulseWidth(0), pulseWidthLow(0), period(0), transientCount(0), level(0), lastLevel(0),
pin(pin), readyVal(0), debounceTime(debounceTime), timeFunctionPtr(timeFunctionPtr)
{
}
bool FreqPeriodCounter::poll()
{ time = timeFunctionPtr();
elapsedTime = time - transientTime;
level = digitalRead(pin);
bool returnVal = false;
if((level != lastLevel) & (elapsedTime > debounceTime)) // if transient
{ transientCount++;
lastLevel = level;
transientTime = time;
if(level == HIGH) pulseWidthLow = elapsedTime;
else pulseWidth = elapsedTime;
if(transientCount >= 6) // 6 for accuracy
{ period = pulseWidth + pulseWidthLow;
transientCount = 1; // next transientCount = 2
readyVal = true;
returnVal = true; // return true if a complete period is measured
}
}
return returnVal;
}
bool FreqPeriodCounter::ready()
{ bool returnVal = readyVal;
readyVal = false; // reset after read
return returnVal;
}
unsigned long FreqPeriodCounter::hertz(unsigned int precision)
{ if (timeFunctionPtr == micros) return (unsigned long)precision*1000000/period;
else return (unsigned long)precision*1000/period;
}
|
Header file
|
#ifndef FREQPERIODCOUNTER_H
#define FREQPERIODCOUNTER_H
/* FreqPeriodCounter
* Version 22-4-2012
* Copyright (C) 2011 Albert van Dalen http://www.avdweb.nl
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses
*/
#include <Arduino.h>
class FreqPeriodCounter
{
public:
FreqPeriodCounter(byte pin, unsigned long (*timeFunctionPtr)(), unsigned debounceTime);
bool poll();
bool ready();
unsigned long hertz(unsigned int precision=1);
unsigned long period, pulseWidth, pulseWidthLow, elapsedTime;
bool level;
protected:
unsigned long time, transientTime;
unsigned debounceTime;
byte transientCount, pin;
bool lastLevel, readyVal;
unsigned long (*timeFunctionPtr)();
};
#endif
|
8. Multiple frequency inputs
For using more frequency inputs, each frequency input must have its own FreqPeriodCounter object. Here is an example with 4 FreqPeriodCounters using 2x polling and 2x interrupt:
|
FreqPeriodCounter counter1(6, micros, 0); // polling
FreqPeriodCounter counter2(7, millis, 100); // polling
FreqPeriodCounter counter3(2, millis, 0); // interrupt 0
FreqPeriodCounter counter4(3, micros, 0); // interrupt 1
void setup(void)
{ ...
attachInterrupt(0, interrupt0, CHANGE);
attachInterrupt(1, interrupt1, CHANGE);
...
}
void loop(void)
{ ...
counter1.poll();
counter2.poll();
...
}
void interrupt0()
{ counter3.poll();
}
void interrupt1()
{ counter4.poll();
}
|
Multiple interrupts can easily cause problems, take care about the timing.
9. Frequency counter unit test
The unit test is done with the Arduino itself; it generates its own frequency signals. Download here the FreqPeriodCounter test program. This is the output of the test program.
|
testFreqPeriodCounterDebounce_ms OK
1 101 11 90
testFreqPeriodCounter millisec OK
1 1023 599 424
testFreqPeriodCounter microsec OK
1 1022976 599388 423588
|
10. High frequency measurement with the Arduino
To measure higher frequencies, we should use the timer/counter, which can be clocked externally on the T0 pin. Sorry, I have not implemented this, but it would be a challenge to extend the library with a high frequency counter.
