Home Arduino Hardware interfacing Frequency counter

Frequency / period counter for 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.

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

 Arduino frequency counter used in scale interface
Constructor:

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.

For troubleshooting see HERESee for the Arduino forum HERE

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.