Wednesday, 14 November 2018

Homebrew SSB Rig based on MC1350P IF Amp

See the YouTube channel for details:

https://www.youtube.com/channel/UCSNPW3_gzuMJcX_ErBZTv2g



Audio Amplifier



Note. Original schematic had a 47uF capacitor between the two stages. That is now a 3.3uF (as above). This reduces the quiet time when changing from TX to RX.


Antenna RF Amplifier




VFO/BFO



The Si5351 outputs go to two 1k ohm trim pots. Note, this is not the correct approach. A 50 ohm pad with the appropriate attenuation should be used. The YouTube videos explain why this is incorrect. The wiper arm goes to the two ADE-1 mixers. Both trim pots are set to approx. 1/3 from min. Adjust from min to get acceptable receive audio quality.  



************************************************************
This is the new code that works with Jason Mildrum's new Si5351 Etherkit library. See https://github.com/etherkit/Si5351Arduino




#include <Wire.h>
#include <SPI.h>
#include <LiquidCrystal_I2C.h>
#include <si5351.h>

const uint32_t bandStart = 7000000;     // start of 40m
const uint32_t bandEnd =   7300000;     // end of 40m
const uint32_t bandInit =  7100000;     // where to initially set the frequency
volatile long oldfreq = 0;
volatile long currentfreq = 0;
volatile int updatedisplay = 0;

volatile uint32_t freq = bandInit ;     // this is a variable (changes) - set it to the beginning of the band
volatile uint32_t radix = 1000;         // how much to change the frequency by, clicking the rotary encoder will change this.
volatile uint32_t oldradix = 1;

volatile uint32_t BFO_freq = 8003000;    // Crystal filter centre freq

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

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

// Instantiate the Objects
LiquidCrystal_I2C lcd(0x27, 16, 2);
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);

  // Set up interrupt pins
  attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);

  // Initialize the display
  lcd.begin();
  lcd.backlight();
  UpdateDisplay();
  delay(1000);

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(31830, SI5351_PLL_INPUT_XO);      // Set to specific Si5351 calibration number
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
  si5351.set_freq((freq * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
}


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

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

  if (digitalRead(pushPin) == LOW)
  {
    delay(10);
    while (digitalRead(pushPin) == LOW)
    {
      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;
            radix = radix / 10;
            if (radix < 1)
              radix = 100000;
          }
          else
          {
            freq = freq + radix;
            if (freq > bandEnd)
              freq = bandEnd;
          }
          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;
            radix = radix * 10;
            if (radix == 100000)
              radix = 1;
          }
          else
          {
            freq = freq - radix;
            if (freq < bandStart)
              freq = bandStart;
          }
          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()
{
  lcd.setCursor(0, 0);
  lcd.setCursor(0, 0);
  lcd.print(freq);
  lcd.setCursor(0, 1);
  lcd.print("ZL2CTM");

  if (radix != oldradix)                          // stops radix display flashing/blinking on freq change
  {
    lcd.setCursor(9, 0);
    lcd.print("       ");
    lcd.setCursor(9, 0);
    if (radix == 1)
      lcd.print("   1 Hz");
    if (radix == 10)
      lcd.print("  10 Hz");
    if (radix == 100)
      lcd.print(" 100 Hz");
    if (radix == 1000)
      lcd.print("  1 kHz");
    if (radix == 10000)
      lcd.print(" 10 kHz");
    if (radix == 100000)
      lcd.print("100 kHz");
    oldradix = radix;
  }
}


void SendFrequency()
{
  si5351.set_freq((freq * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
}


************************************************************



BPF (note, 24g wire used, not 26g)


1st IF Amp


2nd IF Amp

Note. T1 secondary is now from Pin 4 and earth. Pin 6 is tied to earth via a 100nF cap.



Mic Amp (using an electret mic)


Note. Changed output coupling capacitor to a 1uF. Reduces short term CW on TX. A 100nF cap works well too.






Additional replay to allow the BPF and follow RF amplifier to be used for both RX (antenna amplifier) and TX (RF pre-amplifier)


RF Pre-Amp using NE592 Video Amp


Initial idea for the RF power amplifier. Based on two BLF1043 LDMOS devices.
Also using an old laptop power supply. 18.5VDC, 3A.
Please see the YouTube channel for test results! 








  



RF Driver Amplifier.



Current layout of the final radio


AGC. Designed to provide 6-7VDC to the MC1350P.



RC phase shift oscillator for antenna/amp tuning.



Final code supporting dual band function.


#include <Wire.h>
#include <SPI.h>
#include <LiquidCrystal_I2C.h>
#include <si5351.h>

const uint32_t bandStart80 = 3500000;     // start of 80m
const uint32_t bandStart40 = 7000000;     // start of 40m
const uint32_t bandEnd80 =   3900000;     // end of 80m
const uint32_t bandEnd40 =   7300000;     // end of 40m
const uint32_t bandInit =  3690000;       // where to initially set the frequency
volatile int band = 0;                    // 0 = 80m, 1 = 40m
volatile int oldband = 0;
volatile long oldfreq = 0;
volatile long currentfreq = 0;
volatile long freq80 = 3690000;
volatile long freq40 = 7120000;
volatile int updatedisplay = 0;
volatile long SMeterReadings[10];
volatile long SMeterAverage;
int AvCount = 0;

volatile uint32_t freq = bandInit ;     // this is a variable (changes) - set it to the beginning of the band
volatile uint32_t radix = 1000;         // how much to change the frequency by, clicking the rotary encoder will change this.
volatile uint32_t oldradix = 1;

volatile uint32_t BFO_freq = 9001350;

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

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

// Instantiate the Objects
LiquidCrystal_I2C lcd(0x27, 16, 2);
Si5351 si5351;

void setup()
{
  // Set up frequency and radix switches
  pinMode(rotAPin, INPUT);
  pinMode(rotAPin, INPUT_PULLUP);
  pinMode(rotBPin, INPUT);
  pinMode(rotBPin, INPUT_PULLUP);
  pinMode(pushPin, INPUT);
  pinMode(pushPin, INPUT_PULLUP);
  pinMode(bandSW, INPUT_PULLUP);
  pinMode(A7, INPUT);

  // Set up interrupt pins
  attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);

  // Initialize the display
  lcd.begin();
  lcd.backlight();
  UpdateDisplay();
  delay(1000);

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(31830, SI5351_PLL_INPUT_XO);      // Set to specific Si5351 calibration number
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
  si5351.set_freq((freq * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
}


void loop()
{
  band = digitalRead(bandSW);
  if (band != oldband)                      // Check if band has changed
  {
    if (band == 0)                          // Was 40m, now 80m
    {
      freq40 = freq;                        // Store 40m VFO freq
      freq = freq80;                        // Make VFO = stored 80m freq
    }
    if (band == 1)                          // Was 80m, now 40m
    {
      freq80 = freq;                        // Store 80m VFO freq
      freq = freq40;                        // Make VFO = stored 40m freq
    }
    UpdateDisplay();
    oldband = band;
  }

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

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

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

  SMeterReadings[AvCount] = analogRead(A7);
  AvCount++;
  if (AvCount == 10)
  {
    SMeterAverage = (SMeterReadings[0] + SMeterReadings[1] + SMeterReadings[2] + SMeterReadings[3] + SMeterReadings[4]
                     + SMeterReadings[5] + SMeterReadings[6] + SMeterReadings[7] + SMeterReadings[8] + SMeterReadings[9]) / 10;
    UpdateDisplay();
    AvCount = 0;
  }
}


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;
            radix = radix * 10;
            if (radix > 100000)
              radix = 100000;
          }
          else
          {
            freq = freq + radix;
            if ((band == 0) && (freq > bandEnd80))
              freq = bandEnd80;
            if ((band == 1) && (freq > bandEnd40))
              freq = bandEnd40;
          }
          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;
            radix = radix / 10;
            if (radix < 1)
              radix = 1;
          }
          else
          {
            freq = freq - radix;
            if ((band == 0) && (freq < bandStart80))
              freq = bandStart80;
            if ((band == 1) && (freq < bandStart40))
              freq = bandStart40;
          }
          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()
{
  lcd.setCursor(0, 0);
  lcd.setCursor(0, 0);
  lcd.print(freq);
  lcd.setCursor(0, 1);
  lcd.print("ZL2CTM");

  lcd.setCursor(11, 1);
  if (SMeterAverage <= 850)
    lcd.print("S3 ");
  if ((SMeterAverage > 851) && (SMeterAverage <= 853))
    lcd.print("S4 ");
  if ((SMeterAverage > 854) && (SMeterAverage <= 864))
    lcd.print("S5 ");
  if ((SMeterAverage > 865) && (SMeterAverage <= 904))
    lcd.print("S6 ");
  if ((SMeterAverage > 905) && (SMeterAverage <= 989))
    lcd.print("S7 ");
  if ((SMeterAverage > 990) && (SMeterAverage <= 1010))
    lcd.print("S8 ");
  if (SMeterAverage > 1011)
    lcd.print("S9 ");

  if (radix != oldradix)                          // stops radix display flashing/blinking on freq change
  {
    lcd.setCursor(9, 0);
    lcd.print("       ");
    lcd.setCursor(9, 0);
    if (radix == 1)
      lcd.print("   1 Hz");
    if (radix == 10)
      lcd.print("  10 Hz");
    if (radix == 100)
      lcd.print(" 100 Hz");
    if (radix == 1000)
      lcd.print("  1 kHz");
    if (radix == 10000)
      lcd.print(" 10 kHz");
    if (radix == 100000)
      lcd.print("100 kHz");
    oldradix = radix;
  }
}


void SendFrequency()
{
  si5351.set_freq(((BFO_freq - freq) * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
}

Thursday, 1 November 2018

Homebrew Antenna/Scaler Network Analyser






Final software for the antenna/SNA. It's not perfect, but works for me.


#include <UTFTGLUE.h>
#include <SPI.h>
#include <Wire.h>
#include <DDS.h>
#include <Keypad.h>

// Keypad
char keys[4][3] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};
byte rowPins[4] = {35, 45, 43, 39};    //connect to the row pinouts of the keypad
byte colPins[3] = {37, 33, 41};           // connect to the column pinouts of the keypad

// AD9850 pins
const int DATA = 47;
const int W_CLK = 49;
const int FQ_UD = 51;
const int RESET = 53;

// SWR bridge pins
const int fwdAntVoltageInput = A15;
const int revAntVoltageInput = A14;

// AD8307 pin
const int SNAVoltageInput = A12;

// Other constants and variables
const int NoBins = 320;
volatile long freq = 7123000;
volatile long Spotfreq = 7123000;
volatile long oldSpotfreq = 7123000;
volatile long UserLowerfreq = 8995000;
volatile long UserUpperfreq = 9005000;
volatile float fwdVoltage = 0;
volatile float revVoltage = 0;
volatile float MaxrevVoltage = 0;
volatile float OldMaxrevVoltage = 0;
volatile float MinrevVoltage = 0;
volatile float OldMinrevVoltage = 0;
volatile float Ripple = 0;
volatile float SWR = 10;
volatile float oldSWR = 0;
volatile long SWRFreq = 0;
volatile float LowestSWR = 10;
volatile int Cal = 0;
volatile long UserFreq = 0;
volatile long oldUserFreq = 0;
volatile boolean EntryComplete = false;
volatile boolean FreqEntered = false;
volatile long UserFreqArray[8] = {0};
volatile float TestArray[NoBins] = {0};
volatile float CalArray[NoBins] = {0};

// Instantiate the Objects
DDS dds(W_CLK, FQ_UD, DATA, RESET);
UTFTGLUE myGLCD(0, A2, A1, A3, A4, A0);
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, 4, 3 );

void setup()
{
  Serial.begin(19200);

  pinMode(fwdAntVoltageInput, INPUT);
  pinMode(revAntVoltageInput, INPUT);
  pinMode(SNAVoltageInput, INPUT);
  pinMode(5, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(8, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);

  analogReference(INTERNAL2V56);                              // Built-in 2.56V reference

  //Set up display
  myGLCD.InitLCD();
  myGLCD.clrScr();
  myGLCD.setRotation(3);
  DisplayMainMenu();

  // Initialize the AD9850
  dds.init();
  dds.trim(125000000); // (Optional) trim if your xtal is not at 125MHz...
}

void loop()
{
  char key = keypad.getKey();
  if (key != NO_KEY)
  {
    switch (key)                       //switch used to specify which button
    {
      case '1':
        EntryComplete = false;
        DisplayAntSpotFreqMenu();
        break;
      case '2':
        EntryComplete = false;
        DisplayHFMenu();
        break;
      case '3':
        EntryComplete = false;
        DisplayHamMenu();
        break;
      case '4':
        EntryComplete = false;
        DisplayUserScanMenu();
        break;
      case '5':
        EntryComplete = false;
        DisplaySNAMenu();
        break;
      case '*':
        EntryComplete = false;
        DisplayMainMenu();
        break;
      default:
        ;
    }
  }
}

void DisplayMainMenu()
{
  myGLCD.clrScr();
  myGLCD.setTextSize(2);
  myGLCD.setColor(0, 0, 255);
  myGLCD.drawRect(0, 0, 319, 239);
  myGLCD.fillRect(0, 0, 319, 30);
  myGLCD.setColor(255, 255, 0);
  myGLCD.setBackColor(0, 0, 255);
  myGLCD.print("ZL2CTM Ant Analyser/SNA", 20, 8);
  myGLCD.setColor(255, 255, 255);
  myGLCD.setBackColor(0, 0, 0);
  myGLCD.print("1 Spot Frequency", 30, 60);
  myGLCD.print("2 HF Spectrum Scan", 30, 80);
  myGLCD.print("3 Ham Band Scan", 30, 100);
  myGLCD.print("4 User Scan", 30, 120);
  myGLCD.print("5 Scaler Network", 30, 140);
  myGLCD.print("  Analyser", 30, 160);

  myGLCD.print("Select option...", 30, 200);
}

void DisplayAntSpotFreqMenu()
{
  myGLCD.clrScr();
  myGLCD.setColor(0, 0, 255);
  myGLCD.drawRect(0, 0, 319, 239);
  myGLCD.setTextSize(2);
  myGLCD.setColor(255, 255, 255);
  myGLCD.print("1 Enter frequency", 30, 20);
  myGLCD.print("2 Start", 30, 40);
  myGLCD.print("* Main menu", 30, 60);
  myGLCD.print("Select option...", 30, 100);

  myGLCD.setColor(255, 255, 0);
  myGLCD.print("Freq:", 30, 160);
  myGLCD.printNumI(Spotfreq, 110, 160);

  while (EntryComplete == false)
  {
    char key = keypad.getKey();
    if (key != NO_KEY)
    {
      switch (key)                       //switch used to specify which button
      {
        case '1':
          EntryComplete = false;
          Spotfreq = EnterUserFreq();
          DisplayAntSpotFreqMenu();
          break;
        case '2':
          EntryComplete = true;
          AntSpotFreqScan(Spotfreq);
          break;
        case '*':
          EntryComplete = true;
          DisplayMainMenu();
          break;
        default:
          ;
      }
    }
  }
}

void DisplayHFMenu()
{
  myGLCD.clrScr();
  myGLCD.setColor(0, 0, 255);
  myGLCD.drawRect(0, 0, 319, 239);
  myGLCD.setTextSize(2);
  myGLCD.setColor(255, 255, 255);
  myGLCD.print("1 Full HF spectrum", 30, 20);
  myGLCD.print("2 2000-10000 kHz", 30, 40);
  myGLCD.print("3 10000-20000 kHz", 30, 60);
  myGLCD.print("4 20000-30000 kHz", 30, 80);
  myGLCD.print("* Main menu", 30, 100);
  myGLCD.print("Select option...", 30, 140);

  while (EntryComplete == false)
  {
    char key = keypad.getKey();
    if (key != NO_KEY)
    {
      switch (key)                       //switch used to specify which button
      {
        case '1':
          EntryComplete = true;
          SweepAnt(2000000, 30000000);
          break;
        case '2':
          EntryComplete = true;
          SweepAnt(2000000, 10000000);
          break;
        case '3':
          EntryComplete = true;
          SweepAnt(10000000, 20000000);
          break;
        case '4':
          EntryComplete = true;
          SweepAnt(20000000, 30000000);
          break;
        case '*':
          EntryComplete = true;
          DisplayMainMenu();
          break;
        default:
          ;
      }
    }
  }
}

void DisplayHamMenu()
{
  myGLCD.clrScr();
  myGLCD.setColor(0, 0, 255);
  myGLCD.drawRect(0, 0, 319, 239);
  myGLCD.setTextSize(2);
  myGLCD.setColor(255, 255, 255);
  myGLCD.print("1 80m (3500-3900)", 30, 20);
  myGLCD.print("2 40m (7000-7300)", 30, 40);
  myGLCD.print("3 30m (10100-10150)", 30, 60);
  myGLCD.print("4 20m (14000-14350)", 30, 80);
  myGLCD.print("5 17m (18068-18168)", 30, 100);
  myGLCD.print("6 15m (21000-21450)", 30, 120);
  myGLCD.print("7 12m (24890-24990)", 30, 140);
  myGLCD.print("8 10m (28000-29700)", 30, 160);
  myGLCD.print("* Main menu", 30, 180);
  myGLCD.print("Select option...", 30, 215);

  while (EntryComplete == false)
  {
    char key = keypad.getKey();
    if (key != NO_KEY)
    {
      switch (key)                       //switch used to specify which button
      {
        case '1':
          EntryComplete = true;
          SweepAnt(3500000, 3900000);
          break;
        case '2':
          EntryComplete = true;
          SweepAnt(7000000, 7300000);
          break;
        case '3':
          EntryComplete = true;
          SweepAnt(10100000, 10150000);
          break;
        case '4':
          EntryComplete = true;
          SweepAnt(14000000, 14500000);
          break;
        case '5':
          EntryComplete = true;
          SweepAnt(18068000, 18168000);
          break;
        case '6':
          EntryComplete = true;
          SweepAnt(21000000, 21450000);
          break;
        case '7':
          EntryComplete = true;
          SweepAnt(24890000, 24990000);
          break;
        case '8':
          EntryComplete = true;
          SweepAnt(28000000, 29700000);
          break;
        case '*':
          EntryComplete = true;
          DisplayMainMenu();
          break;
        default:
          ;
      }
    }
  }
}

void DisplayUserScanMenu()
{
  myGLCD.clrScr();
  myGLCD.setColor(0, 0, 255);
  myGLCD.drawRect(0, 0, 319, 239);
  myGLCD.setTextSize(2);
  myGLCD.setColor(255, 255, 255);
  myGLCD.print("1 Enter lower freq", 30, 20);
  myGLCD.print("2 Enter upper freq", 30, 40);
  myGLCD.print("3 Start scan", 30, 60);
  myGLCD.print("* Main menu", 30, 80);
  myGLCD.print("Select option...", 30, 120);

  myGLCD.setColor(255, 255, 0);
  myGLCD.print("Lower:", 30, 160);
  myGLCD.print("Upper:", 30, 180);
  myGLCD.printNumI(UserLowerfreq, 120, 160);
  myGLCD.printNumI(UserUpperfreq, 120, 180);

  while (EntryComplete == false)
  {
    char key = keypad.getKey();
    if (key != NO_KEY)
    {
      switch (key)                       //switch used to specify which button
      {
        case '1':
          EntryComplete = false;
          UserLowerfreq = EnterUserFreq();
          DisplayUserScanMenu();
          break;
        case '2':
          EntryComplete = false;
          UserUpperfreq = EnterUserFreq();
          DisplayUserScanMenu();
          break;
        case '3':
          EntryComplete = true;
          if (UserLowerfreq < UserUpperfreq)
            SweepAnt(UserLowerfreq, UserUpperfreq);
          else
            SweepAnt(UserUpperfreq, UserLowerfreq);
          break;
        case '*':
          EntryComplete = true;
          DisplayMainMenu();
          break;
        default:
          ;
      }
    }
  }
}

void DisplaySNAMenu()
{
  myGLCD.clrScr();
  myGLCD.setColor(0, 0, 255);
  myGLCD.drawRect(0, 0, 319, 239);
  myGLCD.setTextSize(2);
  myGLCD.setColor(255, 255, 255);
  myGLCD.print("1 Enter lower freq", 30, 20);
  myGLCD.print("2 Enter upper freq", 30, 40);
  myGLCD.print("3 Calibrate", 30, 60);
  myGLCD.print("4 Single Scan", 30, 80);
  myGLCD.print("5 Continuous Scan", 30, 100);
  myGLCD.print("* Main menu", 30, 120);
  myGLCD.print("Select option...", 30, 160);

  myGLCD.setColor(255, 255, 0);
  myGLCD.print("Lower:", 30, 190);
  myGLCD.print("Upper:", 30, 210);
  myGLCD.printNumI(UserLowerfreq, 120, 190);
  myGLCD.printNumI(UserUpperfreq, 120, 210);

  while (EntryComplete == false)
  {
    char key = keypad.getKey();
    if (key != NO_KEY)
    {
      switch (key)                       //switch used to specify which button
      {
        case '1':
          EntryComplete = false;
          UserLowerfreq = EnterUserFreq();
          DisplaySNAMenu();
          break;
        case '2':
          EntryComplete = false;
          UserUpperfreq = EnterUserFreq();
          DisplaySNAMenu();
          break;
        case '3':
          EntryComplete = false;
          if (UserLowerfreq < UserUpperfreq)
            Calibrate(UserLowerfreq, UserUpperfreq);
          else
            Calibrate(UserUpperfreq, UserLowerfreq);
          DisplaySNAMenu();
          break;
        case '4':
          if (Cal == 0)
          {
            EntryComplete = false;
            myGLCD.clrScr();
            myGLCD.setColor(0, 0, 255);
            myGLCD.drawRect(0, 0, 319, 239);
            myGLCD.setTextSize(2);
            myGLCD.setColor(255, 255, 255);
            myGLCD.print("Calibrate first", 60, 100);
            delay(1500);
            DisplaySNAMenu();
          }
          else
          {
            EntryComplete = true;
            if (UserLowerfreq < UserUpperfreq)
              SingleSweepSNA(UserLowerfreq, UserUpperfreq);
            else
              SingleSweepSNA(UserUpperfreq, UserLowerfreq);
          }
          break;
        case '5':
          EntryComplete = true;
          if (UserLowerfreq < UserUpperfreq)
            MultipleSweepSNA(UserLowerfreq, UserUpperfreq);
          else
            MultipleSweepSNA(UserUpperfreq, UserLowerfreq);
          break;
        case '*':
          EntryComplete = true;
          DisplayMainMenu();
          break;
        default:
          ;
      }
    }
  }
}

long EnterUserFreq()
{
  myGLCD.clrScr();
  myGLCD.setColor(0, 0, 255);
  myGLCD.drawRect(0, 0, 319, 239);
  myGLCD.setTextSize(2);
  myGLCD.setColor(255, 255, 255);
  myGLCD.print("Enter freq between", 40, 20);
  myGLCD.print("2MHz and 30MHz", 40, 40);
  myGLCD.print("then press #", 40, 60);

  myGLCD.setColor(105, 105, 105);
  myGLCD.setTextSize(3);
  myGLCD.print("8", 40, 120);
  myGLCD.print("8", 65, 120);
  myGLCD.print("8", 90, 120);
  myGLCD.print("8", 115, 120);
  myGLCD.print("8", 140, 120);
  myGLCD.print("8", 165, 120);
  myGLCD.print("8", 190, 120);
  myGLCD.print("*", 215, 120);

  FreqEntered = false;
  int x = 0;
  for (int y = 0; y <= 7; y++)
    UserFreqArray[y] = 0;

  while (FreqEntered == false)
  {
    char key = keypad.getKey();
    if (key != NO_KEY)
    {
      if (key == '#')
      {
        FreqEntered = true;
        if (x == 8)
          UserFreq = ((UserFreqArray[0] * 10000000) + (UserFreqArray[1] * 1000000) + (UserFreqArray[2] * 100000) +
                      (UserFreqArray[3] * 10000) + (UserFreqArray[4] * 1000) + (UserFreqArray[5] * 100) + (UserFreqArray[6] * 10) + UserFreqArray[7]);
        if (x == 7)
          UserFreq = ((UserFreqArray[0] * 1000000) + (UserFreqArray[1] * 100000) + (UserFreqArray[2] * 10000) +
                      (UserFreqArray[3] * 1000) + (UserFreqArray[4] * 100) + (UserFreqArray[5] * 10) + UserFreqArray[6]);

        if ((x < 7) || (UserFreq < 2000000) || (UserFreq > 30000000))
        {
          myGLCD.clrScr();
          myGLCD.setColor(0, 0, 255);
          myGLCD.drawRect(0, 0, 319, 239);
          myGLCD.setTextSize(2);
          myGLCD.setColor(255, 255, 255);
          myGLCD.print("Out of bounds", 80, 100);
          UserFreq = oldUserFreq;
          delay(1500);
          EnterUserFreq();
        }
        oldUserFreq = UserFreq;
      }
      else
      {
        UserFreqArray[x] = (key - 48);
        myGLCD.setColor(0, 255, 0);
        myGLCD.setTextSize(3);
        switch (x)
        {
          case 0:
            myGLCD.printNumI(UserFreqArray[0], 40, 120);
            break;
          case 1:
            myGLCD.printNumI(UserFreqArray[0], 40, 120);
            myGLCD.printNumI(UserFreqArray[1], 65, 120);
            break;
          case 2:
            myGLCD.printNumI(UserFreqArray[0], 40, 120);
            myGLCD.printNumI(UserFreqArray[1], 65, 120);
            myGLCD.printNumI(UserFreqArray[2], 90, 120);
            break;
          case 3:
            myGLCD.printNumI(UserFreqArray[0], 40, 120);
            myGLCD.printNumI(UserFreqArray[1], 65, 120);
            myGLCD.printNumI(UserFreqArray[2], 90, 120);
            myGLCD.printNumI(UserFreqArray[3], 115, 120);
            break;
          case 4:
            myGLCD.printNumI(UserFreqArray[0], 40, 120);
            myGLCD.printNumI(UserFreqArray[1], 65, 120);
            myGLCD.printNumI(UserFreqArray[2], 90, 120);
            myGLCD.printNumI(UserFreqArray[3], 115, 120);
            myGLCD.printNumI(UserFreqArray[4], 140, 120);
            break;
          case 5:
            myGLCD.printNumI(UserFreqArray[0], 40, 120);
            myGLCD.printNumI(UserFreqArray[1], 65, 120);
            myGLCD.printNumI(UserFreqArray[2], 90, 120);
            myGLCD.printNumI(UserFreqArray[3], 115, 120);
            myGLCD.printNumI(UserFreqArray[4], 140, 120);
            myGLCD.printNumI(UserFreqArray[5], 165, 120);
            break;
          case 6:
            myGLCD.printNumI(UserFreqArray[0], 40, 120);
            myGLCD.printNumI(UserFreqArray[1], 65, 120);
            myGLCD.printNumI(UserFreqArray[2], 90, 120);
            myGLCD.printNumI(UserFreqArray[3], 115, 120);
            myGLCD.printNumI(UserFreqArray[4], 140, 120);
            myGLCD.printNumI(UserFreqArray[5], 165, 120);
            myGLCD.printNumI(UserFreqArray[6], 190, 120);
            break;
          case 7:
            myGLCD.printNumI(UserFreqArray[0], 40, 120);
            myGLCD.printNumI(UserFreqArray[1], 65, 120);
            myGLCD.printNumI(UserFreqArray[2], 90, 120);
            myGLCD.printNumI(UserFreqArray[3], 115, 120);
            myGLCD.printNumI(UserFreqArray[4], 140, 120);
            myGLCD.printNumI(UserFreqArray[5], 165, 120);
            myGLCD.printNumI(UserFreqArray[6], 190, 120);
            myGLCD.printNumI(UserFreqArray[7], 215, 120);
            break;
          default:
            ;
        }
        x++;
      }
    }
  }
  return UserFreq;
}

void AntSpotFreqScan(long userfrequency)
{
  EntryComplete = false;

  myGLCD.clrScr();
  myGLCD.setColor(0, 0, 255);
  myGLCD.drawRect(0, 0, 319, 239);
  dds.setFrequency(userfrequency);
  delay(5);

  myGLCD.setTextSize(1);
  myGLCD.setColor(0, 255, 0);
  myGLCD.fillRect(45, 35, 73, 40);
  myGLCD.setColor(255, 255, 0);
  myGLCD.fillRect(74, 35, 160, 40);
  myGLCD.setColor(255, 0, 0);
  myGLCD.fillRect(161, 35, 275, 40);
  myGLCD.setColor(255, 255, 255);
  myGLCD.drawRect(45, 34, 275, 200);

  myGLCD.print("1", 45, 20);
  myGLCD.print("1.5", 73, 20);
  myGLCD.print("3", 160, 20);
  myGLCD.print("5", 192, 20);
  myGLCD.print("10", 265, 20);

  myGLCD.setTextSize(2);

  while (EntryComplete == false)
  {
    fwdVoltage = analogRead(fwdAntVoltageInput);
    revVoltage = analogRead(revAntVoltageInput);

    if (revVoltage >= fwdVoltage)
      revVoltage = fwdVoltage - 1;

    if (revVoltage < 1)
      revVoltage = 1;

    SWR = (fwdVoltage + revVoltage) / (fwdVoltage - revVoltage);

    myGLCD.setColor(0, 0, 0);
    if (oldSWR <= 1)
      myGLCD.drawLine(160, 200, 46, 41);
    if (oldSWR == 3)
      myGLCD.drawLine(160, 200, 160, 41);
    if (oldSWR >= 10)
      myGLCD.drawLine(160, 200, 274, 41);
    if ((oldSWR < 3) && (oldSWR > 1))
      myGLCD.drawLine(160, 200, (45 + ((oldSWR - 1) * 57.5)), 41);
    if ((oldSWR > 3) && (oldSWR < 10))
      myGLCD.drawLine(160, 200, (160 + (oldSWR - 3) / 7 * 115), 41);

    myGLCD.printNumF(oldSWR, 2, 125, 212);     // Print Floating Number   2 decimal places

    if (SWR <= 1.5)
      myGLCD.setColor(0, 255, 0);
    if ((SWR > 1.5) && (SWR <= 3))
      myGLCD.setColor(255, 255, 0);
    if (SWR > 3)
      myGLCD.setColor(255, 0, 0);

    if (SWR <= 1)
      myGLCD.drawLine(160, 200, 46, 41);
    if (SWR == 3)
      myGLCD.drawLine(160, 200, 160, 41);
    if (SWR >= 10)
      myGLCD.drawLine(160, 200, 274, 41);
    if ((SWR < 3) && (SWR > 1))
      myGLCD.drawLine(160, 200, (45 + ((SWR - 1) * 57.5)), 41);
    if ((SWR > 3) && (SWR < 10))
      myGLCD.drawLine(160, 200, (160 + (SWR - 3) / 7 * 115), 41);

    myGLCD.printNumF(SWR, 2, 125, 212);       // Print Floating Number   2 decimal places

    oldSWR = SWR;

    char key = keypad.getKey();
    if (key != NO_KEY) {
      switch (key)                       //switch used to specify which button
      {
        case '*':
          EntryComplete = true;
          DisplayMainMenu();
          break;
        default:
          ;
      }
    }
    delay(30);
  }
  dds.init();
  dds.trim(125000000);
}

void SweepAnt(long StartFreq, long StopFreq)
{
  LowestSWR = 100;
  SWRFreq = 0;

  myGLCD.clrScr();
  myGLCD.setColor(0, 0, 255);
  myGLCD.drawRect(0, 0, 319, 239);
  myGLCD.setTextSize(2);
  myGLCD.setColor(255, 255, 255);
  myGLCD.print("Analysing...", 80, 100);

  int x = 0;
  for (freq = StartFreq; freq <= StopFreq; freq = freq + (((StopFreq - StartFreq) / NoBins)))
  {
    dds.setFrequency(freq);
    delay(10);                                      // Let DDS settle then take SWR readings

    fwdVoltage = analogRead(fwdAntVoltageInput);
    revVoltage = analogRead(revAntVoltageInput);

    if (revVoltage >= fwdVoltage)
      revVoltage = fwdVoltage - 1;

    if (revVoltage < 1)
      revVoltage = 1;

    SWR = (fwdVoltage + revVoltage) / (fwdVoltage - revVoltage);

    TestArray[x] = SWR;

    if (SWR < LowestSWR)
    {
      LowestSWR = SWR;
      SWRFreq = freq;
    }
    x++;
  }
  PlotSWR(StartFreq, StopFreq);
}

void Calibrate(long StartFreq, long StopFreq)
{
  int x = 0;
  float MaxCalVoltage = 0;
  long freq = 0;

  for (x = 0; x < NoBins; x++)
    CalArray[x] = 0;
  x = 0;

  for (freq = StartFreq; freq <= StopFreq; freq = freq + ((StopFreq - StartFreq) / NoBins))
  {
    dds.setFrequency(freq);
    CalArray[x] = analogRead(SNAVoltageInput);
    x++;
  }

  for (x = 20; x < NoBins; x++)                                // Start at 20 to skip initial high errors
    if (CalArray[x] > MaxCalVoltage)                              // Determine max calibration voltage
      MaxCalVoltage = CalArray[x];

  for (x = 0; x < NoBins; x++)
    CalArray[x] = MaxCalVoltage - CalArray[x];                    // Populate cal array with error offset values

  Cal = 1;
}

void SingleSweepSNA(long StartFreq, long StopFreq)
{
  int x = 0;
  float MinTestdB = 0;
  long freq = 0;

  for (x = 0; x < NoBins; x++)
    TestArray[x] = 0;

  x = 0;

  for (freq = StartFreq; freq <= StopFreq; freq = freq + ((StopFreq - StartFreq) / NoBins))
  {
    dds.setFrequency(freq);
    TestArray[x] = analogRead(SNAVoltageInput) + CalArray[x];          // test array has corrected raw analog read voltages from 0 to 1024 (0 to 2.56 volts)
    x++;
  }

  for (x = 0; x < NoBins; x++)                                // 2.56V / 25mV/dB = 102.4dB range.
    TestArray[x] = 102.4 - (TestArray[x] * 102.4 / 1024);     // test array now has dB levels from 0dB (2.56V = 1024) to 102dB (0V = 0)

  PlotSNA();
}

void MultipleSweepSNA(long StartFreq, long StopFreq)
{
  int x = 0;
  float TestSNAReading = 0;

  EntryComplete = false;

  myGLCD.clrScr();
  myGLCD.setColor(255, 255, 255);

  while (EntryComplete == false)
  {
    for (freq = StartFreq; freq <= StopFreq; freq = freq + ((StopFreq - StartFreq) / NoBins))
    {
      dds.setFrequency(freq);
      TestSNAReading = analogRead(SNAVoltageInput);
      TestSNAReading = 102.4 - (TestSNAReading * 102.4 / 1024);
      myGLCD.drawPixel(x, TestSNAReading * (240 / 102));

      char key = keypad.getKey();
      if (key != NO_KEY)
      {
        switch (key)                       //switch used to specify which button
        {
          case '*':
            EntryComplete = true;
            break;
          default:
            ;
        }
      }
      x++;
    }
    x = 0;
    myGLCD.clrScr();
  }
  dds.init();
  dds.trim(125000000);
  DisplayMainMenu();
}

void PlotSWR(long StartFreq, long StopFreq)
{
  int ax = 0;
  int bx = 0;
  float ay = 10;
  float by = 10;

  dds.init();
  dds.trim(125000000);
  myGLCD.clrScr();

  ay = TestArray[0];
  if (by > 10)
    ay = 10;
  ax = 0;


  for (bx = 0; bx < NoBins; bx++)
  {
    by = TestArray[bx];
    if (by > 10)
      by = 10;

    if (by > 3)
      myGLCD.setColor(255, 0, 0);     // red
    else if (by <= 1.5)
      myGLCD.setColor(0, 255, 0);     // green
    else
      myGLCD.setColor(255, 255, 0);   // yellow

    myGLCD.drawLine(ax, 240 - (ay * 24), bx, 240 - (by * 24));
    ax = bx;
    ay = by;
  }

  myGLCD.setFont(1);
  myGLCD.setColor(255, 255, 255);
  myGLCD.printNumI(StartFreq / 1000, 5, 220);
  myGLCD.printNumI(StopFreq / 1000, 278, 220);
  myGLCD.printNumI((StopFreq + StartFreq) / 2000, 138, 220);

  myGLCD.setFont(2);
  myGLCD.setColor(255, 255, 255);
  myGLCD.print("Freq:", 120, 20);
  myGLCD.print(" SWR:", 120, 40);
  myGLCD.printNumI(SWRFreq, 210, 20);
  myGLCD.printNumF(LowestSWR, 2, 210, 40);
}

void PlotSNA()
{
  int x = 0;
  myGLCD.clrScr();
  myGLCD.setColor(255, 255, 255);

  for (x = 0; x < NoBins; x++)
    myGLCD.drawPixel(x, TestArray[x] * (240 / 102));

  myGLCD.setTextSize(1);
  myGLCD.drawLine(0, 39, 10, 39);
  myGLCD.print("20dB", 15, 35);
  myGLCD.drawLine(0, 79, 10, 79);
  myGLCD.print("40dB", 15, 75);
  myGLCD.drawLine(0, 119, 10, 119);
  myGLCD.print("60dB", 15, 115);
  myGLCD.drawLine(0, 159, 10, 159);
  myGLCD.print("80dB", 15, 155);
  myGLCD.drawLine(0, 199, 10, 199);
  myGLCD.print("100dB", 15, 195);

  myGLCD.printNumI(UserLowerfreq / 1000, 5, 230);
  myGLCD.printNumI(UserUpperfreq / 1000, 278, 230);
  myGLCD.printNumI((UserUpperfreq + UserLowerfreq) / 2000, 138, 230);

}

Friday, 3 August 2018

Homebrew 80/40m SSB/CW Base Rig

Audio Amplifier


Another audio amplifier option.



Double Balanced Mixers





Plessey Bidirectional IF Amplifiers


Mic Amp



RF Power Amp



Low Pass Filters




#include <Wire.h>
#include <SPI.h>
#include <LiquidCrystal_I2C.h>
#include <si5351.h>

const uint32_t band80mStart = 3500000;    // start of 80m
const uint32_t band80mEnd = 3900000;      // end of 80m
const uint32_t band40mStart = 7000000;    // start of 40m
const uint32_t band40mEnd = 7300000;      // end of 40m

const uint32_t bandInit =  3700000;       // where to initially set the frequency
volatile long oldfreq = 0;
volatile long old80mfreq = 3700000;
volatile long old40mfreq = 7100000;
volatile long currentfreq = 0;
volatile int updatedisplay = 0;
volatile int TX = 0;
volatile int oldTX = 0;
volatile int mode = 0;
volatile int oldmode = 0;
volatile int band = 0;
volatile int oldband = 0;

volatile uint32_t freq = bandInit ;     // this is a variable (changes) - set it to the beginning of the band
volatile uint32_t radix = 1000;         // how much to change the frequency by, clicking the rotary encoder will change this.

volatile uint32_t SSB_BFO_freq = 8999700;   // 8998050 (filter centre) + 2700/2 + 300
//volatile uint32_t SSB_BFO_freq = 9000300;   // 8998050 (filter centre) + 2700/2 + 300
volatile uint32_t CW_BFO_freq = 8997336;    // 8996750 (filter centre) + 700 - 114 (from test)
static const uint32_t CW_Filter_CentreFreq = 8996750;

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

// Mode switches
static const int BandPin = 5;
static const int ModePin = 6;

// Discrete Outputs
static const int CrystalFilterSelectPin = 10;
static const int PTTPin = 11;

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

// Instantiate the Objects
LiquidCrystal_I2C lcd(0x27, 16, 2);
Si5351 si5351;

void setup()
{
  // Set up frequency and radix switches
  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(pushPin, INPUT);
  pinMode(ModePin, INPUT);
  pinMode(BandPin, INPUT);
  pinMode(PTTPin, INPUT);
  pinMode(CrystalFilterSelectPin, OUTPUT);

  // Set up pull-up resistors on inputs
  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(pushPin, HIGH);
  digitalWrite(ModePin, HIGH);
  digitalWrite(BandPin, HIGH);
  digitalWrite(PTTPin, HIGH);
  digitalWrite(CrystalFilterSelectPin, LOW);

  // Set up interrupt pins
  attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);

  // Initialize the display
  lcd.begin();
  lcd.backlight();
  lcd.cursor();
  UpdateDisplay();
  delay(1000);

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0);
  si5351.set_correction(35980);
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
//  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_6MA);
//  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_6MA);  si5351.set_freq((freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);
  si5351.set_freq((SSB_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) == LOW)
  {
    delay(10);
    while (digitalRead(pushPin) == LOW)
    {
      if (updatedisplay == 1)
      {
        UpdateDisplay();
        updatedisplay = 0;
      }
    }
    delay(50);
  }

  if (digitalRead(PTTPin) == HIGH)
    TX = 1;
  else
    TX = 0;

  if (digitalRead(ModePin) == LOW)
  {
    mode = 1;                                       // 0 = LSB, 1 = CW
    digitalWrite(CrystalFilterSelectPin, HIGH);     // Select CW filter
  }
  else
  {
    mode = 0;
    digitalWrite(CrystalFilterSelectPin, LOW);      // Select SSB filter
  }

  if (digitalRead(BandPin) == LOW)
    band = 1;                                       // 0 = 80m, 1 = 40m
  else
    band = 0;

  if (TX != oldTX)                              // Only update the display on mode change
  {
    //UpdateMode();
    UpdateDisplay();
    oldTX = TX;
  }

  if (mode != oldmode)                              // Only update the display on mode change
  {
    //UpdateMode();
    UpdateDisplay();
    SendFrequency();
    oldmode = mode;
  }

  if (band != oldband)                          // Only update the display on band change
  {
    // 0 = 80m, 1 = 40m
    if (band == 0)  // now 80m was 40m
    {
      old40mfreq = freq;
      freq = old80mfreq;
    }
    if (band == 1)  // now 40m was 80m
    {
      old80mfreq = freq;
      freq = old40mfreq;
    }
    UpdateDisplay();
    oldband = band;
  }
}




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 (band == 0)          // 80m
              if (freq > band80mEnd)
                freq = band80mEnd;
            if (band == 1)          // 80m
              if (freq > band40mEnd)
                freq = band40mEnd;
          }
          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 (band == 0)          // 80m
              if (freq < band80mStart)
                freq = band80mStart;
            if (band == 1)          // 80m
              if (freq < band40mStart)
                freq = band40mStart;
          }
          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()
{
  lcd.setCursor(0, 0);
  lcd.print("        ");
  lcd.setCursor(0, 0);
  lcd.print(freq);
  lcd.setCursor(10, 0);
  lcd.print("ZL2CTM");

  lcd.setCursor(0, 1);
  lcd.print("   ");
  lcd.setCursor(0, 1);

  lcd.setCursor(0, 1);
  if (mode == 0)
    lcd.print("LSB");
  if (mode == 1)
    lcd.print("CW ");

  lcd.setCursor(5, 1);
  lcd.print("  ");
  lcd.setCursor(5, 1);
  if (TX == 1)
    lcd.print("TX");

  if (freq > 9999999)
  {
    if (radix == 1)
      lcd.setCursor(7, 0);
    if (radix == 10)
      lcd.setCursor(6, 0);
    if (radix == 100)
      lcd.setCursor(5, 0);
    if (radix == 1000)
      lcd.setCursor(4, 0);
    if (radix == 10000)
      lcd.setCursor(3, 0);
    if (radix == 100000)
      lcd.setCursor(2, 0);
    if (radix == 1000000)
      lcd.setCursor(1, 0);
  }
  if (freq <= 9999999)
  {
    if (radix == 1)
      lcd.setCursor(6, 0);
    if (radix == 10)
      lcd.setCursor(5, 0);
    if (radix == 100)
      lcd.setCursor(4, 0);
    if (radix == 1000)
      lcd.setCursor(3, 0);
    if (radix == 10000)
      lcd.setCursor(2, 0);
    if (radix == 100000)
      lcd.setCursor(1, 0);
    if (radix == 1000000)
      lcd.setCursor(0, 0);
  }
}


void SendFrequency()
{
  // CW_BFO_freq = 8997336;    // 8996750 (filter centre) + 700 - 114 (from test)
  // SSB_BFO_freq = 8999700;   // 8998050 (filter centre) + 2700/2 + 300
  // CW_Filter_CentreFreq = 8996750;

  if (mode == 1)      // CW
  {
    si5351.set_freq(((CW_Filter_CentreFreq - freq) * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);
    si5351.set_freq((CW_BFO_freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK2);
  }
  else                // SSB
  {
    si5351.set_freq(((SSB_BFO_freq - freq) * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);
    si5351.set_freq((SSB_BFO_freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK2);
  }
}