Code. This will be updated as additional SDR DSP functions are added.
#include <Wire.h> // I2C comms
#include <si5351.h> // Si5351 library
#include <LiquidCrystal_I2C.h> // LCD library
// Define Constants and Vaviables
static const long bandStart = 1000000; // start of HF band
static const long bandEnd = 30000000; // end of HF band
static const long bandInit = 3690000; // where to initially set the frequency
volatile long oldfreq = 0;
volatile long freq = bandInit ;
volatile long radix = 1000; // how much to change the frequency by, clicking the rotary encoder will change this.
volatile int updatedisplay = 0;
// Rotary Encoder
static const int pushPin = 39;
static const int rotBPin = 36;
static const int rotAPin = 35;
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
volatile int rotAcc = 0;
// Instantiate the Objects
LiquidCrystal_I2C lcd(0x3F, 16, 2); // set the LCD address to either 0x27 or 0x3F for a 16 chars and 2 line display
Si5351 si5351;
void setup()
{
// Set up input switches
pinMode(rotAPin, INPUT);
pinMode(rotBPin, INPUT);
pinMode(pushPin, INPUT);
digitalWrite(rotAPin, HIGH);
digitalWrite(rotBPin, HIGH);
digitalWrite(pushPin, HIGH);
// Set up interrupt pins
attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);
// Initialise the lcd
lcd.begin();
lcd.backlight();
// Initialise the DDS
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
si5351.set_freq((freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);
UpdateDisplay();
}
void loop()
{
if (freq != oldfreq)
{
UpdateDisplay();
SendFrequency();
oldfreq = freq;
}
if (digitalRead(pushPin) == LOW)
{
delay(10);
while (digitalRead(pushPin) == LOW)
{
if (updatedisplay == 1)
{
UpdateDisplay();
updatedisplay = 0;
}
}
delay(50);
}
}
// 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 > 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;
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 < 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.cursor(); // Turn on the cursor
lcd.setCursor(0, 0);
lcd.print(" ");
lcd.setCursor(0, 0);
lcd.print(freq);
lcd.setCursor(10, 0);
lcd.print("ZL2CTM");
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
if (freq > 9999999)
{
if (radix == 1)
lcd.setCursor(7, 0);
if (radix == 10)
lcd.setCursor(6, 0);
if (radix == 100)
lcd.setCursor(5, 0);
if (radix == 1000)
lcd.setCursor(4, 0);
if (radix == 10000)
lcd.setCursor(3, 0);
if (radix == 100000)
lcd.setCursor(2, 0);
if (radix == 1000000)
lcd.setCursor(1, 0);
}
if (freq <= 9999999)
{
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()
{
si5351.set_freq((freq * 4) * 100ULL, SI5351_PLL_FIXED, SI5351_CLK0);
}
*****************************************************************************
Dual Quadrature NE612 Direct Conversion Front End
Antenna RF Amplifier
Please note the collector inductor below is 1mH NOT 1uH
80m/20m Bandpass Filter
Audio Pass-through Test Code
#include <Wire.h> // I2C comms
#include <si5351.h> // Si5351 library
#include <LiquidCrystal_I2C.h> // LCD library
#include <Audio.h> // Teensy audio library
// Define Constants and Vaviables
static const long bandStart = 1000000; // start of HF band
static const long bandEnd = 30000000; // end of HF band
static const long bandInit = 3690000; // where to initially set the frequency
volatile long oldfreq = 0;
volatile long freq = bandInit ;
volatile long radix = 1000; // how much to change the frequency by, clicking the rotary encoder will change this.
volatile int updatedisplay = 0;
// Rotary Encoder
static const int pushPin = 39;
static const int rotBPin = 36;
static const int rotAPin = 35;
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
volatile int rotAcc = 0;
// Instantiate the Objects
LiquidCrystal_I2C lcd(0x3F, 16, 2); // Set the LCD address to either 0x27 or 0x3F for a 16 chars and 2 line display
Si5351 si5351; // The Si5351 DDS
AudioControlSGTL5000 audioShield; // The Teensy audio CODEC on the audio shield
// Audio shield
AudioInputI2S audioInput; // What we call the input to the audio shield
AudioOutputI2S audioOutput; // What we call the output of the audio shield
AudioConnection patchCord5(audioInput, 0, audioOutput, 0); // Left channel in to left channel out
AudioConnection patchCord10(audioInput, 1, audioOutput, 1); // Right channel in to right channel out
void setup()
{
// Setup input switches
pinMode(rotAPin, INPUT);
pinMode(rotBPin, INPUT);
pinMode(pushPin, INPUT);
digitalWrite(rotAPin, HIGH);
digitalWrite(rotBPin, HIGH);
digitalWrite(pushPin, HIGH);
// Setup interrupt pins
attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);
// Setup the lcd
lcd.begin();
lcd.backlight();
// Setup the DDS
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
si5351.set_freq((freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);
// Setup the audio shield
AudioNoInterrupts();
AudioMemory(16);
audioShield.enable();
audioShield.inputSelect(AUDIO_INPUT_LINEIN);
audioShield.volume(0.7);
audioShield.unmuteLineout();
AudioInterrupts();
UpdateDisplay();
}
void loop()
{
if (freq != oldfreq)
{
UpdateDisplay();
SendFrequency();
oldfreq = freq;
}
if (digitalRead(pushPin) == LOW)
{
delay(10);
while (digitalRead(pushPin) == LOW)
{
if (updatedisplay == 1)
{
UpdateDisplay();
updatedisplay = 0;
}
}
delay(50);
}
}
// 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 > 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;
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 < 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.cursor(); // Turn on the cursor
lcd.setCursor(0, 0);
lcd.print(" ");
lcd.setCursor(0, 0);
lcd.print(freq);
lcd.setCursor(10, 0);
lcd.print("ZL2CTM");
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
if (freq > 9999999)
{
if (radix == 1)
lcd.setCursor(7, 0);
if (radix == 10)
lcd.setCursor(6, 0);
if (radix == 100)
lcd.setCursor(5, 0);
if (radix == 1000)
lcd.setCursor(4, 0);
if (radix == 10000)
lcd.setCursor(3, 0);
if (radix == 100000)
lcd.setCursor(2, 0);
if (radix == 1000000)
lcd.setCursor(1, 0);
}
if (freq <= 9999999)
{
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()
{
si5351.set_freq((freq * 4) * 100ULL, SI5351_PLL_FIXED, SI5351_CLK0);
}
************************************************************************
// Libraries
#include <Wire.h> // I2C comms library
#include <si5351.h> // Si5351Jason library
#include <LiquidCrystal_I2C.h> // LCD library
#include <Audio.h> // Teensy audio library
// Number of Filter Coefficients
#define NO_HILBERT_COEFFS 70 // Used to define the Hilbert transform filter arrays. More typical than 'const int'.
// Define Constants and Vaviables
static const long bandStart = 1000000; // start of HF band
static const long bandEnd = 30000000; // end of HF band
static const long bandInit = 3690000; // where to initially set the frequency
volatile long oldfreq = 0;
volatile long freq = bandInit ;
volatile long radix = 1000; // how much to change the frequency by clicking the rotary encoder will change this.
volatile int updatedisplay = 0;
// Rotary Encoder
static const int pushPin = 39;
static const int rotBPin = 36;
static const int rotAPin = 35;
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
volatile int rotAcc = 0;
// Iowa Hills Hilbert transform filter coefficients
const short Hilbert_Plus_45_Coeffs[NO_HILBERT_COEFFS] = {
(short)(32768 * -0.000287988910943357),
(short)(32768 * -0.000383511439791303),
(short)(32768 * -0.000468041804899774),
(short)(32768 * -0.000529324432676899),
(short)(32768 * -0.000569479602046985),
(short)(32768 * -0.000616670267768531),
(short)(32768 * -0.000731530748681977),
(short)(32768 * -0.001002372095321225),
(short)(32768 * -0.001525299390682192),
(short)(32768 * -0.002370114347025230),
(short)(32768 * -0.003539247773172147),
(short)(32768 * -0.004932965382552984),
(short)(32768 * -0.006337182914262393),
(short)(32768 * -0.007448193692118567),
(short)(32768 * -0.007940501940620482),
(short)(32768 * -0.007570802072162988),
(short)(32768 * -0.006296120449841751),
(short)(32768 * -0.004371955618154949),
(short)(32768 * -0.002391875073164555),
(short)(32768 * -0.001236984700413469),
(short)(32768 * -0.001922560128827416),
(short)(32768 * -0.005356720327533458),
(short)(32768 * -0.012055656297010635),
(short)(32768 * -0.021882952959947619),
(short)(32768 * -0.033888748300090733),
(short)(32768 * -0.046312736456333638),
(short)(32768 * -0.056783367797647665),
(short)(32768 * -0.062699937453677912),
(short)(32768 * -0.061735375084135742),
(short)(32768 * -0.052358513976237808),
(short)(32768 * -0.034257179158167443),
(short)(32768 * -0.008554500746482946),
(short)(32768 * 0.022249911747384360),
(short)(32768 * 0.054622962942346594),
(short)(32768 * 0.084568844473140448),
(short)(32768 * 0.108316122839950818),
(short)(32768 * 0.122979341462627859),
(short)(32768 * 0.127056096658453188),
(short)(32768 * 0.120656295327679283),
(short)(32768 * 0.105420364259485699),
(short)(32768 * 0.084152608145489444),
(short)(32768 * 0.060257510644444748),
(short)(32768 * 0.037105711921879434),
(short)(32768 * 0.017464092086704748),
(short)(32768 * 0.003100559033325746),
(short)(32768 * -0.005373489802481697),
(short)(32768 * -0.008418211280310166),
(short)(32768 * -0.007286730644726664),
(short)(32768 * -0.003638388931163832),
(short)(32768 * 0.000858330713630433),
(short)(32768 * 0.004847436504682235),
(short)(32768 * 0.007476399317750315),
(short)(32768 * 0.008440227567663121),
(short)(32768 * 0.007898970420636600),
(short)(32768 * 0.006314366257036837),
(short)(32768 * 0.004261033495040515),
(short)(32768 * 0.002261843500794377),
(short)(32768 * 0.000680212977485724),
(short)(32768 * -0.000319493110301691),
(short)(32768 * -0.000751893569425181),
(short)(32768 * -0.000752248417868501),
(short)(32768 * -0.000505487955986662),
(short)(32768 * -0.000184645628631330),
(short)(32768 * 0.000087913008490067),
(short)(32768 * 0.000253106348867209),
(short)(32768 * 0.000306473486382603),
(short)(32768 * 0.000277637042003169),
(short)(32768 * 0.000207782317481292),
(short)(32768 * 0.000132446796990356),
(short)(32768 * 0.000072894261560354)
};
// Iowa Hills Hilbert transform filter coefficients
const short Hilbert_Minus_45_Coeffs[NO_HILBERT_COEFFS] = {
(short)(32768 * -0.000072894261560345),
(short)(32768 * -0.000132446796990344),
(short)(32768 * -0.000207782317481281),
(short)(32768 * -0.000277637042003168),
(short)(32768 * -0.000306473486382623),
(short)(32768 * -0.000253106348867259),
(short)(32768 * -0.000087913008490148),
(short)(32768 * 0.000184645628631233),
(short)(32768 * 0.000505487955986583),
(short)(32768 * 0.000752248417868491),
(short)(32768 * 0.000751893569425298),
(short)(32768 * 0.000319493110301983),
(short)(32768 * -0.000680212977485245),
(short)(32768 * -0.002261843500793748),
(short)(32768 * -0.004261033495039842),
(short)(32768 * -0.006314366257036280),
(short)(32768 * -0.007898970420636345),
(short)(32768 * -0.008440227567663343),
(short)(32768 * -0.007476399317751102),
(short)(32768 * -0.004847436504683540),
(short)(32768 * -0.000858330713632029),
(short)(32768 * 0.003638388931162351),
(short)(32768 * 0.007286730644725833),
(short)(32768 * 0.008418211280310565),
(short)(32768 * 0.005373489802483816),
(short)(32768 * -0.003100559033321630),
(short)(32768 * -0.017464092086698697),
(short)(32768 * -0.037105711921871905),
(short)(32768 * -0.060257510644436532),
(short)(32768 * -0.084152608145481672),
(short)(32768 * -0.105420364259479538),
(short)(32768 * -0.120656295327675800),
(short)(32768 * -0.127056096658453216),
(short)(32768 * -0.122979341462631633),
(short)(32768 * -0.108316122839958146),
(short)(32768 * -0.084568844473150454),
(short)(32768 * -0.054622962942358168),
(short)(32768 * -0.022249911747396132),
(short)(32768 * 0.008554500746472333),
(short)(32768 * 0.034257179158159054),
(short)(32768 * 0.052358513976232306),
(short)(32768 * 0.061735375084133286),
(short)(32768 * 0.062699937453678217),
(short)(32768 * 0.056783367797650072),
(short)(32768 * 0.046312736456337288),
(short)(32768 * 0.033888748300094730),
(short)(32768 * 0.021882952959951244),
(short)(32768 * 0.012055656297013388),
(short)(32768 * 0.005356720327535105),
(short)(32768 * 0.001922560128828006),
(short)(32768 * 0.001236984700413229),
(short)(32768 * 0.002391875073163812),
(short)(32768 * 0.004371955618154038),
(short)(32768 * 0.006296120449840938),
(short)(32768 * 0.007570802072162439),
(short)(32768 * 0.007940501940620253),
(short)(32768 * 0.007448193692118624),
(short)(32768 * 0.006337182914262643),
(short)(32768 * 0.004932965382553323),
(short)(32768 * 0.003539247773172483),
(short)(32768 * 0.002370114347025498),
(short)(32768 * 0.001525299390682370),
(short)(32768 * 0.001002372095321316),
(short)(32768 * 0.000731530748682004),
(short)(32768 * 0.000616670267768521),
(short)(32768 * 0.000569479602046963),
(short)(32768 * 0.000529324432676881),
(short)(32768 * 0.000468041804899765),
(short)(32768 * 0.000383511439791304),
(short)(32768 * 0.000287988910943362)
};
// Instantiate the Objects
LiquidCrystal_I2C lcd(0x3F, 16, 2); // Name for the LCD. Set the LCD address to either 0x27 or 0x3F for a 16 chars and 2 line display
Si5351 si5351; // Name for the Si5351 DDS
AudioControlSGTL5000 audioShield; // Name for the Teensy audio CODEC on the audio shield
// Audio shield
AudioInputI2S audioInput; // Name for the input to the audio shield
AudioOutputI2S audioOutput; // Name for the output of the audio shield
// Receiver
AudioFilterFIR RX_Hilbert_Plus_45; // Name for the RX +45 Hilbert transform
AudioFilterFIR RX_Hilbert_Minus_45; // Name for the RX +45 Hilbert transform
AudioMixer4 RX_Summer; // Name for the RX summer
// Audio connections
AudioConnection patchCord5(audioInput, 0, RX_Hilbert_Plus_45, 0); // Left channel in Hilbert transform +45
AudioConnection patchCord10(audioInput, 1, RX_Hilbert_Minus_45, 0); // Right channel in Hilbert transform -45
AudioConnection patchCord15(RX_Hilbert_Plus_45, 0, RX_Summer, 0); // Hilbert transform +45 to receiver summer
AudioConnection patchCord20(RX_Hilbert_Minus_45, 0, RX_Summer, 1); // Hilbert transform -45 to receiver summer
AudioConnection patchCord25(RX_Summer, 0, audioOutput, 0); // Receiver summer to receiver LPF
void setup()
{
// Setup input switches
pinMode(rotAPin, INPUT);
pinMode(rotBPin, INPUT);
pinMode(pushPin, INPUT);
digitalWrite(rotAPin, HIGH);
digitalWrite(rotBPin, HIGH);
digitalWrite(pushPin, HIGH);
// Setup interrupt pins
attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);
// Setup the lcd
lcd.begin();
lcd.backlight();
// Setup the DDS
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
si5351.set_freq((freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);
// Setup the audio shield
AudioNoInterrupts();
AudioMemory(16);
audioShield.enable();
audioShield.volume(0.7); // Constant. Use external volume control on the audio amp
AudioInterrupts();
// Setup transceiver mode
Turn_On_Receiver();
UpdateDisplay();
}
void loop()
{
if (freq != oldfreq) // Check to see if the frequency has changed. If so, update everything.
{
UpdateDisplay();
SendFrequency();
oldfreq = freq;
}
if (digitalRead(pushPin) == LOW) // Update cursor, but also stop it from flickering
{
delay(10);
while (digitalRead(pushPin) == LOW)
{
if (updatedisplay == 1)
{
UpdateDisplay();
updatedisplay = 0;
}
}
delay(50);
}
}
void Turn_On_Receiver()
{
AudioNoInterrupts();
audioShield.inputSelect(AUDIO_INPUT_LINEIN);
audioShield.lineInLevel(5); // Default is 5
audioShield.unmuteHeadphone();
RX_Hilbert_Plus_45.begin(Hilbert_Plus_45_Coeffs, NO_HILBERT_COEFFS);
RX_Hilbert_Minus_45.begin(Hilbert_Minus_45_Coeffs, NO_HILBERT_COEFFS);
if (freq <= 9999999) // LSB
{
RX_Summer.gain(0, 1);
RX_Summer.gain(1, -1);
}
if (freq > 9999999) // USB
{
RX_Summer.gain(0, 1);
RX_Summer.gain(1, 1);
}
AudioInterrupts();
}
// 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 > 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;
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 < 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.cursor(); // Turn on the cursor
lcd.setCursor(0, 0);
lcd.print(" ");
lcd.setCursor(0, 0);
lcd.print(freq);
lcd.setCursor(10, 0);
lcd.print("ZL2CTM");
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
if (freq > 9999999)
{
if (radix == 1)
lcd.setCursor(7, 0);
if (radix == 10)
lcd.setCursor(6, 0);
if (radix == 100)
lcd.setCursor(5, 0);
if (radix == 1000)
lcd.setCursor(4, 0);
if (radix == 10000)
lcd.setCursor(3, 0);
if (radix == 100000)
lcd.setCursor(2, 0);
if (radix == 1000000)
lcd.setCursor(1, 0);
}
if (freq <= 9999999)
{
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()
{
si5351.set_freq((freq * 4) * 100ULL, SI5351_PLL_FIXED, SI5351_CLK0);
}
***************************************************************
Transmit Test Configuration
Transmit Test Code
Note, the formatting has been messed up from cutting and pasting. Use auto format after pasting into the Arduino IDE)
// Libraries
#include <Wire.h> // I2C comms library
#include <si5351.h> // Si5351Jason library
#include <LiquidCrystal_I2C.h> // LCD library
#include <Audio.h> // Teensy audio library
// Number of Filter Coefficients
#define NO_HILBERT_COEFFS 70 // Used to define the Hilbert transform filter arrays. More typical than 'const int'.
// Define Constants and Vaviables
static const long bandStart = 1000000; // start of HF band
static const long bandEnd = 30000000; // end of HF band
static const long bandInit = 3690000; // where to initially set the frequency
//static const long bandInit = 14190000; // where to initially set the frequency
volatile long oldfreq = 0;
volatile long freq = bandInit ;
volatile long radix = 1000; // how much to change the frequency by clicking the rotary encoder will change this.
volatile int updatedisplay = 0;
static const int Mic_Gain = 0; // Range is 0-63dB.
static const int Lineout_Gain = 20; // Range is 13-31. 13 = 3.16 Vp-p, 31 = 1.16 Vp-p
// Rotary Encoder
static const int pushPin = 39;
static const int rotBPin = 36;
static const int rotAPin = 35;
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
volatile int rotAcc = 0;
// Iowa Hills Hilbert transform filter coefficients
const short Hilbert_Plus_45_Coeffs[NO_HILBERT_COEFFS] = {
(short)(32768 * -0.000287988910943357),
(short)(32768 * -0.000383511439791303),
(short)(32768 * -0.000468041804899774),
(short)(32768 * -0.000529324432676899),
(short)(32768 * -0.000569479602046985),
(short)(32768 * -0.000616670267768531),
(short)(32768 * -0.000731530748681977),
(short)(32768 * -0.001002372095321225),
(short)(32768 * -0.001525299390682192),
(short)(32768 * -0.002370114347025230),
(short)(32768 * -0.003539247773172147),
(short)(32768 * -0.004932965382552984),
(short)(32768 * -0.006337182914262393),
(short)(32768 * -0.007448193692118567),
(short)(32768 * -0.007940501940620482),
(short)(32768 * -0.007570802072162988),
(short)(32768 * -0.006296120449841751),
(short)(32768 * -0.004371955618154949),
(short)(32768 * -0.002391875073164555),
(short)(32768 * -0.001236984700413469),
(short)(32768 * -0.001922560128827416),
(short)(32768 * -0.005356720327533458),
(short)(32768 * -0.012055656297010635),
(short)(32768 * -0.021882952959947619),
(short)(32768 * -0.033888748300090733),
(short)(32768 * -0.046312736456333638),
(short)(32768 * -0.056783367797647665),
(short)(32768 * -0.062699937453677912),
(short)(32768 * -0.061735375084135742),
(short)(32768 * -0.052358513976237808),
(short)(32768 * -0.034257179158167443),
(short)(32768 * -0.008554500746482946),
(short)(32768 * 0.022249911747384360),
(short)(32768 * 0.054622962942346594),
(short)(32768 * 0.084568844473140448),
(short)(32768 * 0.108316122839950818),
(short)(32768 * 0.122979341462627859),
(short)(32768 * 0.127056096658453188),
(short)(32768 * 0.120656295327679283),
(short)(32768 * 0.105420364259485699),
(short)(32768 * 0.084152608145489444),
(short)(32768 * 0.060257510644444748),
(short)(32768 * 0.037105711921879434),
(short)(32768 * 0.017464092086704748),
(short)(32768 * 0.003100559033325746),
(short)(32768 * -0.005373489802481697),
(short)(32768 * -0.008418211280310166),
(short)(32768 * -0.007286730644726664),
(short)(32768 * -0.003638388931163832),
(short)(32768 * 0.000858330713630433),
(short)(32768 * 0.004847436504682235),
(short)(32768 * 0.007476399317750315),
(short)(32768 * 0.008440227567663121),
(short)(32768 * 0.007898970420636600),
(short)(32768 * 0.006314366257036837),
(short)(32768 * 0.004261033495040515),
(short)(32768 * 0.002261843500794377),
(short)(32768 * 0.000680212977485724),
(short)(32768 * -0.000319493110301691),
(short)(32768 * -0.000751893569425181),
(short)(32768 * -0.000752248417868501),
(short)(32768 * -0.000505487955986662),
(short)(32768 * -0.000184645628631330),
(short)(32768 * 0.000087913008490067),
(short)(32768 * 0.000253106348867209),
(short)(32768 * 0.000306473486382603),
(short)(32768 * 0.000277637042003169),
(short)(32768 * 0.000207782317481292),
(short)(32768 * 0.000132446796990356),
(short)(32768 * 0.000072894261560354)
};
// Iowa Hills Hilbert transform filter coefficients
const short Hilbert_Minus_45_Coeffs[NO_HILBERT_COEFFS] = {
(short)(32768 * -0.000072894261560345),
(short)(32768 * -0.000132446796990344),
(short)(32768 * -0.000207782317481281),
(short)(32768 * -0.000277637042003168),
(short)(32768 * -0.000306473486382623),
(short)(32768 * -0.000253106348867259),
(short)(32768 * -0.000087913008490148),
(short)(32768 * 0.000184645628631233),
(short)(32768 * 0.000505487955986583),
(short)(32768 * 0.000752248417868491),
(short)(32768 * 0.000751893569425298),
(short)(32768 * 0.000319493110301983),
(short)(32768 * -0.000680212977485245),
(short)(32768 * -0.002261843500793748),
(short)(32768 * -0.004261033495039842),
(short)(32768 * -0.006314366257036280),
(short)(32768 * -0.007898970420636345),
(short)(32768 * -0.008440227567663343),
(short)(32768 * -0.007476399317751102),
(short)(32768 * -0.004847436504683540),
(short)(32768 * -0.000858330713632029),
(short)(32768 * 0.003638388931162351),
(short)(32768 * 0.007286730644725833),
(short)(32768 * 0.008418211280310565),
(short)(32768 * 0.005373489802483816),
(short)(32768 * -0.003100559033321630),
(short)(32768 * -0.017464092086698697),
(short)(32768 * -0.037105711921871905),
(short)(32768 * -0.060257510644436532),
(short)(32768 * -0.084152608145481672),
(short)(32768 * -0.105420364259479538),
(short)(32768 * -0.120656295327675800),
(short)(32768 * -0.127056096658453216),
(short)(32768 * -0.122979341462631633),
(short)(32768 * -0.108316122839958146),
(short)(32768 * -0.084568844473150454),
(short)(32768 * -0.054622962942358168),
(short)(32768 * -0.022249911747396132),
(short)(32768 * 0.008554500746472333),
(short)(32768 * 0.034257179158159054),
(short)(32768 * 0.052358513976232306),
(short)(32768 * 0.061735375084133286),
(short)(32768 * 0.062699937453678217),
(short)(32768 * 0.056783367797650072),
(short)(32768 * 0.046312736456337288),
(short)(32768 * 0.033888748300094730),
(short)(32768 * 0.021882952959951244),
(short)(32768 * 0.012055656297013388),
(short)(32768 * 0.005356720327535105),
(short)(32768 * 0.001922560128828006),
(short)(32768 * 0.001236984700413229),
(short)(32768 * 0.002391875073163812),
(short)(32768 * 0.004371955618154038),
(short)(32768 * 0.006296120449840938),
(short)(32768 * 0.007570802072162439),
(short)(32768 * 0.007940501940620253),
(short)(32768 * 0.007448193692118624),
(short)(32768 * 0.006337182914262643),
(short)(32768 * 0.004932965382553323),
(short)(32768 * 0.003539247773172483),
(short)(32768 * 0.002370114347025498),
(short)(32768 * 0.001525299390682370),
(short)(32768 * 0.001002372095321316),
(short)(32768 * 0.000731530748682004),
(short)(32768 * 0.000616670267768521),
(short)(32768 * 0.000569479602046963),
(short)(32768 * 0.000529324432676881),
(short)(32768 * 0.000468041804899765),
(short)(32768 * 0.000383511439791304),
(short)(32768 * 0.000287988910943362)
};
// Instantiate the Objects
LiquidCrystal_I2C lcd(0x3F, 16, 2); // Name for the LCD. Set the LCD address to either 0x27 or 0x3F for a 16 chars and 2 line display
Si5351 si5351; // Name for the Si5351 DDS
AudioControlSGTL5000 audioShield; // Name for the Teensy audio CODEC on the audio shield
// Audio shield
AudioInputI2S audioInput; // Name for the input to the audio shield (either line-in or mic)
AudioOutputI2S audioOutput; // Name for the output of the audio shield (either headphones or line-out)
// Transmitter
AudioFilterFIR TX_Hilbert_Plus_45; // Name for the TX +45 Hilbert transform
AudioFilterFIR TX_Hilbert_Minus_45; // Name for the TX +45 Hilbert transform
AudioMixer4 TX_I_Sideband_Switch; // Name for the sideband switching summer for the I channel
// Audio connections
AudioConnection patchCord50(audioInput, 0, TX_Hilbert_Plus_45, 0); // Mic audio to Hilbert transform +45
AudioConnection patchCord55(audioInput, 0, TX_Hilbert_Minus_45, 0); // Mic audio to Hilbert transform -45
AudioConnection patchCord60(TX_Hilbert_Plus_45, 0, TX_I_Sideband_Switch, 0); // Hilbert transform +45 to receiver summer
AudioConnection patchCord65(TX_I_Sideband_Switch, 0, audioOutput, 0); // Output to the NE612
AudioConnection patchCord70(TX_Hilbert_Minus_45, 0, audioOutput, 1); // Output to the NE612
void setup()
{
// Setup input switches
pinMode(rotAPin, INPUT);
pinMode(rotBPin, INPUT);
pinMode(pushPin, INPUT);
digitalWrite(rotAPin, HIGH);
digitalWrite(rotBPin, HIGH);
digitalWrite(pushPin, HIGH);
// Setup interrupt pins
attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);
// Setup the lcd
lcd.begin();
lcd.backlight();
// Setup the DDS
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
si5351.set_freq((freq * 100ULL), SI5351_PLL_FIXED, SI5351_CLK0);
// Setup the audio shield
AudioNoInterrupts();
AudioMemory(16);
audioShield.enable();
AudioInterrupts();
// Setup transceiver mode
Turn_On_Transmitter();
UpdateDisplay();
}
void loop()
{
if (freq != oldfreq) // Check to see if the frequency has changed. If so, update everything.
{
UpdateDisplay();
SendFrequency();
oldfreq = freq;
}
if (digitalRead(pushPin) == LOW) // Update cursor, but also stop it from flickering
{
delay(10);
while (digitalRead(pushPin) == LOW)
{
if (updatedisplay == 1)
{
UpdateDisplay();
updatedisplay = 0;
}
}
delay(50);
}
}
void Turn_On_Transmitter()
{
AudioNoInterrupts();
audioShield.inputSelect(AUDIO_INPUT_MIC);
audioShield.micGain(Mic_Gain);
audioShield.unmuteLineout(); // Output to the NE612s
audioShield.lineOutLevel(Lineout_Gain);
TX_Hilbert_Plus_45.begin(Hilbert_Plus_45_Coeffs, NO_HILBERT_COEFFS);
TX_Hilbert_Minus_45.begin(Hilbert_Minus_45_Coeffs, NO_HILBERT_COEFFS);
if (freq <= 9999999) // LSB
{
TX_I_Sideband_Switch.gain(0, 1);
}
if (freq > 9999999) // USB
{
TX_I_Sideband_Switch.gain(0, -1);
}
AudioInterrupts();
}
// 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 > 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;
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 < 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.cursor(); // Turn on the cursor
lcd.setCursor(0, 0);
lcd.print(" ");
lcd.setCursor(0, 0);
lcd.print(freq);
lcd.setCursor(10, 0);
lcd.print("ZL2CTM");
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
if (freq > 9999999)
{
if (radix == 1)
lcd.setCursor(7, 0);
if (radix == 10)
lcd.setCursor(6, 0);
if (radix == 100)
lcd.setCursor(5, 0);
if (radix == 1000)
lcd.setCursor(4, 0);
if (radix == 10000)
lcd.setCursor(3, 0);
if (radix == 100000)
lcd.setCursor(2, 0);
if (radix == 1000000)
lcd.setCursor(1, 0);
}
if (freq <= 9999999)
{
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()
{
si5351.set_freq((freq * 4) * 100ULL, SI5351_PLL_FIXED, SI5351_CLK0);
}
****************************************************************
Final Code (as is).
// Libraries
#include <Wire.h> // I2C comms library
#include <si5351.h> // Si5351Jason library
#include <LiquidCrystal_I2C.h> // LCD library
#include <Audio.h> // Teensy audio library
// Number of Filter Coefficients
#define NO_HILBERT_COEFFS 70 // Used to define the Hilbert transform filter arrays. More typical than 'const int'.
// Define Constants and Vaviables
static const long bandStart = 1000000; // start of HF band
static const long bandEnd = 30000000; // end of HF band
static const long bandInit = 3690000; // where to initially set the frequency
volatile long oldfreq = 0;
volatile long freq = bandInit ;
volatile long radix = 1000; // how much to change the frequency. Pushing the rotary encoder switch will change this.
volatile int updatedisplay = 0;
volatile int mode = 0; // 1 = LSB, 0 = USB
volatile int oldmode = 0;
volatile int Receive = 0; // 0 = Receiver off, 1 = Receiver on
volatile int Transmit = 0; // 0 = Transmitter off, 1 = Transmitter on
// Audio panel gains
static const int Mic_Gain = 0; // Range is 0-63dB.
static const int Lineout_Gain = 20; // Range is 13-31. 13 = 3.16 Vp-p, 31 = 1.16 Vp-p
static const int Linein_Gain = 5; // Range is 0-15. 0 = 3.12 Vp-p, 15 = 0.24 Vp-p. Default = 5
// Rotary Encoder
static const int EncoderPushButton = 39;
static const int rotBPin = 36;
static const int rotAPin = 35;
static const int ModeSwitch = 24;
static const int PTTSwitch = 25;
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
volatile int rotAcc = 0;
// Iowa Hills Hilbert transform filter coefficients
const short Hilbert_Plus_45_Coeffs[NO_HILBERT_COEFFS] = {
(short)(32768 * -0.000287988910943357),
(short)(32768 * -0.000383511439791303),
(short)(32768 * -0.000468041804899774),
(short)(32768 * -0.000529324432676899),
(short)(32768 * -0.000569479602046985),
(short)(32768 * -0.000616670267768531),
(short)(32768 * -0.000731530748681977),
(short)(32768 * -0.001002372095321225),
(short)(32768 * -0.001525299390682192),
(short)(32768 * -0.002370114347025230),
(short)(32768 * -0.003539247773172147),
(short)(32768 * -0.004932965382552984),
(short)(32768 * -0.006337182914262393),
(short)(32768 * -0.007448193692118567),
(short)(32768 * -0.007940501940620482),
(short)(32768 * -0.007570802072162988),
(short)(32768 * -0.006296120449841751),
(short)(32768 * -0.004371955618154949),
(short)(32768 * -0.002391875073164555),
(short)(32768 * -0.001236984700413469),
(short)(32768 * -0.001922560128827416),
(short)(32768 * -0.005356720327533458),
(short)(32768 * -0.012055656297010635),
(short)(32768 * -0.021882952959947619),
(short)(32768 * -0.033888748300090733),
(short)(32768 * -0.046312736456333638),
(short)(32768 * -0.056783367797647665),
(short)(32768 * -0.062699937453677912),
(short)(32768 * -0.061735375084135742),
(short)(32768 * -0.052358513976237808),
(short)(32768 * -0.034257179158167443),
(short)(32768 * -0.008554500746482946),
(short)(32768 * 0.022249911747384360),
(short)(32768 * 0.054622962942346594),
(short)(32768 * 0.084568844473140448),
(short)(32768 * 0.108316122839950818),
(short)(32768 * 0.122979341462627859),
(short)(32768 * 0.127056096658453188),
(short)(32768 * 0.120656295327679283),
(short)(32768 * 0.105420364259485699),
(short)(32768 * 0.084152608145489444),
(short)(32768 * 0.060257510644444748),
(short)(32768 * 0.037105711921879434),
(short)(32768 * 0.017464092086704748),
(short)(32768 * 0.003100559033325746),
(short)(32768 * -0.005373489802481697),
(short)(32768 * -0.008418211280310166),
(short)(32768 * -0.007286730644726664),
(short)(32768 * -0.003638388931163832),
(short)(32768 * 0.000858330713630433),
(short)(32768 * 0.004847436504682235),
(short)(32768 * 0.007476399317750315),
(short)(32768 * 0.008440227567663121),
(short)(32768 * 0.007898970420636600),
(short)(32768 * 0.006314366257036837),
(short)(32768 * 0.004261033495040515),
(short)(32768 * 0.002261843500794377),
(short)(32768 * 0.000680212977485724),
(short)(32768 * -0.000319493110301691),
(short)(32768 * -0.000751893569425181),
(short)(32768 * -0.000752248417868501),
(short)(32768 * -0.000505487955986662),
(short)(32768 * -0.000184645628631330),
(short)(32768 * 0.000087913008490067),
(short)(32768 * 0.000253106348867209),
(short)(32768 * 0.000306473486382603),
(short)(32768 * 0.000277637042003169),
(short)(32768 * 0.000207782317481292),
(short)(32768 * 0.000132446796990356),
(short)(32768 * 0.000072894261560354)
};
// Iowa Hills Hilbert transform filter coefficients
const short Hilbert_Minus_45_Coeffs[NO_HILBERT_COEFFS] = {
(short)(32768 * -0.000072894261560345),
(short)(32768 * -0.000132446796990344),
(short)(32768 * -0.000207782317481281),
(short)(32768 * -0.000277637042003168),
(short)(32768 * -0.000306473486382623),
(short)(32768 * -0.000253106348867259),
(short)(32768 * -0.000087913008490148),
(short)(32768 * 0.000184645628631233),
(short)(32768 * 0.000505487955986583),
(short)(32768 * 0.000752248417868491),
(short)(32768 * 0.000751893569425298),
(short)(32768 * 0.000319493110301983),
(short)(32768 * -0.000680212977485245),
(short)(32768 * -0.002261843500793748),
(short)(32768 * -0.004261033495039842),
(short)(32768 * -0.006314366257036280),
(short)(32768 * -0.007898970420636345),
(short)(32768 * -0.008440227567663343),
(short)(32768 * -0.007476399317751102),
(short)(32768 * -0.004847436504683540),
(short)(32768 * -0.000858330713632029),
(short)(32768 * 0.003638388931162351),
(short)(32768 * 0.007286730644725833),
(short)(32768 * 0.008418211280310565),
(short)(32768 * 0.005373489802483816),
(short)(32768 * -0.003100559033321630),
(short)(32768 * -0.017464092086698697),
(short)(32768 * -0.037105711921871905),
(short)(32768 * -0.060257510644436532),
(short)(32768 * -0.084152608145481672),
(short)(32768 * -0.105420364259479538),
(short)(32768 * -0.120656295327675800),
(short)(32768 * -0.127056096658453216),
(short)(32768 * -0.122979341462631633),
(short)(32768 * -0.108316122839958146),
(short)(32768 * -0.084568844473150454),
(short)(32768 * -0.054622962942358168),
(short)(32768 * -0.022249911747396132),
(short)(32768 * 0.008554500746472333),
(short)(32768 * 0.034257179158159054),
(short)(32768 * 0.052358513976232306),
(short)(32768 * 0.061735375084133286),
(short)(32768 * 0.062699937453678217),
(short)(32768 * 0.056783367797650072),
(short)(32768 * 0.046312736456337288),
(short)(32768 * 0.033888748300094730),
(short)(32768 * 0.021882952959951244),
(short)(32768 * 0.012055656297013388),
(short)(32768 * 0.005356720327535105),
(short)(32768 * 0.001922560128828006),
(short)(32768 * 0.001236984700413229),
(short)(32768 * 0.002391875073163812),
(short)(32768 * 0.004371955618154038),
(short)(32768 * 0.006296120449840938),
(short)(32768 * 0.007570802072162439),
(short)(32768 * 0.007940501940620253),
(short)(32768 * 0.007448193692118624),
(short)(32768 * 0.006337182914262643),
(short)(32768 * 0.004932965382553323),
(short)(32768 * 0.003539247773172483),
(short)(32768 * 0.002370114347025498),
(short)(32768 * 0.001525299390682370),
(short)(32768 * 0.001002372095321316),
(short)(32768 * 0.000731530748682004),
(short)(32768 * 0.000616670267768521),
(short)(32768 * 0.000569479602046963),
(short)(32768 * 0.000529324432676881),
(short)(32768 * 0.000468041804899765),
(short)(32768 * 0.000383511439791304),
(short)(32768 * 0.000287988910943362)
};
// Instantiate the Objects
LiquidCrystal_I2C lcd(0x3F, 16, 2); // Name for the LCD. Set the LCD address to either 0x27 or 0x3F for a 16 chars and 2 line display
Si5351 si5351; // Name for the Si5351 DDS
AudioControlSGTL5000 audioShield; // Name for the Teensy audio CODEC on the audio shield
// Audio shield
AudioInputI2S audioInput; // Name for the input to the audio shield (either line-in or mic)
AudioOutputI2S audioOutput; // Name for the output of the audio shield (either headphones or line-out)
// Receiver
AudioFilterFIR RX_Hilbert_Plus_45; // Name for the RX +45 Hilbert transform
AudioFilterFIR RX_Hilbert_Minus_45; // Name for the RX +45 Hilbert transform
AudioMixer4 RX_Summer; // Name for the RX summer
// Transmitter
AudioFilterFIR TX_Hilbert_Plus_45; // Name for the TX +45 Hilbert transform
AudioFilterFIR TX_Hilbert_Minus_45; // Name for the TX +45 Hilbert transform
AudioMixer4 TX_I_Sideband_Switch; // Name for the sideband switching summer for the I channel
// Audio connections
AudioConnection patchCord5(audioInput, 0, RX_Hilbert_Plus_45, 0); // Left channel in Hilbert transform +45
AudioConnection patchCord10(audioInput, 1, RX_Hilbert_Minus_45, 0); // Right channel in Hilbert transform -45
AudioConnection patchCord15(RX_Hilbert_Plus_45, 0, RX_Summer, 0); // Hilbert transform +45 to receiver summer
AudioConnection patchCord20(RX_Hilbert_Minus_45, 0, RX_Summer, 1); // Hilbert transform -45 to receiver summer
AudioConnection patchCord25(RX_Summer, 0, audioOutput, 0); // Receiver summer to receiver LPF
AudioConnection patchCord50(audioInput, 0, TX_Hilbert_Plus_45, 0); // Mic audio to Hilbert transform +45
AudioConnection patchCord55(audioInput, 0, TX_Hilbert_Minus_45, 0); // Mic audio to Hilbert transform -45
AudioConnection patchCord60(TX_Hilbert_Plus_45, 0, TX_I_Sideband_Switch, 0); // Hilbert transform +45 to receiver summer
AudioConnection patchCord65(TX_I_Sideband_Switch, 0, audioOutput, 0); // Output to the NE612
AudioConnection patchCord70(TX_Hilbert_Minus_45, 0, audioOutput, 1); // Output to the NE612
void setup()
{
// Setup input switches
pinMode(rotAPin, INPUT);
pinMode(rotBPin, INPUT);
pinMode(EncoderPushButton, INPUT);
pinMode(ModeSwitch, INPUT);
pinMode(PTTSwitch, INPUT);
digitalWrite(rotAPin, HIGH);
digitalWrite(rotBPin, HIGH);
digitalWrite(EncoderPushButton, HIGH);
digitalWrite(ModeSwitch, HIGH);
digitalWrite(PTTSwitch, HIGH);
// Setup interrupt pins
attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);
// Setup the lcd
lcd.begin();
lcd.backlight();
lcd.cursor();
// Setup the DDS
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
si5351.set_freq((freq * 4) * 100ULL, SI5351_PLL_FIXED, SI5351_CLK0);
// Setup the audio shield
AudioNoInterrupts();
AudioMemory(16);
audioShield.enable();
AudioInterrupts();
// Setup transceiver mode
Turn_On_Receiver();
Turn_Off_Transmitter();
UpdateDisplay();
}
void loop()
{
if (freq != oldfreq) // Check to see if the frequency has changed. If so, update everything.
{
UpdateDisplay();
SendFrequency();
oldfreq = freq;
}
if (digitalRead(EncoderPushButton) == LOW) // Update cursor, but also stop it from flickering
{
delay(50);
while (digitalRead(EncoderPushButton) == LOW)
{
if (updatedisplay == 1)
{
UpdateDisplay();
updatedisplay = 0;
}
}
delay(50);
}
if (digitalRead(ModeSwitch) == LOW)
mode = 1; // LSB
else
mode = 0; // USB
if (mode != oldmode) // Only update the display on mode change
{
UpdateMode();
UpdateDisplay();
oldmode = mode;
}
if ((digitalRead(PTTSwitch) == 0) && (Receive == 1)) // Transmit
{
delay(50);
if ((digitalRead(PTTSwitch) == 0) && (Receive == 1))
{
Turn_Off_Receiver();
Turn_On_Transmitter();
}
delay(50);
}
if ((digitalRead(PTTSwitch) == 1) && (Transmit == 1)) // Receive
{
delay(50);
if ((digitalRead(PTTSwitch) == 1) && (Transmit == 1))
{
Turn_Off_Transmitter();
Turn_On_Receiver();
}
delay(50);
}
}
void Turn_On_Receiver()
{
AudioNoInterrupts();
audioShield.inputSelect(AUDIO_INPUT_LINEIN);
audioShield.lineInLevel(Linein_Gain);
audioShield.unmuteHeadphone(); // Output to the audio amplifier
audioShield.volume(0.8);
RX_Hilbert_Plus_45.begin(Hilbert_Plus_45_Coeffs, NO_HILBERT_COEFFS);
RX_Hilbert_Minus_45.begin(Hilbert_Minus_45_Coeffs, NO_HILBERT_COEFFS);
UpdateMode();
AudioInterrupts();
Receive = 1;
}
void Turn_Off_Receiver()
{
AudioNoInterrupts();
audioShield.muteHeadphone();
RX_Hilbert_Plus_45.end();
RX_Hilbert_Minus_45.end();
AudioInterrupts();
Receive = 0;
}
void Turn_On_Transmitter()
{
AudioNoInterrupts();
audioShield.inputSelect(AUDIO_INPUT_MIC);
audioShield.micGain(Mic_Gain);
audioShield.unmuteLineout(); // Output to the NE612s
audioShield.lineOutLevel(Lineout_Gain);
TX_Hilbert_Plus_45.begin(Hilbert_Plus_45_Coeffs, NO_HILBERT_COEFFS);
TX_Hilbert_Minus_45.begin(Hilbert_Minus_45_Coeffs, NO_HILBERT_COEFFS);
UpdateMode();
AudioInterrupts();
Transmit = 1;
}
void Turn_Off_Transmitter()
{
AudioNoInterrupts();
audioShield.muteLineout();
TX_Hilbert_Plus_45.end();
TX_Hilbert_Minus_45.end();
AudioInterrupts();
Transmit = 0;
}
void UpdateMode()
{
if (Receive == 1)
{
if (mode == 1) // LSB
RX_Summer.gain(1, -1);
if (mode == 0) // USB
RX_Summer.gain(1, 1);
}
if (Transmit == 1)
{
if (mode == 1) // LSB
TX_I_Sideband_Switch.gain(0, 1);
if (mode == 0) // USB
TX_I_Sideband_Switch.gain(0, -1);
}
}
// 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(EncoderPushButton) == 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 > 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(EncoderPushButton) == 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 < 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.print(freq);
lcd.setCursor(10, 0);
lcd.print("ZL2CTM");
lcd.setCursor(0, 1);
if (mode == 1)
lcd.print("LSB");
else
lcd.print("USB");
if (freq > 9999999)
{
if (radix == 1)
lcd.setCursor(7, 0);
if (radix == 10)
lcd.setCursor(6, 0);
if (radix == 100)
lcd.setCursor(5, 0);
if (radix == 1000)
lcd.setCursor(4, 0);
if (radix == 10000)
lcd.setCursor(3, 0);
if (radix == 100000)
lcd.setCursor(2, 0);
if (radix == 1000000)
lcd.setCursor(1, 0);
}
if (freq <= 9999999)
{
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()
{
si5351.set_freq((freq * 4) * 100ULL, SI5351_PLL_FIXED, SI5351_CLK0);
}
Charlie, thanks a lot for the great video and explanation of the SDR theory and practice, looking forward for future video! 73! UT9UF
ReplyDeleteCheers Andrey. My intent is to explain things in the videos and not here. My typing is waaaaay too slow!
ReplyDelete73s
Charlie ZL2CTM
Great job with the videos as always! I wanted to ask if you've done any segments walking through how you peak the BPF? If not, I think that would be a good addition. I know you spoke on the theory of it in the last video, but seeing thebprocthe is very helpful!
ReplyDeleteThx
W4UNX
Thanks Jason. I might have tuned a BPF a while back, but I cannot recall which video. I'll certainly look to do it again.
Delete73s
Charlie ZL2CTM
Really enjoying this series Charlie. What is the source of the Liquid_crystal_I2C library?
ReplyDeleteThanks Bob. As for the library, I'm prety sure I got it from here:
Deletehttps://github.com/marcoschwartz/LiquidCrystal_I2C
Charlie
This comment has been removed by the author.
DeleteCharlie I have a working SSB SDR reciever!! Thx for blazing the trail with this series.
DeleteHave you posted the complete Teensy SDR transceiver code anywhere?
ReplyDeleteCharlie, thanks for posting this. I was never able to figure out how to use the Teensy Audio board for SDR. It is running just fine now with an rx from qrp-labs using si5351 programmed for quadrature output. Haven't measured opposite side band rejection yet but it so far sounds good.
ReplyDeleteTom, ak2b
Thanks Tom. I'm pleased you find it useful. The radio is never going to be a FlexRadio replacement, but that was never the aim. It's all about learning and seeing what can be done. Make sure you watch the accompanying videos on YouTube, as I do all the explaining there. My typing is way too slow to do it here. Regards.
DeleteCharlie
Maybe it won't be a Flex replacement but I have built radios that SOUND => commercial radios just by paying slight attention to the audio amp. You can get board with any radio except the ones you build yourself :)
DeleteP.S. I watch all your videos and appreciate the work that goes into them. I like the design explanations a lot!
ReplyDeleteVery helpful website and video's. A question on your teensy SSB demod Rx above. How much image rejection do you achieve? Cheers, Rob
ReplyDeleteHi Rob. I'm not too sure on that as I didn't do any specific tests. When I changed the tx to the other SB the rx didn't detect it. If I recall Pete Juliano N6QW built it and did some tests. Try his blog. Sorry about that.
DeleteHi Charlie, ok, I'll look.
DeleteJust roughly put together an SSB Rx using the teensy3.6/AD9850 DDS/SRA-6 mixers/74HC74 I,Q/J310 front-end + BPF's. It does demod but image looks about 10dB down. I read that I/Q phase and level need trimming to null out image and can be done within the teensy. So far all looks promising. Cheers, Rob
Thanks Rob. It was certainly interesting to try the Teensy audio library and see what could be done. Thanks again. Charlie
DeleteNice report on IQ demod and importance of IQ level/phase accuracy. I can now achieve >40dB image reject...
Deletehttps://www.microwavejournal.com/ext/resources/pdf-downloads/IQTheory-of-Operation.pdf?1336590796
Rob
Re-watching these videos and re-reading the blog. I've ordered the Teensy4 version of the Teensy audio board to go with the Teensy4 I recently received. I have the Teensy 3 version of the audio board, but its pinout doesn't match the Teensy 4. This new more powerful Teensy should work out great with an SDR rig.
ReplyDeleteDid you ever post the transceiver code?
ReplyDeleteYes. It's in the 80/40/20 SDR rig post.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteHi Charlie,
ReplyDeleteStumbled upon your videos. I've worked through #3 and am moving forward. Very well done. Have a question on your AF amp circuit schematic. You have put pins 6 and 9 as outputs on the NE612's for the Rx audio. Wondering if that's a typo? I thought it would be pin 5 for both of the NE612's based on listening to video #3?
Thanks,
Andy ...
Hi Andy. Yes you are right. I have updated the blog. Pin 5 for NE612 audio. Pin 6 and 9 is for the I and Q drive from the flip flops.
DeleteHello Charlie, thanks a lot for taking the effort and time to document and publish so many excellent projects. I was reading about your use of 100E to 1000E audio isolation transformers. There was a time these were abundantly available here but not anymore. Has anyone used toroids instead iron core EI type transformer please. Any guidance will be appreciated. Thanks again, Ashok VU2ASB
ReplyDeleteSorry Ashok. I am not aware of any toroid use. I'd say it would be impractical as you'd need mH of inductance.
DeleteHello Charlie, Has anyone explored using the teensy audio module for audio compression please? Thanks. DE VU2ASB Ashok
ReplyDeleteNot sure sorry Ashok. Try the Teensy website I know there is a vibrant community.
DeleteThanks Charlie, shall let you know if I discover something. 73s Ashok
DeleteHi Charlie, I am thinking of using the new and more powerful version 4 of Teensy along with the new audio shield made for it. Do you see an downside of doing so please. Thanks. Ashok VU2ASB
ReplyDeleteHi Ashok. You shouldn't have any problems at all. You might be able to do some more DSP with the extra CPU cycles. I should order one at some stage.
DeleteCharlie
ReplyDeleteI'm trying to learn how to design filters for the PJRC audio library. Specifically, I'm trying to precisely derive your results designing the Hilbert filter(s) with the Iowa Hills Software. Can you explain:
1 The Hilbert filter type you used ("phase offset" I assume)
2 The full set of input parameters
3 How you converted the coefficients into an array of C Shorts.
ThanKS
Joe
W3JDR
Hi Joe. Drop me a line on the QRZ address if you can.
ReplyDeleteCharlie ZL2CTM
This comment has been removed by the author.
ReplyDeleteHi Charlie , thanks for the explanation in video and this blog , i just learn SDR , i hope i can practice it , i will tell you if i done to made it ,
ReplyDeleteRudy
de YG3DBO
That's great Rudy.
ReplyDelete