Saturday 20 April 2019

Portable 20m SSB Rig



See YouTube for video log.







BPF




Ant Amplifier




IF Amp







#include <Wire.h>
#include <SPI.h>
#include <TM1637Display.h>
#include <si5351.h>
#include "LowPower.h"

const uint32_t bandStart = 14000000;    // start of 20m
const uint32_t bandEnd =   14350000;    // end of 20m
const uint32_t bandInit =  14100000;    // where to initially set the frequency
volatile long currentfreq = 0;
volatile long oldfreq = 0;
volatile int currentmode = 0;
volatile int oldmode = 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.

const uint32_t BFO_freq = 8998450;      // 8998450 = high side injection. For low side injection use 9001350;

// Rotary encoder pins and other inputs
static const int rotBPin = 2;
static const int rotAPin = 3;
static const int pushPin = 4;
static const int PTTInput = 8;
static const int brightnessPin = A3;
static const int tunespeedLED = A2;
static const int gnd = 10;
static const int vcc = 11;
static const int DIO = 12;
static const int CLK = 13;

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


volatile long remainder = 0;
volatile long OnesHz = 0;
volatile long TensHz = 0;
volatile long HundredsHz = 0;
volatile long OneskHz = 0;
volatile long TenskHz = 0;
volatile long HundredskHz = 0;
volatile long OnesMHz = 0;
volatile long TensMHz = 0;
volatile int Brightness = 3;
volatile int batterySave = 0;

// Instantiate the Objects
TM1637Display display(CLK, DIO);    // CLK, DIO
Si5351 si5351;

void setup()
{
  // Set up frequency and radix switches
  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(pushPin, INPUT);
  pinMode(brightnessPin, INPUT);
  pinMode(gnd, OUTPUT);
  pinMode(tunespeedLED, OUTPUT);
  pinMode(vcc, OUTPUT);
  pinMode(PTTInput, INPUT);

  // Set up pull-up resistors on inputs
  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(pushPin, HIGH);
  digitalWrite(brightnessPin, HIGH);
  digitalWrite(gnd, LOW);
  digitalWrite(vcc, HIGH);
  digitalWrite(tunespeedLED, LOW);
  digitalWrite(PTTInput, LOW);

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

  // Initialize the display
  display.setBrightness(Brightness, true);
  UpdateDisplay();
  delay(1000);

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(87000, 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.drive_strength(SI5351_CLK2, SI5351_DRIVE_2MA);
  si5351.set_freq((freq * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
}


void loop()
{
  LowPower.idle(SLEEP_60MS, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_ON, SPI_OFF, USART0_OFF, TWI_OFF);

  currentmode = digitalRead(PTTInput);
  if (currentmode != oldmode)
  {
    SendFrequency();
    oldmode = currentmode;
  }


  currentfreq = getfreq();                // Interrupt safe method to get the current frequency
  if (currentfreq != oldfreq)
  {
    UpdateDisplay();
    SendFrequency();
    oldfreq = currentfreq;
  }

  if (digitalRead(brightnessPin) == LOW)
  {
    Brightness--;
    display.setBrightness(Brightness, true);
    if (Brightness == -1)
    {
      display.setBrightness(0, false);
      digitalWrite(tunespeedLED, LOW);
      batterySave = 1;
    }
    if (Brightness == -2)
    {
      Brightness = 3;
      batterySave = 0;
    }
    UpdateDisplay();
    delay(500);
  }

  if ((radix == 100) && (batterySave == 0))
    digitalWrite(tunespeedLED, HIGH);

  if (radix == 1000)
    digitalWrite(tunespeedLED, LOW);
}


void wakeUp()
{
  // Just a handler for the sleep pin interrupt.
}


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)
          {
            if (radix == 1000)
              radix = 100;
            else if (radix == 100)
              radix = 1000;
          }
          else
          {
            freq = (freq + radix);
            if (freq > bandEnd)
              freq = bandEnd;
          }
          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)
          {
            if (radix == 100)
              radix = 1000;
            else if (radix == 1000)
              radix = 100;
          }
          else
          {
            freq = (freq - radix);
            if (freq < bandStart)
              freq = bandStart;
          }
          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()
{
  TensMHz = freq / 10000000;                                // TensMHz = 12345678 / 10000000 = 1
  remainder = freq - (TensMHz * 10000000);                  // remainder = 12345678 - 10000000 = 2345678
  OnesMHz = remainder / 1000000;                            // OnesMhz = 2345678 / 1000000 = 2
  remainder = remainder - (OnesMHz * 1000000);              // remainder = 2345678 - (2 * 1000000) = 345678
  HundredskHz = remainder / 100000;                         // HundredskHz = 345678 / 100000 = 3
  remainder = remainder - (HundredskHz * 100000);           // remainder = 345678 - (3 * 100000) = 45678
  TenskHz = remainder / 10000;                              // TenskHz = 45678 / 10000 = 4
  remainder = remainder - (TenskHz * 10000);                // remainder = 45678 - (4 * 10000) = 5678
  OneskHz = remainder / 1000;                               // OneskHz = 5678 / 1000 = 5
  remainder = remainder - (OneskHz * 1000);                 // remainder = 5678 - (5 * 1000) = 678
  HundredsHz = remainder / 100;                             // HundredsHz = 678 / 100 = 6
  remainder = remainder - (HundredsHz * 100);               // remainder = 678 - (6 * 100) = 78
  TensHz = remainder / 10;                                  // TensHz = 78 / 10 = 7
  remainder = remainder - (TensHz * 10);                    // remainder = 78 - (7 * 10) = 8
  OnesHz = remainder;                                       // OnesHz = 8

  display.showNumberDec(((1000 * HundredskHz) + ( 100 * TenskHz) + (10 * OneskHz) + HundredsHz), true);
}


void SendFrequency()
{
  if (currentmode == 1)             // Transmit
  {
    si5351.set_freq(((freq - BFO_freq + 50) * 100ULL), SI5351_CLK2);
    si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK0);
  }
  else                              // Receive
  {
    si5351.set_freq(((freq - BFO_freq + 50) * 100ULL), SI5351_CLK0);
    si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
  }
}

