Over the last couple weekends, I put together an RF signal generator based on a AD9850 DDS module controlled by an ATmega328 microprocessor. In this entry, I describe both physical construction and the arduino sketch underlying its operation. The most recent versions of both code and schematic are archived on github.
I had originally intended that the AD9850-based DDS serve as VFO in an HF receiver described in Part One of the Let’s Build Something article in QRP Quarterly by Pete Juliano, N6QW, and Ben Kuo, KK6FUT. However, having arrived somewhat late to the party, I didn’t start construction until a couple years after the article was published and Part Two was already out. In Part Two, we learn that the final transceiver project based on the Part One receiver will actually built around an si5351 module rather than an AD9850. However, I had ordered a few AD9850 modules when they first came out — I thought it best to “buy low” when they hit $4 each — so I pressed ahead with the intention of making a freestanding as bench test equipment rather than as part of a rig.
Lots of people have done this before, and AD7C has a particularly nice write up including code. The description by N6QW in QQ and on his website is enough to get started, but the code on his website is intentionally barebones. The routine used to poll the rotary encoder did not work well for me, and as my project went along, I ended up scrapping most of that code.
Since ordering parts takes a long time where I live, my build was constrained by working with components that I had on hand. Also, I wanted the following features:
- Cover full usable range of the DDS module, i.e., 100khz to about 40 Mhz.
- A user interface that would allow relatively rapid adjustment, but with minimal hardware; ideally, just the rotary encoder.
- A couple VFOs memories for convenience.
- User-configured frequency at power-up.
- Operation from 13.8V supply, available on my bench. Ideally, some ability to operate at lower voltages in case it was used on battery power.
- Use of arduino environment for development, but retention of only necessary elements for production (even if production in my case is a one off).
- Enough output power to be useful, like up to around 7-9 dBm.
The project is effectively an assemblage of modules: a power supply, microprocessor, I/O for the microprocessor (LCD panel, buttons, rotary encoder), the DDS itself, and a small RF amplifier. I’ll describe each and then go into a little more detail about the software.
The digital parts of the project (microprocessor, AD9850 board, LCD display) need 5V and the RF amp wants a nominal 12V. I have a 13.8V linear power suppy at the bench and I construct just about everything to plug in via powerpole connectors.
I put a 5V linear regulator on the digital board, and while it is not the sexiest way to get 5V, the output is clean and there is no switching noise to rattle around near the RF amplifier. The current draw is too much for the TO-92 package regulators, so I went with a TO-220. The nearly nine volt drop from 13.8V supply means that this regulator gets hot even at the modest current draw of the DDS, so I added a screw-on heatsink and spaced the filter caps a finger’s breadth away. In that configuration, the regulator never gets too hot to touch.
At the breadboard stage, I did try getting 5V from an LM2596 DC/DC buck converter module. The microprocessor tolerated it, but it added some noise on the RF output, and since I want to use this in a “lab” capacity, I would like the signal as clean as possible. Similarly, I was not happy running the whole thing off my bench power supply, which consists of a switched mode source followed by a DC/DC boost/buck converter. Maybe I could have smoothed out these sources with filtering, but going with linear regulators seemed preferable.
In an article about modding the NorCal 49er to replace varactor tuning with an AD9850-based DDS, it was noted that the AD9850 module ran hot with 5V supply. In that article, a few diodes were placed in the supply line to knock down the voltage. I did not find the AD9850 module unduly warm, so I ran it directly from the 5V rail.
For development, I used a trusty old Arduino Duemilanove, with ATmega328 processor. When it came time to build the project, however, I used a new-from-the-shipping-tube ATmega328 and transferred the final version of the firmware via a USBtiny programmer plugged into my Antananarivoduino. I did this mainly to avoid having an external microprocessor clock oscillator on the digital board. By default, the ATmega328 uses a 1 Mhz internal clock. Before transferring the firmware, I used the programmer to set the clock divider fuse such that the processor would use an 8 Mhz internal clock, which proved fast enough for this application.
For debugging, I left digital pins one and two open since these are used for serial rx and tx. In general, I tried to minimize pin use, since in the back of my mind I was thinking that this core design might get re-used in other project, where other pins would be necessary. Consistent with this, I drove the display from an I2C bus rather than serially, saving some pins, but complicating the project in another way (see Display, below).
Given this layout, the microprocessor needed very few supporting components, just the pull-up resistors for the SDL and SCA lines on the I2C bus, and for the microprocessor RESET pin. The push button switches used the internal MPU pull-up resistors, so no external resistors were needed. For the rotary encoder and these switches, all the debouncing was done in software, so no capacitors were required to smooth the switch closures.
I didn’t need much in terms of diplay: mainly a way to show frequency. Most projects use a 16×2 LCD display because it is cheap, easily interfaced, and has room to show frequency and some sort of status. Most of these projects also make use of an I2C backpack to drive the display using only two control lines. I thought it likely that future projects might involve other items on an I2C bus (such as a real time clock and additional memory), so I went with that option.
I had plenty of 16×2 diplays, but when I raided the junque box for I2C backpacks, I came up emptyhanded. Optimistically, ordering one would take a month, so I made one. Most are built from port expanders, either something in the MCP230* family or PCD8574. I had a tube of MCP23008 on hand, so I made a daughter board around that chip.
I took another short cut when it came to the contrast setting for the LCD panel. Most panels expect a potentiometer on pin 3, but I went with a blue/white panel that was at maximum contrast with pin 3 grounded, so I just hardwired it to ground.
The homemade I2C backpack was not quite as small as commercially produced ones built around SMD components, but it worked well enough to hang off the display by a 16-pin header. The backpack itself sprouted four header pins for the two I2C lines plus 5V and ground.
AD9850 DDS Module
The module came with headers attached, so there was nothing to do in terms of constructing this module; I just had to arrange the rest of the circuit so that the various control lines (Clock, Data, Reset, FQ UP) would line up with module’s pins. There are four output pins on the module, two for square wave, and two for sine wave. Given the layout, I took the output from the sine wave output at the end of the headers strip. I did not mess around with the potentiometer on the board; all the calibration was done in software.
The output from the AD9850 is relatively low level and the amplitude is a function of frequency, dropping off as frequency increases.
|Frequency (Mhz)||Output (mVrms, 50 ohm test load)|
With an eye towards making this a general purpose tool, I borrowed a low-level RF amplifier from the Forty-Niner project, mentioned above. There are plenty of other possible circuits I could have used; this was the one at hand when I was building this.
This amplifier is powered from the 12V rail, and its output level is proportional to voltage. It is based around two 2N3904 transistors, and its efficiency is also a function of frequency. The good news is that at lower frequencies, the voltage out is impressive (over 1Vrms at 15Mhz, higher at lower frequencies), but temper that with the observation that above about 700 mV(rms), the waveform bottoms out and distorts. At 30Mhz, the gain can be cranked all the way up and the output remains sinusoidal, but at lower frequencies, it is easy to crank the power output to the point of distortion.
This was very repeatable, and working into a fixed test load, I found that I could pick a threshold voltage level at low frequency that would be a safe cut off for a clean output. I put a voltage rectifier at the output and used it as input to one of the microprocessor’s ADCs. Empirically, I determined that a level of about 800 mV was a good threshold for this build of the amplifier. To get maximum resolution on the ADC, I used the microprocessor’s internal 1.1V voltage reference as full-scale. When the output voltage registers above 800mV, the red LED lights indicating that the signal generator is at or near maximum non-distorted output. An alternative would have been to put the value itself on the LCD display, but this felt more intuitive. If I ever really need to know the value, it will likely be in a context where I am actually measuring it with an oscilloscope.
Since this VFO will live on the bench, eventually I will probably d0 something stupid, like connect it to the wrong load. As a bit of insurance, I added a 4.7V zener diode on the output in case it ends up feeding a high impedance load. The zener would distort the voltage reading approaching its zener voltage, but down near 1V, where we care about it, the measurement of the rectified voltage remains linear.
I pulled in a few building blocks for starters: the control code for the AD9850 came from the Let’s Build Something Project, inspiration for saving memories and last-setting from AD7C’s VFO project, and the interrupt service routine for reading the rotary encoder from my previous description. I also pulled in the generic “wire” and “eeprom” libraries and the liquid crystal display library from Adafruit (“Adafruit_LiquidCrystal.h).
The main loop keeps an eye on inputs and reacts as necessary to update the display or adjust the AD9850’s output. It also keeps tabs on the value read by the ADC to flag whether the output level is too high by lighting a warning LED.
Microprocessor activity devoted to polling is minimized by tests that check on how often a given input has been read; these test shortcircuit around input reads if it the last read was too recent. Between that and using an ISR to catch spins of the rotary encoder, the interface remains responsive.
The user interaction will be setting frequency via the rotary encoder. A blinking cursor is always present on the frequency readout, and its position determines which unit will be incremented or decremented by spinning the encoder clockwise or counterclockwise, respectively. Pressing the rotary encoder will rotate the position rightward until it reaches the tens of hertz position, at which point it will wrap around to the tens of megahertz. Given the intended uses of the vfo and the resolution of the AD9850, units of hertz are shown as a zero and are not adjustable.
To make adjustments more rapid, I implemented two VFO memories, which are saved in eeprom (non-volatile) memory. A short press recalls a memory, a longer press sets it. When the unit powers on, if no memory is set or if it is corrupted to nonsensical value, the processor defaults to a frequency to 7.150 Mhz (hard-coded). However, once memory slot one is set, it will always set to that frequency when powered up. I had considered AD7C’s code, which allows the VFO to turn on to the last displayed frequency, but this requires periodic writes to eeprom. There are a finite (but very large) number of write cycles for each memory location in eeprom, and his code minimizes the frequency of writes by only writing after memory has been changed and left for a short period, but I prefer to made eeprom writes a conscious choice.
My goal was to get this project coded, built, and boxed so I could actually put it to use, but an obvious refinement would be a menu system. The second line of the display is set up to display a message for a certain amount of time and then wipe it. It would not be too hard to implement a menu mode, where the rotary encoder could be used to dial through a (hopefully) short list of configuration settings and possibly some less frequently-used functions.
A few parameters are hard-coded, but could be set up in this manner: the default starting frequency, and the empirically determined values for high RF output warning and AD9850 crystal frequency. For a one-off project, I thought it fine to hard code these, but if several units were produced, each would likely require some tweaking of these values.
I had a heck of a time figuring out what to put this in. I wanted a metal enclosure, but all my premade project boxes are too small. I’m very limited in terms of metal working tools, so I ended up repurposing a Trader Joe’s Peppermint Bark tin.
Out of dedication to the project, I consumed the entire box as quickly as good manners would allow.