https://www.youtube.com/channel/UCSNPW3_gzuMJcX_ErBZTv2g/
Crystal Filter
IF Amps
Antenna Amp, 700Hz LPF
Initial Class E RF Amplifier Experiment (see YouTube video for explanation)
Final Class E Amplifier
Code
#include <LiquidCrystal_I2C.h>
#include <si5351.h>
const long band80mStart = 3500000; // start of 80m
const long band80mEnd = 3900000; // end of 80m
volatile uint32_t FilterCentreFreq = 8996500;
volatile uint32_t BFO = 8997243; // Filter centre freq plus offset to get 700Hz AF out.
volatile long freq = 3525000; // this is a variable (changes) - set it to the beginning of the band
volatile long radix = 100; // how much to change the frequency by, clicking the rotary encoder will change this.
volatile long oldfreq = 0;
volatile long currentfreq = 0;
volatile int updatedisplay = 0;
volatile int band = 0; // 0=80m, 1=40m
volatile int oldband = 0;
// Rotary encoder pins and other inputs
static const int rotAPin = 2;
static const int rotBPin = 3;
static const int pushPin = 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;
// 700Hz Osc
static const int Osc = 11;
static const int TuneSw = 9;
// Morse Key
static const int Key = 8;
unsigned long BreakInTimer;
unsigned long BreakInDelay = 1000; //1000mS - 1.0Sec
// Relays
static const int AntRelay = 7;
static const int PowerRelay = 6;
void setup()
{
// Set up I/O Pins
pinMode(rotAPin, INPUT_PULLUP);
pinMode(rotBPin, INPUT_PULLUP);
pinMode(pushPin, INPUT_PULLUP);
pinMode(TuneSw, INPUT_PULLUP);
pinMode(Key, INPUT_PULLUP);
pinMode(AntRelay, OUTPUT);
pinMode(PowerRelay, OUTPUT);
pinMode(Osc, OUTPUT);
digitalWrite(AntRelay, HIGH); // Default to Rx
digitalWrite(PowerRelay, HIGH); // Default to Rx
// Set up interrupt pins
attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);
// Initialize the display
lcd.begin();
lcd.backlight();
lcd.cursor();
// Initialize the DDS
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
si5351.set_correction(-400, 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.output_enable(SI5351_CLK1, 0); // Turn off CW carrier
// Update display and send start frequency
UpdateDisplay();
SendFrequency();
}
void loop()
{
if (digitalRead(Key) == LOW) // Check to see if CW key down
{
BreakInTimer = millis(); // Reset the break in timer
digitalWrite(AntRelay, LOW); // Swap over the antenna to Tx
digitalWrite(PowerRelay, LOW); // Rx 12VDC = 0, Tx 12VDC = 12
delay(25); // Allow time for relays to swap over (Spec sheet 10mS)
si5351.output_enable(SI5351_CLK1, 1); // Get ready to transmit CW carrier
SendFrequency(); // Send CW carrier
}
else // Key up
{
si5351.output_enable(SI5351_CLK1, 0); // Turn off the CW carrier
}
if ((millis() - BreakInTimer) >= BreakInDelay) // Check break in timer
{
digitalWrite(AntRelay, HIGH); // Swap over the antenna to Rx
digitalWrite(PowerRelay, HIGH); // Rx 12VDC = 12, Tx 12VDC = 0
}
if (digitalRead(TuneSw) == LOW) // Check tune switch
tone(Osc, 700); // Turn on the 700Hz tome
else
noTone(Osc); // Turn off the 700Hz tome
currentfreq = getfreq(); // Interrupt safe method to get the current frequency
if (currentfreq != oldfreq)
{
UpdateDisplay();
SendFrequency();
oldfreq = currentfreq;
}
if (!digitalRead(pushPin)) {
delay(10);
while (!digitalRead(pushPin))
{
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;
if (radix == 1000000)
radix = 100000;
else if (radix == 100000)
radix = 10000;
else if (radix == 10000)
radix = 1000;
else if (radix == 1000)
radix = 100;
else if (radix == 100)
radix = 10;
else if (radix == 10)
radix = 1;
else
radix = 1000000;
} else
{
freq = (freq + radix);
if (freq > 3900000)
freq = 3900000;
}
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;
if (radix == 1)
radix = 10;
else if (radix == 10)
radix = 100;
else if (radix == 100)
radix = 1000;
else if (radix == 1000)
radix = 10000;
else if (radix == 10000)
radix = 100000;
else if (radix == 100000)
radix = 1000000;
else
radix = 1;
} else
{
freq = (freq - radix);
if (freq < 3500000)
freq = 3500000;
}
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.print(" ");
lcd.setCursor(0, 0);
lcd.print(freq);
lcd.setCursor(10, 0);
lcd.print("ZL2CTM");
if (radix == 1)
lcd.setCursor(6, 0);
if (radix == 10)
lcd.setCursor(5, 0);
if (radix == 100)
lcd.setCursor(4, 0);
if (radix == 1000)
lcd.setCursor(3, 0);
if (radix == 10000)
lcd.setCursor(2, 0);
if (radix == 100000)
lcd.setCursor(1, 0);
if (radix == 1000000)
lcd.setCursor(0, 0);
}
void SendFrequency()
{
// VFO
si5351.set_freq(((freq + FilterCentreFreq) * 100ULL), SI5351_CLK0);
// BFO
si5351.set_freq((BFO * 100ULL), SI5351_CLK2);
// Transmit CW carrier
si5351.set_freq(((freq - 43) * 100ULL), SI5351_CLK1); // 43Hz to align Tx and Rx
}
Hi Charlie,
ReplyDeleteAfter discussing with you on youtube about your latest class E amp, I got inspired to try to get mine working. Changed my driver section to a discrete bjt totem pole and got it working, though still not to the results LTSpice predicted. I wrote it up here: http://kc9dlm.blogspot.com/2020/04/a-little-amplifier-success.html
Sorry the notes are not up to the standards you set :)
Cheers!
Ben, KC9DLM
Thanks Ben. I'll also take a look at a discrete bjt totem pole. I'll use a 3904/3906 combination.
DeleteThe more I read, the more I am convinced I don't know anything about transmitters, even after 4 different IRF510 schematics I've tried... Ed KC8SBV
ReplyDeleteI know the feeling and wallet going thin after 3 burned ldmos blf188, but I finaly got it. I guess knowledges learned during lifetime are most valuable, not the objects itself. Keep it up and good luck in future projects.
DeleteI will say I found this one hard, especially as i am not a RF design engineer. I'm tempted to build another one to see if I can replicate it.
ReplyDelete