2 Channel DIY AD9850 signal generator - Page 1
Forum section: electronics / testgear

author: jdev99
author_rank: Regular Contributor
timestamp: January 23, 2024, 01:57:50 pm
content: 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.

author: jdev99
author_rank: Regular Contributor
timestamp: April 09, 2024, 02:05:03 pm
content: 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.EDIT: Found the original enclosure design. Thank you "jopereira".https://www.thingiverse.com/thing:3091269I 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: AD7Chttp://www.ad7c.com/projects/ad9850-dds-vfo/Jan Cigerhttps://www.jciger.com/archives/396Tim Sinaevehttps://codebender.cc/sketch:83250#AD9850%20Frequency%20Generator.inoI 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.

author: jdev99
author_rank: Regular Contributor
timestamp: April 09, 2024, 02:09:49 pm
content: And the schematic and code.Code:/** 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** Author: Tim Sinaeve* Mods by JdeV* 26-03-2024* Arduino Nano usnig "old bootloader"*/#include <LiquidCrystal.h>#include "avdweb_Switch.h" // https://github.com/avandalen/avdweb_Switch// Pin definitionsconst byte ENCODER_BUTTON_PIN = 0;const byte ENCODER_A_PIN = 2;const byte ENCODER_B_PIN = 3;// ObjectsSwitch encButton = Switch(ENCODER_BUTTON_PIN);// RS En D4 D5 D6 D7LiquidCrystal 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// Settingsconst unsigned long MIN_FREQ1 = 0;const unsigned long MAX_FREQ1 = 21000000; // max 20MHzconst unsigned long MAX_DELTA1 = 10000000;const unsigned long DEFAULT_DELTA1 = 1;const unsigned long MIN_FREQ2 = 0;const unsigned long MAX_FREQ2 = 21000000; // max 20MHzconst 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 encoderunsigned long lastTime1;unsigned long currentTime1;unsigned long lastTime2;unsigned long currentTime2;// Storing the readingsboolean encA;boolean encB;//boolean encButton1;boolean lastA = false;void setup() {pulse(AD9850_RESET_PIN1, AD9850_RESET_PIN2);// configure arduino data pins for output AD9850_1pinMode(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_2pinMode(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 pullupspinMode(ENCODER_A_PIN, INPUT_PULLUP);pinMode(ENCODER_B_PIN, INPUT_PULLUP);// Tot select between 2 AD9850spinMode(SELECTOR_SW, INPUT_PULLUP);// setup AD9850 module 1pulseHigh1(AD9850_RESET_PIN1);pulseHigh1(AD9850_WORD_LOAD_CLOCK_PIN1);pulseHigh1(AD9850_FREQUENCY_UPDATE_PIN1);// setup AD9850 module 2pulseHigh2(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 pollingcurrentTime1 = 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-1lcd.print(" ");lcd.setCursor(15, 0);lcd.print((char)127); // (char)127 = ASCII <-freq1 = reading1;char s;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-2lcd.print(" ");lcd.setCursor(15, 1);lcd.print((char)127); // (char)127 = ASCII <-freq2 = reading2;char s;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^32void sendFrequency1(double frequency1, uint8_t p1) { // function gets frequency and phaseint32_t f1 = frequency1 * 4294967296.0 / AD9850_CLOCK_FREQUENCY1; // calculation for value to sendadPhase1 = p1 << 3; // bitshift for phasefor (int i=0; i<4; i++, f1>>=8) { // shift out the double (=4bytes) via DDS-pinsshiftOut(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-valuepulseHigh1(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^32void 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-valuepulseHigh2(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 timecurrentTime1 = millis();if (currentTime1 >= (lastTime1 + 5)) {// read encoder movementencA = digitalRead(ENCODER_A_PIN);encB = digitalRead(ENCODER_B_PIN);// check if A has gone from high to lowif ((!encA) && (lastA)) {// check if B is highif (encB) {// clockwiseif (reading1 + delta1 <= MAX_FREQ1) {reading1 = reading1 + delta1;}} else {// anti-clockwiseif ((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 looplastA = 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 timecurrentTime2 = millis();if (currentTime2 >= (lastTime2 + 5)) {// read encoder movementencA = digitalRead(ENCODER_A_PIN);encB = digitalRead(ENCODER_B_PIN);// check if A has gone from high to lowif ((!encA) && (lastA)) {// check if B is highif (encB) {// clockwiseif (reading2 + delta2 <= MAX_FREQ2) {reading2 = reading2 + delta2;}} else {// anti-clockwiseif ((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 looplastA = 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