PIC Projects

150MHz Frequency Counter

My goal was to design a simple and user-friendly frequency counter which would be capable to handle radio FM frequencies and have an autonomous power supply. Powering it from batteries benefits to the device portability and makes working with it more convenient by eliminating a mess of power cords in a home lab. I use it just occasionally and a small size is a bonus simplifying its storage in a table drawer. Most of similar devices I have found on the Internet use an LCD module with a built-in controller. Such a device draws pretty much current. Also, many high-speed counters use power-hungry ICs which makes it difficult for a battery operation. Finally, many projects are poorly documented which makes any modification unnecessary difficult. So, I started my own design which uses modern high speed and low-power ICs and can work from a single AA cell.

Device features

  • Tested frequency range: 1Hz - 150MHz
  • Amplitude range: 250mV - 5V
  • Resolution: up to 5 decimal digits
  • Guaranteed accuracy: 4 decimal digits
  • Counting cycle: 0.1sec or 1sec; automatic selection
  • Fully automatic processing, needs just power switch to operate
  • Works from a single AA battery; drawing current < 15mA
  • Total price of components: about $20
Counting 106.25 MHz

Accuracy considerations

Crystals used for generating the timing intervals in frequency counters contribute greatly to their accuracy. I used a standard crystal with nominal frequency f0=100KHz and frequency tolerance Δf/f0 = ±30ppm. This means that the actual frequency is in the range 100KHz·(1 ± 3·10-5). In other words, the actual frequency can differ from the nominal one of 100KHz on maximum 3Hz. How does this affect the accuracy of the counter?

Well, the counter just counts the number of cycles occurred within a measuring interval of 0.1sec. Therefore, its accuracy is determined by the accuracy of the measuring interval. In my counter this interval is set up as the duty cycle of the PIC's PWM module. The formula for the duty cycle is (CCPR1L:CCP1CON<5:4>)·Tosc·(TMR2 prescale value) = 625·Tosc·16, where Tosc = 1/f0 = 10-5sec. Taking into account the crystal accuracy results in the timing interval of 104·10-5(1± 3·10-5)= 0.1± 3·10-6sec. In other words, the accuracy of the timing interval equals the accuracy of the used crystal. It turns out to be a general phenomena - no matter how you form the frequency measuring interval (provided that it is a linear function of the nominal crystal period, which is the case of all counters I have ever seen) the accuracy of that interval just equals the accuracy of the crystal.

Assume one of the extreme cases - the timing interval length is 0.1+3·10-6sec. Let the input frequency is N Hertz (=cycles per second). The counted value will be then N·(0.1+3·10-6) = N/10 + (N/10)·3·10-5. In 0.1sec interval we count N/10 frequency cycles, so the difference between our measurement and the actual value of N/10 is then (N/10)·3·10-5. For frequencies higher than 333KHz (that is, 3.33·105Hz) this difference exceeds 1, so for those frequencies our counter would show an incorrect value of N/10. An important consequence of these considerations is that one can only guarantee 4 higher order digits of the counted frequency N/10, sometimes 5 digits. This is supported by two following experiments of measuring the oscillating frequency. In the first experiment I configured PIC12F683 to be clocked by a 14.318MHz crystal and measure the clock frequency directly at its CLKOUT pin. The counted frequency matches the nominal one of the used crystal. In the second experiment I use an integrated crystal oscillator which is designed to run at 46.61512MHz and which is salvaged from an old computer board (minimum 10 years old). The counted frequency has a small mismatch in the fifth decimal digit.

Counting 14.318 MHz Counting 46.61512 MHz

A similar analysis could also be done for other measuring interval lengths, but the conclusion would be the same: by using a crystal with the frequency tolerance of the order of several tens ppm (that is of the order of const·10-5 with some const>1), one can not guarantee 6 or more higher order decimal digits of the result. As we cannot guarantee the validity of the lower decimal digits, it makes no sense to display them at all. This way I have decided to display only up to 5 higher order digits of the result in my frequency counter and just ignore all the other digits, if any.

Note that the crystal frequency tolerance is not the only limiting factor of the oscillator accuracy. One should also consider temperature influence and the crystal aging effect. However, for the operating temperatures in the range 10°C - 40°C (50°F - 104°F) the contribution of these factors to the total accuracy is at most ±10ppm, so we still can guarantee 4 to 5 higher order decimal digits of the result.

