Thursday, 14 January 2021

Simple SSB Transceiver

 Please see the YouTube channel for details: https://www.youtube.com/channel/UCSNPW3_gzuMJcX_ErBZTv2g




AF Amp






IF Amps










Mixers







BPF




Mic Amp





AGC/Limiter


Relays




RF PA Experiments

1st iteration





2nd iteration






****************************************************************
Final Arduino code

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

static const long bandStart = 3500000;          // start of VFO range
static const long bandEnd =   3900000;          // end of VFO range
static const long bandInit =  3690000;          // where to initially set the frequency
volatile long freq = 3690000;                   // the current freq
volatile long oldfreq = 0;                      // the previous freq
volatile long rx_LSB_BFO_freq = 8997200;           // High side injection thus SB inversion.
volatile long rx_USB_BFO_freq = 8998700;           // High side injection thus SB inversion.
volatile long tx_LSB_BFO_freq = 8996300;           // High side injection thus SB inversion.
volatile long tx_USB_BFO_freq = 8999800;           // High side injection thus SB inversion.
volatile long radix = 1000;                     // How much to change the frequency by, clicking the Up Down switches
volatile long oldradix = 0;                     // the previous radix
volatile int mode = 0;                          // the current mode (0=LSB, 1=USB)
volatile int oldmode = 0;                       // the previous mode (0=LSB, 1=USB)
volatile int TX = 0;                            // 0=RX, 1=TX
volatile int oldTX = 1;                         // the old TX
unsigned int encoderA, encoderB, encoderC = 1;  // rotary encoder variables

// Rotary encoder pins and other inputs
static const int rotAPin = 2;
static const int rotBPin = 3;
static const int radixPin = 4;
static const int modePin = 5;
static const int PTTPin = 6;

// Instantiate the Objects
LiquidCrystal_I2C lcd(0x3F, 16, 2);              // 3F the address of the LCD
Si5351 si5351;


void setup()
{
  // Set up I/O pins
  pinMode(rotAPin, INPUT);
  digitalWrite(rotAPin, HIGH);                    // internal pull-up enabled
  pinMode(rotBPin, INPUT);
  digitalWrite(rotBPin, HIGH);                    // internal pull-up enabled
  pinMode(radixPin, INPUT);
  digitalWrite(radixPin, HIGH);                   // internal pull-up enabled
  pinMode(modePin, INPUT);
  digitalWrite(modePin, HIGH);                    // internal pull-up enabled
  pinMode(PTTPin, INPUT);
  digitalWrite(PTTPin, LOW);                      // internal pull-up disabled

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

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 62100);         // 62100 is the specific calibration factor for this Si5351 board
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_2MA);   // 2 mA for HB mixers
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_2MA);   // 2 mA for HB mixers
}

void loop()
{
  CheckEncoder();
  CheckRadixSwitch();
  CheckModeSwitch();
  CheckPTTPin();
}

void CheckEncoder()
{
  byte encoderA = digitalRead(rotAPin);
  byte encoderB = digitalRead(rotBPin);

  if ((encoderA == HIGH) && (encoderC == LOW))
  {
    if (encoderB == HIGH)
      // Decrease frequency
      freq = constrain(freq - radix, bandStart, bandEnd);
    else
      // Increase frequency
      freq = constrain(freq + radix, bandStart, bandEnd);
  }
  encoderC = encoderA;

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

void CheckRadixSwitch()
{
  if (digitalRead(radixPin) == 0)
  {
    radix = radix / 10;
    if (radix < 1)
      radix = 1000;
    delay(200);
  }

  if (radix != oldradix)
  {
    UpdateRadixDisplay();
    oldradix = radix;
  }
}

void CheckModeSwitch()
{
  if (digitalRead(modePin) == 0)
    mode = 0;                                 // 0=LSB
  if (digitalRead(modePin) == 1)
    mode = 1;                                 // 1=USB

  if (mode != oldmode)
  {
    UpdateDisplay();
    SendFrequency();
    oldmode = mode;
  }
}

void CheckPTTPin()
{
  TX = digitalRead(PTTPin);
  if (TX != oldTX)
  {
    UpdateDisplay();
    SendFrequency();
    oldTX = TX;
  }
}

void UpdateDisplay()
{
  // freq
  lcd.setCursor(0, 0);
  lcd.print(freq);
  // mode
  lcd.setCursor(0, 1);
  if (mode == 0)
    lcd.print("LSB");
  else
    lcd.print("USB");
  lcd.setCursor(10, 1);
  // PTT
  lcd.setCursor(4, 1);
  if (TX == 1)
    lcd.print("TX");
  if (TX == 0)
    lcd.print("RX");
  // callsign
  lcd.setCursor(10, 1);
  lcd.print("ZL2CTM");
}

void UpdateRadixDisplay()
{
  // radix
  if (radix == 1000)
  {
    lcd.setCursor(9, 0);
    lcd.print("      ");
    lcd.setCursor(9, 0);
    lcd.print(radix);
    lcd.setCursor(14, 0);
    lcd.print("Hz");
  }
  if (radix == 100)
  {
    lcd.setCursor(9, 0);
    lcd.print("      ");
    lcd.setCursor(10, 0);
    lcd.print(radix);
    lcd.setCursor(14, 0);
    lcd.print("Hz");
  }
  if (radix == 10)
  {
    lcd.setCursor(9, 0);
    lcd.print("      ");
    lcd.setCursor(11, 0);
    lcd.print(radix);
    lcd.setCursor(14, 0);
    lcd.print("Hz");
  }
  if (radix == 1)
  {
    lcd.setCursor(9, 0);
    lcd.print("      ");
    lcd.setCursor(12, 0);
    lcd.print(radix);
    lcd.setCursor(14, 0);
    lcd.print("Hz");
  }
}

void SendFrequency()
{
  if (mode == 0)                                                          // LSB
  {
    if (TX == 1)                                                          // Transmit
    {
      si5351.set_freq(((tx_LSB_BFO_freq + freq) * 100ULL), SI5351_CLK2);     // VFO
      si5351.set_freq((tx_LSB_BFO_freq * 100ULL), SI5351_CLK0);              // BFO
    }
    else                                                                  // Receive
    {
      si5351.set_freq(((rx_LSB_BFO_freq + freq) * 100ULL), SI5351_CLK0);     // VFO
      si5351.set_freq((rx_LSB_BFO_freq * 100ULL), SI5351_CLK2);              // BFO
    }
  }

  if (mode == 1)                                                          // USB
  {
    if (TX == 1)                                                          // Transmit
    {
      si5351.set_freq(((tx_USB_BFO_freq + freq) * 100ULL), SI5351_CLK2);     // VFO
      si5351.set_freq((tx_USB_BFO_freq * 100ULL), SI5351_CLK0);              // BFO
    }
    else                                                                  // Receive
    {
      si5351.set_freq(((rx_USB_BFO_freq + freq) * 100ULL), SI5351_CLK0);     // VFO
      si5351.set_freq((rx_USB_BFO_freq * 100ULL), SI5351_CLK2);              // BFO
    }
  }
}

22 comments:

  1. Hi Charlie, just building the si5351 and nano on a board. Your timing with this vfo/bfo is perfect timing.
    I will take a look at your coding as thats about where I am up to.
    Cheers Colin vk2jcc




    ReplyDelete
  2. That's great Colin. Check out some earlier blogs for other software versions using a standard rotary encoder.

    ReplyDelete
  3. Hi Charlie,

    I can't get my arduino loaded.
    I am getting an error.
    Can you help me.
    I don't think I have the right library.
    Where can I find the right one?

    Arduino: 1.8.5 (Windows 10), Board: "Arduino / Genuino Uno"

    C: \ Program Files (x86) \ Arduino \ libraries \ LiquidCrystal_I2C \ I2CIO.cpp: 35: 10: fatal error: ../Wire/Wire.h: No such file or directory

    #include <../ Wire / Wire.h>

    ^ ~~~~~~~~~~~~~~~

    compilation terminated.

    Found multiple libraries for "LiquidCrystal_I2C.h"
    Used: C: \ Program Files (x86) \ Arduino \ libraries \ LiquidCrystal_I2C
    Not used: C: \ Program Files (x86) \ Arduino \ libraries \ NewLiquidCrystal_lib
    exit status 1
    Error compiling for board Arduino / Genuino Uno

    This report would contain more information with
    "Show verbose output during compilation"
    option in File -> Preferences.

    greetings Arjan

    ReplyDelete
    Replies
    1. I won't be able to help sorry Arjan. I suggest you open the example sketch that comes with your display library and look at the syntax. Copy that syntax in this sketch.

      Charlie

      Delete
  4. Great Build there Charlie, Just finished the VFO using exact same parts as you are, I found that when pin 5 us grounded i get a continuous flicker between Rx Tx and USB LSB. I will try add some resistance at the switch hope that helps.

    ReplyDelete
  5. Not sure sorry. I don't have that problem here. The internal pull-up resistor is enabled.

    ReplyDelete
  6. Hello Charlie! Would you be able to take some close up photographs of both the IF Amp and the AF Amp completed boards? As I am very new to the hobby I it would help me greatly help to see the component placement on the board. I tried to zoom-in on the video but it still isn't very clear to me. Thank you in advance!

    ReplyDelete
  7. Thanks for adding the photos! Seeing your construction style up close is very helpful.

    ReplyDelete
  8. Charlie

    Any chance of posting final circuit for driver and power amp stages of your transceiver?

    Mike ZL1AXG

    ReplyDelete
  9. Sure. I've just added the notes.

    Charlie

    ReplyDelete
  10. Charlie - I will be making this rig with one or two mods over the next few months. I am assembling the audio amp at present and see you have an extra capacitor at the input in series with the resistor. I presume this is in the order of 10uF? Also, I am not sure why you use two BPF’s and don’t just use another relay on the antenna side. My thought is to use the arduino to switch four 5v relays (with driver transistor) for IF, BPF, antenna, and 12v RX/TX to make it possible to use on CW as well (pulling off balance one of the double balances mixers). I will also be using a 12 MHz IF requiring a few recalculations - only because I have the xtals.... It is 48 years since I completed a scratch build, but I have built a number of kits and smaller projects! You are an inspiration in this regard.

    ReplyDelete
  11. Yes, 10uF will be fine. As for the BPF, I have done it both ways. More often I have it directly after the mixer on TX in order to remove all of the unwanted mixer products before amplification. Having a second BPF makes it easy. Either way will work fine.

    ReplyDelete
  12. I agree - straight after the mixer on the TX pathway is the best approach. I plan to demonstrate testing of each module in our regular Branch 50 shed workshops.

    ReplyDelete
  13. hi charlie, how can i contact you?

    ReplyDelete
  14. Charlie
    My regards.
    Will you kindly enlighten me about why in last 8 lines of the code you have switched vfo from clk2 to clk0 and again to clk2 and similarly bfo also was switched
    between clk0 and clk2 .

    ReplyDelete
    Replies
    1. Hi. Please see the 'relays' section above. I am using the two mixers for different purposes depending on if it is RX or TX. I do this now as I don't have any problems with feedback around the IF. The YouTube videos explain why I have changed.

      Delete
  15. Thanks for your prompt response .
    My vfo/bfo with your coding idea is working nicely with my home brewed rig .

    ReplyDelete