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.

Prototype

Prototype

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.

AD9850 DDS Build

AD9850 DDS Build

Here are a couple of pictures showing the finished DDS function generator.

AD9850 DDS Generator 1

AD9850 DDS Generator 1

AD9850 DDS Generator 2

AD9850 DDS Generator 2

And the followings are some oscilloscope captures of the output waveforms:

Sine output (10 MHz)

Sine output (10 MHz)

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).

PWM 1 (1 KHz)

PWM 1 (1 KHz)

PWM 2 (1 KHz)

PWM 2 (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.

PWM 3 (1 KHz)

PWM 3 (1 KHz)

PWM 4 (2 MHz)

PWM 4 (2 MHz)

Download

DDS function generator source code: DDSGen.tar.gz

Be Sociable, Share!

30 Comments

  1. Janne Strom says:

    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

  2. William says:

    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.

  3. shilpam says:

    can you please give schematic diagram of connections between arduino, keypad and AD9850 module

  4. Boris says:

    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.

  5. xw says:

    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?

    • kwong says:

      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!

      • xw says:

        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, &currentFreq, 8);

        for (int i = 0; i < 8; i++)
        EEPROM.write(t * 10 + i, buf[i]);

        the &currentFreq,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"?

  6. xw says:

    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;
    }

  7. brian says:

    hi kenny,

    Can i used labview to program the arduino and DDS module instead of using keypad ??

  8. kris says:

    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..

  9. kris says:

    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.

  10. kris says:

    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.

  11. Irv says:

    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.

    • kwong says:

      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.

  12. Chris says:

    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.

  13. Chris says:

    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

  14. Mirco says:

    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

  15. tan says:

    hi, didnt you notice that the Vaverage of the sinewave produced not equal to 0V?

  16. necessaryevil says:

    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!

  17. marino says:

    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

Leave a Reply