Here is an older project of mine – another variant of a VFO. This time with the popular Si5351 clock generator, allowing to create a complete solution for a superhet, including both the VFO and BFO oscillators.
This project is an evolution of the older AD9850 based VFO (here and here). However, this time I wanted to build a superhet receiver and AD9850 has only a single output, so the BFO would need to be separate. Si5351 has up to 8 outputs (depending on version), covering all the common radio needs easily.
The VFO has the following features:
- Independent VFO and BFO oscillators
- RIT/XIT offsets
- Tx signal input – used to let the microcontroller know when to apply the XIT offset
- Same display layout as the AD9850 VFO
- Revised controls – gone are the complicated button combinations, there is now a menu for configuring things such as the IF and BFO frequencies, operating mode, IF mode, etc.
- Output limited to 1MHz-30MHz (but easily changeable in the code)
- Supports both 3.3V and 5V LCD modules
- Easy to adapt to different IF filter frequencies and setups
- Arduino compatible
The VFO uses a standard ATMega328P-AU (in TQFP-32 package), running from the internal RC oscillator at 8MHz. The frequency generation is provided by Si5351a, running from a 25MHz crystal, with all 3 outputs routed to the edge of the board (should fit an SMA connector). In my case the loading capacitors C6, C7 weren’t necessary because my crystal needs only few pF of capacitance – the parasitic capacitance of the PCB is sufficient there.
The user interface is provided by 4 tactile switches (SW1, SW2 & SW3 + RESET) and a clickable rotary encoder (SW4). The output uses a standard 1602 LCD module1. Both 3.3V and 5V (most common) LCDs are supported – select the desired voltage by shorting the corresponding pads of the J4 jumper.
The device is powered from from two LD1117S50TR and LD1117S33TR linear regulators – everything except the LCD module is running directly from 3.3V rail, avoiding the need for voltage translation.
Firmware programming is possible either through the standard 6pin AVR ISP connector (J7) or using a serial bootloader on a 3 pin UART header (J8).
In case of use in a transciever it is possible to connect the external Rx/Tx switching circuitry to the J5 header – if the line /RIG_TX is pulled low, the microcontroller will switch to transmit mode.
The board was designed in KiCAD 5.0.2, the complete project is included below. The board is double sided, meant to accomodate everything including connectors, the LCD module and all controls.
Most of the passives are 0805 sized, except of C9, C10 which are 1206 sized. Everything should be relatively easy to hand-solder, the special larger “HandSoldering” footprints were used where applicable.
The most tricky component Si5351a comes in a tiny (3x3mm!) MSOP-10 package with 0.5mm pin pitch but it is still doable with a bit of care and careful inspection (use a magnifier!).
Don’t forget to short the correct side of the J4 voltage selection jumper for the LCD or the display will not work!
I still have a few spare unpopulated boards, if someone is interested in having one (or more), contact me.
The firmware is written in C++ using the Arduino framework for simplicity.
The UI part contains a small template library using both the CRTP pattern to avoid the overhead of virtual functions (saves code memory and a bit of runtime overhead) and compile-time evaluation with variadic templates to permit strong compiler optimizitation. In fact, the version 2.0 of this code used regular inheritance, virtual functions and no templates – and was about 2kB larger. Quite a difference in a micro controller with 32kB of code memory. The drawback is that the code is not exactly easiest to follow and read without some experience in template metaprogramming.
If someone is interested in learning how this works, some good resources are here:
- CRTP pattern and a practical example
- Template Metaprogramming series by Nils Deppe (part 1, part 2, part 3, part 4)
- Variadic templates and More on variadic templates
The code does not depend on the standard library because that is not commonly available for AVR. That is also why I had to include minimal implementations of an array and tuple containers. Those don’t claim to be complete or bug-free by any measure, they are meant to be only used for the purposes of this UI library.
Otherwise the code depends on the Etherkit Si5351a library (not the Adafruit one, that one didn’t work for me), Brian Low’s Rotary encoder library and Bounce2 button debouncing library. Install those either using the Arduino library manager or manually, as described on their Github pages.
The functionality is very similar to the AD9850 VFOs, however I didn’t implement the automatic saving and restoring of the last state to/from the EEPROM. I have rarely used that feature and it only complicated the code and was wearing the microcontroller out (EEPROM has a limited number of writes). The code is prepared for it, if someone needs it, it shouldn’t be too difficult to add this back.
The serial port is also unused in the current version (apart from debugging). It could be used either for remote control or e.g. for band switching in the future if someone wants to implement it. The connector is there.
When uploading the firmware, do make sure to select a board variant that supports 8MHz internal oscillator (normal Arduino Uno uses 16MHz crystal/resonator).
The instructions how to add a custom board configured for the internal 8MHz RC oscillator are, for example, here.
|Encoder left/right||Tuning, menu navigation, changing values|
|Encoder button||Change tuning step, confirm selection|
|Button A||Hold down to enter “cursor” mode where you can rapidly dial in the desired frequency decade by decade|
|Button B||Set RIT offset (XIT when /RIG_TX is active as well)|
|Button C||Enter/exit the settings menu|
The numeric settings (such as the IF and BFO frequencies in the settings) are changed by clicking the encoder, scrolling the cursor to the decade you want to adjust, clicking again to confirm and then rotating the encoder to change the value. Click to confirm again. The mode is exited by moving the cursor completely to the left of the display and clicking the encoder button again.
Operating mode – for the offsets see below
|LSB||BFO freq = BFO center + BFO SSB offset|
|USB||BFO freq = BFO center – BFO SSB offset|
|CW||BFO freq = BFO center – BFO CW offset|
|Off||Frequency on display is the frequency output|
|F+IF||VFO outputs the displayed frequency + the configured IF (additive IF)|
|F-IF||VFO outputs the displayed frequency – the configured IF (subtractive IF)|
IF & BFO frequency
IF and BFO center frequency, useful for peaking filters, etc. The default in the code is 8987.5kHz because that is the center frequency of a surplus Yaesu filter my receiver is using.
A small hack that reverses the meaning the LSB/USB modes when active. Rarely useful, mostly for debugging and testing.
Everything that is configurable is collected in the file
definitions.h. The following values are likely the ones that will need to be customized depending on your radio (filters, etc.):
|VFOFreq||7100000||Initial frequency of the VFO in Hz|
|IFFreq||8987500||Initial IF frequency in Hz|
|BFOFreq||8987500||Initial BFO frequency in Hz|
|BFOCenterFreq||8987500||BFO center frequency in Hz2. Frequency from which the offsets are subtracted/added. Normally this should be the same as BFOFreq|
|BFOSSBOffset||1250||How far to offset the BFO from the center when receiving SSB. Normally it should be 1/2 of the bandwidth of your IF filter.|
|BFOCWOffset||600||How far to offset the BFO from the center when receiving CW.|
|TuningIncrements||100000, 10, 50, 100, 500, 1000, 2500, 5000, 10000||Available tuning steps in Hz|
|RitIncrements||10, 100, 1000, 1||Available RIT/XIT tuning steps in Hz|
|TuningIncrementIdx||4||Initial tuning step (500 Hz in this case)|
|RitIncrementIdx||0||Initial RIT tuning step (10 Hz in this case)|
|Rit||0||Initial RIT in Hz|
|SidebandsSwap||false||Initial value of whether to swap the meaning of LSB/USB mode setting|
|FreqLimitUp||30e6||Upper frequency limit for the VFO (30 MHz)|
|FreqLimitDown||1e6||Lower frequency limit for the VFO (1 MHz)|
|SI5351Addr||0x62||I2C address of the Si5351a – according to the datasheet it should be 0x60 but chips with 0x62, 0x6f are common too. Try to change if the VFO is not tuning.|
|SI5351Correction||2004||Frequency correction for the Si5351 crystal – if you have accurate counter you could try to tweak this to calibrate the frequency of the VFO|
Schematic + PCB:
Firmware (GPL v3 licensed):
- version 3.0 (Initial release)
Some other designs use the cheap 0.96″ OLED displays – I didn’t want to use one of these because they are both very electrically noisy (not something desirable around a sensitive receiver) and I find them too tiny to be comfortably readable, despite the excellent contrast. Also the squarish aspect ratio looks odd on a front panel.
Yes, this is redundant, it would be enough to have only one constant for the BFO center frequency. Will be refactored in a future version.