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:

IMG_1530

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.

IMG_1529

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

IMG_1551

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:

IMG_1567
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.
IMG_1568
The only way to do this is to stack the components, below the LCD I’ve put the atmel 328p
IMG_1570
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
IMG_1571
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 - www.ad7c.com
// 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.  
// YOU MUST PUT THIS BACK TO 0 AND UPLOAD THE SKETCH AGAIN AFTER STARTING FREQUENCY IS SET!
//
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.setCursor(0,1);
   lcd.print("                ");
   lcd.setCursor(hertzPosition,1); 
   lcd.print(hertz); 
   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.setCursor(0,0);
    lcd.print(F("                    "));
   if (millions > 9){lcd.setCursor(1,0);}
   else{lcd.setCursor(2,0);}
    lcd.print(millions);
    lcd.print(".");
    lcd.print(hundredthousands);
    lcd.print(tenthousands);
    lcd.print(thousands);
    lcd.print(".");
    lcd.print(hundreds);
    lcd.print(tens);
    lcd.print(ones);
    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!
   EEPROM.write(0,millions);
   EEPROM.write(1,hundredthousands);
   EEPROM.write(2,tenthousands);
   EEPROM.write(3,thousands);
   EEPROM.write(4,hundreds);       
   EEPROM.write(5,tens);
   EEPROM.write(6,ones);   
   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
  digitalWrite(A0,HIGH);
  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      "));
  delay(3000);
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();
  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(W_CLK);
  pulseHigh(FQ_UD);          // this pulse enables serial mode - Datasheet page 12 figure 10  
  sendFrequency(rx);
  showFreq(rx);
  lcd.setCursor(0,1); lcd.print(F("                "));
  lcd.setCursor(0,2); lcd.print(F("                "));
  lcd.setCursor(hertzPosition,1);    
  lcd.print(hertz);
   // Load the stored frequency  
  if (ForceFreq == 0) {
    freq = String(EEPROM.read(0))+String(EEPROM.read(1))+String(EEPROM.read(2))+String(EEPROM.read(3))+String(EEPROM.read(4))+String(EEPROM.read(5))+String(EEPROM.read(6));
    rx = freq.toInt();  
  }  
}
//
// check frequency and button state
void loop() {
  if (rx != rx2) {
    showFreq(rx);
    sendFrequency(rx);
    rx2 = rx;
  }
  buttonstate = digitalRead(A0);
  if (buttonstate == LOW) {
     setincrement();        
  };  
  // 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

Advertisements

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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.