Saturday, 15 November 2014

SDR first steps - the DDS

This is a new build of a DDS using the Adafruit Si5351 module. It adds a CD74ACT74 as a divide-by-four Johnson counter to generate IQ quadrature signals.

The DDS has two modes, SDR & VFO. It is tuned in programable steps (at the moment 10kHz for SDR & 100Hz for VFO) . The shield RF Bus outputs VFO and I & Q which are derived like this:

freq = indicated frequency on display

D5 HIGH	SDR mode	freq x4 clk0 = VFO x4 output
			freq x4 clk1 = Johnson /4 = I & Q x1 outputs
D5 LOW	VFO mode	freq x1 clk0 = VFO output


The input for tuning is a rotary encoder. An LCD display shows the band and the tuned frequency.

Three bands are generated, 40, 30, & 20m. These are selected in turn by the rotary encoder push button. Selecting a new band tunes a preset band frequency.

Photo 11 22 2014 13 38 00

The 5 connections across the left are for the Rotary Encoder and its Button and 4 for the I2C LCD display. This is documented in the source code.

The schematic diagram is:

Scanner Pro 1

Software

The software is a "Universal VFO" this software uses the new "Si5351.h" library from NT7S and implements a VFO switchable to 40, 30, & 20m bands. At the top of the code are defined constants, SDR & VFO, which determine the output frequency, x4 or x1. This value is set to SDR if you want an output x4 the indicated frequency on the VFO output, and a x4 drive to the onboard CD74ACT74 Johnson counter to generate I & Q signals. The VFO output in SDR mode can be used, for example, for the Softrock Lite SDR board. In VFO mode it changes to a single x1 output on the VFO RF bus pin.
The mode, SDR or VFO, is selected by a High or Low on pin D5, selected at start-up by the red jumper link. The constant STEP4 or STEP1 determine the step of the tuning, which can be, for example, 10kHz for driving an SDR, or 100Hz for a direct tuned radio.

The board thus has a VFO output which can be x1 or x4 the indicated frequency. When the hardware /4 Johnson counter is implemented and used, then two further outputs I & Q, provide outputs with the Si5351 running at x4 the frequency displayed. The VFO output is from CLK0, the IQ drive is from CLK1. Board outputs VFO, I & Q are on the RF bus (see previous post below).

Thus this design, with small changes in the software can satisfy diverse applications. Here's the code:

// Universal VFO, outputs for 40-30-20m
// V0.9
// Function
// Generates VHF & IQ outputs.
// Si 5351 output is x1 or x4 displayed frequency.
// Frequency selected in programmable steps by rotary encoder. Button changes bands 40-30-20m
// Change of band sets new frequency to band
// LCD displays frequency in kHz and band
// -----
// Shield has RF bus with VFO, I & Q outputs
// Two modes, set by link (pin D5):
// SDR (D5 HIGH) outputs x4 from clk0 to VFO, & x1 quadrature to I & Q. 10kHz steps
// VFO (D5 LOW) outputs x1 on clk0 to VFO out, 100Hz steps
// -----
// DDS I2C SI5351 
// SCL = A5
// SDA = A4
// I2C address 0x60
// ------
// display I2C LCD 16 * 2
// o A5 SCL (y)
// o A4 SDA (or)
// o +5     (r)
// o GND    (bwn)
// I2C address 0x27
// -----
// encoder KY-040
// o D2 DT  (y)
// o D3 CLK (g)
// o D4 SW  (or)
// o +5     (r)
// o GND    (bwn)
//-----
// D12-D13 TX-RX enable
// D10-D11 band switch relays 40-30-20
// -----

// I2C, Si5351, LCD and Rotary Encoder libraries
#include "Wire.h"
#include "si5351.h"
#include "LiquidCrystal_I2C.h"
#include "Rotary.h"

// XTAL freq, PLL multiplier, outputs 0-2, IQ Mult, steps (Hz)
#define XTAL 25
#define MULT 36
#define SDR 4
#define VFO 1
#define STEP4 10000
#define STEP1 100

// SDR/VFO link pin 5, button pin 4, Rotary Encoder pins 2 & 3 (A & B)
#define LINK 5
#define BUTTON 4
#define ROTA 2
#define ROTB 3

// dds object
Si5351 dds;

// lcd object
LiquidCrystal_I2C lcd(0x27, 16, 2);

// Rotary Encoder object
Rotary rot = Rotary(ROTA, ROTB);

// start frequencies (Hz)
float bandStart[3] = {
  7000000, 10050000, 14000000};
char bandName[3][4] = {
  "40m", "30m", "20m"}; // band 0 = 40m, 1 = 30m, 2 = 20m

// band & freq, freq steps, mode
byte band;
float freq;
float steps;
byte mode;

void setup()
{
  // encoder, button, link, relays & TXRX pin config
  pinMode(ROTA, INPUT_PULLUP);
  pinMode(ROTB, INPUT_PULLUP);
  pinMode(BUTTON, INPUT_PULLUP);
  pinMode(LINK, INPUT_PULLUP); // default input = HIGH

  mode = digitalRead(LINK); // HIGH (default) = SDR, LOW = VFO
  if(mode == HIGH) steps = STEP4; // set steps STEP4 (SDR) or STEP1 (VFO)
  else steps = STEP1;

  dds.init(SI5351_CRYSTAL_LOAD_8PF); // init si5351

  lcd.init(); // init LCD & backlight
  lcd.backlight();

  // startup band & frequency
  band = 0; // 40m
  freq = bandStart[band];

  freqOut(freq);

  dispMenu(); // display menu
  dispFreq(freq); // display freq
  dispBand(bandName[band]); // display band
}

void loop()
{
  unsigned char dir; // tuning direction CW/CCW
  byte step;

  if(digitalRead(BUTTON) == LOW) // button pressed?
  {
    while(!digitalRead(BUTTON)); // wait for release
    if(band == 2) band = 0; // go around
    else band++;

    freq = bandStart[band]; // set centre freq of new band

    freqOut(freq); 
    dispFreq(freq); // update freq & band display
    dispBand(bandName[band]); 
  }

  dir = rot.process(); // read encoder
  if(dir != DIR_NONE) // turned?
  {
    if(dir == DIR_CW) freq += steps; // increment +/- STEP
    if(dir == DIR_CCW) freq -= steps;

    freqOut(freq);
    dispFreq(freq); // update freq display
  }
}

// freq out x IQ
void freqOut(float freq)
{
  if(mode == HIGH) // SDR mode
  {
    dds.set_freq(freq * SDR, 0, SI5351_CLK0); // VFO out, x4
    dds.set_freq(freq * SDR, 1, SI5351_CLK1); // CD74AC74 quadrature to I & Q
  }
  else
  {
    dds.set_freq(freq * VFO, 0, SI5351_CLK0); // direct VFO out, x1
  }
}

// display menu
void dispMenu()
{
  lcd.setCursor(1, 0); // display caption
  if(mode == HIGH)  lcd.print("SDR");
  else lcd.print("VFO");
  lcd.setCursor(1, 1);
  lcd.print("Band");
}

// display freq
void dispFreq(float f)
{
  float pf;
  pf = f /1000; // kHz

  lcd.setCursor(5, 0);
  lcd.print("       "); // clear last freq display
  if(pf < 9999.9) lcd.setCursor(6, 0);
  else lcd.setCursor(5, 0);
  lcd.print(pf, 1);
  lcd.setCursor(13, 0);
  lcd.print("kHz");
}

// display band
void dispBand(char b[])
{
  lcd.setCursor(5, 1);
  lcd.print("   "); // clear last band display
  lcd.print(b);
}


No comments: