Frequency / period counter for the Arduino
- Arduino frequency counter intro
- Frequency counter library
- Simple example
- FreqPeriodCounter facts
- FreqPeriodCounter variables
- Simple interrupt example
- Using a variable instead of the input pin
- Counting multiple frequencies
- Examples with the frequency generated by the Arduino
- Frequency in decimals
- Frequency counter synchronization
- Frequency counter unit test
- Measuring a frequency of 0Hz is impossible
- To do: high frequency measurement with the Arduino
Arduino frequency counter intro
Here is a frequency counter for the Arduino, it is used in many projects, such as the pedelec legalisation device and the scale interface.
The library is also compatible with Arduino boards that use the SAMD21: Arduino Zero, SAM 15x15, etc.
Important: the Frequency / period counter works without hardware timers.
Frequency counter library
Download the library from GitHub, you will find here program examples too.
Counting multiple frequencies with polling and interrupts
Simple example
#include <FreqPeriodCounter.h> const byte counterPin = d3; FreqPeriodCounter counter(counterPin, micros); void setup(void) { } void loop(void) { counter.poll(); float f = (float)1 / counter.period; }
FreqPeriodCounter facts
- The frequency counter can be used in two ways:
Interrupt triggered by the input signal.
Polled regularly in a loop. - The FreqPeriodCounter is equipped with synchronization so that also the first measurements are valid.
- 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: counter(counterPin, micros, 10);.
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
Simple interrupt 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; // = d3 FreqPeriodCounter counter(counterPin, micros); void setup(void) { attachInterrupt(counterInterrupt, counterISR, CHANGE); } void loop(void) { int period; if(counter.ready()) period = counter.period; } void counterISR() { counter.poll(); }
Using a variable instead of the input pin
Instead of using an input signal on a pin you can also use a bool variable as input. You have to pass the bool variable via the poll() function:
if(myCounter.poll(your_variable)) blabla;
You have to initialize the counter object as follow:
FreqPeriodCounter myCounter(millis, 1000);
Counting multiple frequencies
#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; }
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.
Frequency counter using interrupt
#include <FreqPeriodCounter.h> #include <Albert.h> #include <Streaming.h> #include <TimerOne.h> /* Note: connect d3 to d9 */ const byte counterPin = 3; // connect d3 to d9 const byte counterInterrupt = 1; // = d3 const byte PWMpin = 9; // PWM only d9 or d10 FreqPeriodCounter counter(counterPin, micros, 0); void setup(void) { Serial.begin(9600); pinMode(PWMpin, OUTPUT); Timer1.initialize(); Timer1.pwm(PWMpin, 300, 70); // duty cycle [10 bit], period [us] <8388480 attachInterrupt(counterInterrupt, counterISR, CHANGE); } void loop(void) { if(counter.ready()) { Serial << endl << counter.period; //Serial << endl << counter.level, counter.period, counter.pulseWidth, counter.pulseWidthLow; } } void counterISR() { counter.poll(); }
Frequency counter using polling
#include <FreqPeriodCounter.h> #include <Albert.h> #include <Streaming.h> #include <TimerOne.h> /* Note: connect d3 to d9 */ const byte counterPin = 3; // connect d3 to d9 const byte counterInterrupt = 1; // = d3 const byte PWMpin = 9; // PWM only d9 or d10 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 Timer1.pwm(PWMpin, 300, 40); // duty cycle [10 bit], period [us] <8388480 } void loop(void) { if(counter.poll()) Serial << endl << counter.period; //if(counter.poll()) Serial << endl << counter.level, counter.period, counter.pulseWidth, counter.pulseWidthLow; if(counter.elapsedTime > 50000) Serial << "No signal\n"; }
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
Frequency counter synchronization
It is a fact that a frequency counter can't do valid measurements starting from the first pulse. In most cases, you do not notice this, but for nitpickers, the FreqPeriodCounter is equipped with synchronization so that measurements are never invalid. At the start, firstly two periods must be measured before the measurement is valid and the counter is ready. Hereafter, the counter is synchronized, and the counter is ready after each period.
The FreqPeriodCounter is automatically synchronized from the beginning. With synchronize() can be synchronized, for example, when at frequency changes no wrong measurements may occur in the transition phase.
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.
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
Measuring a frequency of 0Hz is impossible
People ask why the Frequency / period counter can't measure 0Hz. However, this is fundamentally impossible, why?
For 0Hz there is never an input pulse detected, but to know that, you have to wait indefinitely long, because, for example, 1 pulse per day is 0.000011574Hz and that is more than 0Hz. So you have to use a time limit. For example, if the measurement resolution is 1Hz, than all period times greater than 2s correspond with 0Hz
To do: 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. I have not implemented this, but it would be a challenge to extend the library with a high frequency counter.
So you have to use a time limit. For example, if the measurement resolution is 1Hz, than all period times greater than 2s correspond with 0Hz.