An Arduino frequency counter

An Arduino frequency counter by WA5BDU

 

 


 

Introduction

This is going to be a fairly long description of the frequency counter and the ICs and circuits that make it go. That’s for people who might want to roll their own or modify this one. Otherwise, if you’re mainly interested in building one, gloss over all those details and just focus on the schematic diagram and source code.

 

A few years ago I wanted to design and program a frequency counter that could be embedded in a transceiver and output the frequency in Morse. I’d mostly moved from the PIC to the Arduino but I learned that the Arduino’s ATMega family of AVR microcontrollers could not perform high speed frequency counting directly like the PIC can. The PIC12F683 that I used has a prescaler front end that runs independently of its internal clock and can capture counts at over 100 MHz and as high as 140 MHz in my experience. It’s described in my blog linked below:


http://wa5bdu.blogspot.com/2017/11/pic-frequency-counter-with-morse-output.html


While doing my homework for that project, I got a  suggestion from one ham that I try the 74LV8154 32‑bit counter as a front end for an Arduino. It looked interesting so I took note.

My current need or at least my desire, was to design another frequency counter that could output readings at regular intervals to a serial port so they could be captured in a .CSV or other data array format and plotted. This was to plot drift and detect sudden jumps in frequency in the VFO of an old boatanchor I’ve been working on.

 

The 74LV8154 32-bit counter

 

This is a great chip for this purpose. It has a 32 bit counter so it can go to 4.29E9 before rolling over. The 32 bit number is read  from four registers using 8 dedicated pins. Selection of which of the four registers to put on the pins is made using four dedicated pins, GAL, GAU, GBL, GBU, meaning A-lower, A-upper, B-lower, B-upper. (The counter can be divided into two 16-bit counters ‘A’ and ‘B’ but here we use them as one big 32-bit counter.)

Counting proceeds when –CCLR is HIGH and stops with counts cleared to zero when it is LOW. So the task is to make –CCLR HIGH for a precise amount of time (about 1 second in this case) and read the count at the end.

The internal count is transferred to the four output registers by a high to low transition on the RCLK pin. Timing of this action is important as we’ll see.

For the 32-bit counter, pins 1 (CLKA) and 2 (CLKB) are tied together and the signal to be counted is connected there. The chip is being operated at 5 VDC supply voltage, so the input signal should be approximately at the TTL level.

The data sheet doesn’t give the maximum frequency in direct terms, but I’m counting 50.125 MHz on it.

 

The HEF4521B, 24 stage divider with crystal oscillator

 

This is the chip that generates the approximately 1 second timebase signal. I actually made a version at first that didn’t use an external crystal timebase but instead let the Arduino generate it with a timer interrupt. It worked pretty well but had about 5 to 10 Hz of wobble in the last digit when measuring a 7 MHz signal. I wanted to get better stability.

I considered using a 32,768 Hz crystal such as are used in clocks and watches, but I didn’t think that could give the stability I wanted. I chose a 4.096 MHz crystal from my parts bin. It’s not an exact power of 2, so I don’t get an exact 1 second timebase but that’s fine as the actual value is accommodated in software. I need a 0.5 Hz signal to give the 1 second high time. The Q23 output on pin 15 does that, since 2^23 is 8,388,608. This gives me a 0.48828 Hz output with a period of 2.048 seconds and a high time of 1.024 second.

The data sheet gives component values for 50 kHz and 500 kHz crystals. I wasn’t sure if it could go to 4 MHz and did have difficulty until I noticed that in addition to Vdd and Vss pins, there are also Vdd1 and Vss1 pins that need power. No power, no oscillation. I stuck with the recommended values for resistors and capacitors except for making the the series resistor 3k3 Ω instead of 47 kΩ recommended for 500 kHz.

Not much else to say about this chip. It gives a lot of flexibility for your choice of crystal by having outputs for Q18 through Q24.

 

Logic and putting it together

 

 

The basic idea is pretty simple. Connect the 1 second timebase clock to –CCLR so the chip will do a ~1 second count while it is high, then transfer the count to the output registers and read them into the Arduino, assemble and do arithmetic, essentially dividing the number of counts by the time of the timebase to give frequency.

There are a couple of complications, naturally. I’d like the end of the counting period to activate RCLK which will transfer the count to the registers. But this takes a low to high transition and at the end of the counting period, the clock is going from high to low. I hoped to avoid adding any more ICs to do miscellaneous functions, no NAND, NOR or inverter packages if possible. So I used a single transistor as an inverter, which you see in the schematic. Its output is –CLOCK or “NOT CLOCK”, being 180 degrees out of phase from CLOCK and it is used to trigger RCLK.

The next complication is with the signal to –CCLR. It goes low at the end of the counting period, which triggers RCLK, but it also clears the count in the same instant. So I have a potential timing race: Will RCLK cause the count to be latched to the registers before the simultaneous –CCLR clears it to zero? I don’t know the answer, but I didn’t want to leave it to chance.

To deal with this, I have the Arduino delay the low signal to –CCLR with one of its I/O lines. But I don’t want any software latency at the other end, where –CCLR is taken high, so I want the signal to be “armed” before that transition arrives. That’s the function of the two diodes, which I guess you’d call a negative logic AND gate. Both signals must be low for a low level to reach –CCCR.

The Arduino also needs to know when to enable and when to block the clear signal. So another I/O line monitors the clock signal. There’s plenty of time during each half cycle for it to do its thing. During the positive half, it raises the output signal to block the clear. After the signal has gone low, -CLOCK has triggered RCLK and the output signal can be take low to clear the count and allow the CLOCK signal to start counting at the transition to high.

I hope that all made sense.

The single IC version

In this version the HEF4521B and associated components plus the inverter are omitted and the clock and latch signals are provided by the Arduino. I’m posting a separate source code for this version since it works well enough to be useful and simplifies the hardware.  Arduino I/O line D6 goes to the RCLK pin and line D7 goes to –CCLR.

Displaying the output

Since my original intent was to capture data over time for plotting, I’m printing the frequency to the serial port on one second intervals. Note that on the Arduino, you can turn on a serial monitor by pressing Control-Shift-M and see this output. On the version without the crystal timebase I was trying to quantify the amount of ‘jitter’ I get from software uncertainties. I wanted the deviation from average, not from some absolute value, so I keep a running average of the last ten counts and display each reading’s deviation from that average.  Here it is now looking at a 50.125 MHz signal:

frequency: 50124948  Average10: 50124948  Deviation: 0
frequency: 50124948  Average10: 50124948  Deviation: 0
frequency: 50124948  Average10: 50124948  Deviation: 0
frequency: 50124948  Average10: 50124948  Deviation: 0
frequency: 50124948  Average10: 50124948  Deviation: 0
frequency: 50124948  Average10: 50124948  Deviation: 0
frequency: 50124948  Average10: 50124948  Deviation: 0

So this is pretty solid. Usually there’s at least a bobble in the Hertz digit.

Here’s an output taken for the version with timing taken from the Arduino, looking at a 7.150 MHz signal:

frequency: 7149968  Average10: 7149966  Deviation: 6
frequency: 7149965  Average10: 7149967  Deviation: 1
frequency: 7149967  Average10: 7149967  Deviation: -2
frequency: 7149965  Average10: 7149967  Deviation: 0
frequency: 7149972  Average10: 7149966  Deviation: -1
frequency: 7149968  Average10: 7149967  Deviation: 5

I presume that at 50 MHz the deviations would have been about seven times as great. So … this was not bad but the addition of the crystal timebase was worthwhile.

Other incomplete things:

I: Other output options

An LCD or other display independent of a PC or serial monitor would be an obvious addition to consider.

I think my first move in this direction would be to add output in Morse, like the AFA (Audible Frequency Annunciation) type output of my PIC counter. I’d want a pushbutton to tell the Arduino when to speak up. I’m close to having this working.

II:  Other time base and calibration options

The one second time base is good as it gets you 1 Hz resolution. But a 0.1 second measurement would be good too as it would update ten times as fast. Since I’m dealing with powers of two, I’ll probably go with a 1/8 second measurement. Then I just multiply my counts by eight and process as usual.

Also, wasting one second for every second spent measuring isn’t good. I’m sure there’s going to be a way to reset and restart the time base so that the time between measurement periods is kept to a minimum.

 As for calibration, right now my method is pretty crude. I measure a known frequency with no correction, then take a ratio of the reading to the actual frequency and use that to correct the measuerement. It works, but having to revise my source code if things change isn’t good. I haven’t yet tried a trimmer on the crystal to see if I can tweak it in. Doing the software thing once and then tweaking a trimmer thereafter would be a good approach.

Also though, my adjustment math in software requires me to use floating point math. The numbers are just too big even for unsigned long integer math. But floating point limits the resolution to something above one part in 1E7. A good solution would be to have a crystal that will divide down to exactly 1 second, with a trimmer. Then I could avoid the software math corrections entirely. Update: I’ve got some 4.194304 MHz crystals on order that should do the job.

IV:  Input signal conditioning and amplifying.

Currently I’m using the counter with input signals of at least a couple of volts p-p at the input.  Some sort of amplifier plus limiter/squarer is normally used to increase sensitivity. The one from my PIC counter made from a single NAND gate gave me sensitivity in the range of 35 mVpp to 180 mVpp. Take one section of a 74HC00 NAND gate package. With one gate, tie the inputs together and feed them through a 0.1 uF capacitor. Connect a 22 k-ohm resistor from output to input. This is a simple solution that works great.

Link to source code for version with the crystal time base:

http://www.wa5bdu.com/wp-content/uploads/2021/01/Freq_counter_xtal.ino

Link to source code for a simpler version where the Arduino provides the time base:

http://www.wa5bdu.com/wp-content/uploads/2021/01/Freq_counter.ino

Link to the schematic diagram:

http://www.wa5bdu.com/wp-content/uploads/2021/01/Schematic-snip-frequency-counter-1.jpg

Programming the Si5351a synthesizer

 
What is being discussed here

 

I’m going to discuss how a program, or my Arduino program specifically, calculates register values for the Si5351a to put it on a specific frequency. I’m not going into details of how the register values are formatted into bit fields and actually sent to the chip via I2C.

 

Acknowledgements and sources

 

I’m not sure exactly which sources I borrowed pieces of code from. I took bits from libraries or programs and changed a few things. I know the method of producing quadrature output came from Hans Summers, G0UPL, and some of the basic calculation of register values did as well. A lot of what I’ve done here is to simply “reverse engineer” the code to see what it’s doing.

https://www.rfzero.net/tutorials/si5351a/ provides a nice overview of the chip and programming process.

Silicon Labs AN619 is a first resource. Some clarifications and corrections are provided by the page above and Hans Summers’ writings.

 

First, what is available in the component?

 

Below is a diagram of the Si5351a’s sections and functions

 

The Si5351a starts with a PLL/VCO loop. (There are two on the chip but I’m discussing one of them.) This loop adjusts the output frequency of the VCO such that it is an exact multiple of a reference crystal frequency. The program enters a divider number of the form ‘a + b/c’ to this loop and the logic divides the VCO output down to equal the crystal frequency. Put another way, you could say the crystal frequency is multiplied by ‘a + b/c’ to give the VCO output. Typical crystal frequencies are 25 MHz and 27 MHz.  Mine is 25 MHz. The data sheet calls the ‘a + b/c’ divider the Feedback Multisynth Divider, because it is in the feedback loop of the PLL/VCO.

The VCO output should be in the range of 600 MHz to 900 MHz per the data sheet. It will work reliably down to 400 MHz though. This limited range dictates the first step in selecting a divider number, however.  

Note that in the ‘a + b/c’ number, the value of c can be as large as 2^20 – 1 or 1,048,575. That means the VCO frequency can be set to fairly high resolution and in fact this is where we will adjust the frequency to give about 1 Hz resolution on the ham bands. The total of a + b/c can range from 15 to 90.

Following the VCO is another divider stage that divides the VCO frequency by a value of ‘d + e/f’ and can be used to take the frequency down into the low MHz range. However the chip will provide an output with lower jitter if this value is an integer and better still if it is an even integer. So we let e/f be zero and select a value for d that’s an even number. Remember that there is enough resolution in the PLL/VCO stage to provide fine tuning. This approach also simplifies the calculations required and speeds things up. The data sheet calls the ‘d + e/f’ divider the Output Multisynth Divider, because it acts on the output of the PLL/VCO.

I said the previous step allows reaching the low MHz range. If it is desired to go down into the kHz range, there is one more divider called R that can be used for this. R can be set to integer values 1, 2, 4, 8 … 128. In our example R =1 and so this stage has no effect on the output frequency.

Note that we’re down to six values to calculate which are the ‘a, b, c, d, e and f’ of the dividers ‘a + b/c’ and ‘d + e/f’. But it will actually be a lot simpler. We said that the second divider d + e/f will be an even integer, so e and f are not needed. Then in the first divider a + b/c, we will make c a constant so we are now down to three required values: a, b and d.

 

Method:

 

Our inputs are the desired output frequency Fout and reference crystal frequency Fxtal.

Recall that the second divider is to be an even integer. Known as ‘d’ above, it’s called dividerRX in my program. The output frequency of the Si5351a is the VCO frequency Fvco divided by dividerRX or

1)      Fout = Fvco/dividerRX

From practical constraints, a value of 126 is a first approximation for dividerRX.  To see if that value will work and keep the VCO frequency below 900 MHz, rearrange the above to give

2)      Fvco = Fout * dividerRX

The program performs this multiplication and checks on whether the result is > 900 MHz. If it is not, then dividerRX will be 126. If the result is > 900 MHz, a better (smaller) value for dividerRX is determined by rearranging the equation again and using 900 MHz as the target approximate VCO frequency so

3)      dividerRX = 900E6 / Fout

The remainder is truncated. Then the resulting integer is checked for odd or even. If it is odd, it is decremented by one to produce the final value for dividerRX.

Now we have a final value for dividerRX and can use equation (2) to calculate the exact value of Fvco required.

Having that value, we move to the PLL/VCO stage and calculate the required value of ‘a + b/c’. First the total value is calculated and then the individual terms are extracted to be sent to the Si5351a.

4)      Fvco = Fxtal * (a + b/c), or rearrange to

5)      (a + b/c) = Fvco / Fxtal

Equation (5) will give me a floating point number which is an integer plus a decimal fractional part. Truncating the number to just the integer gives me the value of ‘a’ which I call multRX.

Next I need to take the fractional part (remainder) of the result of equation (5) and use it to calculate b.  Call it ‘frac’. I need a ratio b/c that equals that fractional part. I’m allowed to choose any (almost) b and c that will give the best accuracy, but for efficiency I’ll make the denominator ‘c’ be the largest value allowed, which is 2^20 – 1 or 1,048,575. This will allow the finest resolution.  (More about that later.) From this I’ll calculate the value of b which in my program is called numRX.

6)      numRX = frac * 1,048,575

I drop any fractional part of that result and now I have an integer which is numRX, a.k.a. ‘b’.

Now it’s just a matter of sending the three values I’ve calculated to the Si5351a. It’s never exactly that simple though, as the values must be formatted in specific ways I won’t go into here.

What about quadrature?  There are registers in the Si5351a for phase offset called CLK0_PHOFF,  CLK1_PHOFF and CLK02_PHOFF for the three outputs.  Clocks 0 and 1 can be derived from the same PLL/VCO output so we use them. The method is to leave the clock 1 phase as-is (zero) and write the value of dividerRX to CLK0_PHOFF. This produces the 90° offset between the two.

I have a spreadsheet that will calculate the values produced by the program for any frequency, so to demonstrate the output for various desired values of Fout and a crystal frequency of 25 MHz, I have the table below.

 

 

To verify or see what these numbers do, divide Fvco by dividerRX and you’ll get Fout.

Next take multRX plus numRX / 1,048,575 and mulitply that times crystal frequency 25,000,000 and you’ll get Fvco.

Note that below about 7.15 MHz the default dividerRX of 126 is used and Fvco falls out where it will. Above that frequency, smaller values of dividerRX must be used to prevent Fvco from exceeding 900 MHz.

 

About fixing the denominator of b/c at 2^20 – 1

 

Doing this simplifies things and gives the fraction a resolution of about one part per million. However, there are sometimes values of b/c using a smaller denominator that would give a greater accuracy in approximating the desired value. Consider 1/4 for example. It can be expressed as 262,144/1,048,575, but that’s not exactly one-fourth. It’s actually 0.250000238. So you can see why we say “close enough”.

There’s an algorithm for calculating the best b/c for a number with the maximum value of c specified. It’s called the Farey Algorithm. I wrote (actually, translated) a C program to do it but it was too long to include in the Si5351a VFO. That is, it takes longer to execute than my desired program speed requires. So I stay with the quick and dirty method.

 

Go to my Si5351a quadrature page to see my source code:

 

http://www.wa5bdu.com/si5351a-quadrature-vfo/

An Arduino Si5351a quadrature VFO controller

https://github.com/nick6502/Si5351a_quad

I’ve decided to start keeping my files on github.com because updating and maintaining posts on dreamhost using WordPress is just too complicated for me.

But isn’t github also complicated?  Yes, it is. But if you just want to download the files, it’s easy. Just go to that green button that says “Code” and click the drop-down at the right and then select “download zip file”.  You’ll get a zip with all the source code, the manual, schematics and whatever.

Hey, when I inserted the github link, my entire post disappeared! I guess that validates what I was saying. No matter because there’s a complete manual in the github file package.

But I’m going to cross my fingers and see if I can paste in an overview from the manual:

This document describes my Arduino Si5351a controller program for the Arduino.

Features include:

  • A keyed line input, so the controller can swap between TX and RX frequency outputs as required as the user keys and un-keys the transmitter.
  • A TX_Out output pin echoes the keyed line input, but doesn’t change state until the TX frequency registers have been sent, and changes back to key up before the RX frequency registers are sent.
  • An LCD display to show the frequency I’m on, menu options and so on.
  • A user settable CW Pitch value which is the amount that the VFO shifts between TX and RX states if in CW mode.
  • CW/Phone mode selection which functions to enable or disable the CW offset shift when the key line is closed.
  • LSB/USB selection. This chooses the direction of CW offset in CW mode, so the user can listen to a station on either side of zero beat. In CW it’s sometimes called NORMAL and REVERSE operation. There is also an output pin SB_Relay than cab be used control a sideband select relay in the receiver. Also, the phase difference between the outputs alternates between 90° and 270° when this selection is made.
  • RIT control. This provides for separate TX and RX frequencies, with both displayed. The receiver can be tuned without disturbing the transmit frequency. RX and TX frequencies can be swapped and the offset can be zeroed without turning off RIT.
  • Frequency step selection. There are both a menu for selecting from seven step sizes and a quick pushbutton selected step change between fine (10 Hz), medium (100 Hz) and fast (1 kHz).
  • Band selection with all ham bands from 3.5 to 144 MHz selectable, but unwanted bands can be omitted from the sequence.
  • A Save State menu option which writes most of the current parameters into EEPROM so the VFO will start in the same state the next time it is powered on.
  • Three miniature pushbuttons allow quick selection of often used functions while a menu controlled by the rotary encoder is used to access other functions. The pushbuttons operate with the familiar TAP and HOLD logic, giving two uses for each.
  • A sidetone which sounds when the key is closed. A menu option can enable or defeat this function.
  • An option for superhet operation with an I.F. (intermediate frequency) offset added to the displayed frequency when in receive mode.
  • A separate transmit output on clock 2 may be specified. It is keyed via key or PTT input.
  • A simple VFO mode where output is ON when the key is down and OFF otherwise, for driving simple transmitters.
  • The ability to control a PCF8575 IC which has 16 output lines and can select relays on a per-band basis. For example, to select low-pass filters in a transmitter.

wa5bdu-home

Some pages of projects or technical information

 

Links to pages for projects and info:

 

ContentsLink
An Arduino and SI5351a VFO projecthttp://www.wa5bdu.com/si5351a-quadrature-vfo/
Calculating register values for the Si5351ahttp://www.wa5bdu.com/programming-the-si5351a-synthesizer/
The WA5BDU Keyer for the Arduinohttp://www.wa5bdu.com/the-wa5bdu-keyer/