Simple, high accuracy/precision/resolution frequency counter

Article By : Damian Bonicatto and Phoenix Bonicatto

Here is a simple circuit for a frequency counter derived from the GPSDO schematic.

In my previous design idea, a design for a simple GPS disciplined oscillator (GPSDO) 10 MHz reference was presented. A note at the end of that article describes the realization that a frequency counter could be derived by simple changes to the circuit. This article follows up by presenting a simple circuit for a frequency counter derived from the GPSDO schematic.

As shown in Figure 1, we reuse much of the previous design including the power supply, Arduino Nano, 32-bit counter, LCD, and LEDs. However, we remove the VCXO circuitry. We add a second pushbutton, input signal conditioning, and create different outputs. Many parts of the software will also be kept or slightly modified.

Figure 1 Basic block diagram of the GPS enabled frequency counter.

Figure 2 shows the detailed design of the frequency counter. Comparing this schematic to the previous GPSDO schematic, you can see the changes. Along with the removal of the VCXO circuitry, you will see that the 74LS139 has been removed. The frequency counter design freed up some I/O ports so I did not need to add an expander to address the counter (74LV8154).

Figure 2 Schematic of the frequency counter.

You’ll also notice the addition of an analog conditioning circuit feeding the input of the counter. To keep the design simple, only a Schmitt trigger inverter, a cap, and a couple of resistors are used. This is a well-known circuit dating at least back to the 1975 Fairchild application note 140. The circuit uses 2 resistors (R6 and R7) to bias the Schmitt trigger inverter to its center-point (this works because the input is AC coupled via C17). Biasing to the center-point positions means the input signal centers on the Schmitt trigger’s hysteresis. This gives the input signal maximum sensitivity. The capacitor and resistors are selected to allow signals from a little over 10 mHz to 80 MHz to pass to the Schmitt inverter. The 80 MHz max was identified due to some preliminary testing of the circuit. The limit is imposed by the 74LV8154 counter as this appears to be its maximum counting rate (the datasheet is somewhat unclear on this number). The 10 mHz minimum is due a design decision to have a maximum gate time of 100 seconds. With a gate time of 100 seconds, the minimum of 1 count will occur if the input signal has a frequency of 1/100, or 10 mHz. (Gate times 1 second and 10 seconds are also available via the “Gate Time” pushbutton.)

The GPSDO used a push button and was read by an ADC on the Arduino Nano (Figure 3). This frequency counter design uses 2 pushbuttons that are connected to analog comparators to read their states.

Figure 3 Completed frequency counter in 3D printed enclosure with LCD.

So, after the signal is squared up via the Schmitt trigger, the squared-up signal is sent to the 32-bit counter. To measure the frequency of this signal, the Arduino Nano waits for an interrupt from the 1 pulse-per-second (1PPS) signal from the GPS module (~$10). Even an inexpensive GPS module can deliver a very accurate 1PPS signal. This pulse looks like a ~100 ms pulse every second. After receiving this interrupt, the code signals the 74LV8154 to clear the counter. The code then waits for the next 1PPS interrupt and, when received, the Nano signals the counter to latch the current count into its on-chip register and then clears the counter. The code now reads in the latched-in register data. This is a description of a 1 second gate time operation, so the register value is the frequency of the input signal. If a 10 second or a 100 second gate time is selected, by using the “Gate Time” pushbutton, the code waits for 10 or 100 1PPS interrupts before latching in the count. The count is then scaled by 10 or 100 to get the frequency.

More on the firmware; the two projects are actually quite similar and share a lot of code. We still drive the LCD and LEDs, monitor loss of the 1PPS signal via a 2 second watchdog timer (WDT), count pulses and, upon a 1PPS interrupt, read the register in the 74LV8154, etc. One big change is that we no longer need to adjust PWMs as they are not used in this design. This reduces the complexity of the code and saves RAM.

