There are basically three types of frequency synthesiser 1. The Pahse Locked Loop, 2. The purely digital counter - like the Si5351, and 3. the Synthesised sine wave output digital types, like the AD9850.
The AD9850 is able to generate sine wave output from below 1MHz to above 30MHz with low harmonic content. This design for an Arduino shield for an Arduino Uno build a VFO from 1-30MHz with frequency steps of 10Hz to 1MHz.
Here is the VFO inked up to my RF METER and showing an output of 1mW/-3dBM/161mV into a 50R load, yes I know 1mW is not 161mV, but is 225mV, but it's rounded to one decimal place.
And here's a few other photos
The shield has outputs on two of the 8 ins connector, one as a VFO and one as a TX, and alos an output via the SMA connector. The outputs can be selected by the red switch. The connections on the right are for a rotary encoder (frequency) and push button (step size) for tuning and for an LCD I2C display.
Software
Two pieces of software have been written for the AD9850. The first is a ibary with ".h" and ".cpp? files. The ADS9850.h & .cpp files must be put in a library folder called ADS9850 in your "libraries" folder.
// Arduino Library for AD9850 frequency synthesiser module // V1.1 27-2-17 Antony Watts, M0IFA // frequency in Hz and cHz // W_CLK, FQ_UD, DATA, RESET to any pins // void begin(int W_CLK, int FQ_UD, int DATA, int RESET); intialise pins and reset AD9850 // void setFreq(double Hz, float Chz, uint8_t p); set frequency(Hz) and centi-freq(Chz) // void calibrate(double calHz); change xtal frequency from standard 125MHz to new value // void down(); power down, power up with setFreq() #ifndef ADS9850_H #define ADS9850_H #include #define ADS_XTAL 125000000 class ADS9850 { public: ADS9850(); void begin(int W_CLK, int FQ_UD, int DATA, int RESET); void setFreq(unsigned long Hz, unsigned long Chz, uint8_t phase); void calibrate(unsigned long calHz); void down(); private: int _W_CLK; int _FQ_UD; int _DATA; int _RESET; unsigned long _calFreq; void update(unsigned long fr, uint8_t ph); void pulse(int _pin); }; #endif
// Arduino Library for AD9850 frequency synthesiser module // V1.1 27-2-17 Antony Watts, M0IFA // frequency in Hz and cHz, phase in 5bits 0x00 - 0x1F // W_CLK, FQ_UD, DATA, RESET to any pins // void begin(int W_CLK, int FQ_UD, int DATA, int RESET); intialise pins and reset AD9850 // void setFreq(double Hz, float Chz, uint8_t p); set frequency(Hz) and centi-freq(Chz) // void calibrate(double calHz); change xtal frequency from standard 125MHz to new value // void down(); power down, power up with setFreq() #include "Arduino.h" #include "ADS9850.h" ADS9850::ADS9850() { } void ADS9850::begin(int W_CLK, int FQ_UD, int DATA, int RESET) { _W_CLK = W_CLK; _FQ_UD = FQ_UD; _DATA = DATA; _RESET = RESET; _calFreq = ADS_XTAL; pinMode(_W_CLK, OUTPUT); pinMode(_FQ_UD, OUTPUT); pinMode(_DATA, OUTPUT); pinMode(_RESET, OUTPUT); pulse(_RESET); pulse(_W_CLK); pulse(_FQ_UD); } void ADS9850::update(unsigned long fr, uint8_t ph) { shiftOut(_DATA, _W_CLK, LSBFIRST, fr); shiftOut(_DATA, _W_CLK, LSBFIRST, fr >> 8); shiftOut(_DATA, _W_CLK, LSBFIRST, fr >> 16); shiftOut(_DATA, _W_CLK, LSBFIRST, fr >> 24); shiftOut(_DATA, _W_CLK, LSBFIRST, ph & 0xFF); pulse(_FQ_UD); } void ADS9850::setFreq(unsigned long Hz, unsigned long Chz, uint8_t phase) { uint32_t tuneHz, tuneChz; Chz /= 100.0; tuneHz = Hz * pow(2, 32) / _calFreq; tuneChz = Chz * pow(2, 32) / _calFreq; phase = phase << 3; update(tuneHz + tuneChz, phase); } void ADS9850::calibrate(unsigned long calXtal) { _calFreq = calXtal; } void ADS9850::down() { pulse(_FQ_UD); update(0x00000000, 0x04); } void ADS9850::pulse(int _pin) { digitalWrite(_pin, HIGH); digitalWrite(_pin, LOW); }
And the Arduino sketch is
// VFO_DDS freq synth, 100kHz to 150MHz square wave // V2.1 2-3-17 // output G0 // freq steps 10Hz, 100Hz, 1kHz, 10kHz, 100kHz, 1MHz // AD9850 // W_CLK 8 // FQ_UD 9 // DATA 10 // RESET 11 // LCD I2C bus (16x2) // SDA = A4 // SCL = A5 // rotary encoder pins // DT = 2 // CLK = 3 // SW = 4 // ADS9850, LCD and Rotary Encoder libraries #include "ADS9850.h" #include "LiquidCrystal_I2C.h" #include "Rotary.h" // AD9850 pins #define W_CLK 8 #define FQ_UD 9 #define DATA 10 #define RESET 11 // chose address of 0x27 or 0x3F for LCD #define LCDADDR 0x27 //#define LCDADDR 0x3F #define LCDCOLS 16 #define LCDROWS 2 // chose pin 2,3 or 3,2 for encoder //#define DT 2 #define DT 3 //#define CLK 3 #define CLK 2 #define SW 4 // xtal calibration #define CALIBRATE 124999500 // min freq 100kHz, max 30MHz (cHz) #define FREQMIN 100000 #define FREQMAX 30000000 // ads (analog-output digital synthesiser) object ADS9850 ads; // lcd object LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDROWS); // Encoder object Rotary enc = Rotary(DT, CLK); // phase coding, 0-180 in 11.25deg steps uint8_t ph[] = {0x00, 0x01, 0x02, 0x04, 0x08, 0x10}; // initial settings volatile long freq = 7100000; // (Hz) start frequency 7.1MHz volatile long centiFreq = 0; // (cHz) additional 0cHz volatile long freqStep = 10; // (Hz) init 10Hz freqStep uint8_t phase = ph[0]; // init phase // freq change flag volatile bool freqChange; void setup() { // encoder, button pins pinMode(DT, INPUT_PULLUP); pinMode(CLK, INPUT_PULLUP); pinMode(SW, INPUT_PULLUP); // setup interrupts from DT or CLK for tuning attachInterrupt(digitalPinToInterrupt(DT), freqTune, CHANGE); attachInterrupt(digitalPinToInterrupt(CLK), freqTune, CHANGE); // enable interrupts interrupts(); // init LCD lcd.begin(); // init AD9850 ads.begin(W_CLK, FQ_UD, DATA, RESET); // calibrate to xtal actual frequency ads.calibrate(CALIBRATE); freqOut(freq, centiFreq, phase); // Hz, output freq(Hz) & centi freq(cHz), phase freqChange = false; dispMsg(0, 0, "VFO"); // init title dispFreq(5, 0, freq, 2); // display FREQ xxxxxx.xx kHz col 5 row 0 dispMsg(0, 1, "Step"); // init title dispfreqStep(freqStep, 5, 1); // display freqStep xxxxHz|kHz col 5, row 1 } void loop() { // freqStep? if (button()) { dispfreqStep(freqStep, 5, 1); // display freq step } if (freqChange) { freqOut(freq, centiFreq, phase); freqChange = false; dispFreq(5, 0, freq, 2); // display freq } } // ISR - encoder interrupt service routine void freqTune() { unsigned char result; result = enc.process(); if (result == DIR_CW && freq < FREQMAX) { freq += freqStep; freqChange = true; } else if (result == DIR_CCW && freq > FREQMIN) { freq -= freqStep; freqChange = true; } } // change freqStep, 10Hz to 1MHz bool button() { if (digitalRead(SW) == LOW) { // button pressed? while (!digitalRead(SW)); // wait for release if (freqStep == 1000000) freqStep = 10; // back to 10Hz else freqStep = freqStep * 10; // or increase by x10 return true; } else { return false; } } // Output VFO Freq f (Hz) + cf (cHz), phase ph void freqOut(unsigned long f, float cf, uint8_t ph) { ads.setFreq(f, cf, ph); } // display freq at c)ol, r)ow, f (cHz), to d decimal places (kHz) void dispFreq(uint8_t c, uint8_t r, uint64_t f, uint8_t d) { lcd.setCursor(c, r); lcd.print((float)f / 1000, d); // convert to float & kHz lcd.print("kHz "); } // display freqStep s (Hz) at c)ol, r)ow void dispfreqStep(uint64_t s, byte c, byte r) { lcd.setCursor(c, r); switch (s) // display freqStep { case 10: lcd.print("10Hz "); break; case 100: lcd.print("100Hz "); break; case 1000: lcd.print("1kHz "); break; case 10000: lcd.print("10kHz "); break; case 100000: lcd.print("100kHz "); break; case 100000000: lcd.print("1MHz "); break; } } // display msg *m at c)ol, r)ow void dispMsg(uint8_t c, uint8_t r, char *m) { lcd.setCursor(c, r); lcd.print(m); }
No comments:
Post a Comment