DDS Function Generator Build
I picked up an AD9850 DDS module on eBay an couple of weeks ago and decided to build a MCU controlled DDS function generator with it to replace my crude frequency generator I built earlier.
These AD9850 modules are sold very cheaply on eBay. Some people had questioned whether counterfeit or QA-rejected chips were used in these modules. While it is highly likely that these modules — sold at below the cost of the chip — do not meet the all the specifications in the datasheet, they actually work pretty well. The module I received works all the way up to the maximum specified frequency (CLK/2 62.5 Mhz). The amplitude of the output waveform is greatly attenuated after around 30 Mhz however, but this is most likely due to the low pass filter used rather then the limitation of the chip itself. The PCB layout of these boards are not particularly well suited for high frequency operations either. But for hobby uses, the achievable frequency range and the waveform quality should be more than adequate.
The schematics of the module can be found in many places on the web. This one I found appeared to be very accurate. Controlling the AD9850 with an MCU is also rather straight forward. The easiest way is to use the serial interface AD9850 provides to serially shift the turning words into the chip for frequency adjustment. I used an Arduino (ATmega328P) to interface the module. As you can see in the code snippet for the AD9850 below, only four pins are needed to control AD9850 via serial updates.
int PIN_RESET = 13; int PIN_FREQ_UPDATE = 12; int PIN_CLOCK = 11; int PIN_DATA = 10; void AD9850Reset() { digitalWrite(PIN_FREQ_UPDATE, 0); digitalWrite(PIN_RESET, 0); digitalWrite(PIN_RESET, 1); digitalWrite(PIN_RESET, 0); digitalWrite(PIN_CLOCK, HIGH); digitalWrite(PIN_CLOCK, LOW); digitalWrite(PIN_FREQ_UPDATE, 1); digitalWrite(PIN_FREQ_UPDATE, 0); } //frequency in Hertz void AD9850Update(double frequency) { unsigned char i, w; long int tuningWord; tuningWord = frequency* 4294.967296f / 125.0f; shiftOut(PIN_DATA, PIN_CLOCK, LSBFIRST, tuningWord); shiftOut(PIN_DATA, PIN_CLOCK, LSBFIRST, tuningWord >> 8); shiftOut(PIN_DATA, PIN_CLOCK, LSBFIRST, tuningWord >> 16); shiftOut(PIN_DATA, PIN_CLOCK, LSBFIRST, tuningWord >> 24); shiftOut(PIN_DATA, PIN_CLOCK, LSBFIRST, 0x0); digitalWrite(PIN_FREQ_UPDATE, 1); digitalWrite(PIN_FREQ_UPDATE, 0); }
Function AD9850Reset resets the address pointer to W0. Typically you only need to call AD9850Reset once upon power up, during the initial MCU configuration phase.
Function AD9850Update updates the chip output to the frequency value passed in (in Hertz). The five control bytes (32-bit turning words plus 8-bit for control and phase) are serially shifted into AD9850 via shiftOut. Since we don’t need to worry about the phase of the generated signal, the last byte is hard-coded to 0.
The full code listing for the DDS function generator can be downloaded towards the end.
AD9850 has a frequency resolution of 0.0291 Hz (with 125 MHz reference clock), so mHz resolution can be achieved.
Control Interface Design
To make the final DDS function generator more useful, here are some of the features I had in mind:
- Frequency inputs are done using numeric keypad
- Able to save/recall frequency settings
- Adjustable pulse width for the PWM output
- Able to enter/display frequencies in mHz/Hz/MHz
Based on the above requirements, I built a prototype on breadboard to test out the functionalities.
As a bare minimum, 17 control pins are needed from the MCU (4 for AD9850, 7 for the 3×4 keypad and 6 for the 2×16 LCD). This leaves only one spare pin (technically speaking, the Rx/Tx pins used for serial communications can also be utilized, but then I would need to use an external programmer to program the chip) on the ATMega328P. Of course, if you use serial protocol keypad and LCD you will have plenty of unused pins left. For the spare pin, I decided to add a tactile switch which serves as the “function” button.
The following table shows how each of the desired function is achieved using the function button in conjunction with the keypad. The “X” in the table means that a key with the corresponding column header is pressed. For instance if the LCD is currently in display mode, pressing the function key and a number on the numeric keypad will recall the frequency setting that was previously stored in that memory slot.
Mode | Fn | * | # | [0-9] | Action |
Display | X | enter frequency setup screen | |||
X | X | change to the next display unit | |||
X | X | reset output frequency to default (1 kHz) | |||
X | X | save the current frequency to the memory slot indicated by the numeric key | |||
X | recall the frequency stored in the memory slot indicated by the numeric key | ||||
Frequency setup | X | Cancel, go back to the display screen with the previously selected frequency | |||
X | X | change to the next display unit | |||
X | X | reset output frequency to default (1 kHz) | |||
X | set the frequency using the numbers/unit entered and return to display mode |
During the initial power up, the DDS function generator’s output frequency is set to 1 kHz in code. To change the output frequency, press “*” first to switch to the frequency enter screen. The frequency units (mHz, kHz, MHz) can be cycled through by holding down the function button while pressing “*”. Because the keypad does not have a decimal key, you will need to switch to milli-Hertz range to enter frequencies with a resolution finer than 1Hz. After you have entered the desired frequency with the keypad, press “#” to set it. The screen will then change back to the normal display mode (as opposed to the frequency enter mode) with the output set to the updated frequency.
Up to 10 different frequencies can be stored in MCU’s EEPROM to allow fast recalls. This is achieved by pressing the function button and a numeric button while in display mode. The current displayed frequency will be stored into the memory slot indicated by the number ([0-9]) along with the unit used.
To recall a stored frequency, simply press the number on the keypad in display mode. If the recalled memory slot is empty, the recall will be ignored. You can take a look at the code included at the end to see how everthing is done.
Final Build
The picture below shows the layout of the circuitry inside the project box. To ensure that the output signal is as clean as possible, both the power supply portion and the AD9850 portion are shielded (the shield on the AD9850 side was removed for the picture), and both the outputs (square wave output via square wave 1 pin and the filtered sine output via sine 2 pin) are connected to BNC jacks with short wires. In order to change the duty cycle of the output PWM signal, I desoldered the 10K potentiometer on the AD9850 module and replaced it with a panel mount one.
Here are a couple of pictures showing the finished DDS function generator.
And the followings are some oscilloscope captures of the output waveforms:
The amplitude of the sinusoidal output starts to drop off as the frequency goes above 30 MHz, which is approximately the cutoff frequency of LC filter used in the circuit. Depending on how this signal is used, you may need to add an additional amplification stage.
Here are a couple of waveforms from the PWM output (approximately 10% and 90% duty cycle at 1 kHz).
The pulse width can be adjusted with the potentiometer. Since the amplitude of the sine signal changes at higher frequencies, the output duty cycle of the PWM signal will also change as a result. So in order to maintain the same duty cycle, the potentiometer may need to be adjusted with different frequency settings.
As you can see from below, the square wave output remains pretty clean for frequencies up to a couple of megahertz and after which the overshoots become more pronounced. Better layout could alleviate the problem somewhat. But for general uses, this waveform quality should be sufficient at even higher frequencies.
Download
DDS function generator source code: DDSGen.tar.gz
Hello and thanks for a great project.
I have an Arduino mega 2650 , an AD9850 DDS and n combined LCD keypad 16×2 with 6 buttons.
Im new to Arduino programming and im looking for some help with a program to use the buttons to change frequency and have it displayed on the LCD.
I found a couple but they are all for separate LCD and s separate keypad.
Does anyone know where to find a program like this or can help me out writing it?
Regards /Janne Ström
Thank you very much for the valuable information, it is helping me very much put my DSS Function Generator together as you can well imagine, even if one has a lot experience with electronix the work you have done saves us lots of trials and errors. Again, thanks a lot Kerry.
Glad it helped!
can you please give schematic diagram of connections between arduino, keypad and AD9850 module
Hi Kerry,
Very interesting project. Thank you for sharing. Where could I find the schematics for this project?
Andy
It’s pretty simple so I didn’t produce the schematics, if you download the code (towards the end of the post) you should be able to figure out how it is wired up (e.g. the LCD, keypad and the DDS module). Let me know if you need any help.
Does the keypad have any pull-up resistors?
I’ve made one like this guy’s http://www.instructables.com/id/Arduino-30MHZ-DDS-Signal-Generator-In-12/
Would like to upgrade it to a keypad AND encoder using an ATMEGA256 board (Pro-mini won’t have enough pins)I have sitting around collecting dust.
I like your project even more than his.
Also, what do you recommend for adjusting the duty cycle?
Thanks
No, I didn’t add pull-up resistors for this keypad. The AD9850 module has a pot for duty cycle adjustment. I simply replaced that one with a panel mount pot.
Thanks. I thought you replaced the on-board pot, with a DAC or an I2C digital pot to vary the ref voltage to the comparator.
Thank you very much for the information. I repeat passed your way, and it is helping me very much. I’m not very experienced in programming. Your code can be modified to ensure the Sweep mode frequency? This mode is very useful to tune the filters and circuits. Set the start and stop frequency, cycle time and pitch changes.
With respect and gratitude. Boris.
can u explain the last part of your code?
byte buf[8],t,u;
t=(byte)key-48
why need minus 48?between buf[8] is an array right?
48 is the ASCII for “0”, since the key pressed returns the ASCII codes for “0” – “9”, I used t = (byte) key – 48 to convert “0”-“9” to numeric 0-9 so that I could use it to index into the EEPROM. Hope it helps!
Thanks for your replied,it helps:)…..actually i have a lot of question,as I am new to arduino and DDS:
FIrst question,For this part,why the “PIN_RESET” need set to 0,1,0 whereas pin clock and freqeuncy update just set as 1,0 instead of 0,1,0??
void AD9850Reset() {
digitalWrite(PIN_FREQ_UPDATE, 0);
digitalWrite(PIN_RESET, 0);
digitalWrite(PIN_RESET, 1);
digitalWrite(PIN_RESET, 0);
digitalWrite(PIN_CLOCK, HIGH);
digitalWrite(PIN_CLOCK, LOW);
digitalWrite(PIN_FREQ_UPDATE, 1);
digitalWrite(PIN_FREQ_UPDATE, 0);
}
and then second question,I am having a problem to the calculation of the tuning word,what is the meaning of tuning word >>8,>>16 and >>24.For example,if the output freqeuncy is 20Hz,then tuning word=20*4294.967296f / 125.0f=687.the tuning word is 687,how come it can represent into 32 bits?
tuningWord = frequency * 4294.967296f / 125.0f;
shiftOut(PIN_DATA, PIN_CLOCK, LSBFIRST, tuningWord);
shiftOut(PIN_DATA, PIN_CLOCK, LSBFIRST, tuningWord >> 8);
shiftOut(PIN_DATA, PIN_CLOCK, LSBFIRST, tuningWord >> 16);
shiftOut(PIN_DATA, PIN_CLOCK, LSBFIRST, tuningWord >> 24);
shiftOut(PIN_DATA, PIN_CLOCK, LSBFIRST, 0x0);
digitalWrite(PIN_FREQ_UPDATE, 1);
digitalWrite(PIN_FREQ_UPDATE, 0);
}
and the pin button if I PRESS,it means it is in “LOW” status?
third question:
if (functionKeyPressed) { // save frequency setting
memcpy(buf, ¤tFreq, 8);
for (int i = 0; i < 8; i++)
EEPROM.write(t * 10 + i, buf[i]);
the ¤tFreq,the in front "&" of currentFreq stands for ?…and then why "t*10+i"?
sorry,I am quite noob to programming stuff,and between you used "ARDUINO UNO"?
Regarding your first question, the reset sequence is according to what is in the specification (take a look at the datasheet of AD9850)
2. This tool might help you (http://www.kerrywong.com/bcalc/)
3. & means the address of, it’s just standard C/C++. You might want to learn how pointers in C works :)
And to your last question, no, I used the older Duemilanove, but UNO should work as well.
Thanks for your reply…
for the LCD part,can I used this components?
http://www.ebay.com/itm/EKitsZone-LCD-Keypad-Module-for-Arduino-Duemilanove-UNO-MEGA2560-Board-/290673640673?pt=LH_DefaultDomain_0&hash=item43ad7fc4e1
You can, but you will need to modify the code. If you want to use the code unmodified, you just need a standard 16×2 LCD.
This part I am not understand,why t neeed mutiple by 10?
for (int i = 0; i = 0; i–) {
t = inputBuf[bufPtr – 1 – i];
inputBuf[bufPtr – 1 – i] =inputBuf[i];
inputBuf[i] = t;
}
the original message is here:
1)why t need mutiple by 10??
for (int i = 0; i = 0; i–) {
t = inputBuf[bufPtr – 1 – i];
inputBuf[bufPtr – 1 – i] = inputBuf[i];
inputBuf[i] = t;
}
hi kenny,
Can i used labview to program the arduino and DDS module instead of using keypad ??
Are you notice that the amplitude of the 100 Hz and 100Khz waveform generated are different,so how are you going to solve the problem? the possible solution I think is connected it with amplifier..
Yeah, that unfortunately is a major draw back of this simple DDS generator.
Have you think about it on how to solve the amplitude problem? because the output amplitude is 1 Vpp, it is quite low, and some application not quite actually suit it.
Do you have any idea to solve it?might share it ? because the output amplitude is 1 Vpp, it is quite low, and some application not quite actually suit it.
To solve the amplitude issue, you will need to add a wide band gain stage (either AGC or piecewise linear, see http://www.analog.com/static/imported-files/application_notes/28080533AN106.pdf).
Hi,kenny
Have you think about why higher frequency will have lower amplitude? In my opinion, the reason is due to the look up table, is it correct?
Thanks.
I have purchased the AD9850 module from Ebay and used it with a 4×4 keypad and serial LCD module. I have changed your original sketch to communicate with the LCD and added the 4th column of the keypad to the storage memory. Your sketch works well with my Arduino Un0! I am new to Arduino programming so I can not figure out how to make the AD9850 sweep from 100 hertz to 25,000 hertz. I have 3 I/O pins available now so extra Sweep/Normal buttons could be added as needed. Would you please supply me with the code addition that would make the AD9850 sweep over that frequency range. Thank you for your help.
Hi Irv,
Not sure what sweep frequency you had in mind, but you can do a sweep like:
for (double f = 100; f < 25000; f++) { AD9850Update(f); } The above code would sweep from 100 Hz to 25000Hz at 1Hz interval.
I’m a little confused. The picture of your project show the 16X2 LCD display connected to the analog pins of the Arduino. I cannot find anyplace where it explains how to do this. Normally the pins are connected to the digital pins but these are already in use by the keypad and/or the DDS. Can you tell me the wiring of this? This would come in hand with other projects as well.
You can use analog pins as digital pins as well (analog pin 0 to 5 corresponds to digital pin 14 through 19). Hope it helps.
Thank you very much. Looked all over for this info but could not find it even though I read where analog pins could be used. This worked great and will come in hand in other projects where I am limited on how many digital pins I have without using a Mega
Thanks again
Hi Kerry
I enjoyed your project seems very interesting.
I already have the components, and the Arduino dds
I wonder if you can get the schematic and software for Arduino.
thank you
mirco from Italy
hi, didnt you notice that the Vaverage of the sinewave produced not equal to 0V?
Very interesting. I’m wondering how the real AD9850 (25USD at digikey)would perform in the same circuit and on the same PCB. I’m also interested in the AD9833(10USD at digikey)
Thumbs up for your website and your projects!
Excellent !
Could you help me please about programming AD9850 in centesimal Hz?
For example:
24.15 Hz or 12500.55 Hz
I saw a device running in a range from 1.oo Hz to 1 Mhz step 0.01 Hz
Thank you so much
[…] DDS Function Generator Build […]
Hi.
I see that this project is a few years old now but I wonder if it is still supported. So far I’ve not been able to work out the connections to the lcd from the Arduino sketch. I see it uses A0 to A5 on the Arduino but to where to thes connections go on the lcd? Regards
Don m5aky
Just read the other replies and I see now that this has already been answered. Didn’rt see that first time round!
Don
Back again. Sorry, misread that reply. It doesn’t answer my question after all.
So back to my original question: where do the connections A0 to A5 go on the lcd?
Don m5aky
OK I’ve sorted this now.
I’ve managed to adapt the sketch to use an i2c interface for the lcd and everything works fine.
I’d like to add a calibration factor as the frequency is a few 10s of Hertz out.
I would also like to use an i2c adapter for the keypad. This woulf free up a few pins and make the wiring tidier.
If anyone is still following this project I would be grateful for any advice.
Don m5aky
Bristol UK