Skip to main content

Frequency / period counter for the Arduino

Published: 25 November 2011
Last updated: 03 April 2023

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.

Arduino frequency counter used in e-bike Watt meterArduino frequency counter used in e-bike Watt meterFrequency counter used in pedelec legalisation deviceFrequency counter used in pedelec legalisation device

 Arduino frequency counter used in scale interfaceArduino frequency counter used in scale interface

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);.

For troubleshooting see HERESee for the Arduino forum HERE. GitHub see HERE.

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.