Formatting the output

The LCD I used can only display eight 7-segment characters, so a special schema for displaying the frequency ranges is needed. This is shown below. The yellowish boxes indicate (invisible) digit place holders on the LCD. The non-meaningful leading zeros are not displayed. The frequency range is encoded by the two rightmost symbols. The symbol E represents 10 and the number followed it is a power of ten in the frequency reading. Thus E0 stands for 100 (i.e. Hz), E3 is for 103 which is KHz, and E6 is for 106, that is, MHz. To simplify the LCD reading the decimal point is shown for numbers having at least 4 decimal digits.

LCD reading Frequency range Counting time
0. 0. 0. 0. 1 0. E 0 0 - 9 Hz 1 sec
0. 0. 0. 1. 2 0. E 0 10 - 99 Hz 1 sec
0. 0. 1. 2. 3 0. E 0 100 - 999 Hz 1 sec
0. 1. 2. 3. 4 0. E 3 1 - 9.999 KHz 1 sec
1. 2. 3. 4. 5 0. E 3 10 - 99.999 KHz 1 sec
1. 2. 3. 4. 5 0. E 3 100 - 999.99 KHz 0.1 sec
1. 2. 3. 4. 5 0. E 6 1 - 9.9999 MHz 0.1 sec
1. 2. 3. 4. 5 0. E 6 10 - 99.999 MHz 0.1 sec
1. 2. 3. 4. 5 0. E 6 100 - 150 MHz 0.1 sec

The counted frequency is internally represented as an integer number with 1 to 8 decimal digits. The values having more than 5 decimal digits are rounded off to the nearest integer having only 5 non-zero higher order digits. For example, the value 12,345,678 is rounded off to 12,346,000 (and 12.346 E6 will be displayed), whereas 12,345,456 is rounded off to 12,345,000 (and 12.345 E6 will be displayed).


The counting frequency first comes to a preamp built on a high-speed comparator LT1715. According to the datasheet it can flip at 150MHz. Only one comparator is used out of two in the package. The inputs of the other one are attached to the ground and the +5V bus to prevent its flipping and exclude its influence on the working one. The comparator is the slowest device in the chain and determines the frequency counting range. I borrowed the idea to use a comparator from an article at LinuxFocus.org where MAX913 comparator is used. However, it seems that today MAX913 is practically obsolete and LT1715 is a faster device. The 10K resistors shift the comparator inputs levels to approx. 2V. The 100Ohm resistor is used as a small addition to 10K to provide a slightly higher voltage on the inverting input. This way the output in the idle state is 0. The difference between the input DC voltages is about 110mV and this actually determines the preamp sensitivity. To guarantee a reliable flipping however, the input voltage amplitude should not be below 150mV. Since LT1715 has internal hysteresis there is no need in the feedback resistor used in the original article mentioned above. I have also eliminated a 10K resistor at the comparator output (which is still shown on my schematic).

Schematic Board layout

The output of the comparator is connected to a 4-bit asynchronous binary counter SN74LV161A with maximum clock frequency of 220MHz when powered from 5V. The counter is used as a pre-scaler to the PIC's counter TMR1. It divides the input frequency by 16, so the PIC gets maximum 10MHz which is in the range of the minimum input clock period of 60ns (this corresponds to approx. 16MHz) required for TMR1 in the asynchronous mode. Furthermore, the pre-scaler improves the clock waveforms for TMR1 which is a bonus. The TMR1 16-bit counter works in asynchronous mode, which is needed for counting external with respect to the PIC frequencies. All 4 outputs of the counter are connected to PIC and the counter value forms the 4 LSB of the measuring frequency.

The heart of the frequency counter is PIC16F648A microcontroller (PIC16F628A can be used without any changes). It generates time intervals for counting the frequency, formats the counted data according to the schema above and sends it to the LCD controller on PCF8562 by using the I2C interface. Its functionality is described in detail in the software section below.

