The current hw revision


It has been some time since I have posted the article on building a simple VFO/signal generator using the AD9850 module and a microcontroller, originally based on the work Richard, AD7C. I have received quite a bit of feedback from several people, along with a few requests for improvements to make the hardware more suitable for HAM radio use (my original goal was a signal generator!). So here it is, the AD9850 VFO Mk II.

The version of the VFO described in this post is based on the original design from my previous post, so you may want to read that first, especially some of the design rationale and gotchas commonly encountered with the cheap AD9850/9851 DDS modules available from eBay and elsewhere.

The main features of this version of the hardware:

  • RIT/XIT offsets – allow to fine tune/offset the frequency from the one displayed by ±25kHz, in selectable steps
  • TX signal input, so the micro actually knows when to apply the XIT offset
  • Revised display layout, TX indication
  • Revised controls – additional button for RIT/XIT, modified functions of the other buttons and the encoder.
  • Output limited to 1MHz-30MHz to stay within the specs of the DDS

The person most responsible for pushing me towards making this radio-oriented version of the code is Rich Carstensen, W8VK. He is building some really nice QRP rigs with it and using it as a replacement for the classic mechanical dials.

Rich’s W8VK 40m QRP gear, posted with permission.

Hardware revisions

The current version of the generator/VFO has some minor differences compared to the previous one. Namely there is an extra button added for handling the RIT/XIT offsets and there is a new /RIG-TX input signal added for RX/TX switching. The signal is active low (TX), with an internal pull-up, TTL level (either 3.3 or 5V levels, depending on Vcc). As before, the schematics shows the corresponding Arduino pin names for those using an actual Arduino board to build this.


Modified schematics

The current hw revision

The actual hardware

The photo of the hardware shows 4 buttons – the left-most one is Reset, then RIT/XIT (C button), IF (B button) and Cursor mode/Zero (A button). The encoder is of a pushable type with a built-in switch. There is another button barely visible just above the RIT/XIT button – that one I am using to simulate the PTT switch for testing, the schematics shows a connector there instead where the RX/TX switching signal needs to be brought in.


There are several modes which are changed using the buttons and the encoder:

  • The VFO mode – the display shows the frequency, turning the knob tunes using the pre-selected tuning step. Pressing the encoder allows to change the the step.
  • “Cursor” mode – Holding down the A button (closest to the encoder) and turning the knob moves a cursor over the displayed frequency, releasing the button and turning the encoder changes the selected decade directly. Pressing the encoder knob returns back to the normal VFO tuning.
  • IF mode – pressing the B button toggles IF offset on and off. This is indicated on the display by text “±IF”. Pressing both A and B together allows to program in the desired IF offset using the encoder, using the same procedure as the “cursor mode” for the main VFO frequency. Pressing the buttons together again toggles the additive or subtractive IF, pressing the encoder button will return back to the VFO mode.
  • RIT mode – the C button activates the RIT mode when the /RIG-TX signal is inactive (high). Turning the encoder permits to select the desired offset, up to ±25kHz. Pressing the encoder changes the RIT tuning step, pressing the A button resets the offset to zero.
  • XIT mode – the C button activates the XIT mode when the /RIG-TX signal is active (key the rig and press the C for a short moment). Turning the encoder permits to select the desired offset, up to ±25kHz. Pressing the encoder changes the tuning step, pressing the A button resets the offset to zero again. If a non-zero XIT offset is selected it will be displayed whenever the rig is keyed to help avoid transmitting on an incorrect frequency.

The settings – frequencies, offsets, IF state – are all saved in the EEPROM if no change was done for 10 seconds, so next time you will be back on the same frequency with the same settings as when you have turned the rig off.


You will need the following dependencies (the same as in the original article):

Arduino IDE needs to be configured for the LilyPad Arduino because the circuit uses 8MHz clock unlike the original Arduino Uno and similar that use 16MHz clock.

Important note

On the first power up the EEPROM is not programmed so the display may show gibberish and the DDS will output invalid signals. It is best to start without the module connected. Then set the VFO and IF frequencies, check the RIT/XIT and reset them, if required. If the display is showing gibberish, simply keep turning the encoder knob until it hits the frequency limit (either 1MHz or 30MHz) and which point the display will reset. From that point on everything will work as expected.

