1. Arduino frequency counter intro

Here is a frequency counter for the Arduino, it is used in many projects, such as the pedelec legalisation device, the e-bike Watt meter and the scale interface.

Arduino frequency counter used in e-bike Watt meter
Arduino frequency counter used in e-bike Watt meter - Arduino frequency counter used in e-bike Watt meter

Arduino frequency counter used in e-bike Watt meter - Arduino frequency counter used in e-bike Watt meter
Frequency counter used in pedelec legalisation device
Frequency counter used in pedelec legalisation device - Frequency counter used in pedelec legalisation device

Frequency counter used in pedelec legalisation device - Frequency counter used in pedelec legalisation device

 Arduino frequency counter used in scale interface
Arduino frequency counter used in scale interface - Arduino frequency counter used in scale interface

Arduino frequency counter used in scale interface - Arduino frequency counter used in scale interface

Simple example

#include <FreqPeriodCounter.h>
 
const byte counterPin = 3;
FreqPeriodCounter counter(counterPin, micros);
 
void setup(void) 
}
 
void loop(void) 
{ counter.poll();
  float f = (float)1 / counter.period;
}

2. 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

3. 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

4. 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; // = pin 3
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();
}

5. 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;  
}

6. 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.

6.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);
 
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();
}

6.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);
 
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";
}

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

8. 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.

9. Frequency counter library

Copy the code below into a FreqPeriodCounter.cpp and FreqPeriodCounter.h file and place the two files into an Arduino library folder like this: \libraries\FreqPeriodCounter.

/* FreqPeriodCounter
 * Version 28-5-2013
 * 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
5-5-2013 if(transientCount >= 2) // the first 2 measurements are invalid
28-5-2013 measurements are valid from start, added synchronize() 
 
 
               <------------- period ------------>
                  pulseWidth
                 _____________                      ______________                     
               ||             ||                  ||              ||  
               ||             ||  pulseWidthLow   ||              ||              |
 ______________||             ||__________________||              ||______________|
          
 transientCount 1             2                   1               2               1
 transientTime ^              ^                   ^               ^
 level                1                 0                  1                0       
 debounceTime  <-->           <-->                <-->            <--> 
                                                                  <- elapsedTime ->
*/
          
#include "FreqPeriodCounter.h"
 
FreqPeriodCounter::FreqPeriodCounter(byte pin, unsigned long (*timeFunctionPtr)(), unsigned debounceTime):
pin(pin), debounceTime(debounceTime), timeFunctionPtr(timeFunctionPtr) 
{ synchronize();
}
 
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 >= 2) 
    { period = pulseWidth + pulseWidthLow;
      transientCount = 0; 
      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;
}
 
void FreqPeriodCounter::synchronize()
{ transientCount = -2; // skip first two invalid measurements
}

Header file

#ifndef FREQPERIODCOUNTER_H 
#define FREQPERIODCOUNTER_H
 
/* FreqPeriodCounter
 * 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); 
  void synchronize();
  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; 
  char transientCount;
  byte pin;
  
  bool lastLevel, readyVal; 
  unsigned long (*timeFunctionPtr)();
};
 
#endif

10. Counting multiple frequencies with polling and interrupts

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.

11. 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

12. 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.

Do you have any comments? Please let me know.
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.