Saturday, 4 May 2024

Broadcast Band AM Radio



See the YouTube series for more information:  http://www.youtube.com/c/CharlieMorrisZL2CTM











Buffer Amplifier






Low Pass Filter





IF Amplifier, Infinite Impedance Detector







AF Amp and AM Detector













Sunday, 27 August 2023

Digital Modes Transceiver

 


#include <SPI.h>

#include <Wire.h>

#include <Adafruit_GFX.h>

#include <Adafruit_SSD1306.h>

#include <si5351.h>

#include <FreqMeasure.h>


#define MODE_SWITCH 7                                         // Mode switch is on D7

#define PTT 2                                                 // PTT output is on D2


double sum = 0;

int count = 0;

int TX_heartbeat = 0;

int TX_heartbeat_count = 0;

float frequency = 0;

unsigned long start_vox_timer;

unsigned long current_vox_timer;

long Mode = 0;

const long WSPR_40 = 7038600;

const long FT8_40 = 7074000;

const long JS8_40 = 7078000;

const long WSPR_20 = 14095600;

const long FT8_20 = 14074000;

const long JS8_20 = 14078000;


// Instantiate the Objects

Si5351 si5351;

Adafruit_SSD1306 display(4);


void setup()

{

  // Set up switches

  pinMode(MODE_SWITCH, INPUT);

  digitalWrite(MODE_SWITCH, HIGH);

  pinMode(PTT, OUTPUT);

  digitalWrite(PTT, LOW);


  // Initialize the frequency measureing routine

  FreqMeasure.begin();


  Mode = WSPR_40;


  // Initialize the display with the I2C addr 0x3C

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  display.setTextColor(WHITE);

  display.clearDisplay();

  display.setTextSize(2);

  UpdateDisplay();


  // Initialize the DDS

  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);

  si5351.set_correction(123200, 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_2MA);

  si5351.output_enable(SI5351_CLK0, 0);


  start_vox_timer = millis();                               // Initial start of the vox timer


  //Serial.begin(57600);

}


void loop()

{

  current_vox_timer = millis();                             // Update the vox timer in preparation


  if (digitalRead(MODE_SWITCH) == LOW)

  {

    if (Mode == WSPR_40)

      Mode = FT8_40;

    else if (Mode == FT8_40)

      Mode = JS8_40;

    else if (Mode == JS8_40)

      Mode = WSPR_20;

    else if (Mode == WSPR_20)

      Mode = FT8_20;

    else if (Mode == FT8_20)

      Mode = JS8_20;

    else if (Mode == JS8_20)

      Mode = WSPR_40;


    UpdateDisplay();

    delay(100);

  }


  if (FreqMeasure.available())                              // If available, average several frequency readings for greater accuuracy

  {

    TX_heartbeat = 1;                                       // Set the TX_heartbeat to 1 as we are still transmitting

    TX_heartbeat_count = 0;                                 // Reset the TX heartbeat counter

    sum = sum + FreqMeasure.read();

    count = count + 1;


    if (count > 60)

    {

      frequency = FreqMeasure.countToFrequency(sum / count);

      si5351.output_enable(SI5351_CLK0, 1);                                 // Turn on the Si5351

      digitalWrite(PTT, HIGH);                                              // Key the transmitter

      si5351.set_freq(((frequency + Mode) * 100ULL), SI5351_CLK0);          // Convert to TX freq and update the Si5351

      sum = 0;                                                              // Clear the sum ready for next time

      count = 0;                                                            // Reset the count ready for next time

    }

  }


  if (current_vox_timer - start_vox_timer >= 100)           // Test to see if we are still transmitting

  {

    if (TX_heartbeat == 0)

      TX_heartbeat_count = TX_heartbeat_count + 1;          // Increment the counter


    if (TX_heartbeat_count >= 10)                           // Turn off after 1 second of no TX heartbeat (10 x 100mS = 1Sec)

      {

        si5351.output_enable(SI5351_CLK0, 0);               // Turn off the Si5351

        digitalWrite(PTT, LOW);                             // Turn off the transmitter

      }


    start_vox_timer = current_vox_timer;                    // Reset the vox timer ready for next time

  }


  TX_heartbeat = 0;                                         // Set the TX_heartbeat to 0 ready for next check

}



