Friday 3 March 2017

BASIC Tech Group - MyNews 27 - AD9850 Analog Digital Synthesiser

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.

IMG 0801

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

IMG 0802

IMG 0803

IMG 0804

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: