An Arduino based DDS-60 controller

An Arduino based DDS-60 controller

A recent project has been the building of a digital VFO based around the DDS-60 kit and Arduino controller. It's a useful thing to have either as part of a homebrew transmitter or transceiver or as a piece of test equipment. It can generate RF from 1 to 60 MHz in 1 Hz steps with the stability of a crystal oscillator and can run as a QRSS beacon. It's nothing particularly original and it's not rocket science but it might be helpful for someone wanting to get started with DDS and microcontrollers. It's still a work in progress and I'll be happy if anyone wants to contribute ideas or discussion.

Use the links below or at the top right to navigate the pages.

Ross Sun, 03/07/2010 - 08:29

Anonymous (not verified)

Sat, 08/04/2012 - 16:49

Why do you show a +12v supply to the Arduino? I assume the DDS60 needs it (I have the AD9850 which is +5v max). The Arduino, if I recall correctly, has a max input of +7v. What am I missing?

The RBBB board has a three terminal regulator so you can supply it with 12 volts. The Arduino chip itself only gets 5 volts.

I don't recall the regulator on the RBBB getting excessively hot but, now that you've mentioned this, I notice that the specs on the RBBB board say 5 to 12 volts. Given that "12 volt" power is usually more like 13.5 or so, I guess it would be a good idea to put a resistor in series with the power line into the RBBB. If a value was chosen to reduce the voltage a bit then it would help dissipate some power and keep the RBBB's regulator cooler.

The specifications here say 4v p-p into 50 ohms.

It's a while ago now but when I looked at it on an oscilloscope I seem to remember that the waveform looked a little distorted at that level.

If you keep it at, say, 2v p-p then if my math is correct that's about 10 mw of power into 50 ohms. If you can get 3v p-p then that's about 22 mw.

Anonymous (not verified)

Thu, 01/24/2013 - 22:09

You backlight voltage to pin 15 is 12v through a 560 ohm R.
This could easily drop to 5v. Was that the intent, and why not
Feed it 5v directly? My LCD backlight specs at 5v, and I may
feed it 5v directly as I have seen elsewhere.

The backlight on my display is an LED so it definitely needs a current limiting resistor. The 560 ohm gave me reasonable brightness at 12v. I'm no expert on LEDs but current is the important measurement, not voltage. They're basically a forward biased diode so the voltage across them is fairly constant. You control the brightness by varying the current.

I think I have seen circuits with the backlight connected directly to 5v as you describe. I can only assume in those cases that the display has a current limiting resistor built in. Maybe yours is like that but you'd want to be sure. You don't want to connect an LED directly to a voltage source.

It doesn't make sense to me to regulate the voltage to the backlight LED. If it is just going through a built in or external resistor then it doesn't need a precise voltage. The little regulator on the Arduino RBBB board is hot enough already so you wouldn't want to put more current through it unnecessarily.


You comments make good sense, and I agree. The light turned on in my head, THANK YOU! I will try a variable resistor from the Arduino's 12V to bias the backlight LED to "ON", like I have done with LEDs in another project I built. Many thanks!!

Your comments will send me back to the books to study diodes again. This is a hobby I learn so much from!


Dave (not verified)

Tue, 02/26/2013 - 17:54

Thanks for the sketch. I'm new to the Arduino and not a C programmer. I would never have made it without your work.

I couldn't get the buttons to work, and found that the internal pullups were not active. I tried adding external resistors and they worked. In the code to initialize the button ports I had to reverse the order of these lines, like this:
pinMode(_buttonPorts[i], INPUT); // set port as input
digitalWrite(_buttonPorts[i], HIGH); // enable pull-up resistor
When I first set the ports as inputs and then enabled the pullups, all was fine. Strange, I sez.

Thanks again for your efforts.

charles (not verified)

Thu, 10/03/2013 - 01:23

This an excellent VFO. Is there a code example that adds a button to retrieve a saved frequency from a memory address and send it to the DDS module? That is, showing where the additional lines of code should be added to the latest version?

Art (not verified)

Mon, 10/28/2013 - 08:52

I agree that this design makes an excellent VFO! I have built several (single band) versions of this and have settled on this as my 'standard' VFO. One caution to anyone who wants to work on / modify Ross' code - the Arduino compiler vs 1.0 works fine but following versions do not produce executable code (for me). I have been too lazy to try to figure out why, so I just keep a copy of vs 1.0 on hand for when I have to re-compile. Many thanks to Ross for the fine work! OBTW I laid out a PCB for the design with an Ebay module but it uses a 'dork' board for the processor - so its probably not useful to many others.

I built the VFO as a signal generator 2 years ago. I couldn't locate the same encoder so I ended up having to change a little of the code to count properly. Occasionally, the encoder would double increment or not at all over one 360 degree rotation.

Yesterday I decide to attack my code change, noticed I had overlooked how the interrupt was fired (CHANGE) and determined I needed it to fire on a rising edge. After reprogramming the chip, I found a totally unpredictable unit. I searched and searched for a loose connection to no avail. While the IDE was open, I inadvertently clicked on the link to this page, saw the comment about the buttons, checked mine and sure enough - not pulled up. This was a surprise as my unit has worked flawlessly since I built it (not counting the glitches from the encoder).

I did the pinMode order change, and found not only was the unit working again, but my encoder changes worked correctly.

I'm not sure I would have looked for the buttons to have been floating had I not serendipitously got here.

The question is why was the order reversal necessary? I believe I used Arduino 1.00 initially and now am using 1.52b.
What goes on internally as the code compiles is not my forté, but I wouldn't think it should change the order of a user's code.


What about working with display type like this?

RS = 12
E = 13
D7 = 7
D6 = 6
D5 = 5
D4 = 4

mine is not working with

LiquidCrystal lcd(A0, A1, A2, A3, A4, A5);


Anonymous (not verified)

Thu, 02/26/2015 - 06:41

In reply to by Gelvis (not verified)

instead of 'LiquidCrystal lcd(A0, A1, A2, A3, A4, A5);', use
LiquidCrystal lcd(14, 15, 16, 17, 18, 19);

Bob N9KR (not verified)

Tue, 11/24/2015 - 12:10

Hi Ross. Just an update on my experiments with this great code and a Mega2560. I needed more pins than the Mega328 provides for some switching control functions in my shack. Built up a Mega2560 & AD9850 version that works well for me but had to make some hacked up code changes (mostly due to direct port manipulation pin changes between the 2 boards…).
If anyone’s interested, building & demo videos are here-- and here—
The 2nd video has links to a fritzing diagram & my hacked up code in the comments section.
Disclaimer: I’m not a programmer and my code hacks are trial & error & definitely not professional, but worked for my setup.
73, Bob N9KR

Rich Carstensen (not verified)

Sat, 12/12/2015 - 16:31

I'm sad to report after talking with Bob, N9KR, that when I load Versions 1.31 and 1.4 off this Web site they don't work! If I load the software into the Arduino Editor and select the 328 and put one program at a time in, they show Errors! Of coarse I don't have anything attached to the port! Just straight Upload of software off this web Site into the Editor with no Arduino Board attached! Is this normal? Thanks!

Rich W8VK

WB6LA (not verified)

Fri, 02/10/2017 - 05:58

You mention use of this VFO as a QRSS beacon. Are harmonics somewhere suppressed such that no low-pass filter(s) are needed? I
mean, if the beacon is set for 7.1 MHz do we need to block 14.2 and 21.3 MHz, etc? It would be way cool to avoid having to wind the
toroids for so many filters. Cheers,




A recent project has been the building of a digital VFO based around the DDS-60 kit and Arduino controller. It's a useful thing to have either as part of a homebrew transmitter or transceiver or as a piece of test equipment. It can generate RF from 1 to 60 MHz in 1 Hz steps with the stability of a crystal oscillator and can run as a QRSS beacon. It's nothing particularly original and it's not rocket science but it might be helpful for someone wanting to get started with DDS and microcontrollers. It's still a work in progress and I'll be happy if anyone wants to contribute ideas or discussion.

Information about DDS (Direct Digital Synthesizer) devices caught my attention soon after I got back into amateur radio last year after a long absence from the hobby. Years ago I was somewhat fascinated by phase locked loop synthesizers and DDS was a distant dream. DDS technology is now readily available at low cost.

The DDS-60 is a kit from George, N2APB, of Midnight Design Solutions in association with the American QRP club and New Jersey QRP Club. It's a small circuit board about 1 x 2 inches. The key component is an AD9851 chip from Analog Devices.