void UpdateDisplay()

{

  display.clearDisplay();

  display.setCursor(0, 0);

  display.println("Digi Radio");

  display.setCursor(0, 18);

  if (Mode == WSPR_40)

    display.println("40m WSPR");

  if (Mode == FT8_40)

    display.println("40m FT8");

  if (Mode == JS8_40)

    display.println("40m JS8");

  if (Mode == WSPR_20)

    display.println("20m WSPR");

  if (Mode == FT8_20)

    display.println("20m FT8");

  if (Mode == JS8_20)

    display.println("20m JS8");

  display.display();

}

Sunday, 4 September 2022

80m/20m SSB/CW Rig






 

Mic Amp



Crystal Filters

IF Amps and Coupling Transformers




Arduino Code:


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

#define SSB 0
#define CW 1
#define BAND80 0
#define BAND40 1
#define VFOA 0
#define VFOB 1
#define TX 0
#define RX 1
#define ON 0
#define OFF 1

const long band80mStart = 3500000;      // start of 80m
const long band80mEnd = 3900000;        // end of 80m
const long band40mStart = 7000000;      // start of 40m
const long band40mEnd = 7300000;        // end of 40m
volatile long VFOA_freq80 = 3690000;
volatile long VFOB_freq80 = 3700000;
volatile long VFOA_freq40 = 7090000;
volatile long VFOB_freq40 = 7100000;
volatile long currentfreq = 7090000;

volatile long radix = 1000;              // how much to change the frequency by, clicking the rotary encoder will change this.
volatile long oldradix = 1;
volatile long oldfreq = 0;
volatile long CW_FILTER_CENTRE = 8996856;
volatile long SSB_BFO_freq = 8996190;          // 8997975 - 2900/2 - 300Hz = 8996190
volatile long CW_BFO_freq = 8996156;           // 8996856 - 700Hz

volatile int band = BAND40;
volatile int oldband = BAND80;
volatile int VFOA_VFOB = VFOA;
volatile int old_VFOA_VFOB = VFOB;
volatile int mode = SSB;                 // the current mode (0=LSB, 1=USB)
volatile int oldmode = CW;               // the previous mode (0=LSB, 1=USB)
volatile int SSB_TX = RX;
volatile int oldSSB_TX = TX;
volatile int CW_TX = RX;
volatile int oldCW_TX = TX;
volatile int CW_SPOT = OFF;
volatile int oldCW_SPOT = ON;
volatile int ANT_TUNE = OFF;
volatile int oldANT_TUNE = ON;

volatile long CW_TX_Timer = 0;
volatile int Display = 0;

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

// I/O switches
static const int CW_Spot_Switch = 5;
static const int SSB_CW_Switch = 6;
static const int Ant_Tune_Switch = 7;
static const int PTT_Switch = A0;
static const int VFOA_VFOB_Switch = 9;
static const int Band_80m_40m_Switch = 11;
static const int CW_Key = 12;
static const int IF_RELAY = 13;

// Discrete Outputs
static const int Ant_Relay = 8;

// 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, 20, 4);
Si5351 si5351;

