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);
}
Hi Charlie, I just finished building your amp this evening onto a breadboard. It works great! I substituted a LM380N-8 because that was all I had in my junk box. I also used my old Elenco Function Generator to provide a 1kz sine wave into the amp's input. Your amp has plenty of volume and I had to crank it way down not to hurt my ear! Thanks for sharing your circuit and design methodology. I will build the rest of the rig along side of you. 73 de Orville
ReplyDeleteThat's great Alan. It's a simple audio amp, but works really well. Just make sure you have a trim pot for Rf so you can tweak it to suit the final receiver gain. I've just finished the RF amp and that's working great too. I'll put a video up on that today.
ReplyDeleteCharlie
Took me some time to dig this up, check it, and clean the archive. It is a collection of articles by John Pivnichny N2DCH. He used the MC1350 a few times with single ended in/outputs. Tossed in a few articles on xtal filters (he wrote a book), a couple of linear display analog VFOs, and a couple of test gear (signal source, dbm meter, attenuators).
ReplyDeletehttps://bit.ly/2DVX1VB
Thanks again for sharing.
Excellent. Thanks Michael. Looks like some really interesting info in that. Thanks again.
DeleteCharlie
If you were looking for square wave at the drain for switching efficiency then the output of the driver chip should be okay. I see it looking at about 250 ohm at the LDMOS gate. To my way of thinking the load would be 11pf at signal voltage and 9 pf at the sum of the signal voltage and the change in the drain voltage. Maybe 90 pf equivalent. That's about 100 pf at 7 Mhz=270 ohms. If the LDMOS drive voltage needs to be about 5 V then and the chip outputs 10V then a 2:1 transformer. Then the load on the chip would be 2 channels * 100 mW. If it can handle a bit more then direct connection would be good.
ReplyDeleteThat is all guessing you are not trying for class A. Anyway 1 ohm input impedance seems to me way off.
Thanks Sean. I'm going to try some more experiments with a higher Vcc and different quiescent current level. I also know the output transformer is incorrect. I'll also look at potentially driving the LDMOS' directly from the driver as you suggest. Hopefully, no magic smoke will be released!
DeleteI looked at your equations and the circuit for the 2N3904 rf amp and thought of rearranging things a bit:
ReplyDeleteReplace the transformer with a bifilar wound one, windings connected in series. The output is capacitor coupled from the center tap, which provides the necessary 4:1 impedance transformation.
The feedback resistor is connected in series with the 6.8k bias resistor and now goes to the transformer center tap. I think this resistor now needs to be 125 ohms (would use 150). The 6.8K resistor is shunted with a 100nf capacitor.
With this arrangement we could switch around the amp with a DPDT relay. The two 'arms' of the relay are input and output via 100nf caps to the rest of the circuit. The 6.8K in parallel with the 100nf cap, both in series with the 150 ohm feedback resistor are connected to the relay switch arms. The NO contacts go to the base and transformer output connections. The NC contacts are shorted. Now the relay when energized the amp is powered (biased on) is in circuit. With the relay unenergized the amp is bypassed.
Thanks for that, I'll take a look. I'm starting to look more closely at feedback design to increase bandwidth. The hard part is trying to qualitatively determine what impact that has on input and output impedance. Thanks again for the feedback.
ReplyDeleteHi Charlie, I am a passionate radio builder in the field in Romania, I watched with great care the videos posted on youtube, as well as the constructive details on the blog. I am very interested in building such a RIG. Unfortunately, the SSB filter I have in my hand is retrieved from a Kenwood TS-120S with an 8.83 MHz frequency impedance of 600 ohms with 15pF. If you can help me with some details on the attenuator value from the output of the IF amplifier and the adaptor transformer from the input of the SSB filter and if at the input of the IF amplifier with the MC1350P still has an adaptation transformer if I use the Kenwood filter. Thanks in advance, Happy Holidays.
ReplyDelete73! by YO5PQJ, Alex
Hi Charlie, shouln't AvCount be set back to zero once it reaches 10? It's currently being reset to one.
ReplyDeleteHappy new year!
Cheers, Ian
Hi Ian. Yes you are absolutely correct. Well spotted. The code above has been updated.
DeleteThanks again.
Charlie
Hi Charlie,
ReplyDeleteIs the script for your MC1350 SSB rig on BitHub? Or, do I need to just copy it and paste into the IDE editor? I tried the last and some errors showed up that I don't really understand.
Thanks, Jack
This comment has been removed by a blog administrator.
ReplyDeleteno matching function for call to 'LiquidCrystal_I2C::begin()
ReplyDeletehow fix this err
I can't help from here sorry. Find a library that works with your display. Use the library examples to test this. Once it is going, copy across the syntax into the radio sketch.
Delete