The DDS-60 cannot do much by itself. It needs some sort of controller to send it the correct digital commands to tell it what frequency to generate. The website gives some examples on how various people have done this. None of these were really what I wanted although I used the "DDS Controller" program by WA6UFQ to test my newly constructed DDS-60 via a PC parallel printer port. I have previous experience with Microchip PIC so I was thinking of building a PIC controller but then I listened to a great podcast all about something called the Arduino which sounded ideal for the job.

The Arduino is an open source hardware and software project aimed at making it easy for someone to get into using embedded microcontrollers. The hardware is based around the Atmel ATmega328 chip and boards are available from various manufacturers. The software development environment is a free Java based application which you run on Windows, Mac or Linux and use it to edit, compile and transfer your code to the hardware via USB. It's a very cool project with a growing community around it and an ever increasing variety of accesssories being produced. The latest Arduino board is the Duemilanove. It has 14 digital I/O pins and 6 analog inputs. I used a Duemilanove for my prototype but I rebuilt it using a compatible alternative. More about that later.

Ross Sat, 01/08/2011 - 22:09

Main components

Main components

My design goal was to end up with a standalone box that had DC power in, RF out, a tuning knob, an LCD display, a few push buttons to control various functions and still have a few I/O lines left over. These extra lines would be available for transmit / receive switching, RIT or whatever.

Arduino controller board

I used an Arduino Duemilanove for my first experiment with the DDS-60. It worked well but it's a bit of a squeeze mounting it in a small box along with the other components and it contains more circuitry than is really needed in a production device. With more searching around I discovered the The Really Bare Bones Board or RBBB from Modern Device. It's Duemilanove compatible and at $12.50 makes a great general purpose controller for various projects. It does not have a built-in USB interface like the Duemilanove so you also need a USB BUB board. The good news is if you have multiple RBBB projects then you only need one USB BUB board since you only plug it in and use it when transfering firmware from your computer to the controller.

If you prefer a more do it yourself approach, an alternative is to not use any Arduino board at all. Several vendors sell ATmega328 chips preprogrammed with the Arduino bootloader. You could easily put that plus a few components directly on your own board. You'd still need something like the USB Bub board for loading the firmware into the chip. The RBBB just makes it all rather simple. If you get bored with the DDS-60 you can use the RBBB for your next robotics project. :)

Liquid Crystal Display

16 character x 2 line LCD displays are available at low cost. Most are pin compatible with the Hitachi HD44780. These have a parallel data interface and require a minimum of six I/O lines to drive. The Arduino project has a standard code library to drive the HD44780.

I bought a nice display with a blue backlight for $8.50 on eBay on eBay. The seller seems to have a continuing supply of them and other interesting stuff.

Rotary Encoder

The rotary encoder is the tuning knob. It turns continuously in either direction and tells the controller what it's doing. These come in various types. I'm no expert on these but basically optical is more precise and more expensive than mechanical. An important specification is the pulses per revolution or PPR. This is the number of discrete steps you get when turning the knob. After playing with my old Kenwood TS430S I figured that I wanted 64 PPR. Mouser sell an optical 64 PPR encoder for $17. I used this in my first version but then kicked myself when I realized that a cheaper device will work just fine. See here for an explanation of how the incremental or quadrature types work. If the controller is able to detect every rising or falling edge on both A and B switches then it is notified four times for every one of the PPR pulses. I missed that fact earlier. An inexpensive 16 PPR encoder will provide the desired 64 steps per revolution. My second version uses a $2.80 mechanical encoder from Mouser made by Bourns with a part number of 3315C-111-016.

The encoder is connected to the two interupt inputs on the Arduino rather than polled like the push buttons. This ensures that the controller doesn't miss any pulses even when the encoder is turned quickly.

Push Buttons

Three push buttons are used for various functions. These are inexpensive momentary switches from Radio Shack.

Pin Assignments

The following are the Arduino pin assignments. D0 and D1 are hard wired in the Arduino. The encoder must use D2 and D3 since only they can be used with interupts. The others can be changed if it simplifies board layout or wiring and the software is correspondingly changed.

D0 Serial / USB receive
D1 Serial / USB transmit
D2 Rotary encoder pin A
D3 Rotary encoder pin B
D4 DDS-60 data
D5 DDS-60 clock
D6 DDS-60 load
D7 Spare
D8 Push button 1
D9 Push button 2
D10 Push button 3
D11 Spare
D12 Spare
D13 Spare
D14 LCD d4
D15 LCD d5
D16 LCD d6
D17 LCD d7
D18 LCD Enable

Note that D14 to D19 are labeled A0 to A5 on the Arduino board and documentation because they can be used for analog inputs. They can however also be used as general digital I/O pins and addressed as D14 to D19 with the standard I/O functions.

Ross Tue, 02/23/2010 - 08:30



Click here to download a PDF file of the schematic

A GIF image is below. Hopefully it doesn't mess up the page too much with its width.

Some notes on the schematic

The LCD is powered from the 5 volt output of the RBBB. The regulator in the RBBB seems to cope well.

The backlight LED is powered from 12 volts through a current limiting resistor. 560 ohms seems to give plenty of brightness on mine. As I understand it, not all HD44780s have a backlight.

The RC filter carrying power to the DDS-60 fixed a problem that I was having when the DDS-60 sometimes wouldn't start. This seemed to happen when the power-up wasn't totally clean such as when I connected the power wires to a source that was already switched on and there may have been some "bounce" as the connectors touched. The slower clean ramp-up provided by the RC completely fixed the problem. The resistor also helps dissipate some power so the 5 volt regulator in the DDS-60 stays cooler.

The 0.22 uF capacitors on the rotary encoder are for debouncing. These form an RC filter with the 20 K pullup resistors in the ATmega328. The encoder creates an interupt request whenever a transition occurs to which the processor responds almost instantly. If we didn't have a debounce filter then we would quite likely get a burst of several interupts as the mechanical contacts in the encoder touched but we only want one interupt. The RC filter is a simple solution. We don't need to do the same on the push buttons because they are debounced in the software. We poll them repeatedly in a loop which makes it easy to avoid contact bounce by using simple timing code which avoids reading the switches too frequently. We don't have that sort of control on the interupt lines.

The 10 K trimpot adjusts the display contrast. There is an obvious optimum setting between the "on" pixels being too dim and being so bright that the "off" pixels show.

Ross Wed, 03/03/2010 - 23:00

Building the hardware

Building the hardware

Building the DDS-60

The DDS-60 kit uses surface mount technology and can be a challenge to build if, like me, you haven't used SMT before. There's a lot of advice online about SMT construction so I won't go into it much here. You need a good magnifying lamp, a fine tip on your soldering iron, some tweezers and a "third hand" type thing to hold the board while you're working on it. You'll probably need solder wick to pull excess solder away when you form accidental bridges. The wick is a more gentle and precise method than using a pneumatic solder sucker tool.

I have to admit that it seemed pretty daunting when I started but it got easier as I progressed. The instructions say to start with the AD9851 chip. The good news is that it is the most difficult part. The pins are 0.65 mm apart!

If you really don't want to tackle this then the DDS-60 site has a couple of other options. You can buy it already assembled and or make use of a service someone provides to put the SMT components on the board for you to finish off.

I managed to break the little trimpot that adjusts the output level so I replaced it with a 220 ohm fixed resistor. That seems to be about the right value because I'm getting about 4 volts peak to peak output. Much more than that and it would probably start distorting with clipping on the peaks.

Putting it all together

The RBBB is also a kit but with conventional components and very simple to assemble.

I assembled it all on a piece of Veroboard / stripboard and mounted the whole thing in a metal box with dimensions 120 x 85 x 38 mm ( 4 3/4 x 3 1/4 x 1 1/2 in) that I bought from the arts and craft store called Michaels. Unfortunately they don't seem to sell the boxes online.

You'll need some header type connectors. I got these from Modern Device. Here's the 8 pin for the DDS-60 and a couple of 40 pins to chop off to size for the RBBB to plug into. You don't really need the 40 pin headers if you're prepared to solder the RBBB into your main board. The connectors do tend to make the RBBB sit a little unnaturally high above the board but, since my box is big enough, I like the ability to remove the RBBB if necessary.

I don't think there is anything particularly critical about the construction. As you can see from the photos, I'm no mechanical engineer. I wasn't very successful in trying to cut a neat clean cut opening for the display with my Adel nibbling tool. The whole thing could be made much smaller and neater by someone with better skills and tools. I choose the type of push buttons because they are easy to mount simply by drilling a hole but small keypad type buttons would be more attractive. It all works well which of course is the main thing to us hams :)

Ross Thu, 03/04/2010 - 23:42



Click here to download the latest Arduino software  or "sketch" as it's called. You may have to right click and "Save As" or something similar. It's too long to include directly on the page here. You might want to stick with revision 1.31 if you don't want transceiver mode.

I won't attempt to explain how to use the Arduino IDE (integrated development environment). That's well documented on the Arduino web site. Just one tip here. The serial communications with the PC is a useful debugging tool.

I've tried to include plenty of comments in the code so hopefully it makes some sense to someone reading it.

I think it's written in a reasonably modular way. The code that reads the encoder and push buttons create an "event" with a parameter which the other code then processes. Changing port assignments or adding more buttons would be easy.

Update: March 2013:

See the March 2013 note on the update page about a problem with the buttons.

Programing the DDS-60

To set the DDS to a particular frequency, we need to send the "delta" number to it. The formula is:

delta = (2 ^ 32) * frequency / clock frequency.

Both frequencies are in Hz.

2 ^ 32 means 2 to the power of 32 which is 4,294,967,296. The clock frequency is 180,000,000 Hz plus or minus the calibration offset. That is derived from the 30 MHz oscillator with a 6 times multipler in the AD9851.

These numbers and calculations are bigger than the 32 bit long integer data type.

(2 ^ 32) / 180000000 is 23.860929422 (2 repeating) so one way to do this would be to use floating point math and simply multiply the frequency by 23.860929422. That works but we lose some precision. It's probably good enough but I did some informal tests and output frequencies were often out by several Hz .

Fortunately, the Arduino language has a 64 bit integer type and can do 64 bit arithmetic. This is not mentioned in the standard Arduino documentation. The type is called int64_t and the suffix for literal numbers is LL. Note the declaration of the contstant called TWO_E32. If we multiply the frequency by 2 ^ 32 (essentially a binary a left shift) and then divide the 64 bit result by 180,000,000 we get better precision than the floating point. It also makes the calibration more simple and logical.

I was concerned about the speed of the calculation but the Arduino does it in about 125us which is plenty fast enough for us even if the encoder knob is turned quickly.

I notice that when we use the 64 bit data type and arithmetic, the size of the compiled code jumps considerably. I think it was from about 4 Kb to 12 Kb. I assume this is because we are linking in a whole 64 bit library which means we are probably including a lot of stuff we don't need. I guess if we were tight on program space then the solution would be to write or otherwise obtain just a 64 bit division routine. For now, we are nowhere near the 32 Kb limit so there is no problem.

I initially used the Arduino ShiftOut function to send serial data to the DDS. Since then I've written my own code to do this which is faster.

Using the EEPROM

We make use of the 1 Kb of EEPROM in the Arduino to save the current frequency, calibration offset and stored memory channels so that these remain when the power is off. Excessive write operations shorten the life of the EEPROM so we need to take steps to avoid writing more frequently than we really need to. It would be a problem if we saved the frequency for every step as the encoder knob is turned. Another issue is that the write operation takes several milliseconds so this might create timing problems. The solution implemented here is to check every five seconds and save the frequency only if it has changed since last time we checked. To reduce the writes further, the SaveToEEPROM routines write only those bytes that have actually changed when saving a long or int. With 99 memory channels we still have space left for future enhancements.

Writing to the Display

An initial verison used the Arduino LiquidCrystal library. This worked fine but it was a little slow at times. We only need very basic functionality to so I implemented my own code which writes directly to the LCD.

Overall timing

Whenever the encoder knob is turned in Normal mode, the whole operation takes about 1.8 ms. That's how long it takes to calculate the new delta value, send it to the DDS, convert the frequency to a 10 character string using sprintf, position the display cursor and display the new frequency. We could do that about 550 times per second so timing is quite relaxed. You'd need to move the 64 pulse encoder at a speed equivalent to more than eight revolutions per second before you could beat the code in a timing race. Even then, it wouldn't really be a problem because the encoder is interrupt driven. The worst that would happen is that not every step would be calculated and displayed. Sometimes the frequency might jump by more than one step in a single operation. The encoder dial wouldn't "slip". When it stopped moving, the frequency would be the same as if it was moved slowly.

