Wednesday, 7 October 2015

Concept Session 7 - SDR Build

The final session of the Concept program (for now... we may build a LPF, TX and PA in the future) is the build of the SDR shield. The PCB for this shield is a little bit more complicated than the VFO and include one SMD to be mounted.

Concept S7 SDR kit 006

As mentioned in the last posting two toroid colds have to be wound:

Concept S7 SDR kit 007

These are a single coil of 37 turns on a T30-6 toroid (4.2uH) and a tri-filar coil of 10 turns on a T30-6 toroid (0.35uH).

Build sequence

The first component to mount is the FST3253 CMOS switch SMD device. Take a look back at the instructions in the VFO Build session to help with this step. Be sure to get the orientation of the part correct, pin 1 & 16 at the TOP.

Concept S7 SDR kit 009

Concept S7 SDR kit 010

Concept S7 SDR kit 011

Concept S7 SDR kit 012

Concept S7 SDR kit 013

Concept S7 SDR kit 014

Concept S7 SDR kit 015

Connect up and testing

Connect your LCD display and rotary encoder to the VFO shield, plug the SDR shield in above it and connect the audio cable to your PC or USB A-to-D convertor. Then connect an aerial to the RFbus pins RX and RXGND. Many users have preferred to use a larger display of 20 x 4 lines and the sketch below is for this display. Load and run it. Start the SDR program HDSDR on your PC and chose the correct sound put source. Then hit the START button and your SDR should be on the air!

Concept S7 SDR kit 017

Code

NOTE: not all LCD displays have the same I2C address, check your and change "0x27" in the code to your CLDs address (e.g. 0x3F is a common alternative).

// My_SDR_40M_4
// 20 x 4 display
// for 40m, with TX/RX control and bandplan display
// Si5351 I2C bus
// SDA = A4
// SCL = A5
// LCD I2C bus
// SDA = A4
// SCL = A5
// rotary encoder pins§                           §§§
// DT = 2
// CLK = 3
// SW = 4

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

// RTC I2C address
#define RTCADDR 0x68

// LCD
#define LCDADDR 0x27
#define LCDCOLS 20
#define LCDROWS 4

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

// Rx & Tx signals
#define RX 13
#define TX 12
#define KEY 8

// number of band plans
#define PLANS 12

// dds object
Si5351 dds;

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

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

// define plan structure
typedef struct {
  uint32_t lower;
  uint32_t upper;
  char alloc[30];
} plan;

// band plan array contents cHz/cHz/Text
plan bp[PLANS] = {
  {700000000, 700100000, "CW    QRSS   7000.7 "},
  {700100000, 703990000, "CW    PSK31 QRP 7030"},
  {703990000, 704690000, "NB    WSPR 7040     "},
  {704600000, 704990000, "NB    Auto          "},
  {704990000, 705290000, "All   Auto          "},
  {705290000, 705990000, "All   Digital       "},
  {705990000, 706990000, "All                 "},
  {706990000, 707990000, "All   HELL 7077     "},
  {707990000, 709990000, "All   SSB QRP 7090  "},
  {709990000, 712990000, "All   EMGCY 7110    "},
  {712990000, 717490000, "All   SSB CON 7165  "},
  {717490000, 720010000, "All   DX Intnl      "},
};

// start freq & step
uint32_t freq = 700000000; // cHz, start frequency
uint32_t step = 10000; // cHz, init 100Hz step

// RTC time and date
byte Second, prevSecond, Minute, Hour, DoW, Date, prevDate, Month, Year;

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);

  // set 8mA output drive (max possible)
  dds.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);

  // can insert Si5351 calibration here if required

  // enable SDR output CLK2, disable CLK0 & 1
  dds.output_enable(SI5351_CLK0, 0);
  dds.output_enable(SI5351_CLK1, 0);
  dds.output_enable(SI5351_CLK2, 1);

  // encoder, button, RX, TX, band and KEY pins
  pinMode(DT, INPUT_PULLUP);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(SW, INPUT_PULLUP);

  pinMode(RX, OUTPUT);
  pinMode(TX, OUTPUT);
  pinMode(KEY, INPUT_PULLUP);

  xmit(digitalRead(KEY)); // set RX|TX, KEY = LOW is TX
  getRTC(); // get time

  freqOut(freq); // cHz, output freq

  dispDate(0, 0);
  dispTime(12, 0);
  dispFreq(6, 2, freq, 1); // display freq kHz col 4 row 2
  dispMsg(0, 3, scanPlan()); // display band plan col 0 row 3
}

void loop() {
  getRTC(); // get time
  if (Date != prevDate) {
    dispDate(0, 0);
    prevDate = Date;
  }
  if (Second != prevSecond) {
    dispTime(12, 0); // display it, if changed
    prevSecond = Second;
  }

  // tune?
  if (tune()) {
    freqOut(freq); // output freq
    dispFreq(6, 2, freq, 1); // update freq display
    dispMsg(0, 3, scanPlan()); // update band plan display
  }

  // step?
  if (button()) {
    dispStep(step, 17, 2);
  }
  xmit(digitalRead(KEY)); // RX|TX
}

// tune?
bool tune() {
  unsigned char dir; // tuning direction CW/CCW

  dir = rot.process(); // read encoder
  if (dir != DIR_NONE) { // turned?
    if (dir == DIR_CW && (freq < bp[PLANS - 1].upper - step)) freq += step;
    if (dir == DIR_CCW && (freq >= bp[0].lower + step)) freq -= step;
    return true;
  }
  return false;
}

// change step?
bool button() {
  if (digitalRead(SW) == LOW) { // button pressed?
    while (!digitalRead(SW)); // wait for release
    if (step == 1000000) step = 10000; // reset
    else step = step * 10; // or increment by x10
    return true;
  }
  return false;
}

// search for band info
char *scanPlan() {
  for (int i = 0; i < 15; i++) {
    if (freq >= bp[i].lower && freq < bp[i].upper) // find plan
      return bp[i].alloc; // return when found
  }
}

// Output Freq for SDR, on CLK2, f cHz
void freqOut(uint32_t f) {
  dds.set_freq(f * 4ULL, 0ULL, SI5351_CLK2);
}

// Tx/Rx KEY HIGH = RX, LOW = TX
void xmit(bool x)
{
  if (x == LOW) // TX
  {
    dispMsg(0, 2, "TX ");
    digitalWrite(RX, HIGH); // Rx off
    digitalWrite(TX, LOW); // Tx on
  }
  else
  {
    dispMsg(0, 2, "SDR");
    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);
}

// get time from RTC, convert bcd to decimal
void getRTC() {
  // Reset the RTC register pointer
  Wire.beginTransmission(RTCADDR);
  byte zero = 0x00;
  Wire.write(zero);
  Wire.endTransmission();

  // request 7 bytes from the RTC address
  Wire.requestFrom(RTCADDR, 7);

  // get the time data
  Second = bcdToDec(Wire.read()); // 0 - 59
  Minute = bcdToDec(Wire.read()); // 0 - 59
  Hour = bcdToDec(Wire.read() & 0b111111); // mask 12/24 bit
  DoW = bcdToDec(Wire.read()); //0 - 6 = Sunday - Saturday
  Date = bcdToDec(Wire.read()); // 1 - 31
  Month = bcdToDec(Wire.read()); // 0 = jan
  Year = bcdToDec(Wire.read()); // 20xx
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val) {
  return ( (val / 16 * 10) + (val % 16) );
}

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

// display step
void dispStep(uint32_t s, byte c, byte r)
{
  switch (s) // display step
  {
    case 10000:
      dispMsg(c, r, "  ");
      break;
    case 100000:
      dispMsg(c, r, " +");
      break;
    case 1000000:
      dispMsg(c, r, "++");
      break;
  }
}


// display date and time
void dispDate(byte c, byte r) {
  lcd.setCursor(c, r);
  switch (DoW) {
    case 1:
      lcd.print("Mon");
      break;
    case 2:
      lcd.print("Tue");
      break;
    case 3:
      lcd.print("Wed");
      break;
    case 4:
      lcd.print("Thu");
      break;
    case 5:
      lcd.print("Fri");
      break;
    case 6:
      lcd.print("Sat");
      break;
    case 7:
      lcd.print("Sun");
      break;
  }

  lcd.print(" ");
  lcd.print(Date);

  lcd.print(" ");
  switch (Month)
  {
    case 1:
      lcd.print("Jan");
      break;
    case 2:
      lcd.print("Feb");
      break;
    case 3:
      lcd.print("Mar");
      break;
    case 4:
      lcd.print("Apr");
      break;
    case 5:
      lcd.print("May");
      break;
    case 6:
      lcd.print("Jun");
      break;
    case 7:
      lcd.print("Jul");
      break;
    case 8:
      lcd.print("Aug");
      break;
    case 9:
      lcd.print("Sep");
      break;
    case 10:
      lcd.print("Oct");
      break;
    case 11:
      lcd.print("Nov");
      break;
    case 12:
      lcd.print("Dec");
      break;
  }
}

void dispTime(byte c, byte r) {
  lcd.setCursor(c, r);
  if (Hour < 10)
    lcd.print("0");
  lcd.print(Hour);
  lcd.print(":");
  if (Minute < 10)
    lcd.print("0");
  lcd.print(Minute);
  lcd.print(":");
  if (Second < 10)
    lcd.print("0");
  lcd.print(Second);
}

No comments: