Sunday, 17 May 2015

Universal VFO sketch

The Universal VFO hardware is a shield for the Arduino UNO, with a Si5351 DDS and a 74AC74 Johnson counter for IQ generation.

The Shield has four outputs, VFO, BFO, I & Q from the Si5351 DDS CLk0, CLK1 & CLK2.

It covers 40, 30 & 20m with tuning in 100Hz steps.

It connects to an LCD and ROtary Encoder with Button.

Transmit is enabled by a LOW signal on D9 (KEY) input, this controls D31 & D12 (Tx & Rx) outputs.

Photo 05 17 2015 21 45 29

Code

// Universal_VFO outputs for 40-30-20m
// V1.0 M6KWH ganymedeham.blogspot.com
// "Universal SDR|VFO" shield uses CLK0 for VFO output
// Frequency changes in STEPS by rotary encoder. Button changes band for 40-30-20m
// Start frequency on each band is selected on startup or band change
// LCD displays frequency in kHz, band and RX/TX
// note: freq variable is in cHz

// ----- CONNECTIONS
// 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)
// -----
// Control bus definition
// D13  D12  D11  D10  D9   D8   D7   D6   D5   D4   D3   D2   D1   D0
// RX   TX   Band   *  Key           NMEA 1PPS Btn  EncB EncA  PC   PC
// out  out   out      in             in   in   in   in   in
// * for future Band switch control, e.g. transverter
// RX | TX out enable LOW
// Band 40 LOW, 30 & 20 HIGH
// Key in LOW TX, HIGH or O/C RX

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

// LCD
#define LCDADDR 0x27
#define LCDCOLS 16
#define LCDROWS 2

// rotary Encoder pins 2 & 3 (DT & CLK), band change pin 4 (SW)
#define DT 2
#define CLK 3
#define SW 4

// RX & TX (enable = LOW), band relay and XMIT (LOW = TX)
#define RX 13
#define TX 12
#define BAND 11
#define XMIT 9

// tuning freq STEPS (cHz), 100Hz
#define STEPS 10000

// dds object
Si5351 dds;

// LCD object
LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDROWS);

// rotary Encoder object
Rotary rot = Rotary(DT, CLK);

// start frequencies (cHz), band names
uint32_t freqStart[3] = {
  710000000, 1014000000, 1410000000};
char bandName[][4] = {
  "40m", "30m", "20m"};

// band, freq, RXTX flag init
byte band = 0;
uint32_t freq = freqStart[band];

void setup()
{
        
  // init LCD & backlight on
  lcd.init();
  lcd.backlight();
  
  // init dds si5351 module, "0" = default 25MHz XTAL
  dds.init(SI5351_CRYSTAL_LOAD_8PF, 0);
  
  // enable VFO output CLK0, disable CLK1 & 2
  dds.output_enable(SI5351_CLK0, 1);
  dds.output_enable(SI5351_CLK1, 0);
  dds.output_enable(SI5351_CLK2, 0);

  // encoder, button, RX, TX, band and XMIT pins
  pinMode(DT, INPUT_PULLUP);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(SW, INPUT_PULLUP);
  pinMode(RX, OUTPUT); // SDR RX enable
  pinMode(TX, OUTPUT);  // SDR TX enable
  pinMode(BAND, OUTPUT); // PA band relay 40/30&20m
  pinMode(XMIT, INPUT_PULLUP); // key default HIGH
  
  // init RX
  digitalWrite(RX, LOW); // RX enable
  digitalWrite(TX, HIGH); // TX disable
  
  bandRly(band); // switch PA band relays
  freqOut(freq); // output freq

  dispMsg(0, 0, "VFO             "); // display VFO col 0 row 0
  dispFreq(5, 0, freq, 1); // display freq col 5 row 0
  
  dispMsg(0, 1, "Band    "); // display Band col 0 row 1
  dispMsg(5, 1, bandName[band]); // display band col 5 row 1
  
  RxTx(digitalRead(XMIT)); // set RX
}

void loop()
{
  tune(); // rotary encoder tuing
  bandChg(); // button pushed for band change
  RxTx(digitalRead(XMIT)); // PTT/KEY for transmit, 1 = RX, 0 = TX
}

void tune()
{
  unsigned char dir; // tuning direction CW/CCW
  
  // tune?
  dir = rot.process(); // read encoder
  if(dir != DIR_NONE) // turned?
  {
    if(dir == DIR_CW) freq += STEPS; // increment freq +/- STEPS
    if(dir == DIR_CCW) freq -= STEPS;

    freqOut(freq); // output freq
    dispFreq(5, 0, freq, 1); // update freq display
  }
}

void bandChg()
{
    // change band?
  if(digitalRead(SW) == LOW) // button pressed?
  {
    while(!digitalRead(SW)); // wait for release
    if(band == 2) band = 0; // loop
    else band++;

    freq = freqStart[band]; // set centre freq of new band
    
    bandRly(band); // switch PA band relays
    freqOut(freq); // output freq
    
    dispFreq(5, 0, freq, 1); // update freq & band display
    dispMsg(5, 1, bandName[band]); 
  }
}

// frequency (in cHz) for VFO, on CLK0
void freqOut(uint32_t f)
{
    dds.set_freq(f, 0ULL, SI5351_CLK0); // converted to cHz
}

// switch band relay
void bandRly(byte b)
{
  switch(b)
  {
    case 0: // 40m
      digitalWrite(BAND, LOW);
      break;
    case 1: // 30 & 20m
    case 2:
      digitalWrite(BAND, HIGH);
      break;
  }
}

// change RX TX, x = HIGH RX, x = LOW TX
void RxTx(bool x)
{ 
  if(x == LOW) // TX
  {
    dispMsg(13, 1, "TX");
    digitalWrite(RX, HIGH); // RX off
    digitalWrite(TX, LOW); // TX on
  }
  else
  {
    dispMsg(13, 1, "RX");
    digitalWrite(RX, LOW); // RX on
    digitalWrite(TX, HIGH); // TX off
  }
}

// display char msg at col c, row r
void dispMsg(uint8_t c, uint8_t r, char *m)
{
  lcd.setCursor(c, r);
  lcd.print(m);
} 

// display freq in kHz,col c, row r, d decimal places
void dispFreq(uint8_t c, uint8_t r, uint32_t f, uint8_t d)
{
  lcd.setCursor(c, r); // clear last freq display
  lcd.print("           ");
  
  lcd.setCursor(c, r); // clear last freq display
  lcd.print((float)f / 100000, d); // convert to float for print function
  
  lcd.print("kHz");
}

No comments: