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

34 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
    2. Put in 16,2 in the lcd.begin();

      Delete
    3. It worked for me but we may use different displays

      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
  16. Absolutely brilliant work Charlie! I've been searching for someone with your ability for a very long time. Thank you for sharing with the world.

    I have a couple questions: on the PA section, can you show how you calcuated the 5:4 transformer (or did I miss it somewhere)?

    I am very curious about something else - when you make transmitters, do you spend a lot of time with impedance matching withing stages? If so, can you recommend how you calculate that? Can you recommend any good books on the subject? S and Y parameters for small transistors like the 2N3904 have been notoriously hard to find for me, so I've never been able to do it.

    Do you use any good software tools or books to calculate your torroid inductors?

    Thank you again,
    Josh
    KC7KJC
    Seattle, WA, USA

    ReplyDelete
  17. Hi charlie, thank you for your detailed design, I am interested in build a VFO of an old 80 mts SSB BLU (modified :) ). My question is regarding the output frequency of the Si5351, if am not wrong is and sqare output instead of a sine wave no? So if that happens you simply add a filter to get a clean carrier for example? O the IF filter leave all the armonics behind?

    Thanks in avdance!
    LU7VRI from Argentina!
    Regards

    ReplyDelete
    Replies
    1. Hi. Take a look at:
      http://soldersmoke.blogspot.com/2016/03/great-video-on-mixers.html

      That provides a good rundown on square waves and mixers.

      Charlie

      Delete
    2. Thank you Charlie!! I will take a look on that!

      Thanks again..
      LU7VRI
      Ignacio

      Delete
  18. Kia Ora. A quick question: On the mixer, can Charlie or anyone else explain the design choices for the transformers? The number of turns, the guage of the wire, and the cores chosen? They are each 10 turns of 28 guage wire on FT 37-43 cores, but I'm not sure why. Charlie, do you have any good reference books or websites you use to determine values like these, whether for transformers or other parts?

    Thanks
    -Josh
    KC7KJC

    ReplyDelete
    Replies
    1. Hi Josh. I try and use readily available parts so others can give it a go. The FT37-43 toroid is one of those. As for texts, my main source is Solid State Design for the Radio Amateur. An oldie but a goodie!

      Charlie

      Delete
  19. Hola. Me sale errores varios al compilar el código. Necesito hacer solamente el generador de frecuencia para adaptarlo a un equipo Kenwood 120 pero no puedo compilar el código. Lo voy a armar con un displey 16*2 Arduino nano, encoder con pulsador y si5351. Pido si me pued n ayudar. Gracias

    ReplyDelete
    Replies

    1. Le sugiero que use el código de ejemplo que viene con sus módulos, luego transfiera la sintaxis. Eso es todo lo que puedo hacer desde este punto. 73 Charlie ZL2CTM

      Delete
    2. Me ayudarías? La verdad es q no se nada y no puedo hacer q el código funcione. Sería mucho pedirle si me pasa el código y q librería debería usar? Los elementos que tengo son: Arduino nano, si5153, encoder de 5 pins, LCD 16*2 l2c. Desde ya más que agradecido. Lu1dte Daniel. Argentina.

      Delete
    3. No lo siento. Debe comenzar con las bibliotecas (por ejemplo, la pantalla I2C) y sus ejemplos. Necesita encontrar los que funcionen con sus componentes. Utilice la biblioteca 'Etherkit Si5351' para el Si5351. Lo siento, no puedo ser de más ayuda.

      Delete