Friday 24 November 2017

80m SSB Tramping Rig Arduino Code

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <si5351.h>

const uint32_t bandStart = 3500000;     // start of 80m
const uint32_t bandEnd =   3900000;    // end of 80m
const uint32_t bandInit =  3690000;     // where to initially set the frequency
volatile long oldfreq = 0;
volatile long currentfreq;
volatile int updatedisplay = 0;

volatile uint32_t freq = bandInit ;
volatile uint32_t vfo = bandInit;
volatile uint32_t radix = 1000;

volatile uint32_t LSB_IF_freq = 9000000;   // Crystal filter centre freq
volatile uint32_t LSB_BFO_freq = 9001500;   // Crystal filter centre freq

// Rotary encoder pins and other inputs
static const int pushPin = 9;
static const int rotBPin = 3;
static const int rotAPin = 2;

// Rotary encoder variables, used by interrupt routines
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
volatile int rotAcc = 0;

// Instantiate the Objects
Adafruit_SSD1306 display(4);
Si5351 si5351;

void setup()
{

  // Set up frequency and radix switches
  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(pushPin, INPUT);

  // set up pull-up resistors on inputs
  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(pushPin, HIGH);

  attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);

  // Initialize the display with the I2C addr 0x3C
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextColor(WHITE);
  display.clearDisplay();
  display.display();
  delay(1000);
  UpdateDisplay();
  delay(1000);

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0);
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  si5351.set_freq(bandInit, SI5351_PLL_FIXED, SI5351_CLK0);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
  si5351.set_freq((freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);
  si5351.set_freq((LSB_BFO_freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK2);
}


void loop()
{
  currentfreq = getfreq();                // Interrupt safe method to get the current frequency

  if (currentfreq != oldfreq)
  {
    UpdateDisplay();
    SendFrequency();
    oldfreq = currentfreq;
  }

  if (!digitalRead(pushPin)) {
    delay(10);
    while (!digitalRead(pushPin))
    {
      if (updatedisplay == 1)
      {
        UpdateDisplay();
        updatedisplay = 0;
      }
    }
    delay(50);
  }
}

long getfreq()
{
  long temp_freq;
  cli();
  temp_freq = freq;
  sei();
  return temp_freq;
}


// Interrupt routines
void ISRrotAChange()
{
  if (digitalRead(rotAPin))
  {
    rotAval = 1;
    UpdateRot();
  } else
  {
    rotAval = 0;
    UpdateRot();
  }
}


void ISRrotBChange()
{
  if (digitalRead(rotBPin))
  {
    rotBval = 1;
    UpdateRot();
  } else
  {
    rotBval = 0;
    UpdateRot();
  }
}


void UpdateRot()
{
  switch (rotState)
  {

    case 0:                                         // Idle state, look for direction
      if (!rotBval)
        rotState = 1;                               // CW 1
      if (!rotAval)
        rotState = 11;                              // CCW 1
      break;

    case 1:                                         // CW, wait for A low while B is low
      if (!rotBval)
      {
        if (!rotAval)
        {
          // either increment radixindex or freq
          if (digitalRead(pushPin) == LOW)
          {
            updatedisplay = 1;
            if (radix == 1000000)
              radix = 100000;
            else if (radix == 100000)
              radix = 10000;
            else if (radix == 10000)
              radix = 1000;
            else if (radix == 1000)
              radix = 100;
            else if (radix == 100)
              radix = 10;
            else if (radix == 10)
              radix = 1;
            else
              radix = 1000000;
          } else
          {
            freq = (freq + radix);
            if (freq > 3900000)
              freq = 3900000;
          }
          rotState = 2;                             // CW 2
        }
      } else
      {
        if (rotAval)
          rotState = 0;                             // It was just a glitch on B, go back to start
      }
      break;

    case 2:                                         // CW, wait for B high
      if (rotBval)
        rotState = 3;                               // CW 3
      break;

    case 3:                                         // CW, wait for A high
      if (rotAval)
        rotState = 0;                               // back to idle (detent) state
      break;

    case 11:                                        // CCW, wait for B low while A is low
      if (!rotAval)
      {
        if (!rotBval)
        {
          // either decrement radixindex or freq
          if (digitalRead(pushPin) == LOW)
          {
            updatedisplay = 1;
            if (radix == 1)
              radix = 10;
            else if (radix == 10)
              radix = 100;
            else if (radix == 100)
              radix = 1000;
            else if (radix == 1000)
              radix = 10000;
            else if (radix == 10000)
              radix = 100000;
            else if (radix == 100000)
              radix = 1000000;
            else
              radix = 1;
          } else
          {
            freq = (freq - radix);
            if (freq < 3500000)
              freq = 3500000;
          }
          rotState = 12;                            // CCW 2
        }
      } else
      {
        if (rotBval)
          rotState = 0;                             // It was just a glitch on A, go back to start
      }
      break;

    case 12:                                        // CCW, wait for A high
      if (rotAval)
        rotState = 13;                              // CCW 3
      break;

    case 13:                                        // CCW, wait for B high
      if (rotBval)
        rotState = 0;                               // back to idle (detent) state
      break;
  }
}


void UpdateDisplay()
{
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(1);
  display.println("ZL2CTM Tramping Rig");

  display.setCursor(0, 20);
  display.setTextSize(2);
  display.println(freq);

  if (freq > 9999999)
    display.setCursor(12, 30);
  if (freq < 9999999)
    display.setCursor(0, 30);
  switch (radix)
  {
    case 1:
      display.println("      -");
      break;
    case 10:
      display.println("     -");
      break;
    case 100:
      display.println("    -");
      break;
    case 1000:
      display.println("   -");
      break;
    case 10000:
      display.println("  -");
      break;
    case 100000:
      display.println(" -");
      break;
    case 1000000:
      display.println("-");
      break;
  }
  display.setCursor(0, 48);
  display.setTextSize(2);
  display.println("LSB");
  display.setCursor(60, 48);
  if ((freq >= 3500000) && (freq <= 3900000))
    display.println("80m");
  display.display();
}


void SendFrequency()
{
  // VFO
  si5351.set_freq(((LSB_IF_freq - freq + 1500 - 70) * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);

  // BFO
  si5351.set_freq((LSB_BFO_freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK2);
}

80m SSB Tramping Rig Schematics

Here the are the schematics for the rig. Some parts have not been optimised as per the notes on the schematics.











Wednesday 6 September 2017

Superhet Configuration

Hi. Just put up a video on YouTube of a superhet experiment using the mixer below. The mixer is using two J310s as a pseudo dual gate MOSFET. It seems to work well. Plan is to make a second one as use that as the product detector.

73's
Charlie



Thursday 17 August 2017

Design Goals

Thanks for all the feedback. In an effort to keep the cost down where we can, here is a list of the features we'll look to design into the radio:

  • 80m, 40m and 20m pre-selector BPFs
  • 2N3904 based RF receive amp with manual attenuation
  • 2N3904 based IF amps (not bilateral at this stage)
  • Diode low power signal switching (no relays)
  • Junkbox Yaesu XF-92A 9000kHz SSB crystal filter
  • J310 or some other discrete component based mixers. Otherwise homebrew DBMs
  • Teensy (any version should work, I'll use a 3.1 as I have one in the junkbox)
    • FFT spectrum display
    • Software LPFs: CW-N (200Hz), CW-W (700Hz) and perhaps a notch
    • 2.8kHz LPF for the mic audio
    • Compression on the mic audio if we can 
    • 700Hz sinewave for CW modulation
    • Software based audio AGC
    • Drive external analogue S meter
    • Memory channels
    • VFO and Memory scan
  • Si5351 for VFO and BFO. Teensy controlled
  • 0.96" OLED
  • 2N3906 PA pre-driver
  • IRF520 or IRF510 PA (will start with cheaper 520 first)
  • 80m, 40m and 20m LPFs (probably wafer switch selected)

Again, I am not an expert in homebrew, so I'll just do what has worked for me in the past. Hopefully, we can make it an interactive process where we all learn.

I'll be away for a couple of weeks. Once I'm back, we'll start with a basic Teensy setup to drive the screen, rotary encoder and Si5351. It'll have basis audio pass through for initial test. That will then form the basis for the rest of the radio.

Until then.

73's
Charlie, ZL2CTM  



Saturday 12 August 2017

New Radio Project.

Hi. This is a new blog, and am just testing it. Below is a picture of the Teensy driving the OLED screen and the Si5351. On the screen is a FFT spectrum showing 0-22kHz. More to come!