The PCF8562 controller takes care on generating all needed waveforms for the LCD. The one I use (VM-838 by Varitronix) works in the 1:3 multiplex mode with 1/3 voltage bias, thus has 3 backplanes. The LCD bias voltages are generated inside the controller and I would name it as a perfect device for driving LCDs. I use only 24 out of maximum 32 pins for driving my LCD, the remaining ones are just left open on the PCB. The controller is very well documented in the supplied datasheet, which makes working with it a pleasure. Just connect it to the LCD and forget about it. The controller IC is located under the LCD on the board. Its VLCD pin gets a 3.3V for powering the LCD. I used an LDO for this purpose (MCP1700T), but just two resistors shown on the schematic would be fine too, because LCD itself draws less than 100μA of current. In my experiments I short cut VDD and VLCD pins of the controller, thus powering the entire LCD block from 3.3V, it worked fine.

The last IC involved into the project is a DC/DC converter based on NCP1400A. It provides 5V from a single AA battery to power all the devices on board. The current drawing from the converter is about 10mA at idle, out of which 9mA are consumed by the input comparator. However, if you measure the current drawn by the entire circuit from the battery, don't be shocked if you see about 5 - 7 times larger value. The product of the battery voltage and the battery drawn current is about 1.2 times larger than the one for the current drawn from 5V. My digital multimeter shown the peak current through the inductor of about 70mA. This is due to the DC/DC step-up converter and is normal according to the converter datasheet. Since the current peaks are pretty sharp, the average drawing current is much less but at least 40mA. This way the counter can continuously work for about 40 hours from a single AA battery with capacity of about 2000mAh. The PCB is one-side but has several jumpers on the back side. The copper surface on the back side is used for an extra grounding. The back side has only 4 components: input BNC socket, AA battery holder and 4 metal posts (from RadioShack), and a power sliding switch (AS12AH manufactured by NKK). The PCB is designed for SMD resistors and caps in the 0603 package, but the ones in a larger 0805 package can be used as well (that is what I did). The PCB provides 3 pads connected to the unused PIC's pins (RA0, RA1, and RA5) which can be used, for example, to connect the counter with a computer.

Top side of the PCB Bottom side of the PCB

The 10μH inductors can probably be taken off, at least I have not noticed any difference in performance. But I won't do it, anyway. They are salvaged from an old computer sound card. The 22μH power inductor is from PM54 series manufactured by Bourns. The 100KHz crystal is from Citizen CS250S series and has frequency tolerance ±30ppm. I got all these parts from Digikey. The PIC should be soldered to the PCB before SN74LC161A in order it could be programmed via the ICSP interface. I soldered my ICSP cable directly to the PIC pins for programming.

Software design

The frequency starts being counted by SN74LC161A pre-scaler. Every time is overflows (after counting 16 cycles) it produces an input for the PIC's TMR1 counter that works in the asynchronous mode. TMR1 is a 16-bit counter configured with 1:1 (PIC's internal) pre-scaler. Therefore, SN74LC161A together with TMR1 are capable to count 220 = 1,048,576 cycles in 0.1sec (which corresponds to approx. 10MHz input frequency). Every time when TMR1 overflows it rises an interrupt, whose only task is to increment another 8-bit counter. This way we can count up to 228 = 268,435,456 (which would correspond to the input frequency of 26.8GHz). The point is that the last counter never overflows in the range of input frequencies.

As soon as the frequency counting interval is over, the PIC sets the ENP input of SN74LV161A low, thus preventing it from further counting. After this the values of all counters are read and processed for displaying. Since the interrupt processing is involved, the measurement interval timing must be generated in another thread than the CPU one. A standard solution is to use another timer for that (e.g. TMR0). However, this way it is inconvenient to check its overflow event. This is needed for sending a signal to SN74LV161A to stop counting. As TMR0 does not have an output directly to the PIC's pin, and polling it or using an interrupt is inappropriate, because it contributes a hardly predictable delay to the measuring interval. I used PIC's PWM module for composing the control signal ENP for SN74LV161A. Another controlling signal CLR for clearing the counter is provided in software right after the PWM duty cycle is over and the counter value is read. This signal goes up again when PWM is low and this way the counting starts over as soon as PWM gets high again. This cycle repeats over and over. Note that in order to enable SN74LV161A for counting both ENP and CLR inputs must be high.

The above paragraphs briefly describe the device functionality for the default frequency measurement interval of 0.1 sec. Again, the frequency is counted asynchronously with the PIC's clock and the counter value corresponding to the maximum counting frequency has 25 binary bits. The 4 lower bits of that value are stored within the SN74LV161A binary counter. The middle 16 bits are represented by the TMR1H and TMR1L, and the 5 upper bits are stored in a special variable that counts the number of TMR1 overflows. Each TMR1 overflow triggers an interrupt, whose only purpose is to increment a 1-byte value. According to the PIC specifications, the minimum period at T1CKl input is 60ns, which corresponds to the maximum frequency of approx. 16MHz. This way the frequency at the SN74LV161A input is 256MHz which matches its guaranteed speed when being powered from 5V. However, this frequency exceeds the maximum of 150MHz for the LT1715 comparator.

Assuming the maximum frequency at the T1CKl of 16MHz, the time between TMR1 overflows is at least 60ns · 216 = 3.9msec. In the current implementation the interrupt service routine (ISR) takes 12 instruction clocks and each clock interval is 40μsec. Therefore, the ISR processing time is 12 · 40 = 480μsec, which is significantly less than 3.9msec, so the ISR is guaranteed to be completed between any two TMR1 overflow events.

Similarly, assuming the frequency at the T1CKl input is 16MHz, the maximum number of times TMR1 overflows during 0.1sec of frequency counting is 0.1 ·(16·106 / 216) = 25. Therefore, its maximum bit length is 5 resulting in maximum of 4 + 16 + 5 = 25 bits of frequency counting. The maximum value of the counter is then 25 · 24 · 216 = 26,214,400. This implies that the frequency counter decimal value consists of maximum 8 decimal digits. However, we only need to display at most 5 of them, according to the accuracy considerations above.

The frequency counting cycle has length 160msec out of which 100msec are actually for the frequency counting and the rest is for processing. The processing consists of rounding off the counting results, converting it into BCD representation and further to the 7-segment LCD codes, and sending the entire data to the LCD controller PCF7562. Although the I2C interface of PIC8562 is capable to run at 400KHz frequency, we drive it at approx. 2KHz, as sending a bit takes 9 instruction cycles of 40μsec each (the PIC timing is provided by the same 100KHz crystal which is used for the frequency counting; this way the oscillator period is 10μsec and the instruction period is 4 times that value, i.e. 40μsec). Thus, sending a byte to the LCD controller along with necessary initialization takes about 100 operation cycles and updating all its 8 digits (with sending two extra control bytes for the I2C interface) takes about 45msec. It turns out that the entire processing takes up to 1900 instruction clocks in the worst case, out of which 762 clocks (30.5msec) are for the BCD conversion, 44 clocks (1.76msec) is for rounding off, and 24 clocks (0.9msec) is for computing the order and position of the decimal point. All together the entire processing with sending the data to the LCD takes about 87.7msec, so there is no way to complete it during the time when PWM is low. Unfortunately, the longest PWM period by using a 100KHz crystal is about 165msec. Using a slower crystal won't solve the problem, because the processing would also take longer and the ratio between the processing time and the PWM pause remains the same. Note that the BCD conversion code can be much simplified by using, for example, Microchip AN526 and its well-known modification my Mike Keitz. However, this routine is about 3 times slower in the worst case and takes up to 2200 instruction cycles (approx. 90msec). It actually does not matter for this application. I just attempted to fit the entire processing within 60msec PWM pause interval. Although I was not able to do it, I decided to leave the conversion routine as is.

The outcome of the previous discussion is that to complete the processing we need to skip one PWM cycle. This is achieved by three local loops at the end of main program loop. During this time the CLR signal for the SN74LV161A remains low, thus preventing it from counting. Therefore, after the default counting cycle of 0.1 sec we have a processing pause of 220msec, which determined the LCD updating period of 2*160=320msec. This can be lowered by reducing the PWM period down to, say 120msec. In this case, the LCD updating period would be 120*2=240msec. I did not do it because working with non-stable sources the display would update itself too fast. This is a personal preference.

The frequency in binary is represented internally as a 4-byte variable freq. The lower 4 bits of it are always 0, as the SN74LV161A value is taken as the higher-order nibble of PORTB (RB<7:4>). The processing starts with converting it into the BCD representation. This is done by subtracting from freq the powers of 10 starting with 107 down to 101. Since the higher powers of 10 have more than 4 tailing zeros in binary, the subtraction only involves either 3 or 2 bytes because the lowest byte is 0. Lower powers of 10 do not have 4 tailing zeros, but at this time the processing number becomes smaller too, so a 3-byte operation suffices. Actually, after getting 4 higher order decimal digits, the processing number becomes no longer than 16 bits. For this purpose we shift it 4 bits to the right (recall that the 4 lower bits of freq are always zeros), and starting with that point all the operations involve 2 or 1 bytes only. Since computing of each decimal digit has some particularities allowing to save CPU cycles, they are splitted in 7 subroutines. As soon as the first non-zero digit is found, its position is stored in the variable first and starting from that point the code counts the number of decimal digits of freq and stores it in variable freqLen. The rounding off takes place if freqLen is at least 6. The position of the decimal point and the order is uniquely determined by the value of first and is taken from two tables at the beginning of the code. This variable also determines the number of leading blanks to be sent to the LCD.

If it occurs that the counted frequency has only 4 or less decimal digits (but is non-zero), the device switches to a 1sec frequency counting mode and counts the frequency again to get one more decimal digit to display. In this mode (which is stored in the variable mode: it is 0 if the default counting interval of 0.1sec and 1 otherwise) the maximum input frequency does not exceed 99.999KHz, which takes up to 3 bytes in binary. At this point it is important to note that TMR1 never overflows for such frequencies, so TMR1 interrupt is not triggered. However, the PWM module cannot be used for so long periods. But since no interrupts are involved, we do not need a different thread for timing the frequency intervals. I compose the SN74LV161A controlling signals directly from the main CPU thread and to guarantee a 1sec frequency measuring interval between two pulses I use a loop that runs exactly 25000 instruction cycles, thus producing an delay of 25000·40μsec = 1sec. To make sure, the TMR1 interrupt is disabled for that period and is enabled right after it. After a 1sec interval the further processing just goes along the lines as described above. Therefore, if the input frequency is below 100KHz it is measured twice: once with 0.1 interval and again with a 1sec interval. This way the display is updated after every ca. 1.2sec, which is quite appropriate.

Working with the counter

I must note that the counter is best suited to measure the square wave pulses. This partucularly concerns low frequencies (below 1KHz or so). If you attach it to a very slowly raising sine wave, the LT1715 starts to generate and the frequency is shown incorrectly then. I tried to fix this by increasing the comparator hysteresis but did not suceed. A possible work around is to direct such signal directly to the SM74LV161A clock input.

Anyway, my main goal was to count relatively high frequencies exceeding several hundreds of kiloherz. In most cases for strong square-wave signals one does not need to connect the ground of the counter with the ground of the device. Just touch the hot point with the tip of a stangard scope coax cable to measure the frequency. For lower frequencies, however, connection of the grounds is needed.

Fixing some design flaws

If I would do it again...

The battery holder, power switch, and the input connector are mounted too close to each other. Actually, to mount them together I had to file the battery holder a bit. Making the PCB just 1cm wider would eliminate this problem. Alternatively, one could use another battery holder with no wires running out.

Another thing is that the heavy battery makes the PCB not very stable on a table. With the cable attached, the PCB tends to flip over by rotating the cable. I do not know if this is a disadvantage, since I often turn it towards me anyway and let it have about 30° angle with the surface. If needed, this problem could be eliminated by making the PCB a bit higher and moving the posts below the battery. Alternatively, one could screw the posts to a more solid basement or place the unit into a case.

I would replace the power switch with a button that turns the device on for, say, 15 minutes, or so and turns it off afterwards. Several times I forgot to shut is down and discovered this on the next day or later. This does not help to save the battery. This would require some extra code, of course and a line from PIC to NCP1400 to control its EC input. Next time I would try MAX756 DC/DC converter - it seems to be more efficient. Furthermore, consider adding a connector for an external 5V power supply and connect it directly to the output of NCP1400.

Although the frequency order indication is pretty clear, it is a bit cryptic. I would think on using a 14-segment 8-digits LCD instead to be able to display Hz, KHz, and MHz instead of E0, E3, and E6. A suitable LCD model for this is Varitronix VIM-878. The same controller IC can be used with that LCD, as it can drive 32 output pins in the 1/4 multiplex mode.


Last modified:Sun, Jul 20, 2008.