You are here:

Measuring Signal Frequency with Arduino

For a recent project I used a a TSL235R light-to-frequency converter that outputs a square-wave signal with a frequency that increases the amount of light hitting the sensor also increases. There are several Arduino packages out there for using the ATmega328 to measure frequency but I decided to write my own code in order to have complete control over the results. This is what I came up with. My results were very accurate, even more accurate then my DSO when averaging over many (approximately 1000) cycles.

// This code measure the frequency of an incoming
// square-wave signal using Timer/Counter 1 and the onboard
// comparator. The signal I am measuring has a duty-cycle of
// 50% but the code should work regardless of the duty-cycle.
// It should also work for non-square-wave signals so long as
// the signal voltage drops below the bandgap voltage of the
// onboard comparator (approximately 1.1 V) and is sufficiently
// clean to prevent bouncing.
// A variable to keep track of the number of overflows on
// Timer/Counter 1.
volatile unsigned int overflows = 0;
// A variable to keep track of how many rising edges of the
// signal have been counted.
volatile unsigned long edges = 0;
// A variable to keep track of the count on Timer/Counter 1
// when I start counting the edges of the signal.
volatile unsigned long tstart = 0;
// A variable to keep track of the count on Timer/Counter 1
// when I stop counting the edges of the signal.
volatile unsigned long tstop = 0;
// A variable to store temporarily store the count on
// Timer/Counter 1.
volatile unsigned long tnow = 0;
// This specifies how many cycles over which I want to
// average the frequency.
const unsigned long cycles = 1000;
// A variable to store the currently measured frequency
float frequency = 0;
void setup(void) {
pinMode(7,INPUT); // This is the analog comparator negative input.
// This is where the input signal enters the Arduino.
SREG = SREG | B10000000; // Enable gobal interrupts. They should
// already be enabled but I like to do this out of good measure.
Serial.begin(9600); // For printing the frequency to the terminal
}
void loop(void) {
delay(500);
measureFreq();
Serial.println(frequency);
delay(500);
}
void measureFreq(void) {
edges = 0;
ACSR = ACSR | B01000010; // enable analog comparator interrupt
// on failing edge (bit 1) which would actually capture a rising
// edge of the signal and use the internal bandgap reference
// voltage as the positive input (bit 6).
delay(5); // A short wait for bandgap voltage to stabilize.
overflows = 0;
TCCR1A = B00000000; // Set Timer/Counter 1 in normal mode where
// it will count to 0xFFFF then repeat.
TIMSK1 = TIMSK1 | B00000001; // Turn on Timer/Counter 1 overflow
// interrupt (bit 0).
// Turn on the counter with no prescaler.
TCCR1B = TCCR1B | B00000001;
ACSR = ACSR | B00001000; // Enable analog comparator interrupt
// (bit 3).
while (edges < (cycles+1)) {
// Do nothing.
}
// Calculate the frequency.
frequency = (float)16000000*(float)cycles/(float)(tstop - tstart);
}
ISR(TIMER1_OVF_vect)
{
overflows += 1;
}
ISR(ANALOG_COMP_vect)
{
tnow = TCNT1; // current time
edges += 1;
if (edges == 1) { // Start counting edges.
tstart = overflows*65536 + tnow;
}
else if (edges == cycles + 1) { // Stop counting edges.
tstop = overflows*65536 + tnow;
// Turn off Timer/Counter 1 and the comparator.
ACSR = 0;
TCCR1B = 0;
}
}

20 Comments

I’m glad you like the code. I know the Atmega32u4 is used on the Arduino Leonardo board and has four timers (one 8-bit, two 16-bit, and one 10-bit). Therefore the code can be modified to work with the Arduino Leonardo if the correct special function registers are modified. But, I don’t know what these registers are because I don’t have a Leonardo yet. I plan to get a Leonardo and a Due soon but right now I can’t answer your question. In the mean time, take a look at the Atmega32u4 datasheet. I find the Atmega datasheets to be incredibly helpful.

Hi thank you very much for the code.
Can I use this code for measuring the heart rate? The maximum frequency the conditioned square wave can reach is 2Hz. Will there be any compromise on the resolution of the measured frrequency?

A 2 Hz heart beat in Arduino Uno land is 8,000,000 clock cycles per beat. You should be able to measure such a low frequency so long as the variable keeping track of timer overflows between beats is of a data type large enough to keep track of the number of overflows (which might be quite large). An unsigned long might be wise to be safe.

Hi thanks for this code. Currently I’m working on a project that measures fuel quantity and the frequency changes as the fuel level changes which is quite similar to your project. Can I ask if this code works with Arduino Uno?

Thank you very much for this code.
I was been searching for this kind of examples and trying to make a program who can read high frequencies.
By now I spend more than a month only doing that and was very close to give up.
When I saw and tried this code it was a relieve.
THANK YOU VERY MUCH!!

Thanks for the contribution. Could you do some tests for accuracy/precision and post the results here. I suspect there are significant advantages to using the onboard timer interrupts and comparator. -Jeff

Very good code, I am trying to develop an accurate clock using the arduino uno internal clock.
I need to measure the frequency of the Pwm signal to the accuracy of xxx.xx hz.
I am being cheap not buying a frequency counter,I am using your code to measure the frequency.
I am getting a measure of 979.27 and using my fluke 115 is 978.7, I wish I can get another decimal place on my fluke then I will be ok. My question is, there is another way to make this more accurately or I am on a dead end and reaching the limitations of the arduino uno?

The fundamental limitation of using the Uno to measure time is the accuracy of the CPU clock determined by the 16 MHz crystal. The crystal itself oscillates with accuracy of about +/- 0.005%. So, if your measurement is 979.27 Hz, it is actually like 979.27 +/- 0.05 Hz taking into account just this uncertainty associated with the crystal. There may be other systematic or random errors too.

This is really awesome code. I am having a small project that require me to read the frequency from the circuit. May I use your code. And may I ask can I use other pin for INPUT signal or is it must be 7. As what I am understanding, we need a negative analog comparator pin for input signal but i’m not sure if any other pin can work or just only pin 7?