Ross Thu, 03/04/2010 - 23:41

Operating instructions

Operating instructions

I found it quite challenging to make a user interface that does what I wanted while keeping the operation reasonably simple. It's probably the place that could do with the most improvement, perhaps with more buttons and visual clues on the display. I guess it's also an area where personal preferences vary widely. With that said, here's how it works now.

There are several modes. Button 1 cycles around the modes.

Normal mode

This is the default VFO mode. The display shows the frequency and step. Turn the knob to change frequency.

  • Button 1 - Switch to QRSS mode.
  • Button 2 - Toggle Quiet mode on and off. i.e, turn the RF output on and off. My main use of the DDS so far has been to drive a little QRP transmitter. I haven't built a receiver yet so I'm using a completely separate receiver. I switch to quiet mode on receive so I don't hear my own VFO.
  • Button 3 - Cycle around the frequency steps. At power-on this defaults to 100 Hz. Button 3 will change it to 10 Hz, 1 Hz, 100 KHz, 10 KHz, 1 KHz and back to 100 Hz. It's a bit of a pain when you want to go "backwards", e.g. from 10 Hz back to 100 Hz because you need to press the button five times. A better idea might be to use some sort of two button rocker switch type device which can go forward and back.

Transceiver mode

This provides functionality for the simple CW transceiver with a direct conversion receiver as described here.

QRSS mode

QRSS is very low power, slow CW where a dit or dot time is six seconds or more. It uses frequency shift keying with a shift of about 6 Hz. Receiving is usually done visually by feeding the audio from a receiver into a computer sound card and displaying with software such as Argo. This allows the reception of very weak signals.

Here's one of several web sites with information about QRSS.  I have the DDS connected directly to an antenna running in this mode. If my oscilliscope is accurate then I'm getting about 2.6 volts peak to peak into 50 ohms at 10 MHz which is a power of about 17 mW. I've been running it near 10.140 MHz on 30m because that's where most of the grabbers are. As I write this, I've just seen my signal appear on VE1VDM's grabber in Nova Scotia. :)

Before trying it on this mode, you'll need to edit the software to set your own message. This is usually just your callsign and perhaps your grid square locator code.

  • Button 1 - Switch to memory mode
  • Button 2 - Reset sending to the begining of the message.

If you don't want this mode then edit the code in the NormalMode() function so that button 1 goes straight to memory mode.

Memory mode

This lets us save and retrieve frequencies. The display shows frequency and memory or "channel" number. The encoder knob selects the channel from 1 to 99.

  • Button 1 - Switch to Calibrate mode.
  • Button 2 - Retrieve the frequency saved in the selected channel.
  • Momentarily push button 3 - Save the current VFO frequency in that channel location. It prompts with "Save ?" on the display. Pushing button 3 again saves and returns to normal mode. Pushing any other button returns to memory mode without saving.
  • Hold button 3 - After five seconds the selected memory channel will be erased.

Calibrate mode

This is described on its own page.

Ross Fri, 03/05/2010 - 14:15



The DDS-60 is only as stable and accurate as its master clock oscillator. The oscillator is a small single component called a CMX-309FLC from Citizen Crystal. Its datasheet quotes a frequency stability of plus or minus 100 parts per million. There is a simple linear relationship between the DDS output frequency and the clock frequency therefore the output frequency will have the same accuracy. This means that at 10 MHz it could be up to 1 KHz out. This is probably good enough for many uses but it's not quite the accuracy we've come to expect from modern digital transceivers etc.

The hardware has no means of adjustment. i.e, there is no trimmer capacitor on the crystal. That's not a problem because we don't really care that the clock is not exactly 180 MHz. Whatever the clock frequency happens to be, we just need to make use of that frequency in our calculation of the delta number instead of 180,000,000.

The software and user interface has a Calibrate mode to do this. This is the digital equivalent of what we used to do "back in the old days" with radios that had mechanical dials and a clutch mechanism. We would tune the dial so that it read the frequency of a known standard transmission and then we would physically hold the dial stationary while we continued to turn the tuning knob to zero beat to the reference transmission or our own calibration oscillator therefore matching the dial reading with the true frequency. I remember doing this with my old Yaesu FT-101.

It is reasonably easy to calibrate the DDS using a receiver turned to a frequency standard station such as WWV. Some people connect their receiver audio output to a computer running software such as Spectran so they can visually look at beat frequencies etc. I've done that too at times and it's a valid method but I find it easy enough to do it with just a receiver without computer help.

Switch your receiver to AM mode and tune to the reference transmission. I usually use WWV on 5 or 10 MHz. The signal needs to be loud enough to hear clearly and preferably fairly steady without lots of fading but it doesn't need to be really strong. Now, with the DDS in Normal mode, tune the DDS to the same frequency. You should be able to hear the heterodyne on either side of zero beat as you turn the encoder knob. You might need to fiddle with the signal levels to get them roughly equal. This might mean attaching a short piece of wire onto the DDS output as an antenna. You don't want one signal to swamp the other. With the DDS set to exactly the same frequency using 1 Hz steps, press button 1 twice to switch to Calibrate mode. The number in the lower left of the display now shows the offset in Hz that we are using for the clock. e.g, If it shows 1000 then we're using a clock frequency of 180,001,000 Hz in the calculation. Turn the knob until you hear zero beat. When you get within a few Hz, it's no longer a beat "note", but rather a slow pulsing. One pulse per second is, of course, 1 Hz.  It might take some practice but it's reasonably easy to get it down to 1 Hz or less if you can get the pulses more than one second apart. With WWV, it's easier to hear during a silent period when there a no modulation tones.

When you're at zero beat or close to it, press button 2 to save the new offset. To abandon the attempt, press button 1 to return to restore the original offset and return to Normal mode.


Absolutely accuracy is one thing but drift is another. My DDS-60 drifts about 30 Hz at 10 MHz when switched on from cold. After it's warmed up, it stays within 1 or 2 Hz for a long time. Obviously, the calibration procedure is only worth doing when it's warm and stabalized. The DDS-60 is not intended to be a frequency standard so if you're about to use it for something where accuracy is important then I suggest calibrating it immediately before using it.

I have found the DDS useful for things like checking the adjustment of the carrier crystals on my Kenwood TS430S. I used another receiver which could pickup both the DDS and the oscillator in the TS430S and listened for zero beat.

Ross Fri, 03/05/2010 - 14:15



It's built on the back of the box lid.


Here it is in use in my shack. The rough looking thing with the unmarked toggle switch on the front is the QRP transmitter.

Ross Fri, 03/05/2010 - 22:06

Summary of links

Summary of links

Here are the important links mentioned in the article.

Ross Fri, 03/05/2010 - 22:02

Future ideas & final thoughts

Future ideas & final thoughts

It's been an interesting project which has got me back into homebrew and QRP. The final result is probably not any cheaper than if I had bought one of the ready made DDS devices or kits available. This one looks like a winner from N3ZI. The big thing for me was the learning experience and the fact that I wanted to do my own thing with the ability to modify the hardware and software however I like.

With memories of years ago when building transmitters with conventional VFOs, struggling with stability issues and chirpy CW signals etc the DDS is a very nice change. That said, it does somewhat run against my more minimalist tendencies. I hate to think how many total transistors are in the AD9851 and the ATmega328 compared to a simple conventional VFO with a couple of transitors and a variable capacitor for tuning. Despite my earlier struggles, that can work perfectly well if it's built properly. Of course, there's room for both approaches.

My main use for the DDS so far has been to drive a simple QRP CW transmitter on 80 meters. This is basically the RF stages of Wes Hayward's W7ZOI "Universal QRP Transmitter" as described (pdf) in April 2006 issue of QST. I modified the crystal oscillator stage to act like a buffer rather than an oscillator. It's built very roughly using "ugly" or "Manhatten" style construction on a PC board. I want to rebuild that to be in a similar box to the DDS. An obvious next step would be another little box with a receiver. I'll probably start with a simple NE602 type direct conversion receiver. That will require some software enhancement in the DDS to provide the required offset between transmit and receive and provide for RIT (receiver incremental tuning).

There is a more sophisticated beacon mode called WSPR. WSPR is usually generated by software driving a computer sound card and then transmitted using an SSB transmitter. I haven't studied it in detail but, as I understand it, the result is very slow FSK similar to QRSS but with four frequencies which convey a digital error correcting code. That sounds like it should be possible with the DDS. The AD9851 output is phase continous when it changes frequency as required by WSPR. I don't think I'll have the time to do this any time soon but it's an interesting possibility.

If you have an application that requires more I/O ports, then take a look at this project. It uses a shift register chip so that the LCD can be driven with serial rather than parallel data. The author describes a clever technique which gets it down to only two lines rather than the usual six. Rather than use the two line method, you could use the three line method but share the data and clock lines with the DDS-60. The software raises the appropriate enable / load line to determine which device it wants to talk to. I hate to admit it but I implemented this first and it worked well. That was before I realized that the analog ports on the Arduino could be used as regular digital ports :). The code library that the project publishes is almost a drop-in replacement for the standard LiquidCrystal library. As an example, with this technique there would be enough ports to implement a typical 4 x 3 keypad matrix.

That's about it. Leave a comment below with the "Add new comment" link or email me at tetranz (at) if you have a question or anything to add.

Ross  KB1KGA

Ross Fri, 03/05/2010 - 14:16



I've made a few updates since writing the article. In general I will update the text, schematic and software in the main article without necessarily giving the history there but I'll make a note of it here.

March 11th 2010 - Revision 1.1

After some timing tests it seems that the standard LiquidCrystal display library with the Arduino is relatively slow. It is configured by giving it a list of integer numbers to define the ports. This is very flexible and convenient but it costs quite a few clock cycles because of the bit shifting and manuipulation that's involved.

We only need a limited subset of the HD44780 functionality so I've written code to do this and eliminated the LiquidCrystal library. I also changed the pin to pin mapping between the Arduino and the LCD so there is an updated schematic.

Download:  Software 1.1   Schematic 1.1

March 12th 2010 - Revison 1.2

I'm probably guilty of unnecesary optimization here but I've now replace the Arduino ShiftOut function with my own code to serially write to the DDS. ShiftOut is also quite slow for the same reason as the LiquidCrystal slowness described above. We now write 40 bits to the DDS in about 120us instead of 600us with ShiftOut.

The only code that now uses the Arduino digital I/O functions is the code that reads the buttons. I will leave this as it is because speed is not important for the buttons and it makes it easy to add more buttons of other input devices to the spare inputs.

Download: Software 1.2   Schematic 1.1

March 14th 2010 - Revision 1.3

We now have QRSS mode!  See the operating instructions page for more info.

Download: Software 1.3   Schematic 1.1

May 3rd 2010 - Revision 1.31

Bug fix. If you switched out of QRSS while the frequency was shifted for key down it stayed shifted until the encoder knob was turned. It immediately goes key up now.

QRSS dot time is now six seconds. This seems to be the more common setting on the air.

Download: Software 1.31   Schematic 1.1

October 30th 2010 - Revision 1.4

We now have a transceiver mode. This is designed to be used with the CW transceiver described here. You might want to stick with revision 1.31 if you don't want this mode.

Download: Software 1.4   Schematic 1.1

March 2013

Unfortunately I haven't touched this for quite a while sad. I'd like to get back to it and do some enhancements but ... spare time is scarce.

Several people have reported problems with the buttons not working. The pullup resistors on those ports are not being enabled properly. The solution seems to be to reverse two lines in the code so that we set the port as an input first and then enable the pullup resistors.

Change these lines:

// initialize the button ports
for (int i = 0; i < sizeof(_buttonPorts); i++)
  digitalWrite(_buttonPorts[i], HIGH); // enable pull-up resistor
  pinMode(_buttonPorts[i], INPUT); // set port as input


// initialize the button ports
for (int i = 0; i < sizeof(_buttonPorts); i++)
  pinMode(_buttonPorts[i], INPUT); // set port as input
  digitalWrite(_buttonPorts[i], HIGH); // enable pull-up resistor

The original code works on the older Arduinos but there is something slightly different about the newer boards or chips. I probably misread the specs and got it wrong with the original code but luckily it still worked. The encoder ports a few lines down are initialized in the correct order which explains why this is only a problem with the buttons.

I haven't tested this myself so I won't release a new version but I have put a note in the existing code pointing here.

Thanks to those who reported this.

Ross Fri, 03/12/2010 - 22:20