Frequency synthesizer project

Model 1 was built to test the capabilities of the AD9851 chip, and to optimize the code. Model 2 is the miniaturized version so that it becomes an instrument in the shack.

Model 1

The design is based on the AD9851 chip including an atmel 328p with the Arduino bootloader. And of course I use the Arduino IDE:


Top view: AD8951 with a 30 MHz oscillator, I turned the internal 6x multiplier on. There is a jumper for turning the LCD backlight on/off, perhaps I will replace this with a n-channel MOSFET switch.


The rotary encoder includes a push button, the LCD lists the frequency and the increment setting.


Attenuation of 30 dB is enough for direct input to the airspy/spyverter which I use as a calibrated spectum analyzer.

The design of this DDS mostly follows from the instructables website. There is a todo list for this project:

  • Find a case, right now it is half the size of a Velleman PCB.
  • Increase the output level to perhaps 5 Volt
  • Investigate whether I can turn this into a VNA

Model 2:

Miniaturize model 1 so that it fits in a Hammond case, the idea is the same, except that I use 3 mini voltage regulators and that I stack the LCD display on top of the atmel 328p chip.  Also I rewired everything because of the space restrictions. In the end I got this:

The DDS in its mini Hammond box, the window in the lid is a bit critical, but it always turns out to fit. (Use tape and take your time to make the window.
The only way to do this is to stack the components, below the LCD I’ve put the atmel 328p
So this is what you get at the lowest level, mind the three mini 5V regulators. The board takes 100 mA from a 9V battery
And this is the exploded view, not the piece of foam to keep the 9V battery in place.

Arduino IDE sketch for model 2

// Original design by Richard Visokey AD7C -
// Revision 2.0 - November 6th, 2013
// Ernst Schrama PA1EJO added some modifications in dec-2017
// the DDS board is a AD8951, see comments in code, this is 
// model 2 of the DDS, the pins assignments are different on 
// this board compared to model 1.
// The LCD and the DDS should be REMOVED from the board when
// you are programming, reason is that the power supply is 
// handled by three independent 7805 regulators.
// Includes that we need for LCD, rotary encoder and EEPROM
// Very first time use, set forcefreq=1 once and upload again.
// This is only needed when you change the atmel 328p
#include <LiquidCrystal.h>              // include the LCD library code:
#include <rotary.h>                     // 
#include <EEPROM.h>                     //
// define statements 
#define AD9850_CLOCK 180000251         // What I have has a 180 MHz oscillator (added 251 Hz at 20C)
#define W_CLK 13                       // AD9851 Module pins.    
#define FQ_UD 12      
#define DATA  11       
#define RESET 10   
#define pulseHigh(pin) { digitalWrite(pin, HIGH); digitalWrite(pin, LOW); }
// objects in this code
Rotary r = Rotary(2,3); // sets the pins the rotary encoder uses.  Must be interrupt pins.
LiquidCrystal lcd(9, 5, 8, 7, 6, 4); 
// variables
int_fast32_t rx=7200000;         // Starting frequency of VFO
int_fast32_t rx2=1;              // variable to hold the updated frequency
int_fast32_t increment = 100000; // starting VFO update increment in HZ.
int buttonstate = 0;
String hertz = "100 KHz";
int  hertzPosition = 4;
byte ones,tens,hundreds,thousands,tenthousands,hundredthousands,millions ;  //Placeholders
String freq; // string to hold the frequency
int_fast32_t timepassed = millis(); // int to hold the arduino millis since startup
int memstatus = 1;  // value to notify if memory is current or old. 0=old, 1=current.
// The ForceFreq variable alters the EEPROM 
// Change this to 0 after you upload and run a working sketch to activate the EEPROM memory.  
int ForceFreq = 0;  // this is essential
// the order of the functions hereafter is somewhat different 
// transfers a byte, a bit at a time, LSB first to the AD9851 via serial DATA line
void tfr_byte(byte data) {
  for (int i = 0; i < 8; i++, data >>= 1) {
    digitalWrite(DATA, data & 0x01);
    pulseHigh(W_CLK);   //after each bit sent, CLK is pulsed high
// handles increments of the rotary push button action
void setincrement(){
  if (increment == 1) {increment = 10; hertz = "10 Hz", hertzPosition=5;}  // added this
  else if (increment == 10) {increment = 50; hertz = "50 Hz"; hertzPosition=5;}
  else if (increment == 50){increment = 100;  hertz = "100 Hz"; hertzPosition=4;}
  else if (increment == 100){increment = 500; hertz="500 Hz"; hertzPosition=4;}
  else if (increment == 500){increment = 1000; hertz="1 Khz"; hertzPosition=6;}
  else if (increment == 1000){increment = 2500; hertz="2.5 Khz"; hertzPosition=4;}
  else if (increment == 2500){increment = 5000; hertz="5 Khz"; hertzPosition=6;}
  else if (increment == 5000){increment = 10000; hertz="10 Khz"; hertzPosition=5;}
  else if (increment == 10000){increment = 100000; hertz="100 Khz"; hertzPosition=4;}
  else if (increment == 100000){increment = 1000000; hertz="1 Mhz"; hertzPosition=6;}  
  else {
   increment = 1; hertz = "1 Hz"; hertzPosition=6; };  // changed this
   lcd.print("                ");
   delay(250); // Adjust this delay to speed up/slow down the button menu scroll speed.
// set the frequency to the AD9851
void sendFrequency(double frequency) {
  int32_t freq1 = frequency * 4294967295/AD9850_CLOCK;  // note 125 MHz clock on 9850
  for (int b = 0; b < 4; b++, freq1 >>= 8) {
    tfr_byte(freq1 & 0xFF);
  tfr_byte(0x01);                     // Final control byte, 0 for AD9850, 1 for AD9851 chip 
  pulseHigh(FQ_UD);                   // Done!  Should see output
// Show the frequency on the LCD
void showFreq(unsigned long int input) {
    millions = int(input/1000000);
    hundredthousands = ((input/100000)%10);
    tenthousands = ((input/10000)%10);
    thousands = ((input/1000)%10);
    hundreds = ((input/100)%10);
    tens = ((input/10)%10);
    ones = ((input/1)%10);
    lcd.print(F("                    "));
   if (millions > 9){lcd.setCursor(1,0);}
    lcd.print(" Mhz  ");
    timepassed = millis();
    memstatus = 0; // Trigger memory write
// Handles interrupts
ISR(PCINT2_vect) {
  unsigned char result = r.process();
  if (result) {    
    if (result == DIR_CW){rx=rx+increment;}
    else {rx=rx-increment;};       
      if (rx > 50000000){rx=rx2;};  // UPPER VFO LIMIT (Changed this to 50 MHz)
      if (rx < 10){rx=rx2;};        // LOWER VFO LIMIT (Changed this to 10 Hz)
// Store stuff in memory
void storeMEM(){
  //Write each frequency section to a EPROM slot.  Yes, it's cheating but it works!
   memstatus = 1;  // Let program know memory has been written
// the setup function runs once when you press reset or power the board
void setup() {
  pinMode(A0,INPUT); // Connect to a button that goes to GND on push
  lcd.begin(16, 2);  // This is different because of the LCD that I use
  lcd.print(F("DDS PA1EJO 2017 "));
  lcd.setCursor(0,1); lcd.print(F("Cal +251Hz      "));
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  pinMode(FQ_UD, OUTPUT);    // Configure pins for output to AD9850 module.
  pinMode(W_CLK, OUTPUT);
  pinMode(DATA, OUTPUT);
  pinMode(RESET, OUTPUT);
  pulseHigh(RESET);          // Initialise the AD9850 module. 
  pulseHigh(FQ_UD);          // this pulse enables serial mode - Datasheet page 12 figure 10  
  lcd.setCursor(0,1); lcd.print(F("                "));
  lcd.setCursor(0,2); lcd.print(F("                "));
   // Load the stored frequency  
  if (ForceFreq == 0) {
    freq = String(;
    rx = freq.toInt();  
// check frequency and button state
void loop() {
  if (rx != rx2) {
    rx2 = rx;
  buttonstate = digitalRead(A0);
  if (buttonstate == LOW) {
  // Write the frequency to memory if not stored and 2 seconds have passed since the last frequency change.
  if(memstatus == 0){   
    if(timepassed+2000 < millis()) { storeMEM(); }


If the above listing doesn’t help you then ask me in the comment box below.

Last update: 18-Mar-2019 6:17 AM


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.