Friday 3 August 2018

Homebrew 80/40m SSB/CW Base Rig

Audio Amplifier


Another audio amplifier option.



Double Balanced Mixers





Plessey Bidirectional IF Amplifiers


Mic Amp



RF Power Amp



Low Pass Filters




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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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




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


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


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


void UpdateRot()
{
  switch (rotState)
  {

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

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

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

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

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

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

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


void UpdateDisplay()
{
  lcd.setCursor(0, 0);
  lcd.print("        ");
  lcd.setCursor(0, 0);
  lcd.print(freq);
  lcd.setCursor(10, 0);
  lcd.print("ZL2CTM");

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

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

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

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


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

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







3 comments:

  1. Hi Charlie. I have been watching your videos with interest. By the time you read this, your 80-40m rig would be complete. I note you used the Plessy citcuit as bi- lateral IF Amps at 6v. Probably too late now, but at the time, you could have tried the G4GXO bilateral IF Amp circuit. This makes the gain of both stages under AGC and ALC control. I have a design on the drawing board a 80-40m rig myself using a surplus 11.2735MHz Crystal Filter from an old Kraco SSB Deluxe CB rig. The band width is a compromise between SSB and AM, for the same filter is used on both modes. I'll have to put it in a test gig to measure it's perammeters. I'm very interested in the Dual Band VFO used in your project. The code would be interesting to have, but I have no experience in programing. I'll have to find someone to help me make code changes to suit my needs. VFO-A and VFO-B functions would be nice, thinking of using High side injection. The disign is Bringing Valve Tech In To The 21st Century using less than 10 Valves. A challanging project. Have to get the Digital VFO built and tested first. 73s Darryl VK5JDS.

    ReplyDelete
    Replies
    1. Well i don't know bilateral IF Amp circuit it seem a bit of an over kill.
      the Plessay one uses a 2 pole change over relay Why add the complication of an extra IF Amp circuit.
      the G4GXO bilateral IF Amp circuit. uses 2 coils,
      switching diodes and more bit's another complication.
      why not use the switching diodes to do what the relay would do not add the coils. extra bits causes extra noise in the IF I think.
      I wish one could do away with them coils seen in the circuits...Could we
      circuit configuration. charlie morrison circuits build (out of the junk box), may look good but if you are new to Ham radio there are a lot of components types to buy, he uses IC, why has he not used and IC for the audio Amp.
      but whatever tickle your fancy i suppose.


      the G4GXO bilateral IF Amp circuit. uses 2 coils,
      switching diodes and more bit's another complication.
      why not use the switching diodes to do what the relay would do not add the coils. extra bits causes extra noise in the IF I think.
      If one wants to match 50R input, one could use a common base transistor circuit. charlie morrison circuits buikd out of the junk box, may look good but if you are new to Ham radio there are a lot of components types to buy
      but what ever tickle your fancy i supose.

      Delete
  2. Hi Darryl. Sounds like you have a fun project. I've updated the blog to include the code. You should be easy to see where the changes need to be made to suit your filter. I'm going to make a start today on the base 80m/40m CW rig. I'm looking forward to that.

    Charlie ZL2CTM

    ReplyDelete

Note: only a member of this blog may post a comment.