AD9850 VFO/signal generator

By | February 22, 2015

22.3.2016 UPDATE – if you are looking to build a radio VFO, there is a newer version of this code available, with more radio-oriented features.

I have started to play with amateur radio again and wanted to have a decent VFO/signal generator that could cover most of HF without complicated part switching required. So I have got one of those eBay AD9850 DDS modules and built a VFO using the design from Richard, AD7C:

Unfortunately, that Arduino shield has a few flaws:

  1. The firmware is rather basic and user-unfriendly. If you make a mistake when selecting the tuning step, you have to click through the entire list of steps again, there is no way to go back. If you miss it again on the second pass, argh …
  2. The frequency selection using the tuning step is fine for when tuning a VFO over a band, but it is very clumsy when having to perform a large frequency change (e.g. switch from 80m to 20m).
  3. The IF frequency is hardwired in the code. Granted, this isn’t something that is changed often, but it would still be nice to be able to reprogram it without having to reflash new firmware.
  4. Richard is using a slightly different looking module than I have got (there are several variants on eBay) and mine was unreliable when used with an Arduino, refusing to start occasionally.
  5. My module is very unreliable when the data are clocked in through the D7 pin of the module. It needs to use the DATA pin instead. Again, this could be due to the difference in the modules.

In order to address these, I have mostly rewritten the original code and have done a few modifications to the circuit as well. I didn’t want to have it as an Arduino shield, because I expected this to be a tool that will get more use than a one-off hack, so I have built it using a standalone ATMega168 microcontroller, running at 8MHz internal clock. It is still Arduino compatible, so one could use a real Arduino instead as well.

The other reason why I have built the VFO as a standalone device and not using an Arduino is the point 4 above. My Arduino is only a 5V version. At 5 volts the DDS often didn’t want to start correctly, producing no output until the circuit was power cycled. Probing the DDS chip with a scope showed that it was not receiving clock from the oscillator. After a lot of headscratching I have found a post at the AVRFreaks forum ( that the problem is likely that the oscillator isn’t rated to work at 5V, even though the DDS chip itself works fine at that voltage. My module uses an integrated 125MHz oscillator from TXC Corporation (part TXCJHT4D):

AD9850 DDS module, note the TXC oscillator part

I haven’t managed to find this exact oscillator module in their product list, but as most of their production is rated only up to 3.3V, I do suspect that this is the problem with my module as well. When running the DDS module from 3.3V, it is completely reliable. A really nice trap to fall into, fortunately no magic smoke has escaped 🙁

So having that sorted out, it left me with either having to power the DDS module from 3.3V and everything else from 5V, providing some sort of voltage conversion between the DDS and the microcontroller. The alternative was to run everything from 3.3V. That necessitates a 3.3V display module (most are designed for 5V – a common 5V LCD module will not work at 3.3V!) and lowering the clock frequency to at most 8MHz (ATMega168 is not rated to run at higher than about 9MHz at 3.3V Vcc). 8MHz is convenient, because that is the frequency of the internal RC oscillator too, so it saves a crystal and the two loading caps.

I have added two buttons, one for resetting the micro and the other is used for setting the frequency. Then I have brought out the UART pins, those are useful for both debugging and for bootloading the device. The remaining additions are the ISP port and the 3.3V voltage regulator used to power everything. If someone wants to use a real 3.3V Arduino to build this, I have marked the matching Arduino pin numbers in the schematics.

What remains to be added is a proper buffer amplifier, so that the module can actually drive a 50Ohm load. Right now the output is unbuffered, that is sufficient for driving something like the NE602/612 mixer, but in general it is not a good idea. A buffer using LMH6703, BGA2748 opamps or the MAR-6 amplifier module would be preferred if this was to be used as a true signal generator (thanks for the tip, James!).

Schematics of the DDS generator

The firmware defaults to the VFO mode, indicated by the text “VFO” in the second row. The encoder changes frequency using the preset tuning step. The step can be changed by pressing the encoder and then rotating it to select the desired tuning step. Pressing the encoder again goes back to frequency tuning.

Pressing the button A (the closest to the encoder) switches into a “cursor” mode (“CUR” string in the second row) – holding down the button allows to move a cursor over the frequency display and releasing it lets the operator change the selected decade directly. This mode is useful when an exact frequency needs to be set quickly. Pressing the encoder switch will change back to normal VFO mode.

Pressing the button B (the middle one) toggles the IF frequency. IF is subtracted from the selected frequency, so if the display is showing 10MHz and the IF is set to 2MHz, the DDS will generate 8MHz. Whether or not IF is active is indicated by the letters “IF” after the frequency display, with the sign indicating whether the IF is added to (“+IF”) or subtracted (“-IF”) from the displayed frequency.

Pressing both button A and B together permits to set the IF frequency. It works the same as the “cursor” mode described above, except the IF frequency is changed instead of the DDS one (“IF” is shown instead of “CUR”). Pressing the buttons again while in this mode toggles whether the IF is additive or subtractive (F+IF or F-IF). Pressing the encoder button will change back to the VFO mode, with the new IF frequency.

The microcontroller will store both the current DDS frequency and IF frequency in the EEPROM after 2 seconds when no more frequency changes are done, so the settings are remembered and restored on the next power on.

The firmware enforces tuning limits between 1MHz and 30MHz, not allowing to tune beyond these. In case that the limit is hit, it will set the DDS frequency to the highest/smallest allowed frequency, as applicable.

Assembled DDS generator, with the display module removed from the header above the contrast trim pot. The buttons are, from the left: Reset, IF, frequency setting button. The resistor to the IF button is not necessary (removed in the schematics).

Finished DDS driving a simple direct conversion receiver on a breadboard (NE612 used as a mixer, TL082 as a low-pass filter/amplifier and a PC soundcard as amplifier). The thing hanging off the left side is an FTDI FT232R serial to USB module that I was using to program the microcontroller.

In order to compile the Arduino sketch, you will need the following:

Arduino IDE needs to be set to use the LilyPad Arduino board setting because the circuit uses 8MHz clock (not 16MHz external crystal as the normal Arduino Unos and similar). Otherwise you can modify the boards.txt file to add a new entry for this board or use avrdude and a programmer to flash the microcontroller.

Note: After uploading the compiled code to the microcontroller for the first time, the EEPROM is not initialized, so both the DDS frequency and IF frequency will be invalid. It is therefore best to do the first start without the DDS module connected. To fix that it is necessary to set both frequencies manually for the first time. After 2 seconds they will be written into EEPROM and everything will work fine from that point on. Alternatively, it is possible to pre-program the EEPROM using avrdude, the frequencies are stored in little endian format as follows:

DDS frequency: bytes 0-3 of the EEPROM
IF frequency: bytes 4-7 of the EEPROM
IF mode (0 = subtractive, 1 = additive): byte 8 of the EEPROM
IF on/off (0 = off, 1 = on): byte 9 of the EEPROM

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


  • 1 May 2015 – Added saving of the state of the IF
  • 30 Apr 2015 – Added the option for both additive and subtractive IF and more frequency range checking
  • 27 Feb 2015 – Updated the code to use the Bounce2 library instead of the obsolete Bounce
  • 22 Feb 2015 – initial posting


24 thoughts on “AD9850 VFO/signal generator

  1. Luis Alberto

    Hello down everything that I need for the compilation and ide you recommended and I can not compile, Thanks

  2. Luis Alberto

    Hello down everything that I need for the compilation and ide you recommended and I can not compile I mean the VFO with the AD9050, Thanks

  3. Luis Alberto

    Hello down everything that I need for the compilation and ide you recommended and I can not compile I mean the VFO with the AD9850, Thanks

    1. Jan Post author

      Hello Luis,

      I am sorry that you have problems with the code.

      However, if you don’t tell me what exactly went wrong (error messages?), which version of the Arduino IDE are you using and which board are you compiling for, I cannot really help you. It would be pointless shooting in the dark because there are thousands of things that could potentially fail or be wrong.

      All the best,


    1. Jan Post author


      There is an indication +IF/-IF on display when the IF mode is enabled and the display shows the F +/- IF frequency (not the one actually being set on the VFO). The IF frequency itself can be displayed and modified if you press both buttons.


    1. Jan Post author


      It should pretty much work as-is. The only issues related to the 8MHz clock on the standalone chip are the fuse settings, but you don’t need to change those when using an Arduino.

      The wiring should be compatible too, just double check. The Arduino pin numbers are noted in the schematics.

  4. Rich Carstensen

    What an Excellent Upgrade with your software from the original AD7C’s Stuff! I can’t believe the ease of operation and things work! Add the 1 HZ Tuning rate for Morse Code Operators!
    Great Job, Jan!


    1. Jan Post author

      Hello Rich,

      Thanks for the praise, you do make me blush 🙂 I am glad that it works so well for you.



  5. Rich Carstensen

    Hello Jan!
    Yes the software works good, But with the AD9851 I can’t even find my frequencies! I have an if offset on top of it of 4.00 Mhz! So if I tune the VFO for 1.899 kHz on the vfo, and it’s in the 160 band I actually hear 3.524.9 80 CW I guess I’m going to have to buy a AD9850 if I EVER want to see this VFO work! And I barely have enough drive out of the vfo to drive my 40 meter cw rcvr for these tests! It has a SA612 mixer front end. I can barely hear the vfo directly into the front of the icom 735! Why can’t you reply to my email? I tried to build 2 amp boards for the AD9851 with no success! Please reply to my email! I need opinions at this point as the 2 units I’m testing with have good working analog vfo’s! This project has been bad for me! bye and waiting for a reply to my email

  6. Jan Post author

    Hello Rich,

    Sorry about your troubles.

    If you are using AD9851, that could very much be an issue – I recall that that chip has a difference in some registers – some extra bits that need to be handled, otherwise the frequency will be off. They are not 100% compatible with AD9850, I believe AD9851 goes up to almost twice the frequency than AD9850 thus there is a difference in the way the tuning word is calculated!

    I don’t have an AD9851 module, so you will need to check the datasheet for it. Otherwise it shouldn’t be too hard to modify the code to handle a AD9851.

    Re output level – that sounds odd, I have been using AD9850 to drive NE612 LO input without much hassle, even without an output buffer. If your module has a trim pot, you may try to adjust it – it has an effect on the output amplitude. On the other hand, the module doesn’t have a 50Ohm output impedance (more like 200Ohm), so if you are trying to drive that, you will need a buffer.

    However, I cannot speak about AD9851, never used it and it could be different. It also depends on your circuit, without schematic it is difficult to know what could be wrong with it.

    And re e-mail: I didn’t reply to your e-mail because I have never received any, only your comment here. I am sending a copy to the e-mail address you put in the comment form too.

  7. Kevin ZS6KMD

    Hi Jan, Thank you for the hard work in re-working the AD7C code with much needed updates. Switching my working AD7C VFO to your new version was a breeze. Just added the switches and checked the code for changes, uploaded the sketch… Working…

    I am using an Arduino Pro-Mini on mine, I picked up a handful at a flea market for a few dollars.
    I am running mine on an NE612 DC project I am busy with for the local hobby market and it works brilliantly…

    Thank you once again… I now want to figure out how I can get the analogue S meter I built to work on the LCD as well…


    1. Jan Post author

      Hi Kevin,

      I am glad that it works so well for you. I have actually another version ready that adds a few features more relevant for a HAM TRX than a signal generator this was originally meant for. I haven’t posted it online yet, but I can send you a copy if you want. It basically implements RIT/XIT feature and will switch the offsets (if set) depending on a state of an additional pin where you can bring in the tx/rx switching signal.

      Adding an S-meter shouldn’t be too hard – just use something like analog_read() on the Arduino on one of the analog pins to read the voltage from the S-meter. You will need to modify the display update function then.

      Regards and 73!


      1. Kevin

        Fantastic Jan, I would appreciate if you would send me the updated sketch. I have been experimenting with the S meter. I have a working graph, but not yet happy with it. I will have to generate special characters to eventually get what I want.

        Keep up the fantastic work
        73 ZS6KMD

  8. Jim Russell

    Jan, thanks for the sketch and circuit information. It works very well. I would be interested in your updated code, if it’s available, to offset the VFO frequency so that it can be moved out of the RX pass band during transmit.

    73, Jim

    1. Jan Post author

      Hi Jim,

      I have a yet unpublished version that adds RIT (offset from the displayed freq on RX) and XIT (offset on the displayed freq on TX). Will that help you?
      I need to finally sit down and update the page with the new code.



  9. Jim

    That sounds perfect. I am using the vfo to control the transmitter frequency so offsetting the frequency during receive, will move the VFO so as not to interfere with the signal being received.


    1. Jan Post author

      OK, I will try to write it up and put the code online later today.


  10. Heriberto(tito)

    Saludos Jan, disculpe que le escriba en español, confeccione un VFO con Si5351 que usted publico hace algun tiempo, muy buen trabajo y se ajusta a mis necesidades, pero necesito si ayuda si le es posible, ya que no salva los ajustes de IF ni de BFO u necesito que guarde estos valores en eeprom, soy la cm2kmk y le estaria agradecido si me compartiera conmigo esta parte del codigo para arduino
    73 cm2kmk.

    1. Jan Post author

      Hello Heriberto,

      I have replied to you by e-mail, sorry for not noticing this post earlier! (please, post under the right article, this one is about the AD9850 version, not the Si5351)

      BTW, I have just posted version 4.0 of the Si5351 code which does have the EEPROM saving function.


      73, Jan

  11. Pingback: Si5351 VFO – Jan's bits and bytes

Leave a Reply

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