As the code for the Nano was derived from the GPSDO code, it is again written in C using the Arduino IDE. The frequency counter code is basically driven by the 1PPS interrupt. When a 1PPS interrupt occurs,  the interrupt service routine responds by locking in the 32-bit counter, clearing the counter, and setting a reset flag. The piece of code that locks in the current count onto an onboard register in the 74LC8154 and then clears the count to zero, takes some finite time between locking in and clearing. This means we miss some counts. I refer to this time as the latency. The firmware compensates for this latency by adding an offset value to the raw counter value. It turns out there 16 missing counts. (The previous GPSDO article explains how the 16 count latency offset was verified.)  In the operating code, the missing 16 count is added back in on every read of the counter.

Let’s take a look at performance. First, Figure 4 shows the minimum signal versus frequency.

Figure 4 Graph of the minimal signal versus frequency.

The minimum is generally less than 1 Vpp except at frequencies below 1 Hz where the sine wave minimum climbs. The maximum input voltage is 5 Vpp. As for precision, it typically reads frequency with a standard deviation 0.01 Hz (with 100 second gate time). Figure 5 shows the LCD pages for 100 second gate time with 10 MHz input signal.

Figure 5 Statistics on LCD with 100 second gate time with 10 MHz input signal.

On the GPSDO project, there was a page showing a histogram of the readings. This worked for that project as the frequency and the range of the frequency was known a priori, while on this frequency counter project there is no information on the frequency or the range of frequencies to display. Given that, the amount of RAM to store previous readings needed is limited, so that feature was removed.

(A note on standard deviation: engineers oftentimes assume standard deviation implies a normal distribution. That is not true. A standard deviation calculation does not assume any particular distribution.  In this particular case, the deviation I typically see is a distribution skewed to one side—not a normal distribution.)

To avoid suffering the wrath of my old physics 101 professor, care was taken to display data using mathematically correct significant digits. Freq, 1/f (period), Fmean, and Fstd conform (mostly) with standard mathematical rules for significant digits as based on the measured frequency and gate time. For example, after taking 100 readings, Fmean will display an additional digit after the decimal place. There is an exception to the conformity of significant digits. That is, the 1/f (period) displayed when measuring low frequencies…under 10 Hz. An example of the exception: when using an input signal of 6 Hz and a 1 second gate time, the period (1/f) will display 167 ms and not 100 ms as dictated by the rules for significant digits. This feels like it would be less confusing to the user.

Vigilant readers may have spotted a seemingly inconsistent design issue. I previously stated that there are gate times of 1, 10, and 100 seconds. I also stated the design is capable of reading up to 80 MHz using the 32-bit counter. So, what happens when we take a reading of an 80 MHz signal using a gate time of 100 seconds? The counter should read 8,000,000,000 after 100 seconds but we are using a 32-bit counter. A 32-bit counter will only count up to 4,294,967,295 (232-1). To overcome this issue, the code gets a counter reading (without resetting the counter) snapshot at 50 seconds and saves this as the intermediate reading. Then, when the final counter reading is taken at 100 seconds, it is compared to the intermediate reading. If the final reading is smaller than the intermediate reading, we know there was a rollover in the counter. In this case, the code will add 232 to the intermediate reading to get a true counter reading. This method gives us the most accuracy as we do not need to take 50 second readings and add them together, thereby avoiding a counter clear operation and a second offset correction.

All-in-all this frequency counter performs quite well, is simple, requires no calibration, gives good information about the connected signal, and fits nicely on my bench.

Complete project information for this can be found at, the open-source site: (or you can search for “DamianB2”).

Project information includes full KiCad project with schematic, PCB, and PCBA BOM.  Also included is a full assembly BOM, Arduino source code, 3D print files for the enclosure, link to 3D print files for the GPS module enclosure, artwork for the nameplates, various notes, etc.

Damian Bonicatto is a consulting engineer with decades of experience in embedded hardware, firmware and system design. He holds 30 patents.

Phoenix Bonicatto is a freelance writer.

Leave a comment