Saturday, 21 March 2020

Homebrew CW Rig

Please see the YouTube videos for an explanation on the code and circuits:
https://www.youtube.com/channel/UCSNPW3_gzuMJcX_ErBZTv2g/

Crystal Filter





IF Amps









Antenna Amp, 700Hz LPF


Code

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

const long band80mStart = 3500000;    // start of 80m
const long band80mEnd = 3900000;      // end of 80m
volatile uint32_t FilterCentreFreq = 8996500;
volatile uint32_t BFO = 8997243;      // Filter centre freq plus offset to get 700Hz AF out.

volatile long freq = 3525000;         // this is a variable (changes) - set it to the beginning of the band
volatile long radix = 100;            // how much to change the frequency by, clicking the rotary encoder will change this.
volatile long oldfreq = 0;
volatile long currentfreq = 0;
volatile int updatedisplay = 0;
volatile int band = 0;                // 0=80m, 1=40m
volatile int oldband = 0;

// Rotary encoder pins and other inputs
static const int rotAPin = 2;
static const int rotBPin = 3;
static const int pushPin = 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;

// 700Hz Osc
static const int Osc = 11;
static const int TuneSw = 9;

// Morse Key
static const int Key = 8;
unsigned long BreakInTimer;
unsigned long BreakInDelay = 1000;    //1000mS - 1.0Sec

// Relays
static const int AntRelay = 7;
static const int PowerRelay = 6;

void setup()
{
  // Set up I/O Pins
  pinMode(rotAPin, INPUT_PULLUP);
  pinMode(rotBPin, INPUT_PULLUP);
  pinMode(pushPin, INPUT_PULLUP);
  pinMode(TuneSw, INPUT_PULLUP);
  pinMode(Key, INPUT_PULLUP);
  pinMode(AntRelay, OUTPUT);
  pinMode(PowerRelay, OUTPUT);
  pinMode(Osc, OUTPUT);

  digitalWrite(AntRelay, HIGH);         // Default to Rx
  digitalWrite(PowerRelay, HIGH);       // Default to Rx

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

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

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(-400, 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_CLK1, SI5351_DRIVE_8MA);
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
  si5351.output_enable(SI5351_CLK1, 0);                   // Turn off CW carrier

  // Update display and send start frequency
  UpdateDisplay();
  SendFrequency();
}


void loop()
{
  if (digitalRead(Key) == LOW)                // Check to see if CW key down
  {
    BreakInTimer = millis();                  // Reset the break in timer
    digitalWrite(AntRelay, LOW);              // Swap over the antenna to Tx
    digitalWrite(PowerRelay, LOW);            // Rx 12VDC = 0, Tx 12VDC = 12
    delay(25);                                // Allow time for relays to swap over (Spec sheet 10mS)
    si5351.output_enable(SI5351_CLK1, 1);     // Get ready to transmit CW carrier
    SendFrequency();                          // Send CW carrier
  }
  else                                        // Key up
  {
    si5351.output_enable(SI5351_CLK1, 0);     // Turn off the CW carrier
  }

  if ((millis() - BreakInTimer) >= BreakInDelay)    // Check break in timer
  {
    digitalWrite(AntRelay, HIGH);             // Swap over the antenna to Rx
    digitalWrite(PowerRelay, HIGH);           // Rx 12VDC = 12, Tx 12VDC = 0
  }

  if (digitalRead(TuneSw) == LOW)             // Check tune switch
    tone(Osc, 700);                           // Turn on the 700Hz tome
  else
    noTone(Osc);                              // Turn off the 700Hz tome

  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()
{
  lcd.setCursor(0, 0);
  lcd.print("        ");
  lcd.setCursor(0, 0);
  lcd.print(freq);
  lcd.setCursor(10, 0);
  lcd.print("ZL2CTM");

  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()
{
  // VFO
  si5351.set_freq(((freq + FilterCentreFreq) * 100ULL), SI5351_CLK0);

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

  // Transmit CW carrier
  si5351.set_freq(((freq - 43)  * 100ULL), SI5351_CLK1);         // 43Hz to align Tx and Rx
}

Saturday, 15 February 2020

Homebrew DSB-SC Rig

AF Amps Ideas






The AF amp now has a pi filter on the input made up of 100nF - 1mH - 100nF
That removes the high freq noise.



Ant RF Amp



Mic Amp



Test Low Power RF Amplifier
Out of the junk box hence the 13.8VDC VCC.




Initial code for the radio. Please see the YouTube video for an explanation:

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

const long band80mStart = 3500000;    // start of 80m
const long band80mEnd = 3900000;      // end of 80m
const long band40mStart = 7000000;    // start of 40m
const long band40mEnd = 7300000;      // end of 40m
volatile long freq = 3700000;         // this is a variable (changes) - set it to the beginning of the band
volatile long radix = 1000;           // how much to change the frequency by, clicking the rotary encoder will change this.
volatile long oldfreq = 0;
volatile long old80mfreq = 3700000;
volatile long old40mfreq = 7100000;
volatile long currentfreq = 0;
volatile int updatedisplay = 0;
volatile int band = 0;                // 0=80m, 1=40m
volatile int oldband = 0;

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

// Band switch
static const int BandSwPin = 5;

// 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_PULLUP);
  pinMode(rotBPin, INPUT_PULLUP);
  pinMode(pushSwPin, INPUT_PULLUP);
  pinMode(BandSwPin, INPUT_PULLUP);

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

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

  // Initialize the Si5351
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(191, SI5351_PLL_INPUT_XO);
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);

  // Update display and send start frequency
  UpdateDisplay();
  SendFrequency();
}


void loop()
{
  // Check to see if the freq has changed
  currentfreq = getfreq();                    // Interrupt safe method to get the current frequency

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

  // Check the rotary encoder (radix) swith
  if (digitalRead(pushSwPin) == LOW)           // Read the rotary encoder switch
  {
    delay(10);
    while (digitalRead(pushSwPin) == LOW)
    {
      if (updatedisplay == 1)
      {
        UpdateDisplay();
        updatedisplay = 0;
      }
    }
    delay(50);
  }

  // Check the band swith
  if (digitalRead(BandSwPin) == LOW)
    band = 1;                                  // 1=40m
  else
    band = 0;                                  // 0=80m

  if (band != oldband)                         // Only update the display on band change
  {
    if (band == 0)                             // Now 80m was 40m
    {
      old40mfreq = freq;                       // Store old 40m freq
      freq = old80mfreq;                       // Recall old 40m freq
    }
    if (band == 1)                             // Now 40m was 80m
    {
      old80mfreq = freq;                       // Store old 80m freq
      freq = old40mfreq;                       // Recall old 40m freq
    }
    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();
  }
}


// Determine which way the rotary encoder is rotating and action as required
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 the radix or freq
          if (digitalRead(pushSwPin) == 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)                          // 40m
              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 the radix or freq
          if (digitalRead(pushSwPin) == 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)                          // 40m
              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");

  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()
{
  si5351.set_freq((freq * 100ULL), SI5351_CLK0);
}

Saturday, 7 December 2019

40m SSB Tramping Rig

Please see my YouTube channel for details of the build.

http://www.youtube.com/c/CharlieMorrisZL2CTM

2N3904 Antenna Amplifier (initial)

Voltage gain is set for 4 using Rdegen





J310 Antenna Amplifer

First need to determine Vp and Idss




2N3904 Antenna Amplifier (final)

Note, T2 is now a 10:5



7MHz BandPass Filter


2N3904 IF Amplifiers




 Relay and Crystal Configuration




High side versus low side injection



Initial RF Power Amplifier Driver








Software

#include <Wire.h>
#include <SPI.h>
#include <TM1637Display.h>
#include <si5351.h>
#include "LowPower.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 currentfreq = 0;
volatile long oldfreq = 0;
volatile int currentmode = 0;
volatile int oldmode = 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.

//const uint32_t BFO_freq = 9000000;      // Low side injection use 9000000;
const uint32_t BFO_freq = 8996400;      // High side injection use 8996400;

// Rotary encoder pins and other inputs
static const int rotBPin = 2;
static const int rotAPin = 3;
static const int pushPin = 4;
static const int PTTInput = 9;
static const int brightnessPin = A3;
static const int tunespeedLED = A2;
static const int gnd = 10;
static const int vcc = 11;
static const int DIO = 12;
static const int CLK = 13;

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


volatile long remainder = 0;
volatile long OnesHz = 0;
volatile long TensHz = 0;
volatile long HundredsHz = 0;
volatile long OneskHz = 0;
volatile long TenskHz = 0;
volatile long HundredskHz = 0;
volatile long OnesMHz = 0;
volatile long TensMHz = 0;
volatile int Brightness = 3;
volatile int batterySave = 0;

// Instantiate the Objects
TM1637Display display(CLK, DIO);    // CLK, DIO
Si5351 si5351;

void setup()
{
  // Set up frequency and radix switches
  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(pushPin, INPUT);
  pinMode(brightnessPin, INPUT);
  pinMode(gnd, OUTPUT);
  pinMode(tunespeedLED, OUTPUT);
  pinMode(vcc, OUTPUT);
  pinMode(PTTInput, INPUT);

  // Set up pull-up resistors on inputs
  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(pushPin, HIGH);
  digitalWrite(brightnessPin, HIGH);
  digitalWrite(gnd, LOW);
  digitalWrite(vcc, HIGH);
  digitalWrite(tunespeedLED, LOW);
  digitalWrite(PTTInput, LOW);

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

  // Initialize the display
  display.setBrightness(Brightness, true);
  UpdateDisplay();
  delay(1000);

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(29100, 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_2MA);
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_2MA);
  SendFrequency();
}


void loop()
{
  LowPower.idle(SLEEP_60MS, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_ON, SPI_OFF, USART0_OFF, TWI_OFF);

  currentmode = digitalRead(PTTInput);
  if (currentmode != oldmode)
  {
    SendFrequency();
    oldmode = currentmode;
  }


  currentfreq = getfreq();                // Interrupt safe method to get the current frequency
  if (currentfreq != oldfreq)
  {
    UpdateDisplay();
    SendFrequency();
    oldfreq = currentfreq;
  }

  if (digitalRead(brightnessPin) == LOW)
  {
    Brightness--;
    display.setBrightness(Brightness, true);
    if (Brightness == -1)
    {
      display.setBrightness(0, false);
      digitalWrite(tunespeedLED, LOW);
      batterySave = 1;
    }
    if (Brightness == -2)
    {
      Brightness = 3;
      batterySave = 0;
    }
    UpdateDisplay();
    delay(500);
  }

  if ((radix == 100) && (batterySave == 0))
    digitalWrite(tunespeedLED, HIGH);

  if (radix == 1000)
    digitalWrite(tunespeedLED, LOW);
}


void wakeUp()
{
  // Just a handler for the sleep pin interrupt.
}


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)
          {
            if (radix == 1000)
              radix = 100;
            else if (radix == 100)
              radix = 1000;
          }
          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)
          {
            if (radix == 100)
              radix = 1000;
            else if (radix == 1000)
              radix = 100;
          }
          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()
{
  TensMHz = freq / 10000000;                                // TensMHz = 12345678 / 10000000 = 1
  remainder = freq - (TensMHz * 10000000);                  // remainder = 12345678 - 10000000 = 2345678
  OnesMHz = remainder / 1000000;                            // OnesMhz = 2345678 / 1000000 = 2
  remainder = remainder - (OnesMHz * 1000000);              // remainder = 2345678 - (2 * 1000000) = 345678
  HundredskHz = remainder / 100000;                         // HundredskHz = 345678 / 100000 = 3
  remainder = remainder - (HundredskHz * 100000);           // remainder = 345678 - (3 * 100000) = 45678
  TenskHz = remainder / 10000;                              // TenskHz = 45678 / 10000 = 4
  remainder = remainder - (TenskHz * 10000);                // remainder = 45678 - (4 * 10000) = 5678
  OneskHz = remainder / 1000;                               // OneskHz = 5678 / 1000 = 5
  remainder = remainder - (OneskHz * 1000);                 // remainder = 5678 - (5 * 1000) = 678
  HundredsHz = remainder / 100;                             // HundredsHz = 678 / 100 = 6
  remainder = remainder - (HundredsHz * 100);               // remainder = 678 - (6 * 100) = 78
  TensHz = remainder / 10;                                  // TensHz = 78 / 10 = 7
  remainder = remainder - (TensHz * 10);                    // remainder = 78 - (7 * 10) = 8
  OnesHz = remainder;                                       // OnesHz = 8

  display.showNumberDec(((1000 * HundredskHz) + ( 100 * TenskHz) + (10 * OneskHz) + HundredsHz), true);
}


void SendFrequency()
{
  if (currentmode == 1)             // Transmit
  {
    si5351.set_freq(((freq + BFO_freq) * 100ULL), SI5351_CLK2);
    si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK0);
  }
  else                              // Receive
  {
    //si5351.set_freq(((BFO_freq - freq) * 100ULL), SI5351_CLK0);   // Low side injection
    si5351.set_freq(((BFO_freq + freq) * 100ULL), SI5351_CLK0);   // High side injection

    si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
  }
}

Friday, 21 June 2019

Homebrew 80/40/20m SSB SDR Phasing Rig

Please see my YouTube channel for accompanying/explanation videos.

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






Antenna RF amplifer


Final configuration




Test Software for Phasing technique.

// Libraries
#include <Wire.h>                          // I2C comms library
#include <si5351.h>                        // Si5351Jason library
#include "ILI9341_t3.h"
#include <Audio.h>                         // Teensy audio library

#define NO_HILBERT_COEFFS 100               // Used to define the Hilbert transform filter arrays. More typical than 'const int'.

// Define Constants and Vaviables
static const long bandStart = 1000000;     // start of HF band
static const long bandEnd =   30000000;    // end of HF band
static const long bandInit =  3690000;     // where to initially set the frequency
volatile long oldfreq = 0;
volatile long freq = bandInit ;
volatile long radix = 1000;                // how much to change the frequency. Pushing the rotary encoder switch will change this.
volatile long oldradix = 1;
volatile int updatedisplay = 0;
volatile int mode = 1;                     // 1 = LSB, 0 = USB
volatile int oldmode = 0;
volatile int Even_Divisor = 0;
volatile int oldEven_Divisor = 0;

// Audio panel gains
static const int Linein_Gain = 10;          // Range is 0-15. 0 = 3.12 Vp-p, 15 = 0.24 Vp-p. Default = 5

// Rotary Encoder
static const int EncoderPushButton = 39;
static const int rotBPin = 36;
static const int rotAPin = 35;
static const int ModeSwitch = 24;
static const int PTTSwitch = 25;
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
volatile int rotAcc = 0;

// For optimized ILI9341_t3 library
#define TFT_DC    20
#define TFT_CS    21
#define TFT_RST   255  // 255 = unused, connect to 3.3V
#define TFT_MOSI  7
#define TFT_SCLK  14
#define TFT_MISO  12

// Iowa Hills Hilbert transform filter coefficients
const short Hilbert_Plus_45_Coeffs[NO_HILBERT_COEFFS] = {
(short)(32768 * 483.9743406915770E-9),
(short)(32768 * 1.866685817417670E-6),
(short)(32768 * 4.392570072495770E-6),
(short)(32768 * 8.820636712774380E-6),
(short)(32768 * 0.000016184764415848),
(short)(32768 * 0.000027249288101724),
(short)(32768 * 0.000041920439805110),
(short)(32768 * 0.000059016345361008),
(short)(32768 * 0.000076756191268614),
(short)(32768 * 0.000094055071776657),
(short)(32768 * 0.000112261583662573),
(short)(32768 * 0.000136518354793722),
(short)(32768 * 0.000175696544399550),
(short)(32768 * 0.000240067250940666),
(short)(32768 * 0.000336609833081347),
(short)(32768 * 0.000462990951098187),
(short)(32768 * 0.000602421399161912),
(short)(32768 * 0.000722301614841713),
(short)(32768 * 0.000779305755125654),
(short)(32768 * 0.000732056920489209),
(short)(32768 * 0.000559948250896998),
(short)(32768 * 0.000283585816755988),
(short)(32768 * -0.000020215761694049),
(short)(32768 * -0.000216819118897174),
(short)(32768 * -0.000131758715732279),
(short)(32768 * 0.000409948902196685),
(short)(32768 * 0.001528130963104050),
(short)(32768 * 0.003227564604759798),
(short)(32768 * 0.005351504118228274),
(short)(32768 * 0.007567804706902511),
(short)(32768 * 0.009402820162611196),
(short)(32768 * 0.010328512135999630),
(short)(32768 * 0.009894246835039863),
(short)(32768 * 0.007879714166593881),
(short)(32768 * 0.004433421126740721),
(short)(32768 * 0.000156366633965185),
(short)(32768 * -0.003904389468521576),
(short)(32768 * -0.006371988171406650),
(short)(32768 * -0.005761887634323113),
(short)(32768 * -0.000778938771957753),
(short)(32768 * 0.009365085367419172),
(short)(32768 * 0.024681757404317366),
(short)(32768 * 0.044251382160327521),
(short)(32768 * 0.066233642104189930),
(short)(32768 * 0.088062621169129954),
(short)(32768 * 0.106806616459214951),
(short)(32768 * 0.119635362035632908),
(short)(32768 * 0.124309482163432433),
(short)(32768 * 0.119596382589365807),
(short)(32768 * 0.105526834497225247),
(short)(32768 * 0.083435851600156402),
(short)(32768 * 0.055774084237545388),
(short)(32768 * 0.025722508803868269),
(short)(32768 * -0.003316774367731974),
(short)(32768 * -0.028256325810852603),
(short)(32768 * -0.046784860848984686),
(short)(32768 * -0.057671422122216751),
(short)(32768 * -0.060863961385426720),
(short)(32768 * -0.057377469068775784),
(short)(32768 * -0.049008885222883866),
(short)(32768 * -0.037947605693328487),
(short)(32768 * -0.026365178611104038),
(short)(32768 * -0.016063741251826878),
(short)(32768 * -0.008242320709669780),
(short)(32768 * -0.003409329314875374),
(short)(32768 * -0.001436295024424050),
(short)(32768 * -0.001719754923178513),
(short)(32768 * -0.003400970055132929),
(short)(32768 * -0.005589187214751837),
(short)(32768 * -0.007542651980327935),
(short)(32768 * -0.008778747041127889),
(short)(32768 * -0.009105231860961261),
(short)(32768 * -0.008583286966676333),
(short)(32768 * -0.007445876442758468),
(short)(32768 * -0.005999873442098177),
(short)(32768 * -0.004537732597630097),
(short)(32768 * -0.003276341625911221),
(short)(32768 * -0.002330120643710241),
(short)(32768 * -0.001715559019593159),
(short)(32768 * -0.001377498498508186),
(short)(32768 * -0.001224659509681699),
(short)(32768 * -0.001162953813794283),
(short)(32768 * -0.001118850197066992),
(short)(32768 * -0.001049829425495061),
(short)(32768 * -0.000943240226180188),
(short)(32768 * -0.000807608474652057),
(short)(32768 * -0.000661296218869203),
(short)(32768 * -0.000522622836450865),
(short)(32768 * -0.000403818527830429),
(short)(32768 * -0.000309261220275339),
(short)(32768 * -0.000236981246010256),
(short)(32768 * -0.000181726899678497),
(short)(32768 * -0.000137960767232952),
(short)(32768 * -0.000101749677323657),
(short)(32768 * -0.000071268112804006),
(short)(32768 * -0.000046246069278189),
(short)(32768 * -0.000026984224473470),
(short)(32768 * -0.000013519855860183),
(short)(32768 * -5.268419079329310E-6),
(short)(32768 * -1.152120275972750E-6)
};

// Iowa Hills Hilbert transform filter coefficients
const short Hilbert_Minus_45_Coeffs[NO_HILBERT_COEFFS] = {
(short)(32768 * -1.152120275972720E-6),
(short)(32768 * -5.268419079329100E-6),
(short)(32768 * -0.000013519855860182),
(short)(32768 * -0.000026984224473469),
(short)(32768 * -0.000046246069278187),
(short)(32768 * -0.000071268112804004),
(short)(32768 * -0.000101749677323656),
(short)(32768 * -0.000137960767232950),
(short)(32768 * -0.000181726899678496),
(short)(32768 * -0.000236981246010254),
(short)(32768 * -0.000309261220275334),
(short)(32768 * -0.000403818527830420),
(short)(32768 * -0.000522622836450852),
(short)(32768 * -0.000661296218869189),
(short)(32768 * -0.000807608474652046),
(short)(32768 * -0.000943240226180185),
(short)(32768 * -0.001049829425495072),
(short)(32768 * -0.001118850197067017),
(short)(32768 * -0.001162953813794315),
(short)(32768 * -0.001224659509681719),
(short)(32768 * -0.001377498498508169),
(short)(32768 * -0.001715559019593075),
(short)(32768 * -0.002330120643710066),
(short)(32768 * -0.003276341625910951),
(short)(32768 * -0.004537732597629757),
(short)(32768 * -0.005999873442097817),
(short)(32768 * -0.007445876442758181),
(short)(32768 * -0.008583286966676212),
(short)(32768 * -0.009105231860961396),
(short)(32768 * -0.008778747041128323),
(short)(32768 * -0.007542651980328638),
(short)(32768 * -0.005589187214752684),
(short)(32768 * -0.003400970055133710),
(short)(32768 * -0.001719754923178943),
(short)(32768 * -0.001436295024423829),
(short)(32768 * -0.003409329314874256),
(short)(32768 * -0.008242320709667652),
(short)(32768 * -0.016063741251823811),
(short)(32768 * -0.026365178611100354),
(short)(32768 * -0.037947605693324706),
(short)(32768 * -0.049008885222880681),
(short)(32768 * -0.057377469068773987),
(short)(32768 * -0.060863961385426962),
(short)(32768 * -0.057671422122219533),
(short)(32768 * -0.046784860848990188),
(short)(32768 * -0.028256325810860565),
(short)(32768 * -0.003316774367741772),
(short)(32768 * 0.025722508803857579),
(short)(32768 * 0.055774084237534945),
(short)(32768 * 0.083435851600147395),
(short)(32768 * 0.105526834497218600),
(short)(32768 * 0.119596382589362227),
(short)(32768 * 0.124309482163432142),
(short)(32768 * 0.119635362035635753),
(short)(32768 * 0.106806616459220294),
(short)(32768 * 0.088062621169136893),
(short)(32768 * 0.066233642104197507),
(short)(32768 * 0.044251382160334737),
(short)(32768 * 0.024681757404323448),
(short)(32768 * 0.009365085367423625),
(short)(32768 * -0.000778938771955104),
(short)(32768 * -0.005761887634322139),
(short)(32768 * -0.006371988171407000),
(short)(32768 * -0.003904389468522771),
(short)(32768 * 0.000156366633963640),
(short)(32768 * 0.004433421126739255),
(short)(32768 * 0.007879714166592786),
(short)(32768 * 0.009894246835039278),
(short)(32768 * 0.010328512135999562),
(short)(32768 * 0.009402820162611529),
(short)(32768 * 0.007567804706903086),
(short)(32768 * 0.005351504118228925),
(short)(32768 * 0.003227564604760380),
(short)(32768 * 0.001528130963104480),
(short)(32768 * 0.000409948902196933),
(short)(32768 * -0.000131758715732195),
(short)(32768 * -0.000216819118897210),
(short)(32768 * -0.000020215761694147),
(short)(32768 * 0.000283585816755878),
(short)(32768 * 0.000559948250896909),
(short)(32768 * 0.000732056920489157),
(short)(32768 * 0.000779305755125639),
(short)(32768 * 0.000722301614841724),
(short)(32768 * 0.000602421399161936),
(short)(32768 * 0.000462990951098213),
(short)(32768 * 0.000336609833081367),
(short)(32768 * 0.000240067250940678),
(short)(32768 * 0.000175696544399555),
(short)(32768 * 0.000136518354793723),
(short)(32768 * 0.000112261583662573),
(short)(32768 * 0.000094055071776658),
(short)(32768 * 0.000076756191268616),
(short)(32768 * 0.000059016345361010),
(short)(32768 * 0.000041920439805112),
(short)(32768 * 0.000027249288101726),
(short)(32768 * 0.000016184764415849),
(short)(32768 * 8.820636712774440E-6),
(short)(32768 * 4.392570072495580E-6),
(short)(32768 * 1.866685817417500E-6),
(short)(32768 * 483.9743406915230E-9)
};


// Instantiate the Objects
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
Si5351 si5351;                            // Name for the Si5351 DDS
AudioControlSGTL5000    audioShield;      // Name for the Teensy audio CODEC on the audio shield

// Audio shield
AudioInputI2S           audioInput;                                           // Name for the input to the audio shield (either line-in or mic)
AudioOutputI2S          audioOutput;                                          // Name for the output of the audio shield (either headphones or line-out)
// Receiver
AudioFilterFIR          RX_Hilbert_Plus_45;                                   // Name for the RX +45 Hilbert transform
AudioFilterFIR          RX_Hilbert_Minus_45;                                  // Name for the RX +45 Hilbert transform
AudioMixer4             RX_Summer;                                            // Name for the RX summer

// Audio connections
AudioConnection         patchCord5(audioInput, 0, RX_Hilbert_Plus_45, 0);               // Left channel in Hilbert transform +45
AudioConnection         patchCord10(audioInput, 1, RX_Hilbert_Minus_45, 0);             // Right channel in Hilbert transform -45

AudioConnection         patchCord15(RX_Hilbert_Plus_45, 0, RX_Summer, 0);               // Hilbert transform +45 to receiver summer
AudioConnection         patchCord20(RX_Hilbert_Minus_45, 0, RX_Summer, 1);              // Hilbert transform -45 to receiver summer

AudioConnection         patchCord25(RX_Summer, 0, audioOutput, 0);
AudioConnection         patchCord30(RX_Summer, 0, audioOutput, 1);

void setup()
{
  // Setup screen
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  tft.drawRect(31, 0, 257, 37, ILI9341_YELLOW);
  tft.drawRect(31, 36, 257, 103, ILI9341_YELLOW);
  tft.drawRect(31, 138, 257, 102, ILI9341_YELLOW);

  // Setup input switches
  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(EncoderPushButton, INPUT);
  pinMode(ModeSwitch, INPUT);
  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(EncoderPushButton, HIGH);
  digitalWrite(ModeSwitch, HIGH);

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

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);             // 25MHz crystal = 0, 27MHz crystal = 27000000
  si5351.set_correction(62799, SI5351_PLL_INPUT_XO);      // Set to specific Si5351 calibration number
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_6MA);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA);

  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK0);
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK1);
  si5351.set_phase(SI5351_CLK0, 0);
  si5351.set_phase(SI5351_CLK1, Even_Divisor);
  si5351.pll_reset(SI5351_PLLA);

  // Setup the audio shield
  AudioNoInterrupts();
  AudioMemory(12);
  audioShield.enable();
  AudioInterrupts();

  // Setup transceiver mode
  Turn_On_Receiver();
  UpdateDisplay();
}

void loop()
{
  if (freq != oldfreq)                // Check to see if the frequency has changed. If so, update everything.
  {
    EvenDivisor();
    UpdateDisplay();
    SendFrequency();
    oldfreq = freq;
  }

  if (digitalRead(EncoderPushButton) == LOW)    // Update cursor, but also stop it from flickering
  {
    delay(50);
    while (digitalRead(EncoderPushButton) == LOW)
    {
      if (updatedisplay == 1)
      {
        UpdateDisplay();
        updatedisplay = 0;
      }
    }
    delay(50);
  }
}


void Turn_On_Receiver()
{
  AudioNoInterrupts();
  audioShield.inputSelect(AUDIO_INPUT_LINEIN);
  audioShield.lineInLevel(Linein_Gain);
  audioShield.unmuteHeadphone();                                        // Output to the audio amplifier
  audioShield.volume(0.7);
  RX_Hilbert_Plus_45.begin(Hilbert_Plus_45_Coeffs, NO_HILBERT_COEFFS);
  RX_Hilbert_Minus_45.begin(Hilbert_Minus_45_Coeffs, NO_HILBERT_COEFFS);
  RX_Summer.gain(0, 1);
  RX_Summer.gain(1, -1);
  AudioInterrupts();
}


// 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(EncoderPushButton) == 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 > 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(EncoderPushButton) == 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 < 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()
{
  tft.setTextSize(2);
  tft.setCursor(50, 10);
  tft.setTextColor(ILI9341_BLACK);
  tft.println(oldfreq);

  tft.setCursor(50, 10);
  tft.setTextColor(ILI9341_WHITE);
  tft.println(freq);

  if (radix != oldradix)                          // stops radix display flashing/blinking on freq change
  {
    tft.setCursor(170, 10);
    tft.setTextColor(ILI9341_BLACK);
    if (oldradix == 1)
      tft.print("   1 Hz");
    if (oldradix == 10)
      tft.print("  10 Hz");
    if (oldradix == 100)
      tft.print(" 100 Hz");
    if (oldradix == 1000)
      tft.print("  1 kHz");
    if (oldradix == 10000)
      tft.print(" 10 kHz");
    if (oldradix == 100000)
      tft.print("100 kHz");
    if (oldradix == 1000000)
      tft.print("  1 MHz");

    tft.setCursor(170, 10);
    tft.setTextColor(ILI9341_WHITE);
    if (radix == 1)
      tft.print("   1 Hz");
    if (radix == 10)
      tft.print("  10 Hz");
    if (radix == 100)
      tft.print(" 100 Hz");
    if (radix == 1000)
      tft.print("  1 kHz");
    if (radix == 10000)
      tft.print(" 10 kHz");
    if (radix == 100000)
      tft.print("100 kHz");
    if (radix == 1000000)
      tft.print("  1 MHz");

    oldradix = radix;
  }
}


void EvenDivisor()
{
  if (freq < 6850000)
  {
    Even_Divisor = 126;
  }
  if ((freq >= 6850000) && (freq < 9500000))
  {
    Even_Divisor = 88;
  }
  if ((freq >= 9500000) && (freq < 13600000))
  {
    Even_Divisor = 64;
  }
  if ((freq >= 13600000) && (freq < 17500000))
  {
    Even_Divisor = 44;
  }
  if ((freq >= 17500000) && (freq < 25000000))
  {
    Even_Divisor = 34;
  }
  if ((freq >= 25000000) && (freq < 36000000))
  {
    Even_Divisor = 24;
  }
  if ((freq >= 36000000) && (freq < 45000000)) {
    Even_Divisor = 18;
  }
  if ((freq >= 45000000) && (freq < 60000000)) {
    Even_Divisor = 14;
  }
  if ((freq >= 60000000) && (freq < 80000000)) {
    Even_Divisor = 10;
  }
  if ((freq >= 80000000) && (freq < 100000000)) {
    Even_Divisor = 8;
  }
  if ((freq >= 100000000) && (freq < 146600000)) {
    Even_Divisor = 6;
  }
  if ((freq >= 150000000) && (freq < 220000000)) {
    Even_Divisor = 4;
  }
}


void SendFrequency()
{
  //freq = freq + 10;
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK0);
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK1);
  si5351.set_phase(SI5351_CLK0, 0);
  si5351.set_phase(SI5351_CLK1, Even_Divisor);
  if (Even_Divisor != oldEven_Divisor)
  {
    si5351.pll_reset(SI5351_PLLA);
    oldEven_Divisor = Even_Divisor;
  }
}

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

Test software for Weaver technique.

// Libraries
#include <Wire.h>                          // I2C comms library
#include <si5351.h>                        // Si5351Jason library
#include "ILI9341_t3.h"
#include <Audio.h>                         // Teensy audio library

#define NO_LPF_COEFFS 100               // Used to define the Hilbert transform filter arrays. More typical than 'const int'.
int Osc_freq = 1350;


// Define Constants and Vaviables
static const long bandStart = 1000000;     // start of HF band
static const long bandEnd =   30000000;    // end of HF band
static const long bandInit =  3690000 - 1350;     // where to initially set the frequency
volatile long oldfreq = 0;
volatile long freq = bandInit;
volatile long dispfreq = 0;
volatile long olddispfreq = 0;
volatile long radix = 1000;                // how much to change the frequency. Pushing the rotary encoder switch will change this.
volatile long oldradix = 1;
volatile int updatedisplay = 0;
volatile int mode = 1;                     // 1 = LSB, 0 = USB
volatile int oldmode = 0;
volatile int Even_Divisor = 0;
volatile int oldEven_Divisor = 0;


// Audio panel gains
static const int Linein_Gain = 10;          // Range is 0-15. 0 = 3.12 Vp-p, 15 = 0.24 Vp-p. Default = 5

// Rotary Encoder
static const int EncoderPushButton = 39;
static const int rotBPin = 36;
static const int rotAPin = 35;
static const int ModeSwitch = 24;
static const int PTTSwitch = 25;
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
volatile int rotAcc = 0;

// For optimized ILI9341_t3 library
#define TFT_DC    20
#define TFT_CS    21
#define TFT_RST   255  // 255 = unused, connect to 3.3V
#define TFT_MOSI  7
#define TFT_SCLK  14
#define TFT_MISO  12


// Iowa Hills LPF filter coefficients
const short LPF_Coeffs[NO_LPF_COEFFS] = {
(short)(32768 * 306.4951185627353420E-9),
(short)(32768 * 2.389304478278580830E-6),
(short)(32768 * 7.113585052311595550E-6),
(short)(32768 * 14.72828307076125930E-6),
(short)(32768 * 24.58848420405347920E-6),
(short)(32768 * 35.02666821312011080E-6),
(short)(32768 * 43.43839783397800150E-6),
(short)(32768 * 46.61576513715636590E-6),
(short)(32768 * 41.31600135178678100E-6),
(short)(32768 * 24.99939715244948960E-6),
(short)(32768 * -3.381010660280591830E-6),
(short)(32768 * -42.69541258485239870E-6),
(short)(32768 * -89.23441801688076680E-6),
(short)(32768 * -136.7515827038860440E-6),
(short)(32768 * -177.0704045420265800E-6),
(short)(32768 * -201.2975885521037750E-6),
(short)(32768 * -201.5817136692013490E-6),
(short)(32768 * -173.2410096718713530E-6),
(short)(32768 * -116.9727242504059600E-6),
(short)(32768 * -40.76876611355810100E-6),
(short)(32768 * 38.88327362518843700E-6),
(short)(32768 * 96.92273299774292640E-6),
(short)(32768 * 101.0929496871475240E-6),
(short)(32768 * 14.95903728424654490E-6),
(short)(32768 * -197.4484620824715080E-6),
(short)(32768 * -565.6166584117947880E-6),
(short)(32768 * -0.001105659124309112),
(short)(32768 * -0.001813271033445281),
(short)(32768 * -0.002657413489625704),
(short)(32768 * -0.003575681889777568),
(short)(32768 * -0.004472272286104596),
(short)(32768 * -0.005219295852277327),
(short)(32768 * -0.005661909790021988),
(short)(32768 * -0.005627354513012373),
(short)(32768 * -0.004937547773786580),
(short)(32768 * -0.003424434146231942),
(short)(32768 * -946.8768746362549110E-6),
(short)(32768 * 0.002592437620492790),
(short)(32768 * 0.007231787528579316),
(short)(32768 * 0.012938601156477677),
(short)(32768 * 0.019602556730010448),
(short)(32768 * 0.027034742068844295),
(short)(32768 * 0.034973496032183256),
(short)(32768 * 0.043096926615698458),
(short)(32768 * 0.051041434503589389),
(short)(32768 * 0.058424937851007025),
(short)(32768 * 0.064872965661608481),
(short)(32768 * 0.070045425749411874),
(short)(32768 * 0.073661704425816404),
(short)(32768 * 0.075521841389624714),
(short)(32768 * 0.075521841389624714),
(short)(32768 * 0.073661704425816404),
(short)(32768 * 0.070045425749411874),
(short)(32768 * 0.064872965661608481),
(short)(32768 * 0.058424937851007025),
(short)(32768 * 0.051041434503589389),
(short)(32768 * 0.043096926615698458),
(short)(32768 * 0.034973496032183256),
(short)(32768 * 0.027034742068844295),
(short)(32768 * 0.019602556730010448),
(short)(32768 * 0.012938601156477677),
(short)(32768 * 0.007231787528579316),
(short)(32768 * 0.002592437620492790),
(short)(32768 * -946.8768746362549110E-6),
(short)(32768 * -0.003424434146231942),
(short)(32768 * -0.004937547773786580),
(short)(32768 * -0.005627354513012373),
(short)(32768 * -0.005661909790021988),
(short)(32768 * -0.005219295852277327),
(short)(32768 * -0.004472272286104596),
(short)(32768 * -0.003575681889777568),
(short)(32768 * -0.002657413489625704),
(short)(32768 * -0.001813271033445281),
(short)(32768 * -0.001105659124309112),
(short)(32768 * -565.6166584117947880E-6),
(short)(32768 * -197.4484620824715080E-6),
(short)(32768 * 14.95903728424654490E-6),
(short)(32768 * 101.0929496871475240E-6),
(short)(32768 * 96.92273299774292640E-6),
(short)(32768 * 38.88327362518843700E-6),
(short)(32768 * -40.76876611355810100E-6),
(short)(32768 * -116.9727242504059600E-6),
(short)(32768 * -173.2410096718713530E-6),
(short)(32768 * -201.5817136692013490E-6),
(short)(32768 * -201.2975885521037750E-6),
(short)(32768 * -177.0704045420265800E-6),
(short)(32768 * -136.7515827038860440E-6),
(short)(32768 * -89.23441801688076680E-6),
(short)(32768 * -42.69541258485239870E-6),
(short)(32768 * -3.381010660280591830E-6),
(short)(32768 * 24.99939715244948960E-6),
(short)(32768 * 41.31600135178678100E-6),
(short)(32768 * 46.61576513715636590E-6),
(short)(32768 * 43.43839783397800150E-6),
(short)(32768 * 35.02666821312011080E-6),
(short)(32768 * 24.58848420405347920E-6),
(short)(32768 * 14.72828307076125930E-6),
(short)(32768 * 7.113585052311595550E-6),
(short)(32768 * 2.389304478278580830E-6),
(short)(32768 * 306.4951185627353420E-9)
};

// Instantiate the Objects
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
Si5351 si5351;                            // Name for the Si5351 DDS
AudioControlSGTL5000    audioShield;      // Name for the Teensy audio CODEC on the audio shield

// Audio shield
AudioInputI2S           audioInput;                                           // Name for the input to the audio shield (either line-in or mic)
AudioOutputI2S          audioOutput;                                          // Name for the output of the audio shield (either headphones or line-out)
// Receiver
AudioFilterFIR          RX_I_LPF;                                             // Name for the RX LPF
AudioFilterFIR          RX_Q_LPF;                                             // Name for the RX LPF
AudioEffectMultiply     RX_I_Mixer;
AudioEffectMultiply     RX_Q_Mixer;
AudioSynthWaveform      RX_I_Osc;
AudioSynthWaveform      RX_Q_Osc;
AudioMixer4             RX_Summer;                                            // Name for the RX summer

//Audio connections
AudioConnection         patchCord5(audioInput, 0, RX_I_LPF, 0);
AudioConnection         patchCord10(audioInput, 1, RX_Q_LPF, 0);

AudioConnection         patchCord15(RX_I_LPF, 0, RX_I_Mixer, 0);
AudioConnection         patchCord20(RX_Q_LPF, 0, RX_Q_Mixer, 0);

AudioConnection         patchCord25(RX_I_Osc, 0, RX_I_Mixer, 1);              // Left channel in Hilbert transform +45
AudioConnection         patchCord30(RX_Q_Osc, 0, RX_Q_Mixer, 1);              // Right channel in Hilbert transform -45

AudioConnection         patchCord35(RX_I_Mixer, 0, RX_Summer, 0);             // Hilbert transform +45 to receiver summer
AudioConnection         patchCord40(RX_Q_Mixer, 0, RX_Summer, 1);             // Hilbert transform -45 to receiver summer

AudioConnection         patchCord45(RX_Summer, 0, audioOutput, 0);
AudioConnection         patchCord50(RX_Summer, 0, audioOutput, 1);


void setup()
{
  // Setup screen
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  tft.drawRect(31, 0, 257, 37, ILI9341_YELLOW);
  tft.drawRect(31, 36, 257, 103, ILI9341_YELLOW);
  tft.drawRect(31, 138, 257, 102, ILI9341_YELLOW);

  // Setup input switches
  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(EncoderPushButton, INPUT);
  pinMode(ModeSwitch, INPUT);
  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(EncoderPushButton, HIGH);
  digitalWrite(ModeSwitch, HIGH);

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

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);             // 25MHz crystal = 0, 27MHz crystal = 27000000
  si5351.set_correction(62799, SI5351_PLL_INPUT_XO);      // Set to specific Si5351 calibration number
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_6MA);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA);

  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK0);
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK1);
  si5351.set_phase(SI5351_CLK0, 0);
  si5351.set_phase(SI5351_CLK1, Even_Divisor);
  si5351.pll_reset(SI5351_PLLA);

  // Setup the audio shield
  AudioNoInterrupts();
  AudioMemory(16);
  audioShield.enable();
  AudioInterrupts();

  // Setup transceiver mode
  Turn_On_Receiver();
  UpdateDisplay();
}

void loop()
{
  if (freq != oldfreq)                // Check to see if the frequency has changed. If so, update everything.
  {
    EvenDivisor();
    SendFrequency();
    UpdateDisplay();
    oldfreq = freq;
  }

  if (digitalRead(EncoderPushButton) == LOW)    // Update cursor, but also stop it from flickering
  {
    delay(50);
    while (digitalRead(EncoderPushButton) == LOW)
    {
      if (updatedisplay == 1)
      {
        UpdateDisplay();
        updatedisplay = 0;
      }
    }
    delay(50);
  }
}


void Turn_On_Receiver()
{
  AudioNoInterrupts();
  audioShield.inputSelect(AUDIO_INPUT_LINEIN);
  audioShield.lineInLevel(Linein_Gain);
  audioShield.unmuteHeadphone();                                        // Output to the audio amplifier
  audioShield.volume(0.7);
  RX_I_LPF.begin(LPF_Coeffs, NO_LPF_COEFFS);
  RX_Q_LPF.begin(LPF_Coeffs, NO_LPF_COEFFS);
  RX_I_Osc.frequency(Osc_freq);
  RX_I_Osc.amplitude(1);
  RX_I_Osc.phase(0);
  RX_Q_Osc.frequency(Osc_freq);
  RX_Q_Osc.amplitude(1);
  RX_Q_Osc.phase(90);
  RX_Summer.gain(0, 1);
  RX_Summer.gain(1, -1);
  AudioInterrupts();
}


// 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(EncoderPushButton) == 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 > 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(EncoderPushButton) == 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 < 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()
{
  dispfreq = freq + 1350;

  tft.setTextSize(2);
  tft.setCursor(50, 10);
  tft.setTextColor(ILI9341_BLACK);
  tft.println(olddispfreq);

  tft.setCursor(50, 10);
  tft.setTextColor(ILI9341_WHITE);
  tft.println(dispfreq);

  olddispfreq = dispfreq;

  if (radix != oldradix)                          // stops radix display flashing/blinking on freq change
  {
    tft.setCursor(170, 10);
    tft.setTextColor(ILI9341_BLACK);
    if (oldradix == 1)
      tft.print("   1 Hz");
    if (oldradix == 10)
      tft.print("  10 Hz");
    if (oldradix == 100)
      tft.print(" 100 Hz");
    if (oldradix == 1000)
      tft.print("  1 kHz");
    if (oldradix == 10000)
      tft.print(" 10 kHz");
    if (oldradix == 100000)
      tft.print("100 kHz");
    if (oldradix == 1000000)
      tft.print("  1 MHz");

    tft.setCursor(170, 10);
    tft.setTextColor(ILI9341_WHITE);
    if (radix == 1)
      tft.print("   1 Hz");
    if (radix == 10)
      tft.print("  10 Hz");
    if (radix == 100)
      tft.print(" 100 Hz");
    if (radix == 1000)
      tft.print("  1 kHz");
    if (radix == 10000)
      tft.print(" 10 kHz");
    if (radix == 100000)
      tft.print("100 kHz");
    if (radix == 1000000)
      tft.print("  1 MHz");

    oldradix = radix;
  }
}


void EvenDivisor()
{
  if (freq < 6850000)
  {
    Even_Divisor = 126;
  }
  if ((freq >= 6850000) && (freq < 9500000))
  {
    Even_Divisor = 88;
  }
  if ((freq >= 9500000) && (freq < 13600000))
  {
    Even_Divisor = 64;
  }
  if ((freq >= 13600000) && (freq < 17500000))
  {
    Even_Divisor = 44;
  }
  if ((freq >= 17500000) && (freq < 25000000))
  {
    Even_Divisor = 34;
  }
  if ((freq >= 25000000) && (freq < 36000000))
  {
    Even_Divisor = 24;
  }
  if ((freq >= 36000000) && (freq < 45000000)) {
    Even_Divisor = 18;
  }
  if ((freq >= 45000000) && (freq < 60000000)) {
    Even_Divisor = 14;
  }
  if ((freq >= 60000000) && (freq < 80000000)) {
    Even_Divisor = 10;
  }
  if ((freq >= 80000000) && (freq < 100000000)) {
    Even_Divisor = 8;
  }
  if ((freq >= 100000000) && (freq < 146600000)) {
    Even_Divisor = 6;
  }
  if ((freq >= 150000000) && (freq < 220000000)) {
    Even_Divisor = 4;
  }
}


void SendFrequency()
{
  //freq = freq + 10;
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK0);
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK1);
  si5351.set_phase(SI5351_CLK0, 0);
  si5351.set_phase(SI5351_CLK1, Even_Divisor);
  if (Even_Divisor != oldEven_Divisor)
  {
    si5351.pll_reset(SI5351_PLLA);
    oldEven_Divisor = Even_Divisor;
  }
}


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

Transmit Software


// Libraries
#include <Wire.h>                          // I2C comms library
#include <si5351.h>                        // Si5351Jason library
#include <ILI9341_t3.h>
#include <font_Arial.h>                    // Arial font library
#include <Audio.h>                         // Teensy audio library
#include <Metro.h>                         // Metro library

// Define Constants and Variables
// Display
static const int TFT_DC = 20;
static const int TFT_CS = 21;
static const int TFT_RST = 255;             // 255 = unused, connect to 3.3V
static const int TFT_MOSI = 7;
static const int TFT_SCLK = 14;
static const int TFT_MISO = 12;
// Switches, relays
static const int TxRxRelay = 29;
static const int Band80mSwitch = 28;
static const int Band40mSwitch = 27;
static const int Band20mSwitch = 26;
static const int PTTSwitch = 25;
static const int ModeSwitch = 24;
// Audio panel gains
static const int Linein_Gain = 10;         // Range is 0-15. 0 = 3.12 Vp-p, 15 = 0.24 Vp-p. Default = 5
static const int Lineout_Gain = 20;        // Range is 13-31. 13 = 3.16 Vp-p, 31 = 1.16 Vp-p Original was 20
static const int Mic_Gain = 30;            // Range is 0-63dB. Original was 0
// Rotary Encoder
static const int EncoderPushButton = 39;
static const int rotBPin = 36;
static const int rotAPin = 35;
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
volatile int rotAcc = 0;
// Other
static const int NO_HILBERT_COEFFS = 100;         // Used to define the Hilbert transform filter arrays
static const long bandInit = 3690000;             // where to initially set the frequency
static const long band80mStart = 3500000;         // start of 80m
static const long band80mEnd = 3900000;           // end of 80m
static const long band40mStart = 7000000;         // start of 40m
static const long band40mEnd = 7300000;           // end of 40m
static const long band20mStart = 14000000;        // start of 20m
static const long band20mEnd = 14350000;          // end of 20m
volatile long oldfreq = 14123456;
volatile long old80mfreq = 3700000;
volatile long old40mfreq = 7100000;
volatile long old20mfreq = 14100000;
volatile long freq = bandInit ;
volatile long radix = 1000;                // how much to change the frequency. Pushing the rotary encoder switch will change this.
volatile long oldradix = 1000;
volatile int band = 80;                    // 80 = 80m, 40 = 40m, 20 = 20m
volatile int oldband = 0;
volatile int Even_Divisor = 0;
volatile int oldEven_Divisor = 0;
char PTT = 'R';                            // R = receive, T = transmit
volatile int oldPTT = 0;
float raw_sMeter[20] = {0};                // Raw received signal strength
volatile int sMeterBar = 0;                // Received signal strength
volatile int oldsMeterBar = 0;
static const int scale = 60;               // Scale factor for the S Meter
volatile int x = 0;
char mode = 'L';                           // L = LSB, U = USB, C = CQ, A = AM
char oldmode = 'L';
volatile int MHz10, MHz1, kHz100, kHz10, kHz1, Hz100, Hz10, Hz1 = 0;
volatile int oldMHz10, oldMHz1, oldkHz100, oldkHz10, oldkHz1, oldHz100, oldHz10, oldHz1 = 0;

// Iowa Hills Hilbert transform filter coefficients
const short Hilbert_Plus_45_Coeffs[NO_HILBERT_COEFFS] = {
  (short)(32768 * 483.9743406915770E-9),
  (short)(32768 * 1.866685817417670E-6),
  (short)(32768 * 4.392570072495770E-6),
  (short)(32768 * 8.820636712774380E-6),
  (short)(32768 * 0.000016184764415848),
  (short)(32768 * 0.000027249288101724),
  (short)(32768 * 0.000041920439805110),
  (short)(32768 * 0.000059016345361008),
  (short)(32768 * 0.000076756191268614),
  (short)(32768 * 0.000094055071776657),
  (short)(32768 * 0.000112261583662573),
  (short)(32768 * 0.000136518354793722),
  (short)(32768 * 0.000175696544399550),
  (short)(32768 * 0.000240067250940666),
  (short)(32768 * 0.000336609833081347),
  (short)(32768 * 0.000462990951098187),
  (short)(32768 * 0.000602421399161912),
  (short)(32768 * 0.000722301614841713),
  (short)(32768 * 0.000779305755125654),
  (short)(32768 * 0.000732056920489209),
  (short)(32768 * 0.000559948250896998),
  (short)(32768 * 0.000283585816755988),
  (short)(32768 * -0.000020215761694049),
  (short)(32768 * -0.000216819118897174),
  (short)(32768 * -0.000131758715732279),
  (short)(32768 * 0.000409948902196685),
  (short)(32768 * 0.001528130963104050),
  (short)(32768 * 0.003227564604759798),
  (short)(32768 * 0.005351504118228274),
  (short)(32768 * 0.007567804706902511),
  (short)(32768 * 0.009402820162611196),
  (short)(32768 * 0.010328512135999630),
  (short)(32768 * 0.009894246835039863),
  (short)(32768 * 0.007879714166593881),
  (short)(32768 * 0.004433421126740721),
  (short)(32768 * 0.000156366633965185),
  (short)(32768 * -0.003904389468521576),
  (short)(32768 * -0.006371988171406650),
  (short)(32768 * -0.005761887634323113),
  (short)(32768 * -0.000778938771957753),
  (short)(32768 * 0.009365085367419172),
  (short)(32768 * 0.024681757404317366),
  (short)(32768 * 0.044251382160327521),
  (short)(32768 * 0.066233642104189930),
  (short)(32768 * 0.088062621169129954),
  (short)(32768 * 0.106806616459214951),
  (short)(32768 * 0.119635362035632908),
  (short)(32768 * 0.124309482163432433),
  (short)(32768 * 0.119596382589365807),
  (short)(32768 * 0.105526834497225247),
  (short)(32768 * 0.083435851600156402),
  (short)(32768 * 0.055774084237545388),
  (short)(32768 * 0.025722508803868269),
  (short)(32768 * -0.003316774367731974),
  (short)(32768 * -0.028256325810852603),
  (short)(32768 * -0.046784860848984686),
  (short)(32768 * -0.057671422122216751),
  (short)(32768 * -0.060863961385426720),
  (short)(32768 * -0.057377469068775784),
  (short)(32768 * -0.049008885222883866),
  (short)(32768 * -0.037947605693328487),
  (short)(32768 * -0.026365178611104038),
  (short)(32768 * -0.016063741251826878),
  (short)(32768 * -0.008242320709669780),
  (short)(32768 * -0.003409329314875374),
  (short)(32768 * -0.001436295024424050),
  (short)(32768 * -0.001719754923178513),
  (short)(32768 * -0.003400970055132929),
  (short)(32768 * -0.005589187214751837),
  (short)(32768 * -0.007542651980327935),
  (short)(32768 * -0.008778747041127889),
  (short)(32768 * -0.009105231860961261),
  (short)(32768 * -0.008583286966676333),
  (short)(32768 * -0.007445876442758468),
  (short)(32768 * -0.005999873442098177),
  (short)(32768 * -0.004537732597630097),
  (short)(32768 * -0.003276341625911221),
  (short)(32768 * -0.002330120643710241),
  (short)(32768 * -0.001715559019593159),
  (short)(32768 * -0.001377498498508186),
  (short)(32768 * -0.001224659509681699),
  (short)(32768 * -0.001162953813794283),
  (short)(32768 * -0.001118850197066992),
  (short)(32768 * -0.001049829425495061),
  (short)(32768 * -0.000943240226180188),
  (short)(32768 * -0.000807608474652057),
  (short)(32768 * -0.000661296218869203),
  (short)(32768 * -0.000522622836450865),
  (short)(32768 * -0.000403818527830429),
  (short)(32768 * -0.000309261220275339),
  (short)(32768 * -0.000236981246010256),
  (short)(32768 * -0.000181726899678497),
  (short)(32768 * -0.000137960767232952),
  (short)(32768 * -0.000101749677323657),
  (short)(32768 * -0.000071268112804006),
  (short)(32768 * -0.000046246069278189),
  (short)(32768 * -0.000026984224473470),
  (short)(32768 * -0.000013519855860183),
  (short)(32768 * -5.268419079329310E-6),
  (short)(32768 * -1.152120275972750E-6)
};

// Iowa Hills Hilbert transform filter coefficients
const short Hilbert_Minus_45_Coeffs[NO_HILBERT_COEFFS] = {
  (short)(32768 * -1.152120275972720E-6),
  (short)(32768 * -5.268419079329100E-6),
  (short)(32768 * -0.000013519855860182),
  (short)(32768 * -0.000026984224473469),
  (short)(32768 * -0.000046246069278187),
  (short)(32768 * -0.000071268112804004),
  (short)(32768 * -0.000101749677323656),
  (short)(32768 * -0.000137960767232950),
  (short)(32768 * -0.000181726899678496),
  (short)(32768 * -0.000236981246010254),
  (short)(32768 * -0.000309261220275334),
  (short)(32768 * -0.000403818527830420),
  (short)(32768 * -0.000522622836450852),
  (short)(32768 * -0.000661296218869189),
  (short)(32768 * -0.000807608474652046),
  (short)(32768 * -0.000943240226180185),
  (short)(32768 * -0.001049829425495072),
  (short)(32768 * -0.001118850197067017),
  (short)(32768 * -0.001162953813794315),
  (short)(32768 * -0.001224659509681719),
  (short)(32768 * -0.001377498498508169),
  (short)(32768 * -0.001715559019593075),
  (short)(32768 * -0.002330120643710066),
  (short)(32768 * -0.003276341625910951),
  (short)(32768 * -0.004537732597629757),
  (short)(32768 * -0.005999873442097817),
  (short)(32768 * -0.007445876442758181),
  (short)(32768 * -0.008583286966676212),
  (short)(32768 * -0.009105231860961396),
  (short)(32768 * -0.008778747041128323),
  (short)(32768 * -0.007542651980328638),
  (short)(32768 * -0.005589187214752684),
  (short)(32768 * -0.003400970055133710),
  (short)(32768 * -0.001719754923178943),
  (short)(32768 * -0.001436295024423829),
  (short)(32768 * -0.003409329314874256),
  (short)(32768 * -0.008242320709667652),
  (short)(32768 * -0.016063741251823811),
  (short)(32768 * -0.026365178611100354),
  (short)(32768 * -0.037947605693324706),
  (short)(32768 * -0.049008885222880681),
  (short)(32768 * -0.057377469068773987),
  (short)(32768 * -0.060863961385426962),
  (short)(32768 * -0.057671422122219533),
  (short)(32768 * -0.046784860848990188),
  (short)(32768 * -0.028256325810860565),
  (short)(32768 * -0.003316774367741772),
  (short)(32768 * 0.025722508803857579),
  (short)(32768 * 0.055774084237534945),
  (short)(32768 * 0.083435851600147395),
  (short)(32768 * 0.105526834497218600),
  (short)(32768 * 0.119596382589362227),
  (short)(32768 * 0.124309482163432142),
  (short)(32768 * 0.119635362035635753),
  (short)(32768 * 0.106806616459220294),
  (short)(32768 * 0.088062621169136893),
  (short)(32768 * 0.066233642104197507),
  (short)(32768 * 0.044251382160334737),
  (short)(32768 * 0.024681757404323448),
  (short)(32768 * 0.009365085367423625),
  (short)(32768 * -0.000778938771955104),
  (short)(32768 * -0.005761887634322139),
  (short)(32768 * -0.006371988171407000),
  (short)(32768 * -0.003904389468522771),
  (short)(32768 * 0.000156366633963640),
  (short)(32768 * 0.004433421126739255),
  (short)(32768 * 0.007879714166592786),
  (short)(32768 * 0.009894246835039278),
  (short)(32768 * 0.010328512135999562),
  (short)(32768 * 0.009402820162611529),
  (short)(32768 * 0.007567804706903086),
  (short)(32768 * 0.005351504118228925),
  (short)(32768 * 0.003227564604760380),
  (short)(32768 * 0.001528130963104480),
  (short)(32768 * 0.000409948902196933),
  (short)(32768 * -0.000131758715732195),
  (short)(32768 * -0.000216819118897210),
  (short)(32768 * -0.000020215761694147),
  (short)(32768 * 0.000283585816755878),
  (short)(32768 * 0.000559948250896909),
  (short)(32768 * 0.000732056920489157),
  (short)(32768 * 0.000779305755125639),
  (short)(32768 * 0.000722301614841724),
  (short)(32768 * 0.000602421399161936),
  (short)(32768 * 0.000462990951098213),
  (short)(32768 * 0.000336609833081367),
  (short)(32768 * 0.000240067250940678),
  (short)(32768 * 0.000175696544399555),
  (short)(32768 * 0.000136518354793723),
  (short)(32768 * 0.000112261583662573),
  (short)(32768 * 0.000094055071776658),
  (short)(32768 * 0.000076756191268616),
  (short)(32768 * 0.000059016345361010),
  (short)(32768 * 0.000041920439805112),
  (short)(32768 * 0.000027249288101726),
  (short)(32768 * 0.000016184764415849),
  (short)(32768 * 8.820636712774440E-6),
  (short)(32768 * 4.392570072495580E-6),
  (short)(32768 * 1.866685817417500E-6),
  (short)(32768 * 483.9743406915230E-9)
};


// Instantiate the Objects
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
Si5351 si5351;                                                                // Name for the Si5351 DDS
AudioControlSGTL5000    audioShield;                                          // Name for the Teensy audio CODEC on the audio shield
Metro sMeterMetro = Metro(150);                                               // Name for the sMeter update timer

// Audio shield objects
AudioInputI2S           audioInput;                                           // Name for the input to the audio shield (either line-in or mic)
AudioOutputI2S          audioOutput;                                          // Name for the output of the audio shield (either headphones or line-out)
// Receiver
AudioFilterFIR          RX_Hilbert_Plus_45;                                   // Name for the RX +45 Hilbert transform
AudioFilterFIR          RX_Hilbert_Minus_45;                                  // Name for the RX +45 Hilbert transform
AudioMixer4             RX_Summer;                                            // Name for the RX summer
AudioAnalyzePeak        S_Meter;                                              // Received signal strength
// Transmitter
AudioFilterFIR          TX_Hilbert_Plus_45;                                   // Name for the TX +45 Hilbert transform
AudioFilterFIR          TX_Hilbert_Minus_45;                                  // Name for the TX +45 Hilbert transform
AudioMixer4             TX_I_Sideband_Switch;                                 // Name for the sideband switching summer for the I channel

// Audio shield connections
// Receiver
AudioConnection         patchCord5(audioInput, 0, RX_Hilbert_Plus_45, 0);               // Left channel to Hilbert transform +45
AudioConnection         patchCord10(audioInput, 1, RX_Hilbert_Minus_45, 0);             // Right channel to Hilbert transform -45
AudioConnection         patchCord15(RX_Hilbert_Plus_45, 0, RX_Summer, 0);               // Hilbert transform +45 to receiver summer
AudioConnection         patchCord20(RX_Hilbert_Minus_45, 0, RX_Summer, 1);              // Hilbert transform -45 to receiver summer
AudioConnection         patchCord25(RX_Summer, 0, S_Meter, 0);
AudioConnection         patchCord30(RX_Summer, 0, audioOutput, 0);
AudioConnection         patchCord35(RX_Summer, 0, audioOutput, 1);
// Transmitter
AudioConnection         patchCord40(audioInput, 0, TX_Hilbert_Plus_45, 0);              // Mic audio to Hilbert transform +45
AudioConnection         patchCord45(audioInput, 0, TX_Hilbert_Minus_45, 0);             // Mic audio to Hilbert transform -45
AudioConnection         patchCord50(TX_Hilbert_Plus_45, 0, TX_I_Sideband_Switch, 0);    // In phase channel to sideband switch
AudioConnection         patchCord55(TX_I_Sideband_Switch, 0, audioOutput, 0);           // I to left channel out
AudioConnection         patchCord60(TX_Hilbert_Minus_45, 0, audioOutput, 1);            // Q to right channel out


void setup()
{
  // Setup screen
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);

  // Setup input/output
  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(EncoderPushButton, INPUT);
  pinMode(ModeSwitch, INPUT);
  pinMode(Band80mSwitch, INPUT);
  pinMode(Band40mSwitch, INPUT);
  pinMode(Band20mSwitch, INPUT);
  pinMode(PTTSwitch, INPUT);
  pinMode(TxRxRelay, OUTPUT);

  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(EncoderPushButton, HIGH);
  digitalWrite(ModeSwitch, HIGH);
  digitalWrite(Band80mSwitch, HIGH);
  digitalWrite(Band40mSwitch, HIGH);
  digitalWrite(Band20mSwitch, HIGH);
  digitalWrite(PTTSwitch, HIGH);
  digitalWrite(TxRxRelay, LOW);

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

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);             // 25MHz crystal = 0, 27MHz crystal = 27000000
  si5351.set_correction(62799, SI5351_PLL_INPUT_XO);      // Set to specific Si5351 calibration number
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_6MA);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA);
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK0);
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK1);
  si5351.set_phase(SI5351_CLK0, 0);
  si5351.set_phase(SI5351_CLK1, Even_Divisor);
  si5351.pll_reset(SI5351_PLLA);

  // Setup the audio shield
  AudioNoInterrupts();
  AudioMemory(12);
  audioShield.enable();
  AudioInterrupts();

  // Setup initial transceiver state
  Receive();
  UpdateMode();
  UpdateDisplay();
  UpdateFreqDisplay();
}


void loop()
{
  // Check to see if the freq has changed
  if (freq != oldfreq)                          // Check to see if the frequency has changed. If so, update everything.
  {
    EvenDivisor();
    UpdateFreqDisplay();
    SendFrequency();
    oldfreq = freq;
  }

  // Check to see if the radix has changed
  if (radix != oldradix)
  {
    UpdateFreqDisplay();
    oldradix = radix;
  }

  // Check the mode switch
  if (digitalRead(ModeSwitch) == LOW)
    mode = 'L';
  else
    mode = 'U';

  // Check to see if the mode has changed
  if (mode != oldmode)                       // Only update the display on mode change
  {
    UpdateMode();
    UpdateDisplay();
    oldmode = mode;
  }

  // Check the band switch
  if (digitalRead(Band80mSwitch) == LOW)
    band = 80;                               // 80m band

  if (digitalRead(Band40mSwitch) == LOW)
    band = 40;                               // 40m band

  if (digitalRead(Band20mSwitch) == LOW)
    band = 20;                               // 20m band

  // Check to see if the band has changed
  if (band != oldband)                       // Only update the display on band change
  {
    UpdateBand();
    UpdateFreqDisplay();
    oldband = band;
  }

  // Check the PTT switch
  if (digitalRead(PTTSwitch) == LOW)
    PTT = 'T';
  else
    PTT = 'R';

  // Check to see if PTT has changed
  if (PTT != oldPTT)
  {
    if (PTT == 'T')
      Transmit();
    if (PTT == 'R')
      Receive();
    UpdateDisplay();
    oldPTT = PTT;
  }

  // Read raw signal strength (running average)
  if (S_Meter.available())
  {
    raw_sMeter[x] = S_Meter.read() * scale;
    x++;
    if (x == 20)
    {
      x = 0;
      float av_raw_sMeter = 0;
      for (int i = 0; i <= 19; i++)
        av_raw_sMeter = av_raw_sMeter + raw_sMeter[i];
      sMeterBar = (av_raw_sMeter / 20) * 250;
    }
  }

  if (sMeterMetro.check() == 1)         // Update sMeter every 250mS
  {
    UpdateSMeter();
    oldsMeterBar = sMeterBar;
  }
}


void Transmit()
{
  AudioNoInterrupts();
  // Turn off the receiver
  audioShield.muteHeadphone();
  RX_Hilbert_Plus_45.end();
  RX_Hilbert_Minus_45.end();

  // Turn on the trasmitter
  audioShield.inputSelect(AUDIO_INPUT_MIC);
  audioShield.micGain(Mic_Gain);
  audioShield.unmuteLineout();                                          // Output to the TX
  audioShield.lineOutLevel(Lineout_Gain);
  TX_Hilbert_Plus_45.begin(Hilbert_Plus_45_Coeffs, NO_HILBERT_COEFFS);
  TX_Hilbert_Minus_45.begin(Hilbert_Minus_45_Coeffs, NO_HILBERT_COEFFS);
  AudioInterrupts();
  digitalWrite(TxRxRelay, HIGH);                                        // Key the transmitter
}


void Receive()
{
  digitalWrite(TxRxRelay, LOW);                                         // Unkey the transmitter
  AudioNoInterrupts();
  // Turn off the transmitter
  audioShield.muteLineout();
  TX_Hilbert_Plus_45.end();
  TX_Hilbert_Minus_45.end();

  // Turn on the receiver
  audioShield.inputSelect(AUDIO_INPUT_LINEIN);
  audioShield.lineInLevel(Linein_Gain);
  audioShield.unmuteHeadphone();                                        // Output to the audio amplifier
  audioShield.volume(0.7);
  RX_Hilbert_Plus_45.begin(Hilbert_Plus_45_Coeffs, NO_HILBERT_COEFFS);
  RX_Hilbert_Minus_45.begin(Hilbert_Minus_45_Coeffs, NO_HILBERT_COEFFS);
  AudioInterrupts();
}


void UpdateMode()
{
  if (mode == 'L')                                // LSB
  {
    RX_Summer.gain(0, 1);
    RX_Summer.gain(1, -1);                        // Invert Q channel
    RX_Summer.gain(2, 0);
    RX_Summer.gain(3, 0);

    TX_I_Sideband_Switch.gain(0, 1);              // No inversion of I channel
    TX_I_Sideband_Switch.gain(1, 0);
    TX_I_Sideband_Switch.gain(2, 0);
    TX_I_Sideband_Switch.gain(3, 0);
  }
  if (mode == 'U')                                // USB
  {
    RX_Summer.gain(0, 1);
    RX_Summer.gain(1, 1);                         // No inversion of Q channel
    RX_Summer.gain(2, 0);
    RX_Summer.gain(3, 0);

    TX_I_Sideband_Switch.gain(0, -1);             // Invert I channel
    TX_I_Sideband_Switch.gain(1, 0);
    TX_I_Sideband_Switch.gain(2, 0);
    TX_I_Sideband_Switch.gain(3, 0);
  }
}


void UpdateBand()
{
  if ((band == 80) && (oldband == 40))
  {
    old40mfreq = freq;
    freq = old80mfreq;
  }

  if ((band == 80) && (oldband == 20))
  {
    old20mfreq = freq;
    freq = old80mfreq;
  }

  if ((band == 40) && (oldband == 80))
  {
    old80mfreq = freq;
    freq = old40mfreq;
  }

  if ((band == 40) && (oldband == 20))
  {
    old20mfreq = freq;
    freq = old40mfreq;
  }

  if ((band == 20) && (oldband == 80))
  {
    old80mfreq = freq;
    freq = old20mfreq;
  }

  if ((band == 20) && (oldband == 40))
  {
    old40mfreq = freq;
    freq = old20mfreq;
  }
}


// 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(EncoderPushButton) == LOW)
          {
            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 = 100000;
          }
          else
          {
            freq = (freq + radix);
            if (band == 80)
              if (freq > band80mEnd)
                freq = band80mEnd;
            if (band == 40)
              if (freq > band40mEnd)
                freq = band40mEnd;
            if (band == 20)
              if (freq > band20mEnd)
                freq = band20mEnd;
          }
          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(EncoderPushButton) == LOW)
          {
            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
              radix = 1;
          }
          else
          {
            freq = (freq - radix);
            if (band == 80)
              if (freq < band80mStart)
                freq = band80mStart;
            if (band == 40)
              if (freq < band40mStart)
                freq = band40mStart;
            if (band == 20)
              if (freq < band20mStart)
                freq = band20mStart;
          }
          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()
{
  tft.setFont(Arial_16);
  // RX, TX
  if (PTT == 'R')
  {
    tft.drawRoundRect(0, 0, 60, 40, 5, tft.color565(0, 255, 0));
    tft.setCursor(12, 12);
    tft.setTextColor(tft.color565(0, 255, 0));    // Green
    tft.println("RX");

    tft.drawRoundRect(70, 0, 60, 40, 5, tft.color565(160, 160, 160));
    tft.setCursor(83, 12);
    tft.setTextColor(tft.color565(160, 160, 160));
    tft.println("TX");
  }
  if (PTT == 'T')
  {
    tft.drawRoundRect(0, 0, 60, 40, 5, tft.color565(160, 160, 160));
    tft.setCursor(12, 12);
    tft.setTextColor(tft.color565(160, 160, 160));
    tft.println("RX");

    tft.drawRoundRect(70, 0, 60, 40, 5, tft.color565(255, 0, 0));
    tft.setCursor(83, 12);
    tft.setTextColor(tft.color565(255, 0, 0));    // Red
    tft.println("TX");
  }

  // Tune
  tft.drawRoundRect(140, 0, 80, 40, 5, tft.color565(160, 160, 160));
  tft.setCursor(149, 12);
  tft.setTextColor(tft.color565(160, 160, 160));
  tft.println("TUNE");

  // Mode
  if (mode == 'U')
  {
    tft.drawRoundRect(0, 50, 60, 40, 5, tft.color565(160, 160, 160));
    tft.setCursor(9, 62);
    tft.setTextColor(tft.color565(160, 160, 160));
    tft.println("LSB");

    tft.drawRoundRect(70, 50, 60, 40, 5, tft.color565(255, 255, 0));    // Yellow
    tft.setCursor(79, 62);
    tft.setTextColor(tft.color565(255, 255, 0));
    tft.println("USB");

    tft.drawRoundRect(140, 50, 60, 40, 5, tft.color565(160, 160, 160));
    tft.setCursor(150, 62);
    tft.setTextColor(tft.color565(160, 160, 160));
    tft.println("CW");
  }
  if (mode == 'L')
  {
    tft.drawRoundRect(0, 50, 60, 40, 5, tft.color565(255, 255, 0));    // Yellow
    tft.setCursor(9, 62);
    tft.setTextColor(tft.color565(255, 255, 0));
    tft.println("LSB");

    tft.drawRoundRect(70, 50, 60, 40, 5, tft.color565(160, 160, 160));
    tft.setCursor(79, 62);
    tft.setTextColor(tft.color565(160, 160, 160));
    tft.println("USB");

    tft.drawRoundRect(140, 50, 60, 40, 5, tft.color565(160, 160, 160));
    tft.setCursor(150, 62);
    tft.setTextColor(tft.color565(160, 160, 160));
    tft.println("CW");
  }
  if (mode == 'C')
  {
    tft.drawRoundRect(0, 50, 60, 40, 5, tft.color565(160, 160, 160));
    tft.setCursor(9, 62);
    tft.setTextColor(tft.color565(160, 160, 160));
    tft.println("LSB");

    tft.drawRoundRect(70, 50, 60, 40, 5, tft.color565(160, 160, 160));
    tft.setCursor(79, 62);
    tft.setTextColor(tft.color565(160, 160, 160));
    tft.println("USB");

    tft.drawRoundRect(140, 50, 60, 40, 5, tft.color565(255, 255, 0));    // Yellow
    tft.setCursor(150, 62);
    tft.setTextColor(tft.color565(255, 255, 0));
    tft.println("CW");
  }

  // S Meter
  tft.setFont(Arial_8);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(6, 230);
  tft.println("S1");
  tft.setCursor(38, 230);
  tft.println("3");
  tft.setCursor(68, 230);
  tft.println("5");
  tft.setCursor(98, 230);
  tft.println("7");
  tft.setCursor(128, 230);
  tft.println("9");
  tft.setTextColor(ILI9341_RED);
  tft.setCursor(150, 230);
  tft.println("+20");
  tft.setCursor(180, 230);
  tft.println("+40");
  tft.setCursor(210, 230);
  tft.println("+60");

  tft.drawLine(10, 220, 10, 215, ILI9341_WHITE);
  tft.drawLine(25, 220, 25, 218, ILI9341_WHITE);
  tft.drawLine(40, 220, 40, 215, ILI9341_WHITE);
  tft.drawLine(55, 220, 55, 218, ILI9341_WHITE);
  tft.drawLine(70, 220, 70, 215, ILI9341_WHITE);
  tft.drawLine(85, 220, 85, 218, ILI9341_WHITE);
  tft.drawLine(100, 220, 100, 215, ILI9341_WHITE);
  tft.drawLine(115, 220, 115, 218, ILI9341_WHITE);
  tft.drawLine(130, 220, 130, 215, ILI9341_WHITE);
  tft.drawLine(145, 220, 145, 218, ILI9341_WHITE);
  tft.drawLine(160, 220, 160, 215, ILI9341_RED);
  tft.drawLine(175, 220, 175, 218, ILI9341_RED);
  tft.drawLine(190, 220, 190, 215, ILI9341_RED);
  tft.drawLine(205, 220, 205, 218, ILI9341_RED);
  tft.drawLine(220, 220, 220, 215, ILI9341_RED);
  tft.drawLine(235, 220, 235, 218, ILI9341_RED);

  tft.drawLine(10, 221, 159, 221, ILI9341_WHITE);
  tft.drawLine(160, 221, 235, 221, ILI9341_RED);
}


void UpdateFreqDisplay()
{
  oldMHz10 = (oldfreq % 100000000) / 10000000;
  oldMHz1 = (oldfreq % 10000000) / 1000000;
  oldkHz100 = (oldfreq % 1000000) / 100000;
  oldkHz10 = (oldfreq % 100000) / 10000;
  oldkHz1 = (oldfreq % 10000) / 1000;
  oldHz100 = (oldfreq % 1000) / 100;
  oldHz10 = (oldfreq % 100) / 10;
  oldHz1 = (oldfreq % 10) / 1;

  MHz10 = (freq % 100000000) / 10000000;
  MHz1 = (freq % 10000000) / 1000000;
  kHz100 = (freq % 1000000) / 100000;
  kHz10 = (freq % 100000) / 10000;
  kHz1 = (freq % 10000) / 1000;
  Hz100 = (freq % 1000) / 100;
  Hz10 = (freq % 100) / 10;
  Hz1 = (freq % 10) / 1;

  tft.setFont(Arial_40);

  //10MHz
  if (oldfreq > 10000000)
  {
    if (MHz10 != oldMHz10)
    {
      tft.setCursor(10, 115);
      tft.setTextColor(ILI9341_BLACK);
      tft.println(oldMHz10);
    }
  }
  if (freq > 10000000)
  {
    tft.setCursor(10, 115);
    tft.setTextColor(ILI9341_WHITE);
    tft.println(MHz10);
  }

  //1MHz
  if (MHz1 != oldMHz1)
  {
    tft.setCursor(40, 115);
    tft.setTextColor(ILI9341_BLACK);
    tft.println(oldMHz1);
    tft.setCursor(40, 115);
    tft.setTextColor(ILI9341_WHITE);
    tft.println(MHz1);
  }

  // .
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(70, 115);
  tft.println(".");

  //100kHz
  if (kHz100 != oldkHz100)
  {
    tft.setCursor(85, 115);
    tft.setTextColor(ILI9341_BLACK);
    tft.println(oldkHz100);
    tft.setCursor(85, 115);
    tft.setTextColor(ILI9341_WHITE);
    tft.println(kHz100);
  }

  //10kHz
  if (kHz10 != oldkHz10)
  {
    tft.setCursor(115, 115);
    tft.setTextColor(ILI9341_BLACK);
    tft.println(oldkHz10);
    tft.setCursor(115, 115);
    tft.setTextColor(ILI9341_WHITE);
    tft.println(kHz10);
  }

  //1kHz
  if (kHz1 != oldkHz1)
  {
    tft.setCursor(145, 115);
    tft.setTextColor(ILI9341_BLACK);
    tft.println(oldkHz1);
    tft.setCursor(145, 115);
    tft.setTextColor(ILI9341_WHITE);
    tft.println(kHz1);
  }

  // .
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(175, 115);
  tft.println(".");

  //100Hz
  if (Hz100 != oldHz100)
  {
    tft.setCursor(190, 115);
    tft.setTextColor(ILI9341_BLACK);
    tft.println(oldHz100);
    tft.setCursor(190, 115);
    tft.setTextColor(ILI9341_WHITE);
    tft.println(Hz100);
  }

  //10Hz
  if (Hz10 != oldHz10)
  {
    tft.setCursor(220, 115);
    tft.setTextColor(ILI9341_BLACK);
    tft.println(oldHz10);
    tft.setCursor(220, 115);
    tft.setTextColor(ILI9341_WHITE);
    tft.println(Hz10);
  }

  //1Hz
  if (Hz1 != oldHz1)
  {
    tft.setCursor(250, 115);
    tft.setTextColor(ILI9341_BLACK);
    tft.println(oldHz1);
    tft.setCursor(250, 115);
    tft.setTextColor(ILI9341_WHITE);
    tft.println(Hz1);
  }

  // Radix line
  if (oldradix == 1)
    tft.fillRect(258, 160, 15, 4, ILI9341_BLACK);
  if (oldradix == 10)
    tft.fillRect(228, 160, 15, 4, ILI9341_BLACK);
  if (oldradix == 100)
    tft.fillRect(198, 160, 15, 4, ILI9341_BLACK);
  if (oldradix == 1000)
    tft.fillRect(153, 160, 15, 4, ILI9341_BLACK);
  if (oldradix == 10000)
    tft.fillRect(123, 160, 15, 4, ILI9341_BLACK);
  if (oldradix == 100000)
    tft.fillRect(93, 160, 15, 4, ILI9341_BLACK);

  if (radix == 1)
    tft.fillRect(258, 160, 15, 4, ILI9341_WHITE);
  if (radix == 10)
    tft.fillRect(228, 160, 15, 4, ILI9341_WHITE);
  if (radix == 100)
    tft.fillRect(198, 160, 15, 4, ILI9341_WHITE);
  if (radix == 1000)
    tft.fillRect(153, 160, 15, 4, ILI9341_WHITE);
  if (radix == 10000)
    tft.fillRect(123, 160, 15, 4, ILI9341_WHITE);
  if (radix == 100000)
    tft.fillRect(93, 160, 15, 4, ILI9341_WHITE);

  oldMHz10 = MHz10;
  oldMHz1 = MHz1;
  oldkHz100 = kHz100;
  oldkHz10 = kHz10;
  oldkHz1 = kHz1;
  oldHz100 = Hz100;
  oldHz10 = Hz10;
  oldHz1 = Hz1;
}

void UpdateSMeter()
{
  tft.fillRect(10, 205, oldsMeterBar, 4, ILI9341_BLACK);
  tft.fillRect(10, 205, sMeterBar, 4, tft.color565(0, 255, 255));
}


void EvenDivisor()
{
  if (freq < 6850000)
    Even_Divisor = 126;

  if ((freq >= 6850000) && (freq < 9500000))
    Even_Divisor = 88;

  if ((freq >= 9500000) && (freq < 13600000))
    Even_Divisor = 64;

  if ((freq >= 13600000) && (freq < 17500000))
    Even_Divisor = 44;

  if ((freq >= 17500000) && (freq < 25000000))
    Even_Divisor = 34;

  if ((freq >= 25000000) && (freq < 36000000))
    Even_Divisor = 24;

  if ((freq >= 36000000) && (freq < 45000000))
    Even_Divisor = 18;

  if ((freq >= 45000000) && (freq < 60000000))
    Even_Divisor = 14;

  if ((freq >= 60000000) && (freq < 80000000))
    Even_Divisor = 10;

  if ((freq >= 80000000) && (freq < 100000000))
    Even_Divisor = 8;

  if ((freq >= 100000000) && (freq < 146600000))
    Even_Divisor = 6;

  if ((freq >= 150000000) && (freq < 220000000))
    Even_Divisor = 4;
}


void SendFrequency()
{
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK0);
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK1);
  si5351.set_phase(SI5351_CLK0, 0);
  si5351.set_phase(SI5351_CLK1, Even_Divisor);
  if (Even_Divisor != oldEven_Divisor)
  {
    si5351.pll_reset(SI5351_PLLA);
    oldEven_Divisor = Even_Divisor;
  }
}


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