Author Topic: 2 Channel DIY AD9850 signal generator  (Read 476 times)

0 Members and 1 Guest are viewing this topic.

Offline jdev99Topic starter

  • Regular Contributor
  • *
  • Posts: 69
  • Country: england
2 Channel DIY AD9850 signal generator
« on: January 23, 2024, 01:57:50 pm »
I am thinking about building a 2 channel  AD9850  signal generator but have no clue how to design it myself.
I have build a single channel before but would like to have a 2 channel, preferably using 1 display, 1 micro controller and 2x AD9850 modules.

Something like this:


(From this site: https://www.next.gr/circuits/AD9850-DDS-Module-Motherboard-l44154.html )

Did anybody made something like this and can share the design with me?
I know there are many cheap models you can buy, but I want to enjoy the DIY process.

Thank you for any assistance.
« Last Edit: January 23, 2024, 02:02:25 pm by jdev99 »
 

Offline jdev99Topic starter

  • Regular Contributor
  • *
  • Posts: 69
  • Country: england
Re: 2 Channel DIY AD9850 signal generator
« Reply #1 on: April 09, 2024, 02:05:03 pm »
So I got it to work, sort of.
This is my attempt to get two of these AD9850 modules working using 1x Arduino nano, 1x Optical Encoder and 1x  16x2 LCD.
I won't recommend this project to be used in stead of a real function generator. My goal was just to learn more about Arduino and these modules and get some practice with KiCad and designing a 3D printable enclosure.
I got the idea for the enclosure from an internet published design, unfortunately I did not get the name of the original designer, sorry if it is yours for not crediting it to you.

I don't know much about programming, designing electronics or using KiCad, so there is room for A LOT of improvement.
Most of the program and circuit is just copied and pasted from other peoples work.
Thank you for their hard work and sharing it with others.
Credits to: AD7C          http://www.ad7c.com/projects/ad9850-dds-vfo/
            Jan Ciger          https://www.jciger.com/archives/396
            Tim Sinaeve      https://codebender.cc/sketch:83250#AD9850%20Frequency%20Generator.ino

I also read that you can use 1 clock for both modules. Not sure if it improves anything here, did it anyway.
http://tripsintech.com/dual-ad9850-in-quadrature-dual-dds-with-adjustable-phase/


Less pins can be used if you have a I2C LCD module.
The amplifier is not great, can definitely be improved.
The phase shift for channel 2 is done by software. The AD9850 allows for 11.25° steps. I implemented that just because it is available, works kind of OK.

Next I am going to try 2x of the AD9833 modules.
« Last Edit: April 09, 2024, 05:17:27 pm by jdev99 »
 
The following users thanked this post: Aldo22

Offline jdev99Topic starter

  • Regular Contributor
  • *
  • Posts: 69
  • Country: england
Re: 2 Channel DIY AD9850 signal generator
« Reply #2 on: April 09, 2024, 02:09:49 pm »
And the schematic and code.

Code: [Select]
/*
* AD9850 based frequency generator using a rotary encoder for frequency tuning.
*
* Based on  AD9851 DDS sketch by Andrew Smallbone at [url=http://www.rocketnumbernine.com]www.rocketnumbernine.com[/url]
*
* Author: Tim Sinaeve
* Mods by JdeV
* 26-03-2024
* Arduino Nano usnig "old bootloader"
*/

#include <LiquidCrystal.h>
#include "avdweb_Switch.h"                       // [url]https://github.com/avandalen/avdweb_Switch[/url]



// Pin definitions
const byte ENCODER_BUTTON_PIN = 0;
const byte ENCODER_A_PIN = 2;
const byte ENCODER_B_PIN = 3;

// Objects
Switch encButton = Switch(ENCODER_BUTTON_PIN);

//                RS  En  D4 D5 D6 D7
LiquidCrystal lcd(12, 13, 10, 1, 4, 5);

int phasePin = A0;

const byte SELECTOR_SW = 11;
int currentStateSW;

const byte AD9850_WORD_LOAD_CLOCK_PIN1 = A1;   // (CLK)
const byte AD9850_FREQUENCY_UPDATE_PIN1 = A2;  // (FQ)
const byte AD9850_SERIAL_DATA_LOAD_PIN1 = A3;  // (DATA)
const byte AD9850_RESET_PIN1 = A4;             // (RST)

const byte AD9850_WORD_LOAD_CLOCK_PIN2 = 6;    // (CLK)
const byte AD9850_FREQUENCY_UPDATE_PIN2 = 7;   // (FQ)
const byte AD9850_SERIAL_DATA_LOAD_PIN2 = 8;   // (DATA)
const byte AD9850_RESET_PIN2 = 9;              // (RST)

const unsigned long AD9850_CLOCK_FREQUENCY1 = 125000000;
const unsigned long AD9850_CLOCK_FREQUENCY2 = 125000000;

uint8_t adPhase1 = 0;                         // Phase output in increments of 180°, 90°, 45°, 22.5°, 11.25° and any combination thereof.
uint8_t adPhase2 = 0;                         // just a place holder

// Settings
const unsigned long MIN_FREQ1 = 0;
const unsigned long MAX_FREQ1 = 21000000;  // max 20MHz
const unsigned long MAX_DELTA1 = 10000000;
const unsigned long DEFAULT_DELTA1 = 1;
const unsigned long MIN_FREQ2 = 0;
const unsigned long MAX_FREQ2 = 21000000;  // max 20MHz
const unsigned long MAX_DELTA2 = 10000000;
const unsigned long DEFAULT_DELTA2 = 1;

unsigned long delta1 = 1;
unsigned long freq1 = 0;
unsigned long reading1 = 0;
unsigned long delta2 = 1;
unsigned long freq2 = 0;
unsigned long reading2 = 0;

// Timing for polling the encoder
unsigned long lastTime1;
unsigned long currentTime1;
unsigned long lastTime2;
unsigned long currentTime2;

// Storing the readings
boolean encA;
boolean encB;
//boolean encButton1;
boolean lastA = false;


void setup() {
 
  pulse(AD9850_RESET_PIN1, AD9850_RESET_PIN2);
 
  // configure arduino data pins for output AD9850_1
  pinMode(AD9850_FREQUENCY_UPDATE_PIN1, OUTPUT);
  pinMode(AD9850_WORD_LOAD_CLOCK_PIN1, OUTPUT);
  pinMode(AD9850_SERIAL_DATA_LOAD_PIN1, OUTPUT);
  pinMode(AD9850_RESET_PIN1, OUTPUT);

  // configure arduino data pins for output AD9850_2
  pinMode(AD9850_FREQUENCY_UPDATE_PIN2, OUTPUT);
  pinMode(AD9850_WORD_LOAD_CLOCK_PIN2, OUTPUT);
  pinMode(AD9850_SERIAL_DATA_LOAD_PIN2, OUTPUT);
  pinMode(AD9850_RESET_PIN2, OUTPUT);

  // set the two pins as inputs with internal pullups
  pinMode(ENCODER_A_PIN, INPUT_PULLUP);
  pinMode(ENCODER_B_PIN, INPUT_PULLUP);

  // Tot select between 2 AD9850s
  pinMode(SELECTOR_SW, INPUT_PULLUP);

  // setup AD9850 module 1
  pulseHigh1(AD9850_RESET_PIN1);
  pulseHigh1(AD9850_WORD_LOAD_CLOCK_PIN1);
  pulseHigh1(AD9850_FREQUENCY_UPDATE_PIN1);

  // setup AD9850 module 2
  pulseHigh2(AD9850_RESET_PIN2);
  pulseHigh2(AD9850_WORD_LOAD_CLOCK_PIN2);
  pulseHigh2(AD9850_FREQUENCY_UPDATE_PIN2);

  lcd.begin(16, 2);
  lcd.setCursor(1, 0);
  lcd.print("AD9850 SIG GEN");
  lcd.setCursor(3 , 1);
  lcd.print("DUAL 10MHz");
  delay(2000);
  lcd.clear();
  lcd.print("Enter Freq:");
  lcd.setCursor(3, 1);
  lcd.print("0000000 Hz");

  // Set up the timing of the polling
  currentTime1 = millis();
  lastTime1 = currentTime1;
  currentTime2 = millis();
  lastTime2 = currentTime2;

 // Serial.begin(115200);
}  //setup()



void loop() {

  // Phase angle adjustable in 11.25° steps only on channel 1.
  int adPhase_x = analogRead(phasePin);
  if ((adPhase_x <= 1024) && (adPhase_x >= 970)){ adPhase1 = 180;}
  if ((adPhase_x <= 969) && (adPhase_x >= 909)){ adPhase1 = 168.75;}
  if ((adPhase_x <= 908) && (adPhase_x >= 848)){ adPhase1 = 157.5;}
  if ((adPhase_x <= 847)&& (adPhase_x >= 787)){ adPhase1 = 146.25;}
  if ((adPhase_x <= 786) && (adPhase_x >= 726)){ adPhase1 = 135;}
  if ((adPhase_x <= 725) && (adPhase_x >= 665)){ adPhase1 = 123.75;}

  if ((adPhase_x <= 664) && (adPhase_x >= 604)){ adPhase1 = 112.5;}
  if ((adPhase_x <= 603) && (adPhase_x >= 543)){ adPhase1 = 101.25;}
  if ((adPhase_x <= 542) && (adPhase_x >= 482)){ adPhase1 = 90;}
  if ((adPhase_x <= 481)&& (adPhase_x >= 421)){ adPhase1 = 78.75;}
  if ((adPhase_x <= 420) && (adPhase_x >= 360)){ adPhase1 = 67.5;}
  if ((adPhase_x <= 359) && (adPhase_x >= 299)){ adPhase1 = 56.25;}

  if ((adPhase_x <= 298) && (adPhase_x >= 238)){ adPhase1 = 45;}
  if ((adPhase_x <= 237)&& (adPhase_x >= 177)){ adPhase1 = 33.75;}
  if ((adPhase_x <= 176) && (adPhase_x >= 116)){ adPhase1 = 22.5;}
  if ((adPhase_x <= 115) && (adPhase_x >= 55)){ adPhase1 = 11.25;}
  if ((adPhase_x <= 54) && (adPhase_x >= 0)){ adPhase1 = 0;}

  sendFrequency1(freq1,adPhase1);                                     // Send it here to update phase angle adjustments.


  /****** AD9850 1 or 2 selected--******/
  currentStateSW = digitalRead(SELECTOR_SW);
  if (currentStateSW == HIGH) {

    if (readEncoderValue1()) {

      lcd.setCursor(15, 1);  // Top Line for AD9850-1
      lcd.print(" ");
      lcd.setCursor(15, 0);
      lcd.print((char)127);  // (char)127 = ASCII <-

      freq1 = reading1;
      char s[16];
      lcd.setCursor(0, 0);
      if (freq1 < 1000) {
        sprintf(s, "%10ld Hz ", freq1);
      }
      if ((freq1 >= 1000) && (freq1 < 1000000)) {
        sprintf(s, "%10ld KHz", freq1);
      }
      if (freq1 >= 1000000) {
        sprintf(s, "%10ld MHz", freq1);
      }
      lcd.setCursor(0, 0);
      lcd.print(freq1);
      lcd.setCursor(0, 0);
      lcd.print(s);
      int p = int(log10(delta1));
      lcd.setCursor(9 - p, 0);
      lcd.cursor();

      sendFrequency1(freq1,adPhase1);
    }

  } else {

    if (readEncoderValue2()) {

      lcd.setCursor(15, 0);  // Bottom Line for AD9850-2
      lcd.print(" ");
      lcd.setCursor(15, 1);
      lcd.print((char)127);  // (char)127 = ASCII <-

      freq2 = reading2;
      char s[16];
      lcd.setCursor(0, 1);
      if (freq2 < 1000) {
        sprintf(s, "%10ld Hz ", freq2);
      }
      if ((freq2 >= 1000) && (freq2 < 1000000)) {
        sprintf(s, "%10ld KHz", freq2);
      }
      if (freq2 >= 1000000) {
        sprintf(s, "%10ld MHz", freq2);
      }
      lcd.setCursor(0, 2);
      lcd.print(freq2);
      lcd.setCursor(0, 1);
      lcd.print(s);
      int p = int(log10(delta2));
      lcd.setCursor(9 - p, 1);
     
      sendFrequency2(freq2);
    }
  }
}  //Loop()


/*********************************************************/

// frequency calc from datasheet page 8 = <sys clock> * <frequency tuning word>/2^32

void sendFrequency1(double frequency1, uint8_t p1) {                                                  // function gets frequency and phase
  int32_t f1 = frequency1 * 4294967296.0 / AD9850_CLOCK_FREQUENCY1;                                  // calculation for value to send
  adPhase1 = p1 << 3;                                                                                  // bitshift for phase
  for (int i=0; i<4; i++, f1>>=8) {                                                                  // shift out the double (=4bytes) via DDS-pins
    shiftOut(AD9850_SERIAL_DATA_LOAD_PIN1, AD9850_WORD_LOAD_CLOCK_PIN1, LSBFIRST, f1 & 0xFF);
  }
  shiftOut(AD9850_SERIAL_DATA_LOAD_PIN1, AD9850_WORD_LOAD_CLOCK_PIN1, LSBFIRST, adPhase1 & 0xFF);     // shift out phase-value
  pulseHigh1(AD9850_FREQUENCY_UPDATE_PIN1);
} // sendFrequency1)

/*********************************************************/

void pulseHigh1(byte pin1) {
  digitalWrite(pin1, HIGH);
  digitalWrite(pin1, LOW);
}  //pulseHigh1()


/*********************************************************/

// frequency calc from datasheet page 8 = <sys clock> * <frequency tuning word>/2^32
void sendFrequency2(double frequency2) {
  int32_t f2 = frequency2 * 4294967295 / AD9850_CLOCK_FREQUENCY2;
  for (int b2 = 0; b2 < 4; b2++, f2 >>= 8) {
    shiftOut(AD9850_SERIAL_DATA_LOAD_PIN2, AD9850_WORD_LOAD_CLOCK_PIN2, LSBFIRST, f2 & 0xFF);
  }
   shiftOut(AD9850_SERIAL_DATA_LOAD_PIN2, AD9850_WORD_LOAD_CLOCK_PIN2, adPhase2, LSBFIRST & 0xFF);     // shift out phase-value
   pulseHigh2(AD9850_FREQUENCY_UPDATE_PIN2);
}  //sendFrequency2()

/*********************************************************/

void pulseHigh2(byte pin2) {
  digitalWrite(pin2, HIGH);
  digitalWrite(pin2, LOW);
}  //pulseHigh2()


/*********************************************************/

boolean readEncoderValue1() {
  boolean b = false;
  if (encButton.longPress()) {
    reading1 = 1000;
    delta1 = 100;
    b = true;
  }
  if (encButton.doubleClick()) {
    reading1 = MIN_FREQ1;
    delta1 = 1;
    b = true;
  }
  if (encButton.poll()) {
    if (encButton.on()) {
      delta1 *= 10;
      if (delta1 == MAX_DELTA1)
        delta1 = 1;
      b = true;
    }
  }

  // read elapsed time
  currentTime1 = millis();
  if (currentTime1 >= (lastTime1 + 5)) {
    // read encoder movement
    encA = digitalRead(ENCODER_A_PIN);
    encB = digitalRead(ENCODER_B_PIN);
    // check if A has gone from high to low
    if ((!encA) && (lastA)) {
      // check if B is high
      if (encB) {
        // clockwise
        if (reading1 + delta1 <= MAX_FREQ1) {
          reading1 = reading1 + delta1;
        }
      } else {
        // anti-clockwise
        if ((delta1 = 10000000) && (reading1 > delta1)) {
          reading1 = reading1;
        } else if ((delta1 = 1000000) && (reading1 > delta1)) {
          reading1 = reading1;
        } else if ((delta1 = 100000) && (reading1 > delta1)) {
          reading1 = reading1;
        } else if ((delta1 = 10000) && (reading1 > delta1)) {
          reading1 = reading1;
        } else if ((delta1 = 1000) && (reading1 > delta1)) {
          reading1 = reading1;
        } else if ((delta1 = 100) && (reading1 > delta1)) {
          reading1 = reading1;
        } else if ((delta1 = 10) && (reading1 > delta1)) {
          reading1 = reading1;
        } else if ((reading1 = 1) && (delta1 = 1)) {
          reading1 = 1;
        }
        if (reading1 - delta1 >= MIN_FREQ1) {
          reading1 = reading1 - delta1;
        } else {
          reading1 = 0;
        }
      }
    }
    // store reading of A and millis for next loop
    lastA = encA;
    lastTime1 = currentTime1;
  }
  b = b || (freq1 != reading1);
  return b;
}  //readEncoderValue1()

/*********************************************************/

boolean readEncoderValue2() {
  boolean b2 = false;
  if (encButton.longPress()) {
    reading2 = 1000;
    delta2 = 100;
    b2 = true;
  }
  if (encButton.doubleClick()) {
    reading2 = MIN_FREQ2;
    delta2 = 1;
    b2 = true;
  }
  if (encButton.poll()) {
    if (encButton.on()) {
      delta2 *= 10;
      if (delta2 == MAX_DELTA2)
        delta2 = 1;
      b2 = true;
    }
  }

  // read elapsed time
  currentTime2 = millis();
  if (currentTime2 >= (lastTime2 + 5)) {
    // read encoder movement
    encA = digitalRead(ENCODER_A_PIN);
    encB = digitalRead(ENCODER_B_PIN);
    // check if A has gone from high to low
    if ((!encA) && (lastA)) {
      // check if B is high
      if (encB) {
        // clockwise
        if (reading2 + delta2 <= MAX_FREQ2) {
          reading2 = reading2 + delta2;
        }
      } else {
        // anti-clockwise
        if ((delta2 = 10000000) && (reading2 > delta2)) {
          reading2 = reading2;
        } else if ((delta2 = 1000000) && (reading2 > delta2)) {
          reading2 = reading2;
        } else if ((delta2 = 100000) && (reading2 > delta2)) {
          reading2 = reading2;
        } else if ((delta2 = 10000) && (reading2 > delta2)) {
          reading2 = reading2;
        } else if ((delta2 = 1000) && (reading2 > delta2)) {
          reading2 = reading2;
        } else if ((delta2 = 100) && (reading2 > delta2)) {
          reading2 = reading2;
        } else if ((delta2 = 10) && (reading2 > delta2)) {
          reading2 = reading2;
        } else if ((reading2 = 1) && (delta2 = 1)) {
          reading2 = 1;
        }
        if (reading2 - delta2 >= MIN_FREQ2) {
          reading2 = reading2 - delta2;
        } else {
          reading2 = 0;
        }
      }
    }
   
    // store reading of A and millis for next loop
    lastA = encA;
    lastTime2 = currentTime2;
  }
  b2 = b2 || (freq2 != reading2);
  return b2;
}  //readEncoderValue2()

void pulse(int pin1, int pin2){
digitalWrite(pin1, HIGH);
  digitalWrite(pin2, HIGH);
digitalWrite(pin1, LOW);
  digitalWrite(pin2, LOW);
  digitalWrite(pin1, HIGH);
  digitalWrite(pin2, HIGH);
digitalWrite(pin1, LOW);
  digitalWrite(pin2, LOW);
 
}

// END
« Last Edit: April 09, 2024, 02:17:22 pm by jdev99 »
 
The following users thanked this post: pope


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf