Frequency Counting -- it's not sexy, but a precise frequency counter is a great -- and sometimes vital -- piece of kit to have handy, particularly if you're doing HAM RF projects or building scientific instruments. Of course, a good frequency counter isn't cheap, and you can't exactly shove one into a project's case easily -- or hook one up to an Arduino or your other microcontroller of choice. This project aims to solve those problems.
Maximizing precision and flexibility while minimizing cost are the name of the game. The initial design aims at creating a frequency counter that counts to a 1Hz precision in direct-count mode at a minimum of 50 Mhz. (Minor tweaks, selected parts, and a little luck should allow counting at up to 96 MHz.) The nature of the architecture allows for operation in direct or reciprocal mode, or the counter can function as an event counter.
The core of the counter is composed of 74HC series logic, along with an ATMEGA328P running Arduino firmware.
A few people previously have wondered why I'm going through all this hassle when there're plenty of cheap ATMEGA or PIC-based frequency counter projects around. The answer is, simply, that all of those solutions aren't very good when you're looking for something capable of ridiculously high precision, which is my primary goal, or they're unwieldy to use.
Running a signal directly into an MCU, even one with a high clock rate, generally involves using interrupts that trigger constantly. At high frequencies, this leads to having very few, if any, clock cycles available for the processor to do anything else. Even counting a signal that's a couple hundred KHz on an ATMEGA running at 16 MHz results in horribly sluggish performance when doing anything else. Of course, these solutions also rely on the base clock the MCU is running on, which are generally not selected for high precision. Furthermore, unless you're programming in assembly, you don't really know exactly how many clock cycles it takes to perform a calculation, even something as simple as reading a pin and incrementing an integer, so you have no idea how the code itself is affecting the accuracy of your measurement. This circuit eliminates everything to do with the processor (with the exception of silicon bugs in the calculations) as a source of error.
In addition to the above error sources, most of these designs use a divider circuit to lower the frequency of the incoming signal. If you're dividing the frequency by 4, then you reduce your maximum precision accordingly.
There are several sources of error that are inherent to the methods used here. I'm going to talk about a few of them, and how I'm going to reduce or eliminate them.
The first is timebase error. There's not much that can be done to increase the initial accuracy of a crystal, but one can increase its long-term stability by making a ghetto OCXO -- an oven-controlled crystal oscillator. This is actually pretty easy -- and is what I'm planning on doing for the basic timebase. By creating a feedback system using a negative temperature coefficient thermistor, a transistor, and a couple of resistors, you can send current through another few resistors to warm up the crystal and hold it at a stable temperature. This reduces short and long-term drift, particularly if you always keep the OCXO powered.
Another source of error inherent to this design is flow-through error generated by the delay that it takes for the flops to flip in a chain through the counter stages. At lower frequencies this isn't significant, but it could add up to at least a few counts per reading at higher frequencies. This error can affect both the timer counters and the signal counters. However, with a higher-frequency timebase, one can do something like a cross between direct and reciprocal counting -- instead of assuming that the timebase counter has stopped where you want it, you read the overcount. A little math allows you to eliminate or at least significantly mitigate this error. This should work better the higher the frequency of your timebase, and worse the lower the frequency of your timebase. The basic 32KHz timebase will be useless for this kind of correction, because the time for ripple propagation is significantly less than 1/32678th of a second, the period of one cycle of the timebase.
The gate itself should be an insignificant source of error in the circuit; except at frequencies approaching 100 MHz, the MUX and gate operation should be much faster than the frequency being measured.
The Arduino itself is irrelevant to the inherent precision and accuracy of the circuit, the way it is designed. It doesn't matter how sloppy the timing of the code is; all the Arduino does is say "GO!" then wait for the rest of the circuit to say "Done!" before reading the data, doing the calculations, and resetting the circuit.
Finally, I'll include code to allow for calibration of the frequency counter from a reference source. This will basically just add or subtract a value to the timebase count before doing the rest of the frequency calculation.
0: The user sets the trigger points on the signal input stage so that the input is converted from sine waves (or whatever is coming in) to square waves. This is independant of the operation of the frequency counter loop.
1: On power-up, or upon a configuration change command from the master MCU, the Arduino sends a halt signal to the Gate, preventing signals from coming through to the counters. The counters are cleared by a signal from the Arduino. The Arduino selects the bit on either the timebase-counter block or on the signal-counter block which will halt the counting. (For example, to set up a one-second gate time using a 32.768 KHz timebase, it will set the MUX to trigger on Bit 15 of the timebase-counter block.)
2: The Arduino sends the gate-ready signal.
3: The gate waits for a falling edge on the timebase signal and then opens the gates on both signals, allowing them to flow through to their respective counters.
4: When the count on the respective counter block reaches the set bit, the output signal from the MUX battery goes high. The gate closes and halts the signals from both the timebase and signal inputs from coming through, and the Arduino eventually picks up on the fact that the counting is done.
5: The Arduino reads both counters by cycling through the MUX chips. It then calculates the frequency (or the count) based on its settings, and places this value in memory for asynchronous reading via I2C. A flag, new_data, is set TRUE. (Upon an I2C read, new_data is set to FALSE to allow the master MCU to tell if it's reading a new value or one it has previously read.)
The primary considerations when designing this project are precision and cost.
The frequency counter will operate at at least 50 MHz. The 74HC4040 chips can potentially be run up to 96 MHz, but it seems like one may need to test and select the best chips for the first, high-frequency input stage to achieve this maximum rating. Additionally, very high-speed comparators aren't cheap, and I'm trying to keep the entire BOM, including the board, to below $50.
The counter can operate in direct, reciprocal, or event counter modes.
In direct mode, the counter will have a 1Hz precision, +/- 1 Hz, with the timebase error added on top.
The counter will communicate via I2C with another microprocessor. The board is 'headless' -- there is no display included. The counter will be configured and read via I2C. (Setting the input signal setpoints will require knob-twiddling.)
Both the included timebase and the signal input modules will be easily swappable for upgraded modules. This will allow for user-created timebase modules or connection to a surplus rubidium source, and for upgrading the input modules for more flexibility and higher speed.
The board will run on 9-12V input and have an onboard regulator.