ElectronicsInfoline

Test and measurement

How a frequency counter works: gate, ±1 count, timebase, and an Arduino build

By Bulan Sarkar, founder of ElectronicsInfoline (since 2000) · About a 11-minute read

A frequency counter opens a gate for a known time, counts the edges that pass, and divides. The interesting parts are why the last digit jumps about, what decides whether the reading is right, and how far you can push an Arduino UNO doing the job.

The reciprocal method beats a plain count at low frequencies, and the timebase, not the count, decides the accuracy. At the end is an UNO counter you can build, with its limits taken from the datasheets.

A frequency counter counts the signal's edges during a gate of known length: frequency = count ÷ gate time. With a 1 s gate the reading is in hertz, and the last digit can be off by one count, ±1 Hz, because the gate opens at an arbitrary point in the signal (Hewlett-Packard).

Accuracy comes from the timebase that times the gate. An Arduino UNO's 16 MHz ceramic resonator is good to ±0.5 % out of the box (Murata), so it reads 1 MHz up to ±5 kHz off however long you count. The UNO itself counts up to about 6.4 MHz on pin 5 (Microchip).

Count for a second

Feed a 50,000 Hz signal into a counter, open its gate for exactly one second, and 50,000 edges go through. Divide by the gate time and you have the frequency. A conventional counter is built around that: an input stage that turns the signal into clean logic edges, a gate that lets them through, a counter, and a display (Hewlett-Packard).

The gate is opened and closed by a timebase, a crystal or resonator divided down to the gate time. Everything the counter tells you is a comparison between your signal and that timebase. A gate of 0.1 s gives a reading ten times sooner and one digit fewer; a 10 s gate gives one more digit and makes you wait.

signal inSchmittclean edgesgateANDcounteredgesdisplayHztimebasecrystal / resonatorgate control1 s gatereset, latch
Figure 1. A conventional counter. The timebase decides how long the gate is open; the counter totals the edges that pass, and the display shows count ÷ gate time.

Our take: think of a counter as a stopwatch that the signal ticks. The stopwatch is the timebase, and a counter can never be more accurate than its stopwatch.

The ±1 count

The gate does not know where the signal is when it opens. Open it just before an edge and that edge is counted; open it just after and the same edge is missed. Over the same gate length the count can differ by one. That is the ±1 count, and it is in every gated counter (Hewlett-Packard).

inputgate A5 edgesgate B4 edgessame gate length, same signal
Figure 2. The same signal and the same gate length. Gate A opens just before a rising edge and counts 5 of them; gate B opens just after one and counts 4.

One count is 1/gate hertz: ±1 Hz with a 1 s gate, ±10 Hz with 0.1 s. As a fraction of the reading it is 1 ÷ (f × gate), so it hurts low frequencies most. At 10 MHz a 1 s gate resolves one part in ten million. At 1000 Hz the same gate gives ±1 Hz, one part in a thousand, and at 10 Hz it is a 10 % error (Hewlett-Packard).

Reciprocal counting: count the clock instead

The fix for low frequencies is to turn the measurement round. Open the gate on an edge of the signal, close it on a later edge after about a second, and count a fast reference clock in between. You now know the length of a whole number of signal periods to within one clock tick, and the frequency is the number of periods divided by that time. This is a reciprocal counter (Hewlett-Packard).

Its ±1 count is one clock period, so its relative error is 1 ÷ (fclock × gate) whatever the input frequency. With the UNO's own 16 MHz clock and a 1 s gate that is 6.25e-8. A 1000 Hz signal that a direct count reads to ±1 Hz now resolves to ±62.5 µHz. HP's own example: at 10 Hz with a 10 MHz clock, a direct count is ±10 % and a reciprocal one is ±1e-7 (Hewlett-Packard).

