An Arduino Si5351a quadrature VFO controller

Front view of the VFO. The display is indicating the Receive and Transmit frequencies, the step size, and the current mode. Pushbuttons PB1, PB2 and PB3 are surface mount type soldered to a bit of circuit board material.

Here’s a full-featured VFO I built around the Si5351a synthesizer IC and an Arduino controller. I’ve written a manual describing its construction and features and I’ll excerpt bits of it below as a description. I’ll also link to the complete manual as well as the source code. It’s painfully obvious that I don’t know how to use this HTML editor, so please forgive the strange formatting changes and misaligned bits.

Nick Kennedy, WA5BDU

After getting the basic functions working I began to expand my software to add features necessary to meet my needs for a VFO to be used in a transceiver project. Those 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 can be used control a sideband select relay in the receiver. As of V1.7, this function also changes the phase relationship between the two outputs from 90 to 270 degrees, so you don’t have to do sideband switching in the hardware.
  •  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) and fast (100 Hz).
  •  Band selection with all ham bands from 3.5 to 144 MHz selectable.
  •  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.
  • Optional third output with the transmit signal being on clock 2 and receive on clocks 0 and 1 (V1.14)
  • When the above option is used, clocks 0 and 1 can be keyed (off in transmit) for muting (V1.14)
  • For superheterodyne receivers, an intermediate frequency (I.F.) can be specified. It will be added to the displayed frequency (V1.14)
  • Optional x4 output, for receivers using a flip-flop to develop quadrature signals (V1.14)
  • Option of using an LCD display with the standard parallel interface, or one using an I2C interface (V1.14)
  • A single output on clock 0 can be used instead of quadrature on clocks 0 and 1 (V1.14)
  • Setting number of pulses per step, to tame high speed rotary encoders (V1.14)

Details of operation

The pushbuttons offer six actions, one of which opens a menu with additional actions. A brief press of a pushbutton is called a TAP and a long press (> 0.5 second) is a HOLD operation.

There is audio feedback in the form of a short beep immediately as a switch is closed, followed by two short beeps after it has been held long enough for the HOLD function.

Above is a listing of functions accessed via the pushbuttons. I tape a copy to the top of my VFO for reference.

There’s a great deal of detail to discuss concerning use of the functions of the VFO, but I’m going to refer the interested reader to the manual and try to keep the size of this page down. I do want to tell a bit about the hardware.

Hardware requirements:
  • An Arduino board, such as a Nano or Uno

  • A Si5351a module

  • A rotary encoder

  • A standard 16×2 LCD display with Hitachi parallel or I2C interface

  • Three N.O. pushbuttons

  • A few miscellaneous diodes, resistors capacitors and connectors

An Arduino might be $4 to $5 for a Nano or $9 or so for a Uno

A Si5351a module might be $5 or so. I like the module over the bare chip for avoiding difficult soldering and because it has a built in 3.3 V regulator and 5 to 3 volt logic level shifters for the data lines. You can power and talk to it from your 5 V Arduino.

My rotary encoder is a Bourns PEC11L-4020F-S0020 encoder with switch from Mouser.  The switch isn’t used. Update: I’m switching to an optical encoder as these inexpensive mechanical ones do wear out and give erratic performance after a time. 


Below is a schematic for the VFO.   The USB/LSB select relay if used would be driven by A2. Connection options for LCD with parallel or I2C interface are shown.


Here’s a photo of the rear of the VFO. CLK0 and CLK1 are the two quadrature RF outputs.  The KEY/PTT input will cause the VFO to swap between receive and transmit frequencies, it will also generate a sidetone if desired and actuate a Key Out circuit with which to key a transmitter.

Above is a photo of a Si5351a module connected to an Arduino UNO for testing. I think it’s impressive that with just four wires the system functions by coming up on the default frequency with quadrature outputs.

Here’s an oscilloscope trace of the two quadrature outputs. The output is about 3 Vpp open circuit and about 2.1 Vpp into 50 ohms.
A couple of variations

Some receivers have a divide-by-four logic arrangement in front of a circuit that develops I & Q signals. Since this VFO provides I & Q outputs already, there’s generally no need for an output of four times the indicated frequency. However, one friend wanted to use this VFO with his existing hardware which needed the X4 signal. So I added a flag which will cause the VFO to operate in the X4 mode. It requires editing one line in the source code. At startup, the LCD reminds you of which mode is being used.

The Si5351a can have up to three outputs. Friend #2 wanted to use that 3rd output to drive his transmitter while the other two were used for his phasing receiver.  There’s no reason you couldn’t use one of the two quadrature outputs for the transmitter, since it already jumps between TX and RX frequencies. But my friend didn’t want to risk adverse loading effects by having both a RX and a TX stage driven off the same output.  So I did another version which provides a third output on CLK2 for the transmitter. After some comedy of errors keeping it on the right frequency and making sure it didn’t QRM the receiver, I think I finally got it right.  

Changes made in V1.8, released January 20, 2023   (also, V1.9)

I sometimes get feedback from users with fast rotary encoders, meaning they output a lot of pulses per revolution. This can cause the pulses to outrun the software leading to erratic stepping. I decided to change the input signal processing from a polling method to interrupt driven. Making this change required changing Arduino pin usage such that two pairs of wires need to swap places. Don’t install V1.8 if you don’t want to make these changes. They’re described in the manual. 

The interrupt code accumulates step demand pulses in a queue and the main line code works them off as fast as it can. To speed things up, if the queue contains 10 or more demands, it increases the step size by a factor of 10 and does 10 demands in one pulse. 

Some encoders can be very fast, making tuning too sensitive. Another change in V1.8 allows the user to compile the software to give one step per pulse, one step per two pulses, or one step for four pulses. (Now V1.9 allows any value from 1 to 255 as pulses per step.)

Variations in encoders have caused most of the problems reported to me. My own mechanical encoder has become pretty noisy. I’m hoping that switching to an optical encoder will smooth things out. 

I also developed an Arduino program to allow troubleshooting encoder problems. It uses a separate Arduino to generate simulated Arduino pulse trains of varying length and direction and repetition rate. This is mainly to insure a clean pulse train when testing flaky software. Also it can be used to find out how fast the utilization software can process pulses. I may put it on line but if you are interested, email and I’ll send you the sketch and a manual of sorts in Word format.

Changes made in V1.7, released April 29, 2022

I fixed a few bugs you’ll see reported in the comments section, most of which were fairly minor. One more serious bug caused the 90  degree phase offset between the two outputs to be lost, ruining the quadrature relationship. This would occur while adjusting the frequency and crossing a certain threshold, such as going from 7142 kHz to 7143 kHz. That problem has been diagnosed and corrected. Thanks Tony Bertezzolo, IZ3EYY.

Also in some cases, enabling the RIT resulted in the RX and TX outputs being in different bands. Also corrected.

There’s also a new feature. A software switch now allows changing the phase difference from 90 degrees to 270 degrees. The existing control for selecting USB or LSB (tap PB3) will cause this toggle. The advantage is that you won’t have to design upper and lower sideband selection in your hardware – it’s now in the VFO.

Files you’ll want:

Links to the manual for the VFO and for the Arduino source code are given below. The manual is a PDF file. It includes information on processing the source code in the Arduino IDE and sending it to your Arduino. The file ‘si5351a_quad.ino’ is the Arduino source code, which is a plain text file. As of V1.14, there’s also a “config” header file which goes with the main source code file.





Skip to comment form

  1. Hi, Nick,

    Is 160 missing because of some quadrature limit in the hardware? If not, can you add it?



    1. In the code my VFO is based on, 80 meters is a lower limit so the vco frequency doesn’t go too far below its datasheet limit. However, I’m seeing now that there’s an output divider ‘R’ that’s not being used. I’m going to see if it can put the VFO on 160.
      73, Nick

        • Mont Pierce on January 1, 2021 at 5:18 pm
        • Reply

        I don’t believe the Divider will work with Si5351a programmed for Quadrature signal output… As an alternative, using a 12MHz crystal instead of the normal 25-27MHz crystal will get down to 160M range. But of course, it will limit the upper range…

        I’ll be very interested to see your Divider results.

        Are you using Arduino Sketches? Or Atmel Studio?
        The later, while more complex, will give you faster tighter code. There is a lot of overhead in the Arduino libraries…

        1. Yeah, I wondered if it might be necessary to change the phase offset value to keep the same 90 degree offset. I’d hope that it might be that simple, but I’m not sure yet.
          I’m using the Arduino IDE. I’ve done bare ATmega AVRs in the past with AVR studio but this is so much simpler. I’m not aware of overhead other than memory for stuff like the bootloader, but there may be some for a clock that seems to be always available.

    • Steven Dick on January 1, 2021 at 4:16 pm
    • Reply

    Great Project!

    -Steve K1RF

    • Mont Pierce on January 1, 2021 at 5:33 pm
    • Reply

    Another option to get down to 160M:
    In the Si5351 datasheet, section “5.6. Applying a Reference Clock at XTAL Input” means it’s possible to use a programmable clock in place of the 25/27 MHz Crystal.

    So as an option, you could use the “ProgRock” from QRP-Labs, have it programmed for 25/27MHz and for 12MHz, and with one i/o pin from Arduino, switch your Si5351a’s reference frequency when tuning below/above the 3.2MHz limit.

    Just a thought…

    PS. Yes, 12MHz reference xtal/clock is below the Si5351a’s datasheet specs. but I’ve heard that it does work just fine… Your mileage may vary ??

    1. Sounds like a possible option.

    • Gary on January 1, 2021 at 5:45 pm
    • Reply

    Excellent project and website Nick!
    Arduinos and Si5351s are my favorite
    toys. 73 WB6OGD

    • Bob McClements on January 3, 2021 at 10:31 pm
    • Reply

    Built a proof of concept version using an I2C version of the lcd, worked perfectly straight off, an interesting well documented design, thank you.
    Curious as why the and diodes for the pb3 switch when there are spare digital io ports availabole.

    73 Bob GM4CID ex VP2LI

    1. Yeah, good observation. I guess I did the diode logic thing on another project and just carried it to this one because it’s easy. But if I did away with it, it would simplify the hardware a tiny bit …
      I think I may have mentioned this in the manual.

        • Bob McClements on January 4, 2021 at 12:29 pm
        • Reply

        I did read and digest the manual, outstanding documentation, and had noticed your reference to use of the diodes.
        Must admit, I too recycle lots of my previously used sketch snippets.

      • jack margolis on January 10, 2022 at 11:21 pm
      • Reply

      Hi Bob,
      I prefer to use the I2C versions of those LCDs so if you would kindly share the code with us, it would make my breadboard setup much cleaner. Of course, I have it working with all those wires but it still would be cleaner with only four. Thanks, Jack

    • Bob Bruno on January 29, 2021 at 4:12 am
    • Reply

    Hey Nick,
    Wonderful info. It gives me a nice firm place to start with my future projects.
    I will check back from time to time to see if you add anything,
    Take care es 73…
    Bob de k2ki

    1. Great, Bob. Have fun with it.

    • Bob on February 12, 2022 at 8:49 am
    • Reply

    Nick – great project, all working! Am I blind or not, cannot find an IF offset frequency facility in your sketch – for upper & lower sideband, applications. Output from CLK identical to display. I.E. maybe only designed for a DSB rig? Am I missing something? Incidentally I have built about 8 different DDS synthesizers over the years, but yours is the best sketch up to now. Look forward to a replay before making a PCB.
    Regards, ZS6RZ

    1. Hi Bob and thanks for the comment. This VFO is mainly aimed at a phasing type receiver where the I & Q from the VFO go to a Tayloe mixer which also receives RF from the antenna. In short – it’s a direct conversion scheme. The audio I & Q out of the Tayloe Detector goes to an all-pass filter which is what gives single-signal reception. But those audio I & Q signals going to the all-pass filter are first routed through a DPDT switch so they can be swapped to give the ability to select LSB or USB.
      73 – Nick, WA5BDU

    • ian G3VAJ on March 14, 2022 at 11:46 am
    • Reply

    Hi Nick,
    Trying your vfo for my direct conversion receiver. All functions working well except that frequency change is 4* step value, eg when 1Hz step selected, frequency changes by 4Hz for each step of the encoder. Encoder works ok for other functions eg menu selection, step size, band select. Any suggestions please. I am fine with making minor changes to your code.
    Many thanks
    Ian G3VAJ

    1. Hello Ian,

      That’s very strange, although differences between different types of rotary encoders often cause problems in projects like this one. I typically use every state change, to maximize resolution. When that makes encoder actions too fast, as in selecting menu options, I might throw away half of the changes. I’m not sure if I have ever seen a factor of four issue. Is the behavior consistent and repeatable?
      A crude fix might be to require four ‘ticks’ for every step taken. It would take a little thought on how to implement smoothly. I’ll take a look at the code when I get a minute. Feel free to email direct – I’m good in QRZ. Although I’d like for the final resolution to appear here for the benefit of others.
      73 – Nick, WA5BDU

      1. Here’s a follow up to my reply. I did make the change to give 1 encoder pulse out for every 4 in and it worked OK for Ian.
        Rotary encoders are a common source of difficulty for projects like this. Sometimes different encoders work somewhat differently and sometimes you’ll just find one that’s a bit erratic. Encoders with detents seem to work differently from those without detents, which I generally use.
        Also, Ian noted a bug in the current program in the function to select ham bands. It should roll-over going up from 2 meters to 80 meters and going down from 80 meters to 2 meters, but it inserts an invalid band between those steps. It’s not fatal as you can continue to turn the knob and arrive at the right place.
        I’m not uploading a new version at this time, but you could edit your source code if desired in these two lines:
        Line 935:
        if (hamBand_index > 10) hamBand_index = 0; // V1.6 10 was 11 (change ‘> 11’ to ‘> 10’)
        Line 939:
        if(hamBand_index == 0) hamBand_index = 11; // v1.6, 11 was 12 (change ‘= 12’ to ‘= 11’)
        Or email me and I’ll email you the latest code back.


        Nick, WA5BDU

    • antonio on March 25, 2022 at 7:49 am
    • Reply

    Hi! This is IZ3EYY Antonio.
    I built it the sinthesizer for using with the glorious miniR2 by kk7b.
    Everything runs quite well, exept the quadrature.
    In fact, while i can have total cancellation of unwanted side band by using other vfo’s, analogue and digital…i can’t obtain the same with this, it seems that the 2 outputs are equals both in amplitude and phase. I dont have a scope at moment, but receiver functions with other vfo’s..- I’d like to solve the issue, because this vfo is a very beatiful project.
    P.s. si5351 i currently use is not an original adafruit board.
    Thanks a lot!

    1. Hello Antonio and thanks for the comment.

      It is strange for the 90 degree shift to go away without some modification to the source code.
      The miniR2 uses a single L.O. input and performs the phase shift in its circuitry. In order to use a quadrature (two channel) L.O. input, I assume it was necessary for you to modify the miniR2. Or maybe not? Is your miniR2 the circuit shown in figure 9.62 of EMRFD? If you are driving diode mixers directly, there may be problems caused by the Si5351a not having a constant 50 ohms source resistance. You may need attenuators between your L.O. and your diode mixers. And if you add them, some amplification might also be necessary. I’m speculating here because I’m not really familiar with your hardware. I think the Si5351a will put about +10 dBm into 50 ohms.


      Nick, WA5BDU

        • antonio on March 25, 2022 at 9:39 pm
        • Reply

        Thanks Bob for the prompt answer, well, i measured the output and it seems to be in the +10/12 dbm on 50 ohm, it’s the same output i have on Andy Summers vfo…so no problem, i suppose that the problem, considered the fact that nobody complains for this issue, is related to my si5351 board..i’ll try to change it, and i’ll let you know….thanks again, best 73’s…tony.
        Anyway mini r2 is a phase cancellation in analog way, and it works very well, be it with analog phase shifters, dividers by 4 and I/Q oscillators as yours…the 2 diode mixers work correctly…Bye!

  2. One more response to Ian’s comment. I overlooked the fact where he stated that roll-over and roll-under on the step size selection function also has a glitch in which a bogus step size appears between the highest and lowest value. The fix again is a quick edit so I won’t re-publish the whole file at this time. I added this comment:
    // V1.6 with step_index as a uint8_t, the test for roll-under to
    // negative fails because it is unsigned. A char can be used as
    // a signed or unsigned 8-bit number, so change to signed char
    That’s just FYI. The line you need to change is this one, somewhere around line 260 or so:

    signed char step_index = 2; // start with 100 Hz steps V1.6 changed type

    Currently, the line starts with ‘uint8_t’. Change that to ‘signed char’ as shown above.

    73 – Nick

    • antonio on March 30, 2022 at 6:14 am
    • Reply

    Sorry if I call you Bob in my latest emails…Hi Hi!
    I just want to thank you for your beatiful project. Now i have substituted the si5351 board and it works well. I used an original Arduino Uno board, and si5351 bought cheaply on the net, Maybe one has to buy a lot of ten for having 3 working! Perhaps it’s only bad luck, but this is what has happened to me. Nice vfo’s indeed. I’ll follow you in future projects, bye Antonio.

    1. Thanks for updating us, Antonio, and I’m glad you’ve got it working.
      I had been thinking of how you could verify the phase shift without an oscilloscope. If you had an RF voltage probe, you could measure the voltage at OUT0 and OUT1 to ground. Say they’re equal and call the magnitude V. If they are in phase, the voltage from OUT0 to OUT1 would be 0. If they are 180 out, it would be 2V. If they are 90 degrees apart, then SQRT(2) * V. This wouldn’t tell you the exact phase relationship of course, just an approximation.
      But of course you couldn’t measure this voltage with a simple RF probe because one side is grounded. You’d have to isolate from ground with a transformer connected between OUT0 and OUT1. Or maybe there’s a simpler way.

      73-Nick, WA5BDU

        • Antonio on March 30, 2022 at 6:21 pm
        • Reply

        Tnx Nick, precious advice!

    • antonio on April 7, 2022 at 9:43 am
    • Reply

    Hi Nick, i have another question to ask.
    Yes vfo as i said it works, even though, i noticed that spanning through frequencies, for example tuning up on 40 meter band, but not only, there is at a certain point, say around 7143.00, a click (maybe the insertion of some divider o something like that) is heard and it resets the phase from 90° to aproximately 0°loosing the unwanted side band cancellation. I discovered that to turn again at 90°, i have to do a reset on the si5351 board, by short-circuiting the xtal, or by turning off and on the board. It seems that a software reset is missing from arduino sketch, am I wrong?
    I wouldn’t like to put a hardware switch for reset it, obviously. Thanks a lot for your attention.
    P.s. the same happens at 14060 more or less, at 25000, and other points…I didnt try other frequencies, but it seems bands as 80 or 17 meter no clicks are heard and no undesidered phase reset are noticed…
    Best regards

    1. Thanks for those observations Antonio. I wish I could investigate right away but it will be a couple of weeks before I can get back to the workbench. I’ll try to replicate what you have seen as soon as possible, and then try to figure out a cure.

      73- Nick, WA5BDU

      1. Thanks Tony. Your specific descriptions of how to reproduce the problem allowed me to see it and go to work on fixing it. That has been done and V1.7 has the problem corrected.
        As noted in my description of V1.7 changes in the main body of the blog, I also added the ability to switch the phase shift from 90 degrees to 270 degrees. Other less serious bugs reported by others and sometimes by me have also been fixed. Users should download V1.7 and replace their existing source code.

        73 – Nick, WA5BDU

    • antonio on May 3, 2022 at 9:08 am
    • Reply

    That’s great Nick!
    I’ve tested it and now it’s very functional and stable. Cancellation of unwanted side band is 100% obtained an all frequencies. Really appreciated even the lsb/usb done with a simple touch of toggle switch. What can I say? I’ m convinced that this quadrature vfo is one of the best around with a plus of several features for gettin promptly on the air. Great project indeed! Thanks. Tony IZ3EYY

    • Maciek sp9wfh on May 14, 2022 at 7:41 pm
    • Reply

    Hello! I don’t know how to connect to the Arduino Nano. The diagram is hardly readable for me. Pity.

    • Maciek on May 14, 2022 at 7:43 pm
    • Reply

    Czy ktoś z Kolegów móglby mi podesłać na e-maila schemat tego projektu na Arduino Nano? będę bardzo wdzieczny.

    • Maciek on May 14, 2022 at 7:49 pm
    • Reply

    Hello! Can any of my colleagues send me the connection diagram of this project for Arduino Nano. I will be very grateful.
    Maciek sp9wfh

    • Maciek sp9wfh on May 15, 2022 at 8:52 am
    • Reply

    Hi Nick! I have a sincere request to you, I want to build a Vfo according to your project. I am 66 years old, I do not know arduino. I know I need a sketch and all the libraries I need, could you please provide me with your latest sketch for this project and all required libraries? My mail:

    73 Maciek sp9wfh

    • NICK KENNEDY on May 23, 2022 at 6:48 pm
    • Reply

    Hello Maciek,

    If the diagram in the post is not clear on your monitor, try downloading the manual which is linked in the post. It has the same diagram. It also has a list of terminals for the Arduino and for the LCD that can help with interconnections.

    The sketch you ask for is also linked in the post. I don’t think there are any libraries that are not part of the standard Arduino IDE installation, but there are instructions in the manual telling how to add libraries.

    Nick, WA5BDU

    • Daniel on February 10, 2023 at 10:13 am
    • Reply

    Hi Nick
    Where can I download version 1.7?
    With 1.8 I have a problem with the rotary encoder.

    1. I’ll email it to you. Hardware changes (swap a couple of pairs of wires) are necessary to go to 1.8. I should probably have the old version still linked here. Send me your email. Mine is good in QRZ.COM or maybe I have it here on this web page somewhere.

  3. HI Nick,
    Thanks for posting this project. Very well documented and I love your uncluttered web site.


    • Antonio on June 27, 2024 at 9:49 pm
    • Reply

    Excellent work Nick! A superb project, everything works great, now encoder runs smoothly, i really appreciate tx mode on vfo n.2. Best 73. Thanks

Leave a Reply to ian G3VAJ Cancel reply

Your email address will not be published.