An alternative solution to the above procedure is to pre-program the EEPROM as follows:

  • bytes 0-3 – the main VFO frequency (32 bit signed integer), stored from the least significant byte to the most significant byte.
  • bytes 4-7 – IF frequency, stored as signed 32 bit integer, stored from least to most significant byte.
  • byte 8 – IF frequency polarity, 0 – subtractive IF, any other value – additive IF
  • byte 9 – IF state, 0 off, any other value IF on
  • bytes 10-11 – RIT offset, 16 bit integer, stored from least significant byte
  • bytes 12-13 – XIT offset, 16 bit integer, stored from least significant byte
  • byte 14 – selected VFO tuning step
  • byte 15 – selected RIT/XIT tuning step

If in doubt, refer to the functions storeFreq() and loadFreq() in the code.

Seen in the wild


  • Sajeev commented on September 25, 2016 Reply

    Hi Jan,
    I done this program in my arduino pro mini and it is working well .So thanks my friend. But I am facing two problems. I am using BITX 40 transceiver board and here the IF is 12MHz .Inside the DDS vfo, When I set the offset IF as 7MHz-2MHz(F-IF)
    I got 5MHz at DDS out and the display of DDS shows 7.000.000Hz.But when it tune to reverse direction only I got the 40m stations . So I try to set the IF in (F-IF) as 12MHz then suddenly the display frequency(value for F) changed to 13.000.000Hz and it cannot tune into anti clockwise. I also try to change the F-IF as IF-F in program but the problem is not rectified.
    Also I cannot set the RIT/XIT mode.Which pin I have to use for this.I am using Arduino Pro mini board.
    Please help me as early as possible.

    • Jan commented on September 25, 2016 Reply


      I am not very familiar with the BITX TRX, so I am not quite sure what your problem is. What you are describing (display showing 7MHz and 5MHz output) is normal behaviour for 2MHz IF – and your BITX is obviously receiving the 40m (7MHz) stations.

      If you keep F as 7MHz and set IF to 12MHz with the F-IF setting, that will cause a numeric overflow (there is no way to get -5MHz that you have asked it to output!). It then gets clamped to the minimum possible output frequency to keep the DDS within sane limits, which is 1MHz. And to get that the F has to be 13MHz – which is what you are seeing.

      You need to set the IF correctly, then the problem will go away – if you want to subtract IF from the display frequency, it must be smaller than the display frequency. If you want to have 12MHz IF and work on 40 meters, you will need to use the F+IF mode (upconverting).

      The RIT/XIT mode uses the C button (A3 pin), together with the transmitter key/PTT signal (A2 pin). Pressing C in RX mode lets you set RIT, pressing C while the TX is keyed will let you set XIT. It is described in the text on the website, along with the modified schematics.

      All the best,


  • Sajeev commented on September 28, 2016 Reply

    Dear friend,
    Thanks for the response and the valuable suggestions. Every thing ok but one problem is going on . I done F+IF(Up converting) method. Now I can tune the7MHz station in forward direction but cannot decode the SSB stations clearly. Jan what I to do for that my friend?
    Thanking You
    De Vu2lpj

  • Jan commented on September 28, 2016 Reply

    Hello Sajeev,

    You are likely having issue with sideband inversion. If you are using F+IF on the VFO and thus have IF at 12MHz, then when receiving 40m signals the sidebands will be inverted and SSB will sound like gibberish:

    “Sideband reversal occurs in mixing only if the signal with the modulation is subtracted from the signal that isn’t modulated.”

    Keep in mind that what is on the display (F+IF or F-IF) *has nothing* to do with with the way you are doing the mixing – whether you are taking a sum or the difference of the VFO and the signal from the antenna.

    The F+IF/F-IF only indicates which way is the VFO frequency offset from the number shown on the display (whether the IF frequency is added or subtracted to get the actual VFO output frequency).

    So in order to solve your issue, either put the VFO *below* the 7MHz (e.g. 2MHz or so for 5MHz IF), or you need to take the sum of the two frequencies instead – i.e. using 19MHz IF. Then the sidebands won’t get inverted.



  • Mike Donovan commented on October 7, 2016 Reply

    Hi Jan,

    I am stumped. The VFO is working and I can see the correct output from 1 mhz to 30 mhz. The signal on the AD9850 is very clean. The encoder is working fine, but I can not get anything but jibberish on the 16×2 lcd screen. When I power back up it comes to last frequency as well. Any ideas?
    I get the correct frequency on the Serial Monitor as well.

    Mike VE7EPQ

    • Mike Donovan commented on October 7, 2016 Reply

      Eureka. I had the data lines to the lcd backwards!

      Now i can read the display. Great vfo!

      Mike VE7EPQ

      • Jan commented on October 7, 2016 Reply

        You actually made me check the code, because the schematics has the data pins wired in an odd-ball way – pin 4 to D7 and pin 7 do D4, indeed. No surprise that you got confused by that.

        It seems to be an artifact from the original code by Rich AD7C, I have never realized that he had the pins wired counterintuitively backwards.

        I really should correct that in the next update 🙁

    • Jan commented on October 7, 2016 Reply

      Hello Mike,

      That sounds like you have connected the LCD module incorrectly. Make sure that you didn’t swap the RS and E pins on the LCD, R/W must be grounded. A common mistake is to connect the data pins D0-D3 instead of D4-D7 too.

      73! Jan

      • Jan commented on October 7, 2016 Reply

        Ah, you were faster than I have managed to write the reply 🙂 I am glad it is working for you.



  • Mike Donovan commented on October 12, 2016 Reply

    Now building a stand alone VFO using an ARDUINO nano and a modified AD9850 (as per the Yahoo Group PHSNA). Removing the low pass filter from the board (it’s defective) and putting LP filter external improves output range and increases power output.

    • Jan commented on October 12, 2016 Reply


      I don’t actually think the filter is defective, but some of the modules have a very steep one that rolls off early, limiting the useful range of the VFO.

      BTW, if you want to use an external filter, there is no need to remove the one on the board – the DDS has two outputs and only one is filtered on most modules, the other one is bare, directly from the chip. So you can use that one. There are also two square wave outputs.

      See here:

  • Mike Donovan commented on October 13, 2016 Reply

    The group PHSNA was adapting the VFO for an existing project. You are right, I forgot the other output. It would have saved me grief.
    My AD9850 puts out the following:
    1 MHz. -6.7 dBu
    15 MHz. -8.7 dBu
    30 MHz. -24.41 dBu
    Removing the LP filter and one extra resister brings up the output and makes it even from 1-30 MHz

    • Jan commented on October 13, 2016 Reply

      That looks like you had one of the “bad” modules. Some modules have such tight filters. Mine starts to drop off somewhere around 24-25MHz by about 6dB.

  • George commented on October 14, 2016 Reply

    Hello Jan!
    Thank you for the gift you make of radio amateurs. Everything works perfectly. Some time ago I came across a construction First test it on Arduino Uno R3, then made an “ugly” board. When I met your design found that more things coincide /normal – everything comes from AD7C/ . I just removed the two resistors added 4 buttons and your SOFTWARE and all work correctly. Now I have a new VFO with many more options.
    Once again – thank you very much!
    Greetings! LZ1OR – George
    PS The translation is made from Bulgarian to English by go?gle translator!

    • Jan commented on October 14, 2016 Reply

      You are welcome!

      Yes, I took the original circuit and code from Rich AD7C, however I found his code was difficult to use, so I rewrote the majority of it to make it a bit better usable.
      On the other hand, this is still nothing really fancy – there is a much better VFO code that is explicitly targeting radio use (my goal was to build an RF generator, not VFO) done by M0XPD ( which uses a larger display and a slightly different wiring.

      I am glad that it works fine for you.

      All the best & 73!


  • Mike Donovan commented on October 22, 2016 Reply

    Hi Jan,

    I have removed the VFO from the breadboard, used a nano and installed everything in a copper clad box. Thanks again for this project

    Mike Donovan. VE7EPQ

    • Jan commented on October 22, 2016 Reply

      I am glad that it works for you.

      Enjoy and 73,


  • Robert KH2BR commented on November 12, 2016 Reply

    Hi Jan,
    Just finished building the VFO and it worked first try. I am a cw op and I am using the VFO in a transmitter I built. I want to use my Icom receiver that has been gathering dust. The problem I have is that the vfo can be heard all the time in the reciever so I decided to use RIT mode. While in recieve, the vfo is off set by 3khz and I nolonger hear the vfo on the operating frequency which is what I want. When I key rig-tx with my K3NG arduino cw keyer at 5wpm it works fine but when I go up to my normal speed of 25 wpm, the vfo is not switching fast enough. I am using full break-in. You can hear between the dits and dahs. Is there a way to speed up the switching while keying the rig-tx pin? The 25 wpm dits are not switching the vfo into tx . I hope there is a quick fix, Thanks.. Robert KH2BR

    • Jan commented on November 12, 2016 Reply

      Hello Robert,

      Uff, I am afraid you are using the thing for something a bit beyond what it has been designed for 🙂 The TX/RX offset switching is debounced because it is meant to be used with a mechanical button/relay contact. So there is an unavoidable delay which is likely what causes your problem at higher speeds. The default debouncing time is 10ms.

      You could try to change that by adding the following after the line btnTX.attach… in the setup() function in the “configure debouncers” block:


      That will set the debouncing time to 1ms. I haven’t tested this as I don’t really have the means to do so (I am not a CW op). However, this isn’t going to be magic – the circuit needs a bit of time to retune anyway, so if you need a really quick transitions, this isn’t really the best solution, IMO.

      If you need full break-in, I would say you should keep the VFO running constantly and add some transistors or a multiplexer to switch the outputs off when in RX instead. That would be orders of magnitude faster than anything the firmware in the Arduino can do. Also shielding the VFO, using a coax to connect it to the rig and putting chokes in the power lines would help from stray RF getting into your receiver.

  • Serge commented on January 14, 2017 Reply

    Hi JAN! I am love CW QRP.So may you explain to me, is it possible to get Tx frequency without mixing with BFO. And during RX as usial F+or F_minus Beat frequency. If this is possible, it will simplify TX chain.
    No TX mixer – only may be filter and PA. Big majority of ham synt VFO dont give such ability.
    May be you will be first who do a nice present to CW QRP fraturnity.
    Serge RW3DF

    • Jan commented on January 14, 2017 Reply

      Hello Serge,

      There is nothing in the code or the build that would prevent you using the VFO as-is for CW, sans mixing. Don’t set the IF offset and off you go.

      This VFO is not meant to be used as BFO, the F+IF or F-IF are meant for IF mixing in superhets. If you want the VFO frequency to be offset on receive only in a direction conversion setup (you mix the incoming signal with the VFO down to audio range), you can use use the RIT/XIT function to give you an offset of 600Hz (or whatever tone you prefer) on receive.



  • Serge commented on January 14, 2017 Reply

    Jan! Good afternoon! Yes I understand very well how to use such VFO in usial superheterodine technics. But I try to simplify CW QRP TRCVs. If in TX mode VFO produce signal on our working frequency ( on RX frequency) without additional mixing ( like it made in VHF trcvs) we simlify TX chain in cw trcv. For example, RX mode – Fvfo= F+ Fbfo or minus Fbfo.
    TX mode Fvfo=F. In all modes F is Frx. Numeric explain is much easyer for understanding – BAND 14mHz, IF 9 mHz . RX mode – Fvfo is 14-9=5 mHz.
    TX mode F vfo =14 Mhz.
    All others (RIT,XIT, A/B) we yet have in your nice soft.
    Serge RW3DF

  • Serge commented on January 14, 2017 Reply

    Sorri, Jan! I am only now notice my mistakes when I wrote formules – RX mode Frx=Fvfo+Fbfo and
    in TX mode Ftx= Frx.
    Sorri! Serge RW3DF

    • Jan commented on January 14, 2017 Reply

      Yes, I understand what you want to do. I just don’t see what is the problem with the VFO or what are you actually asking.

      There is nothing inherent about it that would force you to use a mixer. Just filter it, amplify it and off you go if that is your preference. If you don’t set any offsets (IF or RIT/XIT), the frequency you see on the display is the frequency that is at the output.

      My comment about the BFO was because I have assumed you want to simplify the design as much as you can and go with a direct conversion receiver. Then the RIT offset will provide you a way to get around the need of a separate BFO, but it is not the only way to solve that problem.

      If you want to have no IF on transmit but have IF on receive only (that’s what I assume from your 14MHz-9MHz = 5MHz equation), then no, that isn’t possible without modifying the code.

      You can do it by modifying the sendFrequency() function, just change the condition:

      frequency = frequency + if_freq;
      frequency = frequency – if_freq;


      if(if_active && !tx_active)
      frequency = frequency + if_freq;
      frequency = frequency – if_freq;

      That will turn on the IF frequency offset only on receive. Then set your IF offset as you need it.

  • Serge commented on January 15, 2017 Reply

    Jan! Thank you very, very much! I have built former winter synts LU5xxx, OK1DX, UR3ILF – but no one allow to work in such mode during CW. My present project – synt constructed by UT5QBC with STM+5351+TFT disp. After it I will return to arduino – my eyesight become poorer each year – I am over 70 now. You explanations of the code I hope allow to make useful project of simple CW QRP trcv.

    • Jan commented on January 15, 2017 Reply

      You are welcome. Feel free to come back to me should you have any issues with it.

      What you want to do is not a very common configuration (usually the IF offset is always on), so most other VFO systems don’t really take it into account. It would be fairly easy to modify the code for the others to do this too, though – the DDS doesn’t really care what numbers you send in if they are within the valid ranges.

    • Jan commented on January 15, 2017 Reply

      Hmm, I quite like the UT5QBC design, I may use it in my current project too – a Bitx/Minima inspired receiver for 40m. I was going to use Si5351 anyway, so I can as well save time and use this instead.

      I need to dust-off my Russian and start reading Russian/Ukrainian blogs too 🙂

  • Serge commented on January 16, 2017 Reply

    Hi Jan!
    I am glad that my info is useful to you also.
    My browser -Google chrom alow to read even japaneese sites – but I ask Google to translate it into English. The quality of such translahion is much better then translation into Russian. But slavic languages Google Chrom translate into Russian without mistakes – I could recommend it to you.
    Another project with 5351 is discribed in the blog of UR5FFR. But he is using arduino. Type of display also could be TFT.
    Best wishes SERGE RW3DF

    • Jan commented on January 16, 2017 Reply

      Oh no worries, my Russian is rusty, I didn’t speak it for 20 years, but I can still read it (surprisingly) fine “natively” without going through Google Translate 🙂 We used to have Russian mandatory in school as everywhere in the Eastern Bloc, so I had 6 years of it.

      Conversation would be another matter, though – I can still understand most of it (I am Slovak by origin, so slavic languages are close for me), but finding the right words to respond would require some work after so many years not speaking it.

      Unfortunately UT5QBC doesn’t publish the source code for his device, only a compiled binary. That’s no good for me, because I wouldn’t be able to adapt it to my needs (different wiring, USB, etc) 🙁

      I have found the UR5FFR one too, but that one uses a normal 1602 LCD module from what I can see. That is not really much different from what I have already – I have basic Si5351 control code running already, so adapting the VFO code to drive this instead of the DDS would be simple.

      73, OM2ATC

  • Craig G1VQI commented on February 5, 2017 Reply

    Hi Jan , made the dds, great stuff , almosr worked first time , i had to change the r in rotary . How do you change the upper feq limits_ At present i have a 10.695 if which is additicve meaning it shutts down at 19 meg…if i want to go over 30 meg , with either this or the 9851 ( master clock freq aside) How do i do it? set clamp from 30e6 to 40e6?

    • Jan commented on February 6, 2017 Reply

      Hello Craig,

      You can change the limits in the function clampFreq(). Replace 30e6 with 40e6, yes.

      However, do keep in mind that above that 30MHz the output of the DDS starts to be very nasty, lots of noise, harmonics and distortion. That is why I have put the upper limit at 30. You may need to change the output filter and perhaps even amplify/buffer the output, because the signal level drops with frequency. If you decide to move the limits, I strongly suggest checking the output with a scope for both distortion and stability, especially if you are planning to use it in a transmitter.

      All the best,

      73, Jan

  • George commented on February 13, 2017 Reply

    Hi Jan!
    I have an optical encoder that gives 400 pulses per cycle. I connected it to the VFO, but the setting is very steep. I would like to ask whether software is able to reduce the number of input pulses thing as a preliminary divider.
    I tried to 74HC74 with 74HC393, but nothing happened. Apparently things are not as simple as I want (HI).

    • Jan commented on February 13, 2017 Reply

      Hello George,

      I am afraid that is not so simple. The firmware is designed to work with a cheap mechanical quadrature encoder where the signals from the two phases are shifted by 90 degrees relative to each other. The pulses from the two lines generate something called a Gray code – a very specific pattern that the library uses to decode in which direction and by how many steps did the encoder move.

      You cannot simply divide those pulses, it would break this relationship and the Rotary library used to decode it will get confused.

      If you want to use your optical encoder, as a quick an dirty hack you could try to modify the ISR(PCINT2_vect) interrupt handler function. You could make it ignore every other step of the encoder, effectively dividing the resolution by half (e.g. by adding a counter variable and process the pulse only when it reaches a certain value, otherwise you only increment the counter).

      Alternatively you could change the tuning step in the same function, but the navigation between the decades and changing of the tuning steps would still be too fast.

  • George commented on February 14, 2017 Reply

    Hi Jan!
    Thanks for responsiveness.
    I will continue to use mechanical encoders.
    I have a large stock of them.
    All the best.

    • Jan commented on February 14, 2017 Reply

      OK, that should work fine. The optical encoders have much higher resolution than the mechanical cheapies, that’s why it doesn’t work so well with it.

      Good luck,

      73, Jan

  • Bob Gunnels commented on March 12, 2017 Reply

    Hello Jan,

    Thank you for your wonderful work! I just built a DDS VFO with an Arduino Nano board. Everything seems to work fine except when I have IF activated, the output frequency does not return to the TX frequency when transmitting even though “TX” is displayed. Any idea where I went wrong?

    FYI, for a buffer amplifier I used a LT1227 chip, running on 12 VDC single supply, and get almost 10 V P-P output with very flat response from 1 – 30 MHz even into fairly low impedance loads!

    Bob N1KW

    • Jan commented on March 12, 2017 Reply

      Hello Bob,

      Do you mean that the IF stays applied regardless of the state of the TX line? That is intentional, the hw was designed for a superhet where there is always a mixer “in the loop”.

      If you are building a CW rig that directly drives the PA without going through mixing, then yes, that will be a problem. It is relatively easy to change in the code, though – see my reply to Serge, he had the same issue:

      73, Jan

      • Bob Gunnels commented on March 12, 2017 Reply

        Thank you Jan! That did the trick. Your program is the best yet for controlling the Arduino/DDS that I have seen and it is very easy to operate!

        Bob N1KW

        • Jan commented on March 12, 2017 Reply

          You are welcome. I am glad that it works for you.

          73, Jan

          • Bob Gunnels commented on March 12, 2017

            Hi Jan,

            I have encountered another problem. I operate QSK and unfortunately the VFO is not switching between RX and TX modes fast enough. I changed btnTX.interval value to zero and that helps but there is still something not right. I need output on the TX frequency as soon as possible after A2 goes low and return to RX output quickly as well when A2 goes high. Hopefully it is not a limitation of the A9850 module.

            Bob N1KW

          • Jan commented on March 13, 2017

            Uff, you are pushing the device a bit beyond of what it has been designed for. Robert KH2BR asked a similar thing (see his comment above). If you have reduced the debounce interval already (btw, I suggest something like 1ms, not 0 – that could potentially trigger some bug in the debouncing library) and it is still not enough, then there isn’t much you can do. The DDS doesn’t switch frequencies instantly, it takes a bit of time to retune and stabilize.

            If you really must have the output instantly, then you need another solution. Have two separate VFOs/oscillators and switch between them using e.g. PIN diodes or transistors. Si5351 based solution could better for that because there you have 3 separate frequency outputs on the same chip.



Leave a Reply

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