Friday 15 March 2019

Homebrew RF Power Amplifier

RF Power amplifier based on the W6JL amp. The intent is to use readily available parts in the first instant. These may be swapped out depending on how well they perform.

See YouTube for videos. https://www.youtube.com/channel/UCSNPW3_gzuMJcX_ErBZTv2g

The original circuit (minus the biasing RFCs)



My version of the bias network





The final amp has used a BN-43-202 for T1 (8:4) and a BN-43-3312 for T2 (1:3). I also left off the RFCs for the YouTube video. Adding the RFCs would increase the gain a little. 

Friday 8 March 2019

Homebrew Panadapter

Homebrew panadapter using a Teensy 3.5.


Test Code


#include "SPI.h"
#include "ILI9341_t3.h"
#include <si5351.h>                        // Si5351Jason library
#include <Audio.h>

//const int myInput = AUDIO_INPUT_MIC;
const int myInput = AUDIO_INPUT_LINEIN;

uint16_t WaterfallData[100][128] = {1};
int Gain = 50;

static const long bandInit =  9008450;     // 8800000 8565000 to initially set the frequency. Was 9020000
volatile long freq = bandInit ;


// For optimized ILI9341_t3 library
#define TFT_DC    20
#define TFT_CS    21
#define TFT_RST   255  // 255 = unused, connect to 3.3V
#define TFT_MOSI  7
#define TFT_SCLK  14
#define TFT_MISO  12

ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
Si5351 si5351;                            // Name for the Si5351 DDS

// Setup audio shield
AudioInputI2S            audioInput;
AudioMixer4              InputAmp;
AudioAnalyzeFFT256       FFT;

// Setup the audio connections
AudioConnection          patchCord1(audioInput, 0, InputAmp, 0);
AudioConnection          patchCord2(InputAmp, 0, FFT, 0);

// Instantiate the Audio Shield
AudioControlSGTL5000 audioShield;

void setup()
{
  Serial.begin(9600);

  // Setup screen
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  tft.drawRect(31, 0, 257, 37, ILI9341_YELLOW);
  tft.drawRect(31, 36, 257, 103, ILI9341_YELLOW);
  tft.drawRect(31, 138, 257, 102, ILI9341_YELLOW);

  // Setup audio shield.
  AudioMemory(12);
  audioShield.enable();
  audioShield.inputSelect(myInput);
  InputAmp.gain(0, Gain);
  FFT.windowFunction(AudioWindowHanning256);
  FFT.averageTogether(30);

  // Setup the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_8MA);
  si5351.set_freq(freq * 100ULL, SI5351_CLK1);
}


void loop()
{
  if (FFT.available())
    UpdateDisplay();

  if (Serial.available() > 0)
  {
    char c = Serial.read();
    switch (c)
    {
      case 'w': Gain = Gain + 10; break;
      case 's': Gain = Gain - 10; break;
    }
    Serial.println(Gain);
    InputAmp.gain(0, Gain);
  }
}


void UpdateDisplay()
{
  int bar = 0;
  int xPos = 0;
  int low = 0;

  // Spectrum
  for (int x = 0; x <= 127; x++)
  {
    WaterfallData[0][x] = abs(FFT.output[x]);
    bar = WaterfallData[0][x];
    if (bar > 100)
      bar = 100;
    tft.drawFastVLine(32 + (xPos * 2), 138 - bar, bar, ILI9341_GREEN); //draw green bar
    tft.drawFastVLine(32 + (xPos * 2), 38, 100 - bar, ILI9341_BLACK);  //finish off with black to the top of the screen
    xPos++;
  }

  // Waterfall
  for (int row = 99; row >= 0; row--)
    for (int col = 0; col <= 127; col++)
    {
      WaterfallData[row][col] = WaterfallData[row - 1][col];

      if (WaterfallData[row][col] >= low + 75)
        tft.drawPixel(32 + (col * 2), 139 + row, ILI9341_RED);

      else if ((WaterfallData[row][col] >= low + 50) && (WaterfallData[row][col] < low + 75))
        tft.drawPixel(32 + (col * 2), 139 + row, ILI9341_MAGENTA);

      else if ((WaterfallData[row][col] >= low + 30) && (WaterfallData[row][col] < low + 50))
        tft.drawPixel(32 + (col * 2), 139 + row, ILI9341_YELLOW);

      else if ((WaterfallData[row][col] >= low + 20) && (WaterfallData[row][col] < low + 30))
        tft.drawPixel(32 + (col * 2), 139 + row, ILI9341_BLUE);

      else if (WaterfallData[row][col] < low + 20)
        tft.drawPixel(32 + (col * 2), 139 + row, ILI9341_BLACK);
    }
}


void SendFrequency()
{
  si5351.set_freq(freq * 100ULL, SI5351_CLK1);
}

Saturday 26 January 2019

Si5351 Quadrature Clock Output down to 3MHz

Credit to Brian Harper M1CEM and Miguel BartiĆ© PY2OHH


Step 1. Edit si5351.h file. Change the SI5351_PLL_VCO_MIN to 380000000, i.e.,

#define SI5351_PLL_VCO_MIN              380000000


Step 2. Example code snippet:



volatile long freq = 3500000;
volatile int Even_Divisor = 0;
volatile int oldEven_Divisor = 0;




void EvenDivisor()
{
  if (freq < 6850000)
  {
    Even_Divisor = 126;
  }
  if ((freq >= 6850000) && (freq < 9500000))
  {
    Even_Divisor = 88;
  }
  if ((freq >= 9500000) && (freq < 13600000))
  {
    Even_Divisor = 64;
  }
  if ((freq >= 13600000) && (freq < 17500000))
  {
    Even_Divisor = 44;
  }
  if ((freq >= 17500000) && (freq < 25000000))
  {
    Even_Divisor = 34;
  }
  if ((freq >= 25000000) && (freq < 36000000))
  {
    Even_Divisor = 24;
  }
  if ((freq >= 36000000) && (freq < 45000000)) {
    Even_Divisor = 18;
  }
  if ((freq >= 45000000) && (freq < 60000000)) {
    Even_Divisor = 14;
  }
  if ((freq >= 60000000) && (freq < 80000000)) {
    Even_Divisor = 10;
  }
  if ((freq >= 80000000) && (freq < 100000000)) {
    Even_Divisor = 8;
  }
  if ((freq >= 100000000) && (freq < 146600000)) {
    Even_Divisor = 6;
  }
  if ((freq >= 150000000) && (freq < 220000000)) {
    Even_Divisor = 4;
  }
}


void SendFrequency()
{
  EvenDivisor();
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK0);
  si5351.set_freq_manual(freq * SI5351_FREQ_MULT, Even_Divisor * freq * SI5351_FREQ_MULT, SI5351_CLK2);
  si5351.set_phase(SI5351_CLK0, 0);
  si5351.set_phase(SI5351_CLK2, Even_Divisor);
 
if(Even_Divisor != oldEven_Divisor)
  {
    si5351.pll_reset(SI5351_PLLA);
    oldEven_Divisor = Even_Divisor;
  }
}




Monday 14 January 2019

Homebrew Power/SWR Meter

Please see YouTube for details:

https://www.youtube.com/channel/UCSNPW3_gzuMJcX_ErBZTv2g

This is my first idea for a directional coupler. Please note that this is not suppose to be a competition or commercial grade device. My aim is to keep the costs down, but still have a functional power/SWR meter at the end.

Note also the 51 ohm (below) termination resistor is now 150 ohm.


Tandem coupler experiments. This is looking very promising with a wide frequency and power range.










Final Software:

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

volatile float FwdVoltage = 0;
volatile float RevVoltage = 0;
volatile float PWR_Cal = 0;
volatile float SWR_Cal = 0;
volatile float PWR = 0;
volatile float SWR = 0;
volatile float FwdVoltageReadings[10];
volatile float FwdVoltageAverage;
volatile float RevVoltageReadings[10];
volatile float RevVoltageAverage;
volatile int FwdNumberOfLines = 0;
volatile int RevNumberOfLines = 0;
volatile int FwdNumberOfLinesToBlank = 0;
volatile int RevNumberOfLinesToBlank = 0;
volatile int AvCount = 0;

volatile double oldPWR = 0;
volatile double oldSWR = 0;

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

//Custom bar characters
const byte Bar1Array[8] = {B10000, B10000, B10000, B10000, B10000, B10000, B10000, B10000};
const byte Bar2Array[8] = {B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11000};
const byte Bar3Array[8] = {B11100, B11100, B11100, B11100, B11100, B11100, B11100, B11100};
const byte Bar4Array[8] = {B11110, B11110, B11110, B11110, B11110, B11110, B11110, B11110};
const byte Bar5Array[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};

void setup()
{
  pinMode(A0, INPUT);                     //Fwd power voltage pin
  pinMode(A1, INPUT);                     //Rev power voltage pin
  pinMode(A2, INPUT);                     //PWR cal pin
  pinMode(A3, INPUT);                     //SWR cal pin

  //analogReference(EXTERNAL);

  Serial.begin(9600);
  // Initialize the display
  lcd.begin();
  lcd.backlight();
  lcd.createChar(1, Bar1Array);           //Special charaters for the PWR and SWR bar display
  lcd.createChar(2, Bar2Array);
  lcd.createChar(3, Bar3Array);
  lcd.createChar(4, Bar4Array);
  lcd.createChar(5, Bar5Array);
  UpdateDisplay();
}


