https://www.youtube.com/channel/UCSNPW3_gzuMJcX_ErBZTv2g
Audio Amplifier
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.
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
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);
}