11e-31e-61e-91 Hz100 Hz10 kHz1 MHz100 MHz16 MHz clockdirect, 1 sdirect, 0.1 sreciprocal, 16 MHz clock, 1 sinput frequency
Figure 3. Relative ±1 count error against input frequency, both axes logarithmic. The direct count improves by ten for every decade of frequency; the reciprocal count is flat. They meet where the input equals the reference clock.

Above the clock frequency the direct count wins again, so bench counters switch method by themselves and most reciprocal counters are built to go no higher than their clock (Hewlett-Packard).

Our take: below a few kilohertz, measure the period, not the frequency. On an Arduino that means timing edges with Timer1's input capture rather than counting them.

The timebase sets the accuracy

Resolution is how finely a counter can split the reading. Accuracy is whether the reading is right, and that belongs to the timebase. If the timebase runs 0.5 % fast, the gate is 0.5 % short and every reading is 0.5 % low. A longer gate does not average this away.

The UNO's ATmega328P runs from a Murata CSTCE16M0V53-R0 ceramic resonator (Arduino). Murata specifies it to ±0.5 % initially, a further ±0.3 % over −20 to 80 °C and ±0.2 % for aging (Murata): up to ±1.0 % if all three line up. At 1 MHz that is ±10 kHz, against a ±1 count of ±1 Hz. The display shows hertz; the resonator is right to the nearest ten kilohertz.

initial±5 kHztemperature±3 kHzaging±2 kHzall three±10 kHz±1 count±1 Hz
Figure 4. Errors at 1 MHz with a 1 s gate on an Arduino UNO. The ±1 count is too small to see beside what the resonator's tolerance, temperature drift and aging allow.

You can calibrate out the initial part. Measure a source you trust, say a 1.000000 MHz reference that reads 1,003,120, and multiply every later reading by 1,000,000 ÷ 1,003,120 = 0.996890. That removes the board's +0.312 %. It cannot remove the drift: up to ±0.5 % from temperature and aging is still allowed by the resonator's specification.

For better accuracy the timebase has to change. A quartz crystal is specified in parts per million rather than tenths of a percent, a temperature-compensated oscillator (TCXO) is better again, and an oven-controlled one (OCXO), which some old bench counters were upgraded with, holds the crystal at a fixed temperature. Read the tolerance in the part's datasheet and multiply by your frequency to see what it means in hertz.

Our take: quote an Arduino counter's reading to three significant figures unless you have calibrated it, and to four if you have. Digits past that are finer than the resonator can promise.

UNO T1 limit
6.4 MHz
With ÷16 prescaler
24 MHz guaranteed
±1 count, 1 s gate
±1 Hz
Resonator, worst case
±1.0 %

Clean edges: the input stage

A counter counts every edge it sees, including the ones noise makes. A slow sine with a little noise on it can cross a single threshold several times at each zero crossing, and each crossing becomes a count. A Schmitt trigger has two thresholds. The output goes high only when the input rises past the upper one and low only when it falls past the lower one, so small wiggles in between do nothing.

VT+ 2.38 VVT− 1.40 Vslow, noisy inputone threshold: 8 edgesSchmitt trigger: 3 edges
Figure 5. A slow, noisy sine through a single threshold and through a Schmitt trigger. The single threshold turns noise near the crossing into extra edges; the Schmitt trigger gives one clean edge each way.

A 74HC14 is a cheap Schmitt trigger. At a 4.5 V supply its upper threshold is typically 2.38 V and its lower one 1.40 V, so a typical part needs about 0.98 V peak to peak to switch both ways. The datasheet lets the upper threshold be as high as 3.15 V and the lower as low as 0.90 V, so to be sure of switching on any part the signal must swing at least 2.25 V peak to peak, centred near 2.02 V, and a little more in practice (Nexperia).

The input stage in Figure 6 does three jobs. A 15 kΩ / 10 kΩ divider from 5 V holds the 74HC14 input at 2.0 V, near that centre. A 1 µF capacitor lets the signal swing around it without its own DC level getting in the way; with the divider's 6 kΩ it passes everything above about 27 Hz. A 1 kΩ series resistor limits the current into the chip's clamp diodes, whose absolute limit is ±20 mA (Nexperia): a signal 10 V beyond the supply rails pushes only 10 mA through it.

in1 kΩ1 µF15 kΩ+5 V10 kΩ2.0 V74HC14to counter
Figure 6. The input stage: 1 kΩ series resistor, 1 µF coupling capacitor, a 15 kΩ / 10 kΩ divider biasing the 74HC14 input at 2.0 V, and one Schmitt inverter. The inversion does not matter: each cycle still gives one rising edge.

Our take: if your signal is already a clean 5 V logic signal, skip the capacitor and the divider and drive the 74HC14 directly. Keep the series resistor.

Build one with an Arduino UNO

The ATmega328P on an UNO can count external edges in hardware. Timer1 takes its clock from pin T1, which is PD5, pin 5 on the board (Microchip). Each edge is counted without the program doing anything, so the count is exact; the program only has to open and close the gate. The sketch below holds the gate open for 1000 interrupts of a 1 ms tick from Timer2 (16 MHz ÷ 128 ÷ 125 = 1 kHz) and counts Timer1's overflows to extend its 16 bits.

The T1 pin is sampled by the processor clock, so the datasheet requires the input to stay below half of it, 8 MHz, and Microchip recommends staying below 16 ÷ 2.5 = 6.4 MHz to allow for clock tolerance and duty cycle (Microchip). Each edge also takes 156 to 219 ns to reach the counter, which is why the input has to be clean: two edges closer together than that are one edge to the chip.

To go higher, put a divider in front. A 74HC4040 ripple counter tapped at Q3 (pin 5) divides by 16. Nexperia guarantees it to 24 MHz at a 4.5 V supply from −40 to 85 °C, with 82 MHz typical at room temperature (Nexperia). The chain is then good to 24 MHz on any part and usually to about 82 MHz, at the cost of a ±1 count that is now ±16 Hz.

input stageFigure 674HC4040÷16 at Q3CP 10Q3 5MR 11 → GNDArduino UNOD5 = T1 (PD5)link instead of the 4040direct to D5: up to 6.4 MHzthrough ÷16: 24 MHz guaranteed, ~82 MHz typicalshared ground; 100 nF across each chip's supply pins
Figure 7. The UNO counter. The input stage feeds a 74HC4040 (clock on pin 10, reset pin 11 tied to ground) whose Q3 output, pin 5, divides by 16 and drives UNO pin 5. For signals under 6.4 MHz, link the input stage straight to pin 5 instead.
// Frequency counter for an Arduino UNO: signal on pin 5 (T1), 1 s gate.
volatile uint16_t overflows;
volatile uint16_t ms;
volatile bool done;

ISR(TIMER1_OVF_vect) { overflows++; }

ISR(TIMER2_COMPA_vect) {          // fires every 1 ms
  if (++ms == 1000) {
    TCCR1B = 0;                   // close the gate: stop counting
    TIMSK1 = 0;                   // a late overflow stays in TIFR1
    TCCR2B = 0;
    done = true;
  }
}

void setup() { Serial.begin(115200); }

void loop() {
  Serial.flush();                 // no serial interrupts during the gate
  uint8_t t0 = TIMSK0;
  noInterrupts();
  TIMSK0 = 0;                     // pause millis() so it cannot delay Timer2
  TCCR1A = 0; TCCR1B = 0; TCNT1 = 0;
  TIFR1 = _BV(TOV1); TIMSK1 = _BV(TOIE1);
  TCCR2A = _BV(WGM21); TCCR2B = 0; TCNT2 = 0;
  OCR2A = 124;                    // 125 ticks of 16 MHz / 128 = 1 ms
  TIFR2 = _BV(OCF2A); TIMSK2 = _BV(OCIE2A);
  overflows = 0; ms = 0; done = false;
  TCCR1B = _BV(CS12) | _BV(CS11) | _BV(CS10); // open the gate: count rising edges on T1
  TCCR2B = _BV(CS22) | _BV(CS20);             // start the 1 ms tick (prescaler 128)
  interrupts();
  while (!done) {}
  if (TIFR1 & _BV(TOV1)) overflows++;         // an overflow that landed as the gate closed
  uint32_t count = ((uint32_t)overflows << 16) | TCNT1;
  TIMSK0 = t0;
  Serial.println(count);          // Hz; multiply by 16 with the 74HC4040 fitted
}