void loop()
{
  FwdVoltage = analogRead(A0);
  RevVoltage = analogRead(A1);
  PWR_Cal = analogRead(A2);
  SWR_Cal = analogRead(A3);

  FwdVoltageReadings[AvCount] = FwdVoltage;             // Put fwd power reading into array
  RevVoltageReadings[AvCount] = RevVoltage;             // Put rev power reading into array
  AvCount++;

  if (AvCount == 9)                                     // 0 to 9 = 10 bins
  {
    FwdVoltageAverage = 0;
    RevVoltageAverage = 0;

    for (int x = 0; x <= 9; x++)
      FwdVoltageAverage = FwdVoltageAverage + FwdVoltageReadings[x];
    FwdVoltageAverage = FwdVoltageAverage / 10;                           // Calc average

    FwdVoltageAverage = FwdVoltageAverage - 136;                          // Subtract noise
    FwdVoltageAverage = abs(FwdVoltageAverage);                           // Make absolute (remove any negative)
    FwdVoltageAverage = FwdVoltageAverage * 5 / 1024;                     // covert into voltage
    FwdVoltageAverage = FwdVoltageAverage + (PWR_Cal * 5 / 1024);         // Cal to scope (no between 0 and 5)

    for (int x = 0; x <= 9; x++)
      RevVoltageAverage = RevVoltageAverage + RevVoltageReadings[x];

    RevVoltageAverage = RevVoltageAverage / 10;                           // Calc average
    RevVoltageAverage = RevVoltageAverage - 166;                          // Subtract noise
    RevVoltageAverage = abs(RevVoltageAverage);                           // Make absolute (remove any negative)
    RevVoltageAverage = RevVoltageAverage * 5 / 1024;                     // covert into voltage

    // Lookup table for forward power
    if (FwdVoltageAverage < 1.855)
      PWR = 0;
    else if ((FwdVoltageAverage >= 1.855) && (FwdVoltageAverage < 1.97))
      PWR = 1;
    else if ((FwdVoltageAverage >= 1.97) && (FwdVoltageAverage < 2.017))
      PWR = 2;
    else if ((FwdVoltageAverage >= 2.017) && (FwdVoltageAverage < 2.051))
      PWR = 3;
    else if ((FwdVoltageAverage >= 2.051) && (FwdVoltageAverage < 2.056))
      PWR = 4;
    else if ((FwdVoltageAverage >= 2.056) && (FwdVoltageAverage < 2.07))
      PWR = 5;
    else if ((FwdVoltageAverage >= 2.07) && (FwdVoltageAverage < 2.085))
      PWR = 6;
    else if ((FwdVoltageAverage >= 2.085) && (FwdVoltageAverage < 2.109))
      PWR = 7;
    else if ((FwdVoltageAverage >= 2.109) && (FwdVoltageAverage < 2.124))
      PWR = 8;
    else if ((FwdVoltageAverage >= 2.124) && (FwdVoltageAverage < 2.134))
      PWR = 9;
    else if ((FwdVoltageAverage >= 2.134) && (FwdVoltageAverage < 2.144))
      PWR = 10;
    else if ((FwdVoltageAverage >= 2.144) && (FwdVoltageAverage < 2.153))
      PWR = 11;
    else if ((FwdVoltageAverage >= 2.153) && (FwdVoltageAverage < 2.163))
      PWR = 12;
    else if ((FwdVoltageAverage >= 2.163) && (FwdVoltageAverage < 2.173))
      PWR = 13;
    else if ((FwdVoltageAverage >= 2.173) && (FwdVoltageAverage < 2.183))
      PWR = 14;
    else if ((FwdVoltageAverage >= 2.183) && (FwdVoltageAverage < 2.192))
      PWR = 15;
    else if ((FwdVoltageAverage >= 2.192) && (FwdVoltageAverage < 2.197))
      PWR = 16;
    else if ((FwdVoltageAverage >= 2.197) && (FwdVoltageAverage < 2.212))
      PWR = 17;
    else if ((FwdVoltageAverage >= 2.212) && (FwdVoltageAverage < 2.217))
      PWR = 18;
    else if ((FwdVoltageAverage >= 2.217) && (FwdVoltageAverage < 2.22))
      PWR = 19;

    // 20 - 24W
    else if ((FwdVoltageAverage >= 2.220) && (FwdVoltageAverage < 2.246))
      PWR = ((FwdVoltageAverage - 2.22) * 5 / .026) + 20;

    // 25 - 29W
    else if ((FwdVoltageAverage >= 2.246) && (FwdVoltageAverage < 2.266))
      PWR = ((FwdVoltageAverage - 2.246) * 5 / .02) + 25;

    // 30 - 34W
    else if ((FwdVoltageAverage >= 2.266) && (FwdVoltageAverage < 2.280))
      PWR = ((FwdVoltageAverage - 2.266) * 5 / .014) + 30;

    // 35 - 39W
    else if ((FwdVoltageAverage >= 2.280) && (FwdVoltageAverage < 2.300))
      PWR = ((FwdVoltageAverage - 2.280) * 5 / .02) + 35;

    // >= 40W
    else if (FwdVoltage >= 2.3)
      PWR = ((FwdVoltageAverage - 2.3) * 60 / .07) + 40;


    if ((PWR > 0) && (PWR < 3))
      FwdNumberOfLines = 1;
    else
      FwdNumberOfLines = PWR / 3.33;                        // Power bar. 100W / 30 segments = 3.33W per segment

    if (PWR == 0)
      SWR = 0;
    else
    {
      SWR = (1 + sqrt(RevVoltageAverage / FwdVoltageAverage)) / (1 - sqrt(RevVoltageAverage / FwdVoltageAverage));
      SWR = SWR / (SWR_Cal * 5 / 1024);                     // Cal to scope (no between 0 and 5)
    }
    RevNumberOfLines = SWR * 5;                             // One segment per SWR increment

    AvCount = 0;
  }

  if ((PWR != oldPWR) || (SWR != oldSWR))                   // Update display if PWR or SWR has changed
  {
    //SendTelemetryData();
    UpdateDisplay();
    oldPWR = PWR;
    oldSWR = SWR;
  }
}


void SendTelemetryData()
{
  Serial.print("Foward Reading Average: ");
  Serial.println(FwdVoltageAverage, 3);
  Serial.print("Reverse Reading Average: ");
  Serial.println(RevVoltageAverage, 3);
  Serial.print("Power to Load: ");
  Serial.println(PWR);
  Serial.print("SWR: ");
  Serial.println(SWR);
  Serial.println("");
}

void UpdateDisplay()
{
  //Display PWR
  lcd.setCursor(0, 0);
  lcd.print("PWR");
  lcd.setCursor(3, 0);
  lcd.print("     ");
  lcd.setCursor(4, 0);
  lcd.print(PWR, 0);

  //Display SWR
  lcd.setCursor(8, 0);
  lcd.print("SWR");
  lcd.setCursor(11, 0);
  lcd.print("     ");
  lcd.setCursor(12, 0);
  lcd.print(SWR, 1);

  //Draw PWR Bar
  FwdNumberOfLinesToBlank = 29 - FwdNumberOfLines;
  lcd.setCursor(0, 1);
  lcd.print("P");
  while (FwdNumberOfLines >= 5)
  {
    lcd.write(5);
    FwdNumberOfLines = FwdNumberOfLines - 5;
  }

  if (FwdNumberOfLines == 1)
    lcd.write(1);
  if (FwdNumberOfLines == 2)
    lcd.write(2);
  if (FwdNumberOfLines == 3)
    lcd.write(3);
  if (FwdNumberOfLines == 4)
    lcd.write(4);

  while (FwdNumberOfLinesToBlank  >= 5)
  {
    lcd.print(" ");
    FwdNumberOfLinesToBlank = FwdNumberOfLinesToBlank - 5;
  }

  // Blank sedment before 'S'
  lcd.setCursor(7, 1);
  lcd.print(" ");

  //Draw SWR Bar
  RevNumberOfLinesToBlank = 29 - RevNumberOfLines;

  lcd.setCursor(8, 1);
  lcd.print("S");
  while (RevNumberOfLines >= 5)
  {
    lcd.write(5);
    RevNumberOfLines = RevNumberOfLines - 5;
  }

  if (RevNumberOfLines == 1)
    lcd.write(1);
  if (RevNumberOfLines == 2)
    lcd.write(2);
  if (RevNumberOfLines == 3)
    lcd.write(3);
  if (RevNumberOfLines == 4)
    lcd.write(4);

  while (RevNumberOfLinesToBlank >= 5)
  {
    lcd.print(" ");
    RevNumberOfLinesToBlank = RevNumberOfLinesToBlank - 5;
  }
}

Friday 11 January 2019

Homebrew 2 Tone Audio Oscillator


2 Tone oscillator. Based on two Wein bridge oscillators using LM358 op amps. See YouTube for detaails:

https://youtu.be/H3f-ex2S4Xw



Wednesday 14 November 2018

Homebrew SSB Rig based on MC1350P IF Amp

See the YouTube channel for details:

https://www.youtube.com/channel/UCSNPW3_gzuMJcX_ErBZTv2g



Audio Amplifier



Note. Original schematic had a 47uF capacitor between the two stages. That is now a 3.3uF (as above). This reduces the quiet time when changing from TX to RX.


Antenna RF Amplifier




VFO/BFO



The Si5351 outputs go to two 1k ohm trim pots. Note, this is not the correct approach. A 50 ohm pad with the appropriate attenuation should be used. The YouTube videos explain why this is incorrect. The wiper arm goes to the two ADE-1 mixers. Both trim pots are set to approx. 1/3 from min. Adjust from min to get acceptable receive audio quality.  



************************************************************
This is the new code that works with Jason Mildrum's new Si5351 Etherkit library. See https://github.com/etherkit/Si5351Arduino




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

const uint32_t bandStart = 7000000;     // start of 40m
const uint32_t bandEnd =   7300000;     // end of 40m
const uint32_t bandInit =  7100000;     // where to initially set the frequency
volatile long oldfreq = 0;
volatile long currentfreq = 0;
volatile int updatedisplay = 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 oldradix = 1;

volatile uint32_t BFO_freq = 8003000;    // Crystal filter centre freq

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

// 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);

  // Set up pull-up resistors on inputs
  digitalWrite(rotAPin, HIGH);
  digitalWrite(rotBPin, HIGH);
  digitalWrite(pushPin, HIGH);

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

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

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(31830, 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_CLK2, SI5351_DRIVE_8MA);
  si5351.set_freq((freq * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), 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);
  }
}


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;
            radix = radix / 10;
            if (radix < 1)
              radix = 100000;
          }
          else
          {
            freq = freq + radix;
            if (freq > bandEnd)
              freq = bandEnd;
          }
          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;
            radix = radix * 10;
            if (radix == 100000)
              radix = 1;
          }
          else
          {
            freq = freq - radix;
            if (freq < bandStart)
              freq = bandStart;
          }
          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.setCursor(0, 0);
  lcd.print(freq);
  lcd.setCursor(0, 1);
  lcd.print("ZL2CTM");

  if (radix != oldradix)                          // stops radix display flashing/blinking on freq change
  {
    lcd.setCursor(9, 0);
    lcd.print("       ");
    lcd.setCursor(9, 0);
    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;
  }
}


void SendFrequency()
{
  si5351.set_freq((freq * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
}


************************************************************



BPF (note, 24g wire used, not 26g)


1st IF Amp


2nd IF Amp

Note. T1 secondary is now from Pin 4 and earth. Pin 6 is tied to earth via a 100nF cap.



Mic Amp (using an electret mic)


Note. Changed output coupling capacitor to a 1uF. Reduces short term CW on TX. A 100nF cap works well too.






Additional replay to allow the BPF and follow RF amplifier to be used for both RX (antenna amplifier) and TX (RF pre-amplifier)


RF Pre-Amp using NE592 Video Amp


Initial idea for the RF power amplifier. Based on two BLF1043 LDMOS devices.
Also using an old laptop power supply. 18.5VDC, 3A.
Please see the YouTube channel for test results! 








  



RF Driver Amplifier.



Current layout of the final radio


AGC. Designed to provide 6-7VDC to the MC1350P.



RC phase shift oscillator for antenna/amp tuning.



Final code supporting dual band function.


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

const uint32_t bandStart80 = 3500000;     // start of 80m
const uint32_t bandStart40 = 7000000;     // start of 40m
const uint32_t bandEnd80 =   3900000;     // end of 80m
const uint32_t bandEnd40 =   7300000;     // end of 40m
const uint32_t bandInit =  3690000;       // where to initially set the frequency
volatile int band = 0;                    // 0 = 80m, 1 = 40m
volatile int oldband = 0;
volatile long oldfreq = 0;
volatile long currentfreq = 0;
volatile long freq80 = 3690000;
volatile long freq40 = 7120000;
volatile int updatedisplay = 0;
volatile long SMeterReadings[10];
volatile long SMeterAverage;
int AvCount = 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 oldradix = 1;

volatile uint32_t BFO_freq = 9001350;

// Rotary encoder pins and other inputs
static const int pushPin = 9;
static const int rotBPin = 2;
static const int rotAPin = 3;
static const int bandSW = 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;

void setup()
{
  // Set up frequency and radix switches
  pinMode(rotAPin, INPUT);
  pinMode(rotAPin, INPUT_PULLUP);
  pinMode(rotBPin, INPUT);
  pinMode(rotBPin, INPUT_PULLUP);
  pinMode(pushPin, INPUT);
  pinMode(pushPin, INPUT_PULLUP);
  pinMode(bandSW, INPUT_PULLUP);
  pinMode(A7, INPUT);

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

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

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(31830, 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_CLK2, SI5351_DRIVE_8MA);
  si5351.set_freq((freq * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
}


void loop()
{
  band = digitalRead(bandSW);
  if (band != oldband)                      // Check if band has changed
  {
    if (band == 0)                          // Was 40m, now 80m
    {
      freq40 = freq;                        // Store 40m VFO freq
      freq = freq80;                        // Make VFO = stored 80m freq
    }
    if (band == 1)                          // Was 80m, now 40m
    {
      freq80 = freq;                        // Store 80m VFO freq
      freq = freq40;                        // Make VFO = stored 40m freq
    }
    UpdateDisplay();
    oldband = band;
  }

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

  SMeterReadings[AvCount] = analogRead(A7);
  AvCount++;
  if (AvCount == 10)
  {
    SMeterAverage = (SMeterReadings[0] + SMeterReadings[1] + SMeterReadings[2] + SMeterReadings[3] + SMeterReadings[4]
                     + SMeterReadings[5] + SMeterReadings[6] + SMeterReadings[7] + SMeterReadings[8] + SMeterReadings[9]) / 10;
    UpdateDisplay();
    AvCount = 0;
  }
}


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;
            radix = radix * 10;
            if (radix > 100000)
              radix = 100000;
          }
          else
          {
            freq = freq + radix;
            if ((band == 0) && (freq > bandEnd80))
              freq = bandEnd80;
            if ((band == 1) && (freq > bandEnd40))
              freq = bandEnd40;
          }
          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;
            radix = radix / 10;
            if (radix < 1)
              radix = 1;
          }
          else
          {
            freq = freq - radix;
            if ((band == 0) && (freq < bandStart80))
              freq = bandStart80;
            if ((band == 1) && (freq < bandStart40))
              freq = bandStart40;
          }
          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.setCursor(0, 0);
  lcd.print(freq);
  lcd.setCursor(0, 1);
  lcd.print("ZL2CTM");

  lcd.setCursor(11, 1);
  if (SMeterAverage <= 850)
    lcd.print("S3 ");
  if ((SMeterAverage > 851) && (SMeterAverage <= 853))
    lcd.print("S4 ");
  if ((SMeterAverage > 854) && (SMeterAverage <= 864))
    lcd.print("S5 ");
  if ((SMeterAverage > 865) && (SMeterAverage <= 904))
    lcd.print("S6 ");
  if ((SMeterAverage > 905) && (SMeterAverage <= 989))
    lcd.print("S7 ");
  if ((SMeterAverage > 990) && (SMeterAverage <= 1010))
    lcd.print("S8 ");
  if (SMeterAverage > 1011)
    lcd.print("S9 ");

  if (radix != oldradix)                          // stops radix display flashing/blinking on freq change
  {
    lcd.setCursor(9, 0);
    lcd.print("       ");
    lcd.setCursor(9, 0);
    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;
  }
}


void SendFrequency()
{
  si5351.set_freq(((BFO_freq - freq) * 100ULL), SI5351_CLK0);
  si5351.set_freq((BFO_freq * 100ULL), SI5351_CLK2);
}