by

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

19 Comments


  1. // Reply

    It is very powerful code to measure frequency.
    I’ve a problem: How can I modify your code to work with atmega32u4 ?


    1. // Reply

      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.


  2. // Reply

    Hey there

    I was wondering if I could use this code to measure the frequency of a function generator I created for a physics lab project. You would receive attribution for it.

    Thanks for posting this, it’s very helpful.


    1. // Reply

      Sorry for the delay getting back to you. Sure you can use the code. Please let me know when the project is complete. I would love to see the final report.


  3. // Reply

    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?


    1. // Reply

      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.


  4. // Reply

    Can this code be adopted for a Pololu Orangutan SV-328?
    If so would there be many changes to the code?


  5. // Reply

    Wonderful code !! Amazing accuracy !!


  6. // Reply

    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?


  7. // Reply

    what about the circuit? can you share? thanks


  8. // Reply

    hey thank u very much for code..
    but please can u tell me what changes would i have to make if i want to measure frequency of another wave at same time


  9. // Reply

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


  10. // Reply

    Hello,

    Nice Code

    Can we measure Multiple Frequencies at the same time?

    Regards


  11. // Reply

    // Arduino native function PulseInLong does the same, in just 1 line of code

    float Frequency = 1000000.0 / (pulseInLong(pin, HIGH) + pulseInLong(pin, LOW));


    1. // Reply

      // … and you can calculate Duty Cycle the same way

      float DutyCycle = (float) pulseInLong(pin, HIGH) / (pulseInLong(pin, HIGH) + pulseInLong(pin, LOW));


      1. // Reply

        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


  12. // Reply

    Hi,

    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?

    Thanks for your time

    serch


    1. // Reply

      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.


  13. // Reply

    How can i use this code for measuring the rpm value using an inductive proximity sensor

Leave a Reply

Your email address will not be published. Required fields are marked *