The sketch pauses the millis() interrupt and waits for the serial port to finish sending during the gate, so no other interrupt can delay the 1 ms tick that closes it. And it checks for an overflow that arrived just as the gate shut, which would otherwise lose 65,536 counts once in a while.

Test it against something whose frequency you know: a function generator, or a packaged crystal oscillator. If it reads a steady value with only the last digit changing, the input stage is clean. If it jumps by large amounts, edges are being doubled; check the signal level against the 2.25 V figure above.

Our take: build it without the 74HC4040 first and check it against a known source at a few kilohertz. Add the divider only when you need more than 6.4 MHz.

Tips and checks

  • Ground first. Connect the signal ground to the UNO ground before the signal itself; without a shared ground the counter reads noise.
  • A long unscreened lead picks up hum and radio. Keep the input wire short or use coax to the input stage.
  • Put a 100 nF capacitor across the supply pins of each chip, as close as you can. A 74HC4040 switching at tens of megahertz needs it.
  • To change the resolution, change the gate. A 1 s gate gives hertz directly; 10 s adds a decimal place, 0.1 s gives ten readings a second with one digit fewer.
  • Calibrate once at room temperature against a trusted source, write the factor on the board, and remember that it drifts with temperature.
  • The 74HC4040 counts on falling edges and the 74HC14 inverts. Neither changes the count: a whole cycle has one edge of each kind.

What we'd do: for audio and low frequencies, a reciprocal (period) measurement on the UNO; for 10 kHz to 6.4 MHz, the direct count in the sketch above; above that, the 74HC4040 in front. For anything where the reading must be right to better than a percent, a counter with a crystal or TCXO timebase, or calibration against a known source.

Questions people ask

Why does my counter read one more or one less each time?

That is the ±1 count. The gate opens at a random point in the signal, so it catches either n or n + 1 edges. With a 1 s gate it is ±1 Hz; with 0.1 s it is ±10 Hz.

Can an Arduino measure 100 MHz?

Not on its own. The UNO's Timer1 input is limited to about 6.4 MHz by its 16 MHz clock. A ÷16 74HC4040 in front is guaranteed to 24 MHz; above that you need a faster divider or a dedicated prescaler chip.

Why is my reading thousands of hertz out at 1 MHz?

The UNO's resonator is only specified to ±0.5 %, which is ±5 kHz at 1 MHz. Calibrate against a known source, or use a board with a crystal or TCXO.

What is a reciprocal counter?

One that times a whole number of signal periods with a fast clock and then takes the reciprocal. Its resolution does not depend on the input frequency, so it is far better than a direct count at low frequencies.

Do I need the Schmitt trigger?

For sine waves, slow edges or anything noisy, yes: without it one cycle can count as several. A clean, fast 5 V logic signal can go straight to pin 5.

Lessons behind the numbers

Sources and assumptions

Read on 9 October 2026. Our own choices, not taken from any source: the Arduino UNO as the counter, the 1 s gate from Timer2, the 74HC4040 tapped at Q3, the 15 kΩ / 10 kΩ divider, the 1 µF coupling capacitor, the 1 kΩ series resistor, the 1.003120 MHz calibration reading, and the 1 kHz and 1 MHz examples. The 74HC14 and 74HC4040 figures are the datasheets' 4.5 V values; an UNO supplies 5 V. The resonator's three tolerances are added as a worst case.