Si5351 VFO

By | January 3, 2019

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.

Features

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
  • 2019/11/9 – EEPROM persistence for the settings and frequencies

Schematic

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.

si5351synth.png

Figure 1: VFO Schematic (click to enlarge)

PCB

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!

vfo-si5351-pcb-top.png

Figure 2: Top side (click to enlarge)

vfo-si5351-pcb-bottom.png

Figure 3: Bottom side (click to enlarge)

2019-01-03_15.29.59.jpg

Figure 4: Populated board driving a simple 40m receiver

2019-01-03_15.30.48.jpg

Figure 5: Populated board driving a simple 40m receiver

I still have a few spare unpopulated boards, if someone is interested in having one (or more), contact me.

Firmware

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:

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.

IMPORTANT

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.

Basic controls

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.

Settings

Operating mode – for the offsets see below

AM BFO off
LSB BFO freq = BFO center + BFO SSB offset
USB BFO freq = BFO center – BFO SSB offset
CW BFO freq = BFO center – BFO CW offset

IF mode

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.

Invert sidebands

A small hack that reverses the meaning the LSB/USB modes when active. Rarely useful, mostly for debugging and testing.

Configuration

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.):

Variable Initial value Meaning
VFOFreq 7100000 Initial frequency of the VFO in Hz
IFFreq 8987500 Initial IF frequency in Hz
BFOFreq 8987500 Initial BFO frequency in Hz. BFO center frequency in Hz. Frequency from which the offsets are subtracted/added.
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

Downloads

Schematic + PCB:

  • KiCAD 5.0.2 project, including the Gerber files (in the “gerbers” subdirectory), so no need to install KiCAD if you only want to have the PCBs manufactured. Just extract the gerbers and upload them to the board fabricator of your choice.
  • Bill of material

Firmware (GPL v3 licensed):

Footnotes:

1

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.

8 thoughts on “Si5351 VFO

  1. Ben

    Just wanted to say thank you for this. I just built this and it is working wonderfully. (I build your AD9850 version a few years ago.)

    Reply
  2. Tony

    Hi Jan,
    I am rebuilding a hf man-pack (tr28) with a range of 1-8Mhz.
    I have tried many si5351 vfo’s which were just not right.
    Then I found your vfo and after lots of attempts I got it right…… learning Arduino etc.
    It is everything in one practical package .. brilliant!!
    As the man-pack has side-band filters I changed the mode to AM SSB CW. 1976 vintage
    Would it be possible to change the tuning-steps to a cursor under the relevant number ( same line)?
    Now I am pushing my luck———What about resetting the previous numbers to zero when changing the
    tuning-step eg 7,069,400 to 7,070,000 ?
    Once again great design!
    Regards,
    Tony ZR6ALG

    Reply
    1. Jan Post author

      Hello,

      I am glad that the thing works for you.

      Re display of the tuning steps – you mean to display it as a list of numbers with the selected step underlined? I am afraid that wouldn’t fit on the display (there is only 16 characters per line!). But feel free to hack, the display is defined in vfo-screen.h, in the render_impl() function. It is commented and should be fairly obvious what needs to be modified if you prefer a different style of display.

      If you want to reset the frequency to the default one when changing the step (not sure why you would want to do that, though), you can easily change that yourself. In vfo-screen.h, search for the function handle_input_impl() (line 195) and you will see two if statements that change the step up or down depending on the sign of the ev.ay variable (the encoder rotation). If you want to reset the frequency, just add there:


      m_state.vfo_freq = Defs::VFOFreq;
      m_state.vfo_dirty = true;

      And recompile. That should do the job. You can replace Defs::VFOFreq with your own frequency if you don’t want to use the default starting frequency from definitions.h.

      Reply
      1. Tony

        Hi Jan,
        Thanks for the prompt reply.
        I am sorry I did not explain my self properly re tuning-steps
        Instead of the freq being displayed on the bottom line in Hz is it possible
        to place the cursor under the appropriate digit on the main freq, eg if the
        tuning-step is 100Hz place the cursor under the 3rd digit, so you will see
        the step position at a glance, and not have the freq displayed below.
        The other query is if you are on 7.022500 Mhz and want to go to 7.070000 Mhz
        and select the 10000Khz step you will have (after tuning) 7.722500 Mhz.
        If the last 5 digits are reset to zero when the 10000 Khz step is selected you would have 7.070000 Mhz.
        This would save you from having to step back for each digit.
        My programming skills are still in the baby steps mode!! hi
        Thanks again
        Tony ZR6ALG

        Reply
        1. Jan Post author

          Ah I see.

          Re cursor – feel free to add that yourself for your radio but I am certainly not going to add this. I am afraid it would just confuse people because they would think the cursor tuning mode is enabled. Furthermore, the tuning steps are not only powers of 10 (whole decades). How will you indicate e.g. a very common 500Hz or 2500Hz tuning step then?

          The question about resetting the frequency – I think what you want is to actually round the frequency to the nearest multiple of the tuning step (again, zeroing the digits is not enough – the tuning steps are not only whole decades!). Feel free to add this in the place I have indicated above but I am not going to add this into my code. Again, I think that would be confusing and annoying if the receiver jumps around/changes frequency merely because you have changed the tuning step.

          >My programming skills are still in the baby steps mode!!

          Well, good opportunity to learn something new! These modifications you are asking for are a good starting project, because they are relatively simple and you don’t need to go deep into the C++ “plumbing” of the code (especially the CRTP templates are tricky).

          Reply
          1. Tony

            Hi Jan,
            Points taken, I will soldier on and keep you posted.
            An odd thing occurred when I powered up the the vfo, there was no freq displayed,and if I turned the encoder a freq of 1Mhz appeared and it would not save.
            I reloaded a clean copy same problem. I then reloaded a fresh bootloader and loaded the vfo s/w … problem solved. It appeared that the bootloader had got corrupted.
            Thanks for your help and patience!
            Cheers
            Tony ZR6ALG

  3. Jan Post author

    Feel free to get in touch if you have any questions.
    Re the weird problem you had – that could be also EEPROM corruption. If the data in the EEPROM are not what the program expects, all bets are off. If the EEPROM is erased/empty, it will detect that and initialize it properly but if there are some old data in there, anything could happen.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *