Introduction: High Resolution Frequency Counter
This instructable shows a reciprocal frequency counter capable of measuring frequencies fast and with reasonable precision. It is made with standard components and can be made in a weekend (it took me a bit longer :-) )
EDIT: The code is now available on GitLab:
Step 1: Old School Frequency Counting
The old school way to measure the frequency of a signal is to use a logic AND-gate, feed the signal to be measured into one port and a signal with an exactly 1 second high time to the other port and count the output. This works quite well for signals of a few kHz well into the GHz. But what if you want to measure a low frequency signal with good resolution? Say you want to measure the frequency of mains (here 50 Hz). With the old school method you will see a constant 50 on your display if you are lucky, but more likely you will see the display switch from 49 to 50 or 50 to 51. The resolution is 1 Hz, and that's it. You will never see 50.002 Hz unless you are willing to increase the gate time to a 1000 seconds. That's more than 16 minutes, for a single measurement!
A better way to measure low frequency signals is to measure the period of it. Taking mains as an example again, has a period of 20 millisecond. Take the same logic AND-gate, feed it with, say 10 MHz (0.1 us pulses) and your signal on the other port and out come 200000 pulses, so the period time is 20000.0 uS and that translates back into 50Hz. When you measure just 199650 pulses the frequency is 50.087 Hz, that's a lot better, and it is in just one second measuring time.
Unfortunately this does not work well with higher frequencies. Take for example, we now want to measure 40 kHz. With the same 10 MHz input frequency as the reference we now measure just 250 pulses. When we count just 249 pulses the calculation gives 40161 Hz and with 251 the result is 39840 Hz. That's not an acceptable resolution. Of course increasing the reference frequency improves the results but there is a limit to what you can use in a micro controller.
Step 2: The Reciprocal Way
A solution that works for both low and higher frequencies is a reciprocal frequency counter. I'l try to explain its principle.
You start off with a measuring time that is approximately 1 second, it does not have to be very precise but it is a reasonable time for a measurement. Feed this 1 Hz signal into a D-flipflop on the D-input. Nothing happens as yet on the output(s). Connect the signal that you want to measure to the CLOCK input of the D-flipflop.
As soon as the this signal goes from LOW to HIGH, the output of the D-flipflop transfers the state of the D-input to the output (Q). This RISING signal going is used to start counting the input signal as well as a reference clock signal.
So you are counting TWO signals at exactly the same time, the signal you want to measure and a reference clock. This reference clock has to have a precise value and be stable, a normal crystal oscillator is fine. The value isn't very important as long as it is a high frequency and its value is known well.
After some time, say a few milliseconds, you make the D-input of the D-flipflop low again. At the next CLOCK-input the output Q follows the state of the input, but nothing else happens because the micro controller is set to react to a RISING signal only. Then, after the measuring time is over (approx. 1 second) you make the D-input HIGH.
Again at the next CLOCK-input the Q output follows and this RISING signal triggers the micro controller, this time to end the counting of both counters.
The result is two numbers. The first number is the number of pulses counted from the reference. As we know the reference frequency, we also know the time it took to count those pulses.
The second the number is the number of pulses from the input signal we are measuring. As we started exactly on the RISING edges of this signal we are very confident about the number of pulses of this input signal.
Now it is just a calculation to determine the frequency of the input signal.
An example, lets say we have these signals and we want to measure f-input. The reference is 10 MHz, generated by a quartz crystal oscillator. f_input = 31.416 Hz f_reference = 10000000 Hz (10 MHz), the measuring time is approx. 1 second
In this time we counted 32 pulses. Now, one period of this signal takes 1 / 31.416 = 31830.9 uS. So 32 periods took us 1.0185892 seconds, which is just over 1 second.
In this 1.0186 second we also will have counted 10185892 pulses of the reference signal.
This gives us the following information: input_count = 32 reference_count = 10185892 f_reference = 10000000 Hz
The formula to calculate the resulting frequency is this: freq = ( input_count * f_reference ) / ref_count
In our example that is: f-input = (32 * 10000000) / 10185892 = 31.416 Hz
And this works well for low frequencies as well as high frequencies, only when the input signal comes close (or even higher than) to the reference frequency it is better to use the standard "gated"way of measuring. But then we could also simply add a frequency-divider to the input signal as this reciprocal method has the same resolution for any frequency (up to the reference again). So whether you measure 100 kHz directly of divided by an external 1000x divider, the resolution is the same.
Step 3: Hardware and Its Schematic
I have made a few of this type of frequency counters. Long ago I made one with an ATMEGA328 (the same controller as there is in an Arduino), later with ARM micro controllers from ST. The latest was made with an STM32F407 clocked at 168 MHz. But now I wondered what if I do the same with a *much* smaller one. I chose an ATTINY2313, that has just 2kbyte of FLASH memory and 128 bytes of RAM. The display I have is a MAX7219 with 8 seven segment displays on it, these displays are available on Ebay for just 2 Euros. An ATTINY2313 can be bought for around 1.5 Euros the rest of the parts I used cost just cents a piece. Most expensive was probably the plastic project box.
Later I decided to make it run on a lithium-ion battery so I needed to add a (LDO) 3.3V voltage stabilizer a battery-charging-module and the battery itself. This increases the price somewhat, but I guess it can be build for less than 20 Euros.
Step 4: The Code
The code was written in C with Atmel (Microchip) Studio 7 and programmed into the ATTINY2313 using an OLIMEX AVR_ISP (clone?). Open the (main.c) in the zip file below if you want to follow the description here.
First the ATTINY2313 was set to use an external crystal as the internal RC-oscillator is useless for measuring anything. I use a 10 MHz crystal that I tune to the correct 10 000 000 Hz frequency with a small variable capacitor. The initialization takes care of setting ports to inputs and outputs, setting up the timers and enabling interrupts and initialization of the MAX7219. TIMER0 is setup to count an external clock, TIMER1 the internal clock and also to capture the value of the counter at the rising edge of ICP, coming from the D-flipflop.
I'll discus the main program last, so next are the interrupt routines.
As TIMER0 counts up to 255 (8 bits) and then rolls over to 0 we need an interrupt to count the number of overflows. That's all TIMER0_OVF does, just count the number of overflow. Later this number is combined with the value of the counter itself.
TIMER1 can count up to 65536 (16 bits), so the interrupt TIMER1_OVF also counts the number of overflows. But it does more. It also decrements from 152 to 0 which takes about 1 second and then sets an output pin, going to the D-input of the flipflop. And the last thing that is done in this interrupt routine is to decrement the timeout-counter, going from 765 to 0, which takes about 5 seconds.
This is the TIMER1_CAPT interrupt that is triggered everytime the D-flipflop sends it a signal, at the rising edge of the input signal (as explained above). The capture logic takes care of saving the value of the TIMER1 counter at the moment of the capture, it is saved as well as the overflow counter. Unfortunately TIMER0 does not have an input capture function so here its current value and its current value of the overflow counter is read. A message-variable is set to one for he main program to tell it these is new data.
Next are two functions to control the MAX7219
While there is an Universal Serial Interface (USI) available in the chip I chose not to use it. The MAX7219 display needs to be controlled via SPI and that is possible with the USI. But bitbanging SPI is so simple that I didn't take the time to do it with the USI.
The protocol to setup the MAX7219 also is quite simple once you have read the manual of it. It needs a 16 bit value for every digit that consists of 8 bits for the digit number (1 to 8) followed by 8 bits for the number it needs to display.
The last thing is to explain the main program. It runs in an infinite loop ( while(1) ) but only actually does do something when there is a message (1) from the interrupt routine or when the timeout counter has run down to zero (no input signal).
The first thing to do when the variable message is set to one, is reset the timeout counter, after all we know that there is a signal present. The D-flipflop is reset to make it ready for the next trigger that will come after the measuring time (wait-a-second).
The numbers registered in the capture interrupt are added to give the reference count and input-frequency count. (we have to make sure that the reference never can be zero as we will divide by it later on)
Next is a the calculation of the actual frequency. I surely do not want to use floating numbers on a microcontroller with just 2kbytes of flash and only 128 bytes of ram I use integers. But frequencies can be like 314.159 Hz, with several decimals. Therefore I multiply the input-frequency not only with the reference frequency but also with a multiplier, and then add a number to where the decimal point should go. These numbers will get very very large when you do that. E.g. with an input of 500 kHz, a reference of 10 MHz and a multiplier of 100, this gives 5 x 10^14, that's really huge! They will no langer fit in a 32 bit number so I use 64 bit numbers that will go all the way up to 1.8 x 10^19 (that works fine on an ATTINY2313)
And the last thing to do is to send the result to the MAX7219 display.
The code compiles into some 1600 bytes, so it fits into the 2048 bytes flash available in the ATTINY2313.
Step 5: Accuracy and Precision
Accuracy and precision are two separate beasts. The precision here is seven digits, what the actual precision is depends on the hardware and the calibration. I calibrated the 10 MHz (5 MHz on the test point) with an other frequency counter that has a GPS disciplined oscillator.
And it works quite well, the lowest frequency I tried is 0.2 Hz, the highest 2 MHz. It is spot on. Above 2 MHz the controller starts to loose interrupts, not really surprising when you know that at 2 MHz input signal TIMER0 generates over 7800 interrupts per second. And the ATTINY2313 has to do other things too, the interrupts from the TIMER1, at another 150 interrupts per second and of course do the calculations, controlling the display and D-flipflop.
When you look at the actual device you'll see that I use just seven of the eight digits of the display. I do this for several reasons.
First is that the calculation of the input frequency is a division, it will almost always have a remainder, that you do not see as it is an integer division. Second is that the quartz crystal oscillator isn't temperature stabilized.
The capacitors that tune it to the correct 10 MHz are ceramic, very sensitive to temperature changes. Then there is the fact that TIMER0 does not have capture logic build in, and the interrupt functions all take some time to do their work. I think seven digits is good enough anyway.