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


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