void setup()
{
  // Set up input and output pins
  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(pushPin, INPUT);
  pinMode(CW_Spot_Switch, INPUT);
  pinMode(SSB_CW_Switch, INPUT);
  pinMode(Ant_Tune_Switch, INPUT);
  pinMode(PTT_Switch, INPUT);
  pinMode(VFOA_VFOB_Switch, INPUT);
  pinMode(Band_80m_40m_Switch, INPUT);
  pinMode(CW_Key, INPUT);
  pinMode(Ant_Relay, OUTPUT);
  pinMode(IF_RELAY, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(A2, OUTPUT);

  // Set up pull-up resistors on input and output pins
  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(pushPin, HIGH);
  digitalWrite(CW_Spot_Switch, HIGH);
  digitalWrite(SSB_CW_Switch, HIGH);
  digitalWrite(Ant_Tune_Switch, HIGH);
  digitalWrite(PTT_Switch, HIGH);
  digitalWrite(VFOA_VFOB_Switch, HIGH);
  digitalWrite(Band_80m_40m_Switch, HIGH);
  digitalWrite(CW_Key, HIGH);
  digitalWrite(Ant_Relay, LOW);
  digitalWrite(IF_RELAY, LOW);
  digitalWrite(A1, LOW);            // Antenna Relay
  digitalWrite(A2, LOW);            // External Amplifier PTT

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

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

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(28350, 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.set_freq((currentfreq * 100ULL), SI5351_CLK0);
  si5351.set_freq((SSB_BFO_freq * 100ULL), SI5351_CLK2);
}


void loop()
{
  ReadBandSwitch();
  ReadModeSwitch();
  ReadVFOSwitch();
  ReadPTTSwitch();
  ReadCWKeySwitch();
  ReadCWSpotSwitch();
  ReadAntTuneSwitch();
  ControlAntPARelay();
  UpdateFrequency();
  if (Display == 1)
    UpdateDisplay();
}


void ReadBandSwitch()
{
  band = digitalRead(Band_80m_40m_Switch);
  if (band != oldband)
  {
    if ((band == BAND80) && (VFOA_VFOB == VFOA))
      currentfreq = VFOA_freq80;
    if ((band == BAND80) && (VFOA_VFOB == VFOB))
      currentfreq = VFOB_freq80;
    if ((band == BAND40) && (VFOA_VFOB == VFOA))
      currentfreq = VFOA_freq40;
    if ((band == BAND40) && (VFOA_VFOB == VFOB))
      currentfreq = VFOB_freq40;

    SendFrequency();
    Display = 1;
    oldband = band;
  }
}


void ReadModeSwitch()
{
  mode = digitalRead(SSB_CW_Switch);
  if (mode != oldmode)
  {
    if (mode == CW)
      radix = 10;
    else
      radix = 1000;
    SendFrequency();
    Display = 1;
    oldmode = mode;
  }
}


void ReadVFOSwitch()
{
  VFOA_VFOB = digitalRead(VFOA_VFOB_Switch);
  if (VFOA_VFOB != old_VFOA_VFOB)
  {
    if ((band == BAND80) && (VFOA_VFOB == VFOA))
      currentfreq = VFOA_freq80;
    if ((band == BAND80) && (VFOA_VFOB == VFOB))
      currentfreq = VFOB_freq80;
    if ((band == BAND40) && (VFOA_VFOB == VFOA))
      currentfreq = VFOA_freq40;
    if ((band == BAND40) && (VFOA_VFOB == VFOB))
      currentfreq = VFOB_freq40;

    SendFrequency();
    Display = 1;
    old_VFOA_VFOB = VFOA_VFOB;
  }
}


void ReadPTTSwitch()
{
  SSB_TX = digitalRead(PTT_Switch);
  if (SSB_TX != oldSSB_TX)
  {
    if (SSB_TX == TX)
      digitalWrite(IF_RELAY, HIGH);
    if (SSB_TX == RX)
      digitalWrite(IF_RELAY, LOW);
    SendFrequency();
    Display = 1;
    oldSSB_TX = SSB_TX;
  }
}


void ReadCWKeySwitch()
{
  if ((digitalRead(CW_Key) == LOW) && (mode == CW))
  {
    CW_TX = TX;
    CW_TX_Timer = millis();
    si5351.output_enable(SI5351_CLK1, 1);
  }
  if ((digitalRead(CW_Key) == HIGH) && (mode == CW))
  {
    if ((CW_SPOT == OFF) && (ANT_TUNE == OFF))
      si5351.output_enable(SI5351_CLK1, 0);
  }
  if ((digitalRead(CW_Key) == HIGH) && ((millis() - CW_TX_Timer) >= 1000))
  {
    CW_TX_Timer = 0;
    CW_TX = RX;
  }
  if (CW_TX != oldCW_TX)
  {
    Display = 1;
    oldCW_TX = CW_TX;
  }
}


void ReadCWSpotSwitch()
{
  CW_SPOT = digitalRead(CW_Spot_Switch);
  if (CW_SPOT != oldCW_SPOT)
  {
    if (CW_SPOT == ON)
    {
      if (mode == SSB)
        si5351.set_freq((currentfreq * 100ULL), SI5351_CLK1);
      if (mode == CW)
        si5351.set_freq(((currentfreq - 700) * 100ULL), SI5351_CLK1);
      si5351.output_enable(SI5351_CLK1, 1);
    }
    if (CW_SPOT == OFF)
    {
      si5351.set_freq((currentfreq * 100ULL), SI5351_CLK1);
      si5351.output_enable(SI5351_CLK1, 0);
    }
    SendFrequency();
    Display = 1;
    oldCW_SPOT = CW_SPOT;
  }
}


void ReadAntTuneSwitch()
{
  ANT_TUNE = digitalRead(Ant_Tune_Switch);
  if (ANT_TUNE != oldANT_TUNE)
  {
    if (ANT_TUNE == ON)
      si5351.output_enable(SI5351_CLK1, 1);
    if (ANT_TUNE == OFF)
      si5351.output_enable(SI5351_CLK1, 0);
    SendFrequency();
    Display = 1;
    oldANT_TUNE = ANT_TUNE;
  }
}


void ControlAntPARelay()
{
  if ((SSB_TX == TX) || (CW_TX == TX) || (ANT_TUNE == ON))
  {
    delay(100);
    digitalWrite(A1, HIGH);
    digitalWrite(A2, HIGH);
  }
  if ((SSB_TX == RX) && (CW_TX == RX) && (ANT_TUNE == OFF))
  {
    digitalWrite(A1, LOW);
    digitalWrite(A2, LOW);
  }
}


void UpdateFrequency()
{
  currentfreq = getfreq();                  // Interrupt safe method to get the current frequency
  if (currentfreq != oldfreq)
  {
    SendFrequency();
    Display = 1;
    oldfreq = currentfreq;
  }
  if (radix != oldradix)
    Display = 1;
}


long getfreq()
{
  long temp_freq;
  cli();

  if ((band == BAND80) && (VFOA_VFOB == VFOA))
    temp_freq = VFOA_freq80;

  if ((band == BAND80) && (VFOA_VFOB == VFOB))
    temp_freq = VFOB_freq80;

  if ((band == BAND40) && (VFOA_VFOB == VFOA))
    temp_freq = VFOA_freq40;

  if ((band == BAND40) && (VFOA_VFOB == VFOB))
    temp_freq = VFOB_freq40;

  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)
          {
            radix = radix * 10;
            if (radix > 100000)
              radix = 100000;
          }
          else
          {
            if ((band == BAND80) && (VFOA_VFOB == VFOA))
            {
              VFOA_freq80 = VFOA_freq80 + radix;
              if (VFOA_freq80 > band80mEnd)
                VFOA_freq80 = band80mEnd;
            }
            if ((band == BAND80) && (VFOA_VFOB == VFOB))
            {
              VFOB_freq80 = VFOB_freq80 + radix;
              if (VFOB_freq80 > band80mEnd)
                VFOB_freq80 = band80mEnd;
            }
            if ((band == BAND40) && (VFOA_VFOB == VFOA))
            {
              VFOA_freq40 = VFOA_freq40 + radix;
              if (VFOA_freq40 > band40mEnd)
                VFOA_freq40 = band40mEnd;
            }
            if ((band == BAND40) && (VFOA_VFOB == VFOB))
            {
              VFOB_freq40 = VFOB_freq40 + radix;
              if (VFOB_freq40 > band40mEnd)
                VFOB_freq40 = 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)
          {
            radix = radix / 10;
            if (radix < 1)
              radix = 1;
          }
          else
          {
            if ((band == BAND80) && (VFOA_VFOB == VFOA))
            {
              VFOA_freq80 = VFOA_freq80 - radix;
              if (VFOA_freq80 < band80mStart)
                VFOA_freq80 = band80mStart;
            }
            if ((band == BAND80) && (VFOA_VFOB == VFOB))
            {
              VFOB_freq80 = VFOB_freq80 - radix;
              if (VFOB_freq80 < band80mStart)
                VFOB_freq80 = band80mStart;
            }
            if ((band == BAND40) && (VFOA_VFOB == VFOA))
            {
              VFOA_freq40 = VFOA_freq40 - radix;
              if (VFOA_freq40 < band40mStart)
                VFOA_freq40 = band40mStart;
            }
            if ((band == BAND40) && (VFOA_VFOB == VFOB))
            {
              VFOB_freq40 = VFOB_freq40 - radix;
              if (VFOB_freq40 < band40mStart)
                VFOB_freq40 = 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()
{
  // VFOA/B
  if (VFOA_VFOB == VFOA)
  {
    lcd.setCursor(0, 0);
    lcd.print("*");
    lcd.setCursor(0, 1);
    lcd.print(" ");
  }

  if (VFOA_VFOB == VFOB)
  {
    lcd.setCursor(0, 0);
    lcd.print(" ");
    lcd.setCursor(0, 1);
    lcd.print("* ");
  }

  // Freq
  if (band == BAND80)
  {
    lcd.setCursor(2, 0);
    lcd.print(VFOA_freq80);
    lcd.setCursor(2, 1);
    lcd.print(VFOB_freq80);
  }

  if (band == BAND40)
  {
    lcd.setCursor(2, 0);
    lcd.print(VFOA_freq40);
    lcd.setCursor(2, 1);
    lcd.print(VFOB_freq40);
  }

  // Mode
  if (mode == SSB)
  {
    lcd.setCursor(16, 0);
    lcd.print("SSB");
  }

  if (mode == CW)
  {
    lcd.setCursor(16, 0);
    lcd.print("CW ");
  }

  // Test CW Timer
  if (CW_TX == TX)
  {
    lcd.setCursor(16, 2);
    lcd.print("CWTX");
  }

  if (CW_TX == RX)
  {
    lcd.setCursor(16, 2);
    lcd.print("    ");
  }

  // CW Spot
  if (CW_SPOT == ON)
  {
    lcd.setCursor(16, 3);
    lcd.print("SPOT");
  }

  if (CW_SPOT == OFF)
  {
    lcd.setCursor(16, 3);
    lcd.print("    ");
  }

  // Ant Tune
  if (ANT_TUNE == ON)
  {
    lcd.setCursor(2, 3);
    lcd.print("TUNE");
  }

  if (ANT_TUNE == OFF)
  {
    lcd.setCursor(2, 3);
    lcd.print("    ");
  }

  // TX/RX
  if ((SSB_TX == TX) || (CW_TX == TX) || (ANT_TUNE == ON))
  {
    lcd.setCursor(7, 3);
    lcd.print("XMIT");
  }

  if ((SSB_TX == RX) && (CW_TX == RX) && (ANT_TUNE == OFF))
  {
    lcd.setCursor(7, 3);
    lcd.print("    ");
  }


  // Radix
  if (radix != oldradix)                          // stops radix display flashing/blinking on freq change
  {
    lcd.setCursor(12, 1);
    lcd.print("       ");
    lcd.setCursor(12, 1);
    if (radix == 1)
      lcd.print("   1 Hz");
    if (radix == 10)
      lcd.print("  10 Hz");
    if (radix == 100)
      lcd.print(" 100 Hz");
    if (radix == 1000)
      lcd.print("  1 kHz");
    if (radix == 10000)
      lcd.print(" 10 kHz");
    if (radix == 100000)
      lcd.print("100 kHz");
    oldradix = radix;
  }

  Display = 0;
}


void SendFrequency()
{
  if (mode == SSB)
  {
    if (SSB_TX == RX)
    {
      SSB_BFO_freq = 8996190;
      si5351.set_freq(((SSB_BFO_freq + currentfreq) * 100ULL), SI5351_CLK0);
      si5351.set_freq((SSB_BFO_freq * 100ULL), SI5351_CLK2);
    }
    if (SSB_TX == TX)
    {
      SSB_BFO_freq = 8995800;
      si5351.set_freq(((SSB_BFO_freq + currentfreq) * 100ULL), SI5351_CLK2);
      si5351.set_freq((SSB_BFO_freq * 100ULL), SI5351_CLK0);
    }
  }

  if (mode == CW)
  {
    si5351.set_freq(((CW_FILTER_CENTRE + currentfreq) * 100ULL), SI5351_CLK0);
    si5351.set_freq((CW_BFO_freq * 100ULL), SI5351_CLK2);
  }

  si5351.set_freq((currentfreq * 100ULL), SI5351_CLK1); 
}




Saturday, 26 June 2021

Arduino Clock






 Initial code using a 120x120 TFT display and a realtime clock module.






/*****************************************************************************
   The ST7789 IPS SPI display.is a 3.3v Device, do not use with a 5v Arduino
    unless you level shift.

    Display          |    Arduino
    -------------------------------------------
    VCC              |    3.3v
    GND              |    Gnd
    SCL              |    Pin 13 (SCK)
    SDA              |    Pin 11 (MOSI)
    RES              |    Pin 9
    DC               |    Pin 8
    BLK              |    Leave disconnected
 *****************************************************************************/

#include <Adafruit_GFX.h>
#include <Arduino_ST7789.h>
#include <SPI.h>
#include "RTClib.h"

#define TFT_DC    8
#define TFT_RST   9
#define TFT_MOSI  11
#define TFT_SCLK  13
#define DAYLIGHT_SAVING_PIN  5

// Clock Variables
int _Day, _OldDay = 0;
int _Hours, _OldHours = 0;
int _Minutes, _OldMinutes = 0;
int _DaylightSaving = 0;
int _xH, _yH, _OldxH, _OldyH = 0;
int _xM, _yM, _OldxM, _OldyM = 0;

Arduino_ST7789 tft = Arduino_ST7789(TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK);   //No CS pin
RTC_DS3231 rtc;

void setup(void)
{
  pinMode(DAYLIGHT_SAVING_PIN, INPUT);                            // Setup daylight saving sw port
  digitalWrite(DAYLIGHT_SAVING_PIN, HIGH);

  // initialize the realtime clock
  rtc.begin();

  // initialize the display
  tft.init(240, 240);
  tft.setRotation(3);
  tft.fillScreen(BLACK);
  tft.setTextSize(2);
  DrawClockFace();
  DrawClockNumbers();
}

void loop()
{
  DateTime now = rtc.now();
  _Day = now.day();
  _Hours = now.hour();
  _Minutes = now.minute();

  if (digitalRead(DAYLIGHT_SAVING_PIN) == 0)
    delay(50);
  if (digitalRead(DAYLIGHT_SAVING_PIN) == 0)
    _DaylightSaving = !_DaylightSaving;

  if (_DaylightSaving == 1)
    _Hours++;

  if ((_Hours != _OldHours) || (_Minutes != _OldMinutes))
  {
    UpdateHands();
    DrawClockNumbers();
    _OldHours = _Hours;
    _OldMinutes = _Minutes;
  }

  if (_Day != _OldDay)
  {
    UpdateDate();
    _OldDay = _Day;
  }

  delay(1000);
}

void UpdateHands()
{
  //x = x0 + r * Math.Cos(theta * Math.PI / 180);
  //y = y0 + r * Math.Sin(theta * Math.PI / 180);

  //Hours hands (erase old hours hand then redraw new one)
  tft.drawLine((tft.width() / 2), (tft.height() / 2), _OldxH, _OldyH, BLACK);

  _xH = (tft.width() / 2) + 70 * cos((((30 * _Hours) + (0.5 * _Minutes)) - 90) * PI / 180);
  _yH = (tft.height() / 2) + 70 * sin((((30 * _Hours) + (0.5 * _Minutes)) - 90) * PI / 180);
  tft.drawLine((tft.width() / 2), (tft.height() / 2), _xH, _yH, WHITE);
  _OldyH = _yH;
  _OldxH = _xH;

  //Minutes hands (erase old minutes hand then redraw new one)
  tft.drawLine((tft.width() / 2), (tft.height() / 2), _OldxM, _OldyM, BLACK);

  _xM = (tft.width() / 2) + 95 * cos(((6 * _Minutes) - 90) * PI / 180);
  _yM = (tft.height() / 2) + 95 * sin(((6 * _Minutes) - 90) * PI / 180);
  tft.drawLine((tft.width() / 2), (tft.height() / 2), _xM, _yM, WHITE);
  _OldxM = _xM;
  _OldyM = _yM;

  tft.fillCircle((tft.width() / 2), (tft.height() / 2), 3, WHITE);
}

void UpdateDate()
{
  tft.setTextColor(BLACK);
  tft.setCursor(210, 220);
  tft.print(_OldDay);

  tft.setTextColor(GREEN);
  tft.setCursor(210, 220);
  tft.print(_Day);

  tft.setTextColor(WHITE);
}

void DrawClockFace()
{
  int _x1, _y1 = 0;

  //x = x0 + r * Math.Cos(theta * Math.PI / 180);
  //y = y0 + r * Math.Sin(theta * Math.PI / 180);

  tft.drawCircle((tft.width() / 2), (tft.height() / 2), 119, WHITE);
  tft.drawCircle((tft.width() / 2), (tft.height() / 2), 118, WHITE);

  // Minute marks
  for (int x = 0; x <= 60; x++)
  {
    _x1 = (tft.width() / 2) + 118 * cos(((6 * x) - 90) * PI / 180);
    _y1 = (tft.height() / 2) + 118 * sin(((6 * x) - 90) * PI / 180);
    tft.drawLine((tft.width() / 2), (tft.height() / 2), _y1, _x1, WHITE);
  }
  tft.fillCircle((tft.width() / 2), (tft.height() / 2), 110, BLACK);

  // 5 minute marks
  for (int x = 0; x <= 12; x++)
  {
    _x1 = (tft.width() / 2) + 118 * cos(((30 * x) - 90) * PI / 180);
    _y1 = (tft.height() / 2) + 118 * sin(((30 * x) - 90) * PI / 180);
    tft.drawLine((tft.width() / 2), (tft.height() / 2), _y1, _x1, WHITE);
  }
  tft.fillCircle((tft.width() / 2), (tft.height() / 2), 105, BLACK);
}

void DrawClockNumbers()
{
  tft.setTextColor(WHITE);
  tft.setCursor(163, 32);
  tft.print("1");

  tft.setCursor(196, 65);
  tft.print("2");

  tft.setCursor(210, 113);
  tft.print("3");

  tft.setCursor(199, 160);
  tft.print("4");

  tft.setCursor(164, 193);
  tft.print("5");

  tft.setCursor(115, 207);
  tft.print("6");

  tft.setCursor(70, 195);
  tft.print("7");

  tft.setCursor(35, 160);
  tft.print("8");

  tft.setCursor(20, 113);
  tft.print("9");

  tft.setCursor(33, 66);
  tft.print("10");

  tft.setCursor(66, 34);
  tft.print("11");

  tft.setCursor(109, 20);
  tft.print("12");
}

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