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.