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


Initial Class E RF Amplifier Experiment (see YouTube video for explanation)




Final Class E Amplifier












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
}

5 comments:

  1. Hi Charlie,

    After discussing with you on youtube about your latest class E amp, I got inspired to try to get mine working. Changed my driver section to a discrete bjt totem pole and got it working, though still not to the results LTSpice predicted. I wrote it up here: http://kc9dlm.blogspot.com/2020/04/a-little-amplifier-success.html

    Sorry the notes are not up to the standards you set :)

    Cheers!
    Ben, KC9DLM

    ReplyDelete
    Replies
    1. Thanks Ben. I'll also take a look at a discrete bjt totem pole. I'll use a 3904/3906 combination.

      Delete
  2. The more I read, the more I am convinced I don't know anything about transmitters, even after 4 different IRF510 schematics I've tried... Ed KC8SBV

    ReplyDelete
    Replies
    1. I know the feeling and wallet going thin after 3 burned ldmos blf188, but I finaly got it. I guess knowledges learned during lifetime are most valuable, not the objects itself. Keep it up and good luck in future projects.

      Delete
  3. I will say I found this one hard, especially as i am not a RF design engineer. I'm tempted to build another one to see if I can replicate it.

    ReplyDelete

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