Sensors & Signal Processing
How to measure a signal's frequency with Arduino
To measure a signal's frequency with an Arduino, count its rising crossings: every wave crosses the middle going up exactly once, so the number of rising crossings in one second is the frequency in hertz. For slow signals, time one wave instead: frequency = 1000 ÷ period in ms. Either way, count edges, not levels, and use two thresholds so noise can't add crossings.
Edges, not levels
"Above the middle" is a level: true for half of every wave, many samples in a row. "Just went above the middle" is an edge: true once a wave.
bool high = analogRead(A0) > 512;
if (high && !wasHigh) ticks = ticks + 1; // rising edge
wasHigh = high;
Count the level instead, if (high), and at 100 samples a second a 6 Hz wave counts about 50 a second: half the samples, and nothing to do with its frequency.
Two lines beat one
Real signals carry noise, and near a single line the noise crosses back and forth and adds edges. Count when the reading rises past an upper line, say 600, and don't count again until it has fallen below a lower one, 424. That gap is hysteresis, and it must be wider than the noise.
Count, or time
Counting for a fixed second only sees whole waves, so a 1.5 Hz signal reads 1 or 2. For slow signals, time the gap between rising edges:
unsigned long now = millis();
if (lastEdge > 0) hz = 1000.0 / (now - lastEdge);
lastEdge = now;
Edges at 1200 ms and 1600 ms give 2.5 Hz. The first edge has nothing to measure from, which is why the code checks lastEdge > 0.
Live experiment
Count edges, not levels
A wind gauge spins 6 turns a second, then gusts to 10. Count every sample above the middle and it reads hundreds of turns a second; count the rising edge and it reads the true 6, then 10; count every edge and it reads double. The red marks show which samples tick.
“Count the wiggles” is lesson 2 of 6 in Alias Atoll, Sensors & Signal Processing World 3. Free with an account. Lessons unlock in order, so you’ll do the 14 before it first.
Mind the sample rate
You can only see crossings you sample. Sample at least twice as fast as the signal, and better ten times, or the count aliases to a false, lower frequency. For signals above a few kilohertz, skip analogRead, feed a digital pin, and let the hardware time edges: pulseIn(), an interrupt on each rising edge, or a timer's input capture, which records the exact clock tick of each edge.
Where you'll meet it
An anemometer's cups make one wave a turn, and a weather station turns the count into wind speed. Bike computers count a magnet passing the fork, car rev counters count ignition pulses, heart-rate straps time the gap between beats, and guitar tuners time crossings of a string's note.
Free lesson
Now build it yourself
Each lesson comes in three short parts: see the idea, try it on a live board, then build it yourself, with quick checks as you go.
“Count the wiggles” is lesson 2 of 6 in Alias Atoll, Sensors & Signal Processing World 3. Free with an account. Lessons unlock in order, so you’ll do the 14 before it first.
Or start from the very first lesson, no account neededPractice questions
Three quick questions from the lessons. Pick an answer to check it.
Show the answer
Answer: 50 a second: half of all the samples. Half of every wave is above the middle, so half the samples count. That's a level, not an edge.
- Why not “6 a second”? 6 a second is what the rising edge counts. A level is true for many samples of every wave.
- Why not “100 a second”? Only the samples above the middle count, and that's half of them.
- Why not “12 a second”? 12 a second would be counting every edge, up and down. This counts samples, not edges.
Show the answer
Answer: Noise near one line crosses it back and forth and counts extra edges. With a gap between the lines, the noise can't carry the reading from one to the other: one edge per wave. That gap is called hysteresis.
- Why not “One line can't see slow signals”? One line sees slow signals fine. Its trouble is noise near the line.
- Why not “Two lines count twice as fast”? Two lines count once per wave, the same as a clean single line. They stop extra counts, not add them.
- Why not “It saves samples”? The board samples just as often with two lines. They change how the samples are judged.
Show the answer
Answer: Both are whole numbers otherwise, so the division throws away the fraction: 1.5 becomes 1. 1000 ÷ 667 in whole numbers is 1. With 1000.0 it's 1.499.
- Why not “1000.0 is bigger”? 1000.0 and 1000 are the same size. The .0 changes the kind of sum, not the value.
- Why not “millis() needs it”? millis() returns a whole number either way. The .0 is about the division.
- Why not “It doesn't matter”? It does: with whole numbers 1000 ÷ 667 is 1, so a 1.5 Hz signal reads 1.
“Count the wiggles” is lesson 2 of 6 in Alias Atoll, Sensors & Signal Processing World 3. Free with an account. Lessons unlock in order, so you’ll do the 14 before it first.
Questions people ask
How do I measure frequency with an Arduino?
For a clean digital signal, time the period with pulseIn() or count rising edges with attachInterrupt() over a fixed time. For an analog signal, sample it, detect rising crossings of the middle with two thresholds, and count them per second or time the gap between them.
Should I count pulses or measure the period?
Count for fast signals: many pulses in a second gives a fine reading. Time the period for slow ones: counting a 1.5 Hz signal for a second only ever gives 1 or 2. Timing several periods and averaging works well across the range.
Why does my frequency reading jump around?
Usually noise near the threshold adding extra edges. Use two thresholds with a gap wider than the noise, smooth the signal first, or time several waves instead of one.
Related topics
- What is hysteresis, and how does it stop flicker?On below one line, off above another: the gap that stops a noisy reading flickering.
- What is aliasing, and what is the Nyquist rate?Sample too slowly and a fast wheel seems to crawl backwards.
- How a wheel encoder measures distanceCount the notches as the wheel turns, and you know how far the robot went.
- millis() vs delay() on ArduinoWhy delay() freezes your sketch, and how millis() runs several jobs at once.