Improved audio quality and IMD3 performance and experimental (amplitude) pre-distortion and calibration. Fixed an issue with spurious transmission for RX-TX-RX transitions.

In detail:
Reduced sample jitter by matching sampling rate to a multiple of OCR0A timer divider.
Unit angle now configurable and scalable so that full precision (of integer range) can be used to calculate phase differences; the unit angle is configured to be an exact multiple of the samp$
Hilbert transformer fixed bug, simplified and improved in precision (improved from -15dB to -40dB in an image rejection setting).
Arctan rounding errors reduced from approx. 5 degree to 0.8 degree.
Moved from 8 bit to 16 bit precision for audio input and I/Q values.
Microphone-input response now flat from 0-2200Hz (removed original high-pass filter with 20dB roll-off at 800Hz).
Experimental code (currently commented-out) for measuring phase differences between programmatically synchronised SI5351 clocks.
Fixed an issue where for every RX-TX transition a burst of random RF frequencies were emitted.
Improved precision of smeter, and found that disabling smeter prevent spurs leaking in from LCD data transfer into RXi (smeter still enabled).
Added a LUT-based pre-distortion algorithm that can be calibrated via an internal amplitude measurement.
Fixed issue with the envelope generation through Q6 impacting the IMD-performance, dynamic range, efficiency and thermal-stability; this fix REQUIRES the removal of C31 and increase of C32 to$
Fixed an issue where VOX was unintentially triggering TX by ADC noise leaking through key-shaping circuit, and simplied VOX algorithm.
Generaized PLL and MultiSynth algorithms and added experimental algorithm for setting a second frequency (via a different MS divider and where PLL is the same).
Experimental code for measuring the phase of the transmitted signal (via PA and QSD), this could give insight in antenna impedance and PA phase-response as function of amplitude.
experimental
guido 2019-04-02 18:34:03 +02:00
rodzic 43544ed2f4
commit aaa0df404e
2 zmienionych plików z 587 dodań i 202 usunięć

Wyświetl plik

@ -1,8 +1,8 @@
// Arduino Sketch of the QCX-SSB: SSB with your QCX transceiver (modification)
//
// https://github.com/threeme3/QCX-SSB
//
#define VERSION "1.00"
#define VERSION "1.01"
// QCX pin defintion
#define LCD_D4 0
@ -22,7 +22,7 @@
#define DVM 16 //A2
#define BUTTONS 17 //A3
#define LCD_RS 18
#define SDA 18 //hmm.. shared with LCD_RS
#define SDA 18 //shared with LCD_RS
#define SCL 19
#include <LiquidCrystal.h>
@ -125,19 +125,19 @@ inline void i2c_suspend()
#define SI_CLK2_PHOFF 167
#define SI_PLL_RESET 177
#define SI_R_DIV_1 0b00000000 // R-division ratio definitions
#define SI_R_DIV_32 0b01010000
#define SI_R_DIV_128 0b01110000
#define SI_MS_INT 0b01000000 // Clock control
#define SI_CLK_SRC_PLL_A 0b00000000
#define SI_CLK_SRC_PLL_B 0b00100000
#define SI_CLK_SRC_MS 0b00001100
#define SI_CLK_IDRV_8MA 0b00000011
#define SI_MSx_INT 0b01000000
#define SI_XTAL_FREQ 27003843 // Measured crystal frequency of XTAL2 for CL = 10pF
#define SI_XTAL_FREQ 27003843 // Measured crystal frequency of XTAL2 for CL = 10pF (default), calibrate your QCX 27MHz crystal frequency here.
#define log2(n) (log(n) / log(2))
volatile int32_t si5351_raw_freq;
volatile uint8_t si5351_prev_divider;
volatile int32_t si5351_raw_freq;
volatile uint8_t si5351_divider; // note: because of int8 only freq > 3.6MHz can be covered for R_DIV=1
volatile uint8_t si5351_mult;
volatile uint8_t si5351_pll_data[8];
@ -152,11 +152,11 @@ void si5351_SendRegister(uint8_t reg, uint8_t data)
i2c_stop();
}
inline void si5351_SendPLLBRegister()
inline void si5351_SendPLLBRegisterBulk() // fast freq change of PLLB, takes about [ 2 + 7*(8+1) + 2 ] / 840000 = 80 uS
{
i2c_start();
i2c_SendByte(SI_I2C_ADDR << 1);
i2c_SendByte(SI_SYNTH_PLL_B + 3); // Skip the first three pll_data bytes (first two always 0xFF and thirds not often changing
i2c_SendByte(SI_SYNTH_PLL_B + 3); // Skip the first three pll_data bytes (first two always 0xFF and third not often changing
i2c_SendByte(si5351_pll_data[3]);
i2c_SendByte(si5351_pll_data[4]);
i2c_SendByte(si5351_pll_data[5]);
@ -165,7 +165,8 @@ inline void si5351_SendPLLBRegister()
i2c_stop();
}
// Set up specified PLL with si5351_mult, num and denom
/*
// Set up specified PLL with mult, num and denom
// si5351_mult is 15..90
// num is 0..1,048,575 (0xFFFFF)
// denom is 0..1,048,575 (0xFFFFF)
@ -191,16 +192,16 @@ void si5351_SetupPLL(uint8_t pll, uint8_t mult, uint32_t num, uint32_t denom)
si5351_SendRegister(pll + 7, (P2 & 0x000000FF));
}
// Set up si5351_multiSynth with integer si5351_divider and R si5351_divider
// R si5351_divider is the bit value which is OR'ed onto the appropriate register
void si5351_SetupMultisynth(uint8_t synth, uint32_t divider, uint8_t rDiv)
// Set up MultiSynth with integer divider and R divider
// R divider is the bit value which is OR'ed onto the appropriate register
void si5351_SetupMultisynthInt(uint8_t synth, uint32_t divider, uint8_t rDiv)
{
uint32_t P1; // Synth config register P1
uint32_t P2; // Synth config register P2
uint32_t P3; // Synth config register P3
P1 = 128 * divider - 512;
P2 = 0; // P2 = 0, P3 = 1 forces an integer value for the si5351_divider
P2 = 0; // P2 = 0, P3 = 1 forces an integer value for the divider
P3 = 1;
si5351_SendRegister(synth + 0, (P3 & 0x0000FF00) >> 8);
@ -213,6 +214,63 @@ void si5351_SetupMultisynth(uint8_t synth, uint32_t divider, uint8_t rDiv)
si5351_SendRegister(synth + 7, (P2 & 0x000000FF));
}
// Set up MultiSynth with fractional divider, num and denom and R divider
// divider is 8..900 (and in addition 4,6 for integer mode)
// num is 0..1,048,575 (0xFFFFF)
// denom is 0..1,048,575 (0xFFFFF)
// for integer mode set: num = 0, denom = 1
void si5351_SetupMultisynthFrac(uint8_t synth, uint8_t divider, uint32_t num, uint32_t denom, uint8_t rDiv)
{
uint32_t P1; // Synth config register P1
uint32_t P2; // Synth config register P2
uint32_t P3; // Synth config register P3
P1 = (uint32_t)(128 * ((float)num / (float)denom));
P1 = (uint32_t)(128 * (uint32_t)(divider) + P1 - 512);
P2 = (uint32_t)(128 * ((float)num / (float)denom)); // P2 = 0, P3 = 1 forces an integer value for the divider
P2 = (uint32_t)(128 * num - denom * P2);
P3 = denom;
si5351_SendRegister(synth + 0, (P3 & 0x0000FF00) >> 8);
si5351_SendRegister(synth + 1, (P3 & 0x000000FF));
si5351_SendRegister(synth + 2, (P1 & 0x00030000) >> 16 | rDiv);
si5351_SendRegister(synth + 3, (P1 & 0x0000FF00) >> 8);
si5351_SendRegister(synth + 4, (P1 & 0x000000FF));
si5351_SendRegister(synth + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
si5351_SendRegister(synth + 6, (P2 & 0x0000FF00) >> 8);
si5351_SendRegister(synth + 7, (P2 & 0x000000FF));
}
*/
// Set up MultiSynth for register reg=MSNA, MNSB, MS0-5 with fractional divider, num and denom and R divider (for MSn, not for MSNA, MSNB)
// divider is 15..90 for MSNA, MSNB, divider is 8..900 (and in addition 4,6 for integer mode) for MS[0-5]
// num is 0..1,048,575 (0xFFFFF)
// denom is 0..1,048,575 (0xFFFFF)
// num = 0, denom = 1 forces an integer value for the divider
// r_div = 1..128 (1,2,4,8,16,32,64,128)
void si5351_SetupMultisynth(uint8_t reg, uint8_t divider, uint32_t num, uint32_t denom, uint8_t r_div)
{
uint32_t P1; // Synth config register P1
uint32_t P2; // Synth config register P2
uint32_t P3; // Synth config register P3
P1 = (uint32_t)(128 * ((float)num / (float)denom));
P1 = (uint32_t)(128 * (uint32_t)(divider) + P1 - 512);
P2 = (uint32_t)(128 * ((float)num / (float)denom));
P2 = (uint32_t)(128 * num - denom * P2);
P3 = denom;
si5351_SendRegister(reg + 0, (P3 & 0x0000FF00) >> 8);
si5351_SendRegister(reg + 1, (P3 & 0x000000FF));
si5351_SendRegister(reg + 2, (P1 & 0x00030000) >> 16 | ((int)log2(r_div) << 4) );
si5351_SendRegister(reg + 3, (P1 & 0x0000FF00) >> 8);
si5351_SendRegister(reg + 4, (P1 & 0x000000FF));
si5351_SendRegister(reg + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
si5351_SendRegister(reg + 6, (P2 & 0x0000FF00) >> 8);
si5351_SendRegister(reg + 7, (P2 & 0x000000FF));
}
// this function relies on cached (global) variables: si5351_divider, si5351_mult, si5351_raw_freq, si5351_pll_data
inline void si5351_freq_calc_fast(int16_t freq_offset)
{ // freq_offset is relative to freq set in si5351_freq(freq)
// uint32_t num128 = ((si5351_divider * (si5351_raw_freq + offset)) % SI_XTAL_FREQ) * (float)(0xFFFFF * 128) / SI_XTAL_FREQ;
@ -225,6 +283,9 @@ inline void si5351_freq_calc_fast(int16_t freq_offset)
// Set up specified PLL with si5351_mult, num and denom: si5351_mult is 15..90, num128 is 0..128*1,048,575 (128*0xFFFFF), denom is 0..1,048,575 (0xFFFFF)
uint32_t P1 = 128 * si5351_mult + (num128 / 0xFFFFF) - 512;
uint32_t P2 = num128 % 0xFFFFF;
//si5351_pll_data[0] = 0xFF;
//si5351_pll_data[1] = 0xFF;
//si5351_pll_data[2] = (P1 >> 14) & 0x0C;
si5351_pll_data[3] = P1 >> 8;
si5351_pll_data[4] = P1;
si5351_pll_data[5] = 0xF0 | (P2 >> 16);
@ -232,60 +293,54 @@ inline void si5351_freq_calc_fast(int16_t freq_offset)
si5351_pll_data[7] = P2;
}
uint16_t si5351_div(uint32_t num, uint32_t denom, uint32_t* b, uint32_t* c)
{ // returns a + b / c = num / denom, where a is the integer part and b and c is the optional fractional part 20 bits each (range 0..1048575)
uint16_t a = num / denom;
if(b && c){
uint64_t l = num % denom;
l <<= 20; l--; // l *= 1048575;
l /= denom; // normalize
*b = l;
*c = 0xFFFFF; // for simplicity set c to the maximum 1048575
}
return a;
}
volatile int32_t pll_freq; // temporary
void si5351_freq(uint32_t freq, uint8_t i, uint8_t q)
{
uint8_t r_div = (freq > 4000000) ? 1 : (freq > 400000) ? 32 : 128; // make sure that si5351_divider is in range 6..255
freq *= r_div; // Calculate frequency before r_div
{ // Fout = Fvco / (R * [MSx_a + MSx_b/MSx_c]), Fvco = Fxtal * [MSPLLx_a + MSPLLx_b/MSPLLx_c]; MSx as integer reduce spur
uint8_t r_div = (freq > 4000000) ? 1 : (freq > 400000) ? 32 : 128; // helps si5351_divider to be in range
freq *= r_div; // take r_div into account, now freq is in the range 1MHz to 150MHz
si5351_raw_freq = freq; // cache frequency generated by PLL and MS stages (excluding R divider stage); used by si5351_freq_calc_fast()
// freq is in the range 1MHz to 150MHz
si5351_divider = 900000000 / freq; // Calculate the division ratio. 900,000,000 is the maximum internal PLL freq: 900MHz
if(si5351_divider % 2) si5351_divider--; // Ensure an even integer division ratio
si5351_divider = 900000000 / freq; // Calculate the division ratio. 900,000,000 is the maximum internal PLL freq (official range 600..900MHz but can be pushed to 300MHz..~1200Mhz)
if(si5351_divider % 2) si5351_divider--; // si5351_divider in range 8.. 900 (including 4,6 for integer mode), even numbers preferred. Note that uint8 datatype is used, so 254 is upper limit
if( (si5351_divider * (freq - 5000) / SI_XTAL_FREQ) != (si5351_divider * (freq + 5000) / SI_XTAL_FREQ) ) si5351_divider -= 2; // Test if si5351_multiplier remains same for freq deviation +/- 5kHz, if not use different si5351_divider to make same
int32_t pll_freq = si5351_divider * freq; // Calculate the pll_freq: the si5351_divider * desired output freq
/*int32_t*/ pll_freq = si5351_divider * freq; // Calculate the pll_freq: the si5351_divider * desired output freq
uint32_t num, denom;
si5351_mult = si5351_div(pll_freq, SI_XTAL_FREQ, &num, &denom); // Determine the mult to get to the required pll_freq (in the range 15..90)
si5351_raw_freq = freq; // frequency of PLL and MS without taking R divider into account; used by si5351_freq_calc_fast()
si5351_mult = pll_freq / SI_XTAL_FREQ; // Determine the si5351_multiplier to get to the required pll_freq (in the range 15..90)
uint64_t l = pll_freq % SI_XTAL_FREQ; // // distance of pll_freq with si5351_multiple of xtal frequency. It has three parts:
l <<= 20; l--; // l *= 1048575;
l /= SI_XTAL_FREQ; // normalize
uint32_t num = l; // the actual si5351_multiplier is si5351_mult + num / denom
//num and denom are the fractional parts, the numerator and denominator each is 20 bits (range 0..1048575)
const uint32_t denom = 0xFFFFF; // For simplicity we set the denominator to the maximum 1048575
// Set up specified PLL with si5351_mult, num and denom: si5351_mult is 15..90, num is 0..1,048,575 (0xFFFFF), denom is 0..1,048,575 (0xFFFFF)
uint32_t term = num * 128 / denom; // 128.0 * (num / denom)
uint32_t P1 = 128 * si5351_mult + term - 512;
uint32_t P2 = 128 * num - denom * term;
uint32_t P3 = denom;
si5351_pll_data[0] = 0xFF;
si5351_pll_data[1] = 0xFF;
si5351_pll_data[2] = (P1 >> 14) & 0x0C;
si5351_pll_data[3] = P1 >> 8;
si5351_pll_data[4] = P1;
si5351_pll_data[5] = 0xF0 | ((P2 & 0x000F0000) >> 16);
si5351_pll_data[6] = P2 >> 8;
si5351_pll_data[7] = P2;
// Set up PLL A and PLL B with the calculated si5351_multiplication ratio
si5351_SetupPLL(SI_SYNTH_PLL_A, si5351_mult, num, denom);
si5351_SetupPLL(SI_SYNTH_PLL_B, si5351_mult, num, denom);
// Set up si5351_multiSynth si5351_divider 0,1,2 with the calculated si5351_divider, from 6..1800.
// The final R division stage can divide by a power of two, from 1..128 represented by constants SI_R_DIV1 to SI_R_DIV128
// Set up specified PLL with mult, num and denom: mult is 15..90, num is 0..1,048,575 (0xFFFFF), denom is 0..1,048,575 (0xFFFFF)
// Set up PLL A and PLL B with the calculated multiplication ratio
si5351_SetupMultisynth(SI_SYNTH_PLL_A, si5351_mult, num, denom, 1);
si5351_SetupMultisynth(SI_SYNTH_PLL_B, si5351_mult, num, denom, 1);
//if(denom == 1) si5351_SendRegister(22, SI_MSx_INT); // FBA_INT: MSNA operates in integer mode
//if(denom == 1) si5351_SendRegister(23, SI_MSx_INT); // FBB_INT: MSNB operates in integer mode
// Set up MultiSynth 0,1,2 with the calculated divider, from 4, 6..1800.
// The final R division stage can divide by a power of two, from 1..128
// if you want to output frequencies below 1MHz, you have to use the final R division stage
uint8_t rDiv = r_div == 1 ? SI_R_DIV_1 : r_div == 32 ? SI_R_DIV_32 : SI_R_DIV_128;
si5351_SetupMultisynth(SI_SYNTH_MS_0, si5351_divider, rDiv);
si5351_SetupMultisynth(SI_SYNTH_MS_1, si5351_divider, rDiv);
si5351_SetupMultisynth(SI_SYNTH_MS_2, si5351_divider, rDiv);
si5351_SetupMultisynth(SI_SYNTH_MS_0, si5351_divider, 0, 1, r_div);
si5351_SetupMultisynth(SI_SYNTH_MS_1, si5351_divider, 0, 1, r_div);
si5351_SetupMultisynth(SI_SYNTH_MS_2, si5351_divider, 0, 1, r_div);
// Set I/Q phase
si5351_SendRegister(SI_CLK0_PHOFF, i * si5351_divider / 90); // one LSB equivalent to a time delay of Tvco/4 range 0..127
si5351_SendRegister(SI_CLK1_PHOFF, q * si5351_divider / 90); // one LSB equivalent to a time delay of Tvco/4 range 0..127
// Switch on the CLK0, CLK1 output to be PLL A and set si5351_multiSynth0, si5351_multiSynth1 input
// (0x4F = SI_MS0_INT | SI_CLK_SRC_MS | SI_CLK_IDRV_8MA)
si5351_SendRegister(SI_CLK0_CONTROL, 0x4F | SI_CLK_SRC_PLL_A);
si5351_SendRegister(SI_CLK1_CONTROL, 0x4F | SI_CLK_SRC_PLL_A);
// Switch on the CLK0, CLK1 output to be PLL A and set si5351_multiSynth0, si5351_multiSynth1 input (0x0F = SI_CLK_SRC_MS | SI_CLK_IDRV_8MA)
si5351_SendRegister(SI_CLK0_CONTROL, 0x0F | SI_MSx_INT | SI_CLK_SRC_PLL_A);
si5351_SendRegister(SI_CLK1_CONTROL, 0x0F | SI_MSx_INT | SI_CLK_SRC_PLL_A);
// Switch on the CLK2 output to be PLL B and set si5351_multiSynth2 input
si5351_SendRegister(SI_CLK2_CONTROL, 0x4F | SI_CLK_SRC_PLL_B);
si5351_SendRegister(SI_CLK2_CONTROL, 0x0F | SI_MSx_INT | SI_CLK_SRC_PLL_B);
// Reset the PLL. This causes a glitch in the output. For small changes to
// the parameters, you don't need to reset the PLL, and there is no glitch
if((abs(pll_freq - si5351_prev_pll_freq) > 16000000L) || si5351_divider != si5351_prev_divider) {
@ -296,14 +351,32 @@ void si5351_freq(uint32_t freq, uint8_t i, uint8_t q)
si5351_SendRegister(SI_CLK_OE, 0b11111100); // Enable CLK1_EN|CLK0_EN
}
void si5351_alt_clk2(uint32_t freq)
{
uint32_t num, denom;
uint16_t mult = si5351_div(pll_freq, freq, &num, &denom);
si5351_SetupMultisynth(SI_SYNTH_MS_2, mult, num, denom, 1);
// Switch on the CLK2 output to be PLL A and set si5351_multiSynth2 input
si5351_SendRegister(SI_CLK2_CONTROL, 0x0F | SI_CLK_SRC_PLL_A);
si5351_SendRegister(SI_CLK_OE, 0b11111000); // Enable CLK2_EN|CLK1_EN|CLK0_EN
si5351_SendRegister(SI_CLK0_PHOFF, 0 * si5351_divider / 90); // one LSB equivalent to a time delay of Tvco/4 range 0..127
si5351_SendRegister(SI_CLK1_PHOFF, 90 * si5351_divider / 90); // one LSB equivalent to a time delay of Tvco/4 range 0..127
si5351_SendRegister(SI_CLK2_PHOFF, 45 * si5351_divider / 90); // one LSB equivalent to a time delay of Tvco/4 range 0..127
si5351_SendRegister(SI_PLL_RESET, 0xA0);
}
void si5351_freq_clk2(uint32_t freq)
{
uint8_t r_div = (freq > 4000000) ? 1 : (freq > 400000) ? 32 : 128; // make sure that si5351_divider is in range 6..255
uint8_t r_div = (freq > 4000000) ? 1 : (freq > 400000) ? 32 : 128; // helps si5351_divider to be in range
freq *= r_div; // Calculate frequency before r_div
// freq is in the range 1MHz to 150MHz
si5351_divider = 900000000 / freq; // Calculate the division ratio (in the range 6..1800). 900,000,000 is the maximum internal PLL freq: 900MHz
if (si5351_divider % 2) si5351_divider--; // Ensure an even integer division ratio
si5351_divider = 900000000 / freq; // Calculate the division ratio. 900,000,000 is the maximum internal PLL freq (official range 600..900MHz but can be pushed to 300MHz..>900Mhz)
if(si5351_divider % 2) si5351_divider--; // si5351_divider in range 4,6.. 254 (could be 900 for uint16 datatype), even numbers preferred
if( (si5351_divider * (freq-5000) / SI_XTAL_FREQ) != (si5351_divider * (freq+5000) / SI_XTAL_FREQ) ) si5351_divider-=2; // Test if si5351_multiplier remains same for freq deviation +/- 5kHz, if not use different si5351_divider to make same
uint32_t pll_freq = si5351_divider * freq; // Calculate the pll_freq: the si5351_divider * desired output freq
@ -327,7 +400,42 @@ void si5351_freq_clk2(uint32_t freq)
si5351_pll_data[5] = 0xF0 | ((P2 & 0x000F0000) >> 16);
si5351_pll_data[6] = P2 >> 8;
si5351_pll_data[7] = P2;
si5351_SendPLLBRegister();
si5351_SendPLLBRegisterBulk();
si5351_SendRegister(SI_CLK_OE, 0b11111000); //CLK2_EN=en, CLK1_EN, CLK0_EN
}
void si5351_freq_clk2_10millihz(uint32_t freq)
{
uint8_t r_div = ((freq/100UL) > 4000000) ? 1 : ((freq/100UL) > 400000) ? 32 : 128; // helps si5351_divider to be in range
freq *= r_div; // Calculate frequency before r_div
// freq is in the range 1MHz to 150MHz
si5351_divider = 900000000 / (freq/100UL); // Calculate the division ratio. 900,000,000 is the maximum internal PLL freq (official range 600..900MHz but can be pushed to 300MHz..>900Mhz)
if(si5351_divider % 2) si5351_divider--; // si5351_divider in range 4,6.. 254 (could be 900 for uint16 datatype), even numbers preferred
if( (si5351_divider * ((freq/100UL)-5000) / SI_XTAL_FREQ) != (si5351_divider * ((freq/100UL)+5000) / SI_XTAL_FREQ) ) si5351_divider-=2; // Test if si5351_multiplier remains same for freq deviation +/- 5kHz, if not use different si5351_divider to make same
uint64_t pll_freq = (uint64_t)si5351_divider * (uint64_t)freq; // Calculate the pll_freq: the si5351_divider * desired output freq
si5351_mult = pll_freq / (SI_XTAL_FREQ*100ULL); // Determine the si5351_multiplier to get to the required pll_freq (in the range 15..90)
uint64_t l = pll_freq % (SI_XTAL_FREQ*100ULL); // distance of pll_freq with si5351_multiple of xtal frequency. It has three parts:
l <<= 20; l--; // l *= 1048575;
l /= (SI_XTAL_FREQ*100ULL); // normalize
uint32_t num = l; // the actual si5351_multiplier is si5351_mult + num / denom
//num and denom are the fractional parts, the numerator and denominator each is 20 bits (range 0..1048575)
const uint32_t denom = 0xFFFFF; // For simplicity we set the denominator to the maximum 1048575
// Set up specified PLL with si5351_mult, num and denom: si5351_mult is 15..90, num is 0..1,048,575 (0xFFFFF), denom is 0..1,048,575 (0xFFFFF)
uint32_t term = num * 128 / denom; // 128.0 * (num / denom)
uint32_t P1 = 128 * si5351_mult + term - 512;
uint32_t P2 = 128 * num - denom * term;
uint32_t P3 = denom;
si5351_pll_data[0] = 0xFF;
si5351_pll_data[1] = 0xFF;
si5351_pll_data[2] = (P1 >> 14) & 0x0C;
si5351_pll_data[3] = P1 >> 8;
si5351_pll_data[4] = P1;
si5351_pll_data[5] = 0xF0 | ((P2 & 0x000F0000) >> 16);
si5351_pll_data[6] = P2 >> 8;
si5351_pll_data[7] = P2;
si5351_SendPLLBRegisterBulk();
si5351_SendRegister(SI_CLK_OE, 0b11111000); //CLK2_EN=en, CLK1_EN, CLK0_EN
}
@ -335,123 +443,275 @@ volatile bool usb = true;
volatile bool change = true;
volatile int32_t freq = 7074000;
void set_tx(bool en)
{
si5351_SendRegister(SI_CLK_OE, en ? 0b11111011 : 0b11111100); //CLK2_EN=en, CLK1_EN=CLK0_EN=/en
digitalWrite(RX, en ? LOW : HIGH);
}
inline int16_t arctan2(int8_t q, int8_t i)
{
int16_t phase;
int8_t abs_q = abs(q);
if(i < 0)
phase = 135 - 45 * (i + abs(q)) / ((abs(q) - i) == 0 ? 1 : (abs(q) - i));
else
phase = 45 - 45 * (i - abs(q)) / ((i + abs(q)) == 0 ? 1 : (i + abs(q)));
return (q < 0) ? -phase : phase; // negate if in quad III or IV
}
volatile uint8_t vox_hangtime;
volatile uint8_t tx = 0;
volatile bool vox_enable = false;
#define VOX_THRESHOLD 4
inline void vox(int8_t amp)
//volatile uint8_t vox_hangtime = 1;
/*
inline void vox(bool vox_trigger)
{
if(vox_enable){
bool vox_trigger = (amp > VOX_THRESHOLD); // vox_enable sensitivity
switch(vox_hangtime){
switch(vox_hangtime){
case 0: // step 1: VOX triggered -> TX on, minimum initial hangtime
if(vox_trigger){
vox_hangtime = 2;
set_tx(true);
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
digitalWrite(RX, LOW); // TX
}
return 0;
case 1: // step 3: VOX not triggered recently (hangtime exceeded) -> TX off
vox_hangtime--;
set_tx(false);
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
return 0;
default: // step 2: still VOX triggered -> reset hangtime
vox_hangtime = (vox_trigger) ? 255 : vox_hangtime - 1; // 255/F_SAMP = 50ms ( =~ 15 300Hz periods)
break;
}
#ifdef notdef
if(vox_trigger) vox_hangtime++; else vox_hangtime--;
switch(vox_hangtime){
case 0: vox_hangtime++; break; // clip to lower bound
case 3: // negative strobe : transit from TX to RX
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
vox_hangtime = 1;
break;
case 2: // positive strobe : transit from RX to TX
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
digitalWrite(RX, LOW); // TX
vox_hangtime = 254; // hangtime = 254 / 4402 = 58ms (the time that TX at least stays on when not further triggered)
break;
case 255: vox_hangtime--; break; // clip to upper bound
}
#endif
#ifdef notdef
vox_hangtime += (vox_trigger) ? 1 : -1;
switch(vox_hangtime){
case 0: vox_hangtime++; break; // clip to lower bound
case 3: // negative strobe : transit from TX to RX
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
vox_hangtime = 1;
break;
case 2: // positive strobe : transit from RX to TX
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
digitalWrite(RX, LOW); // TX
vox_hangtime = 254;
break;
case 255: vox_hangtime--; break; // clip to upper bound
}
#endif
}
*/
inline void vox(bool trigger)
{
if(trigger){
if(!tx){
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
digitalWrite(RX, LOW); // TX
}
tx = 255; // hangtime = 255 / 4402 = 58ms (the time that TX at least stays on when not triggered again)
} else {
if(tx){
tx--;
if(!tx){
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
}
}
}
}
volatile uint8_t drive = 4;
#define F_SAMP 4401 //4400 // ADC sample-rate; is best a multiple _UA and fits exactly in OCR0A = ((F_CPU / 64) / F_SAMP) - 1 , should not exceed CPU utilization (validate with test_samplerate)
#define _UA (4401) //360 // unit angle; integer representation of one full circle turn or 2pi radials or 360 degrees, should be a integer divider of F_SAMP and maximized to have higest precision
#define MAX_DP (_UA/1) //(_UA/2) // the occupied SSB bandwidth can be further reduced by restricting the maximum phase change (set MAX_DP to _UA/2).
#define F_SAMP 4400
#define MAX_DP 360 // the occupied SSB bandwidth can be further reduced by restricting the maximum phase change (set MAX_DP to 180).
inline int16_t arctan2(int8_t q, int8_t i) // error ~ 5 degree
{ // source: https://dspguru.com/dsp/tricks/fixed-point-atan2-with-self-normalization/
int16_t phase;
if(i < 0)
phase = ((_UA*3)/8) - (_UA*1/8) * (i + abs(q)) / ((abs(q) - i) == 0 ? 1 : (abs(q) - i));
else
phase = (_UA*1/8) - (_UA*1/8) * (i - abs(q)) / ((i + abs(q)) == 0 ? 1 : (i + abs(q)));
return (q < 0) ? -phase : phase; // negate if in quad III or IV
}
inline int16_t ssb(uint8_t in)
inline int16_t arctan3(int16_t q, int16_t i) // error ~ 0.8 degree
{ // source: [1] http://www-labs.iro.umontreal.ca/~mignotte/IFT2425/Documents/EfficientApproximationArctgFunction.pdf
#define _atan2(z) (_UA/8 - _UA/22 * z + _UA/22) * z //derived from (5) [1]
//#define _atan2(z) (_UA/8 - _UA/24 * z + _UA/24) * z //derived from (7) [1]
int16_t r;
if(abs(q) > abs(i))
r = _UA/4 - _atan2(abs(i)/abs(q)); // arctan(z) = 90-arctan(1/z)
else
r = (i == 0) ? 0 : _atan2(abs(q)/abs(i)); // arctan(z)
r = (i < 0) ? _UA/2 - r : r; // arctan(-z) = -arctan(z)
return (q < 0) ? -r : r; // arctan(-z) = -arctan(z)
}
uint8_t lut[256];
inline int16_t ssb(int16_t in)
{
static uint8_t prev_in;
static int16_t prev_in;
// [i, q] = hilbert(in);
int8_t i, q;
static int8_t v[25];
int16_t i, q;
uint8_t j;
for (j = 0; j != 25; j++)
// static int8_t v[27];
// for (j = 0; j != 26; j++)
// static int16_t v[24];
// for (j = 0; j != 23; j++)
static int16_t v[16];
for (j = 0; j != 15; j++)
v[j] = v[j+1];
v[25] = in - prev_in; // differential of input signal
prev_in = in;
i = v[13];
// Hilbert transformation (90 degrees phase shift)
q = ( (v[0] - v[26] + v[2] - v[24]) >> 5) + ((v[4] - v[22]) >> 4) + ((v[6] - v[20] + v[8] - v[18]) >> 3) + ((v[10] - v[16]) >> 2) + ((v[12] - v[14]) );
uint16_t amp = (abs(i) + abs(q)); // approximation of: amp = sqrt(i*i + q*q);
// v[26] = in;
// v[23] = in;// - prev_in;
// v[15] = in;// - prev_in;
// prev_in = in;
prev_in += (in - prev_in) / 2;
v[15] = in - prev_in; // DC decoupling
// i = v[13];
// q = ((v[0]-v[26]) + (v[2]-v[24]) + (v[4]-v[22])*2 + (v[6]-v[20])*4 + (v[8]-v[18])*5 + (v[10]-v[16])*10 + (v[12]-v[14])*32) / 50;
// i = v[11];
// q = ((v[0]-v[22]) + (v[2]-v[20])*3 + (v[4]-v[18])*6 + (v[6]-v[16])*12 + (v[8]-v[14])*24 + (v[10]-v[12])*80) / 128; // Hilbert transform, 40dB side-band rejection in 250..1950Hz (4402 SPS) when used in image-rejection scenario
i = v[7];
q = ((v[0]-v[14])*2 + (v[2]-v[12])*8 + (v[4]-v[10])*21 + v[6]*79 - v[8]*79) / 128; // Hilbert transform, 40dB side-band rejection in 400..1900Hz (4402 SPS) when used in image-rejection scenario
#ifdef notdef
uint16_t amp = (abs(i) + abs(q)); // approximation of: amp = sqrt(i*i + q*q);
amp *= drive; //scale so that input amplitude is full scale; drive=4 seems a good drive
amp = (amp > 255) ? 255 : amp; // clip
// Based on amplitude set the PA voltage through PWM. The following voltages at L4 have been measured for various PWM values: 0x00 (0.48V), 0x10 (0.50V), 0x1A (0.54V), 0x1D (0.66V), 0x20 (3.4-7V), 0x30 (6.5-11V), 0x40 (9.96V), 0x60 (11V), 0x80 (11.5V), 0xFF (11.8V)
#define KEY_OUT_PWM_MIN 0x1D // The PWM (threshold) value where the voltage over L4 just start rising ~0.6V
#define KEY_OUT_PWM_MAX 0x60 // The PWM value where the maximum voltage over L4 is approximated ~11V
if(drive == 0) // constant-carrier SSB
OCR1BL = 0xFF;
else
else {
//if(amp > KEY_OUT_PWM_MAX) OCR1BL = 255; else
//if(amp < KEY_OUT_PWM_MIN) OCR1BL = 0; else
OCR1BL = amp / (0xFF/(KEY_OUT_PWM_MAX - KEY_OUT_PWM_MIN)) + KEY_OUT_PWM_MIN; // Set PWM amplitude at OC1B (KEY_OUT) in the working range of the PA supply (1 to 11V) --> the current implmentation is quite crude: it would be nice if we can linearize this via a calibration technique
}
#endif
//uint16_t amp = (abs(i) + abs(q)); // approximation of: amp = sqrt(i*i + q*q);
uint16_t amp = abs(i) > abs(q) ? abs(i) + abs(q)/4 : abs(q) + abs(i)/4; // approximation of: amp = sqrt(i*i + q*q); error 0.95dB
//uint16_t amp = abs(i) > abs(q) ? abs(i) + 3*abs(q)/8 : abs(q) + 3*abs(i)/8; // approximation of: amp = sqrt(i*i + q*q); error 0.57dB
vox(v[1]+v[6]+v[13]+v[17]+v[24]);
//i+=1; // make phasor off-center redcudes noise: may improve quality
//q+=1;
//#define VOX_THRESHOLD 4
//if(vox_enable) vox(((int8_t)(v[1]+v[6]+v[13]+v[17]+v[22]) > VOX_THRESHOLD));
#define VOX_THRESHOLD (1 << 1) // 1*6=6dB above ADC noise level
if(vox_enable) vox((amp > VOX_THRESHOLD));
//if(amp < 16) amp = 0;
amp = amp << drive;
amp = ((amp > 255) || (drive == 8)) ? 255 : amp;
OCR1BL = (tx) ? lut[amp] : 0;
static int16_t prev_phase;
int16_t phase = arctan2(q, i);
//int16_t phase = arctan2(q, i);
int16_t phase = arctan3(q, i);
int16_t dp = phase - prev_phase; // phase difference and restriction
prev_phase = phase;
if(dp < 0) dp = dp + 360; // prevent negative frequencies to reduce spur on other sideband
if(dp > MAX_DP){ // dp should be less than 180 in order to keep frequencies below F_SAMP/2
if(dp < 0) dp = dp + _UA; // prevent negative frequencies to reduce spur on other sideband
if(dp > MAX_DP){ // dp should be less than half unit-angle in order to keep frequencies below F_SAMP/2
prev_phase = phase - (dp - MAX_DP); // substract restdp
dp = MAX_DP;
}
if(usb)
return dp * ( F_SAMP/360); // calculate frequency-difference based on phase-difference
//return dp * F_SAMP/_UA; // calculate frequency-difference based on phase-difference
return dp * ( F_SAMP/_UA); // calculate frequency-difference based on phase-difference
else
return dp * (-F_SAMP/360);
//return dp * -F_SAMP/_UA;
return dp * (-F_SAMP/_UA);
}
volatile uint16_t numSamples = 0;
#define MIC_ATTEN 0 // 0*6=0dB attenuation, since LSB bits anyway are quite noisy
#define MIC_ATTEN 0
/* This is the ADC ISR, issued with sample-rate via timer1 compb interrupt.
* It performs in real-time the ADC sampling, calculation of SSB phase-differences,
* calculation of SI5351 frequency registers and send the registers to SI5351 over I2C.
*/
// This is the ADC ISR, issued with sample-rate via timer1 compb interrupt.
// It performs in real-time the ADC sampling, calculation of SSB phase-differences, calculation of SI5351 frequency registers and send the registers to SI5351 over I2C.
ISR(ADC_vect) // ADC conversion interrupt
{
si5351_SendPLLBRegister(); // submit frequency registers to SI5351 over ~840kbit/s I2C
uint8_t low = ADCL; // ADC sample 10-bits analog input, first ADCL, then ADCH
{ // jitter dependent things first
//#define RXADC 1
#ifndef RXADC
si5351_SendPLLBRegisterBulk(); // submit frequency registers to SI5351 over ~840kbit/s I2C
uint8_t low = ADCL; // ADC sample 10-bits analog input, first ADCL, then ADCH
uint8_t high = ADCH;
uint16_t adc = (high << 8) | low;
int16_t adc = ((high << 8) | low) - 512;
int16_t df = ssb(adc >> MIC_ATTEN); // convert analog input into phase-shifts (carrier out by periodic frequency shifts)
si5351_freq_calc_fast(df); // calculate SI5351 registers based on frequency shift and carrier frequency
//OCR1BL = amp; // Set PWM amplitude at OC1B (KEY_OUT)
numSamples++;
#else
uint8_t low = ADCL; // ADC sample 10-bits analog input, first ADCL, then ADCH
uint8_t high = ADCH;
int16_t in = ((high << 8) | low) - 512;
process(in);
numSamples++;
#endif
}
///// RX ADC specific processing code
volatile uint16_t amp;
volatile int16_t phase;
inline void process(int16_t in)
{
static int16_t prev_in;
int16_t i, q;
uint8_t j;
static int16_t v[16];
for (j = 0; j != 15; j++)
v[j] = v[j+1];
prev_in += (in - prev_in) / 2;
v[15] = in - prev_in; // DC decoupling
i = v[7];
q = ((v[0]-v[14])*2 + (v[2]-v[12])*8 + (v[4]-v[10])*21 + v[6]*79 - v[8]*79) / 128; // Hilbert transform, 40dB side-band rejection in 400..1900Hz (4402 SPS) when used in image-rejection scenario
amp = abs(i) > abs(q) ? abs(i) + abs(q)/4 : abs(q) + abs(i)/4; // approximation of: amp = sqrt(i*i + q*q); error 0.95dB
//_phase = (360 * (arctan3(q, i) - 0)) / _UA; // 1000 = (2*M_PI*1000*_UA)/(F_SAMP*2*M_PI);
if((numSamples % 4) == 0) phase = arctan2(q, i);
}
void test_phase()
{
adc_start(0, false);
si5351_prev_pll_freq = 0; //enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
si5351_alt_clk2(freq + 1101); // si5351_freq_clk2(freq + 1000);
si5351_SendRegister(SI_CLK_OE, 0b11111000); // CLK2_EN=1, CLK1_EN,CLK0_EN=1
digitalWrite(RX, LOW); // TX
int16_t j;
for(j = 64; j >= 0; j-=1) // determine amp for PWM value i
{
OCR1BL = j;
int x; for(x=0;x!=500;x++) delay(100);
cli();
uint16_t _amp = amp;
int16_t _phase = phase;
uint16_t _numSamples = numSamples;
sei();
lcd.setCursor(0,1); lcd.print(_amp); lcd.print(" "); lcd.print((int)((_phase/12.225 + 180) - _numSamples*0.012) % 360);
lcd.print(" ");
}
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
change=true; //restore original frequency setting
adc_stop();
}
//end RX ADC specific code
//////////////////
ISR (TIMER0_COMPB_vect) // Timer0 interrupt
{
ADCSRA |= (1 << ADSC); // start ADC conversion (triggers ADC interrupt)
@ -462,15 +722,19 @@ uint8_t old_TCCR0B;
uint8_t old_TCNT0;
uint8_t old_TIMSK0;
void adc_start()
void adc_start(uint8_t adcpin, uint8_t ref1v1)
{
DIDR0 |= (1 << 2); // disable digital input
DIDR0 |= (1 << adcpin); // disable digital input
ADCSRA = 0; // clear ADCSRA register
ADCSRB = 0; // clear ADCSRB register
ADMUX = 0; // clear ADMUX register
ADMUX |= (2 & 0x0f); // set analog input pin
ADMUX |= (1 << REFS1) | (1 << REFS0); // set reference voltage Internal 1.1V (much more gain compared to AREF reference )
ADCSRA |= (1 << ADPS1) | (1 << ADPS0); // ADPS=011: 8 prescaler for 153.8 KHz; sampling rate is [ADC clock] / [prescaler] / [conversion clock cycles] for Arduino Uno ADC clock is 20 MHz and a conversion takes 13 clock cycles: ADPS=011: 8 prescaler for 153.8 KHz, ADPS=100: 16 prescaler for 76.9 KHz; ADPS=101: 32 prescaler for 38.5 KHz; ADPS=110: 64 prescaler for 19.2kHz; // ADPS=111: 128 prescaler for 9.6kHz
ADMUX |= (adcpin & 0x0f); // set analog input pin
ADMUX |= ((ref1v1) ? (1 << REFS1) : 0) | (1 << REFS0); // set reference voltage Internal 1.1V (-> more gain compared to AREF reference; otherwise: set reference voltage AREF (5V) )
//ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // 128 prescaler for 9.6kHz*1.25
//ADCSRA |= (1 << ADPS2) | (1 << ADPS1); // 64 prescaler for 19.2kHz*1.25
//ADCSRA |= (1 << ADPS2) | (1 << ADPS0); // 32 prescaler for 38.5 KHz*1.25
//ADCSRA |= (1 << ADPS2); // 16 prescaler for 76.9 KHz*1.25
ADCSRA |= (1 << ADPS1) | (1 << ADPS0); // ADPS=011: 8 prescaler for 153.8 KHz; sampling rate is [ADC clock] / [prescaler] / [conversion clock cycles] for Arduino Uno ADC clock is 20 MHz and a conversion takes 13 clock cycles: ADPS=011: 8 prescaler for 153.8 KHz, ADPS=100: 16 prescaler for 76.9 KHz; ADPS=101: 32 prescaler for 38.5 KHz; ADPS=110: 64 prescaler for 19.2kHz; // ADPS=111: 128 prescaler for 9.6kHz
ADCSRA |= (1 << ADIE); // enable interrupts when measurement complete
ADCSRA |= (1 << ADEN); // enable ADC
ADCSRA |= (1 << ADSC); // start ADC measurements
@ -504,6 +768,8 @@ void adc_start()
void adc_stop()
{
OCR1BL = 0x00;
// restore Timer 0, is shared with delay(), micros(), etc.
TCCR0A = old_TCCR0A;
TCCR0B = old_TCCR0B;
@ -568,7 +834,7 @@ ISR(PCINT2_vect) { // Interrupt on rotary encoder turn
noInterrupts();
uint8_t curr_state = (digitalRead(ROT_B) << 1) | digitalRead(ROT_A);
switch((last_state << 2) | curr_state){ //transition
#ifdef ENCODER_ENHANCED_RESOLUTION // Enhance encoder from 24 to 96 steps/revolution: See: https://www.sdr-kits.net/documents/PA0KLT_Manual.pdf
#ifdef ENCODER_ENHANCED_RESOLUTION // Option: enhance encoder from 24 to 96 steps/revolution, see: https://www.sdr-kits.net/documents/PA0KLT_Manual.pdf
case 0b1101: case 0b0100: case 0b0010: case 0b1011: encoder_vect(1); break;
case 0b1110: case 0b1000: case 0b0001: case 0b0111: encoder_vect(-1); break;
#else
@ -625,6 +891,7 @@ uint32_t sample_amp(uint8_t pin)
uint32_t rms = 0;
uint16_t i;
for(i=0; i!=16; i++){
//for(i=0; i!=128; i++){
uint16_t adc = analogRead(pin);
avg = (avg + adc) / 2;
}
@ -661,6 +928,89 @@ void test_iq()
digitalWrite(SIG_OUT, false); // loopback off
}
void test_amp()
{
TCCR1A = 0; // Timer 1: PWM mode
TCCR1B = 0;
TCCR1A |= (1 << COM1B1); // Clear OC1A/OC1B on Compare Match when upcounting. Set OC1A/OC1B on Compare Match when downcounting.
TCCR1B |= ((1 << CS10) | (1 << WGM13)); // WGM13: Mode 8 - PWM, Phase and Frequency Correct; CS10: clkI/O/1 (No prescaling)
ICR1H = 0x00; // TOP. This sets the PWM frequency: PWM_FREQ=312.500kHz ICR=0x1freq bit_depth=5; PWM_FREQ=156.250kHz ICR=0x3freq bit_depth=6; PWM_FREQ=78.125kHz ICR=0x7freq bit_depth=7; PWM_FREQ=39.250kHz ICR=0xFF bit_depth=8
ICR1L = 0xFF; // Fpwm = F_CPU / (2 * Prescaler * TOP) : PWM_FREQ = 39.25kHz, bit-depth=8
OCR1BH = 0x00;
OCR1BL = 0x00; // PWM duty-cycle (span set by ICR).
//freq= 7096800;
si5351_prev_pll_freq = 0; // enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
si5351_freq_clk2(freq);
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
digitalWrite(RX, LOW); // TX
uint16_t i;
for(i = 0; i != 256; i++)
{
OCR1BL = lut[i];
si5351_freq_clk2(freq + i*10);
wdt_reset();
delay(200);
}
OCR1BL = 0x00;
delay(500);
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
}
void test_calibrate()
{
TCCR1A = 0; // Timer 1: PWM mode
TCCR1B = 0;
TCCR1A |= (1 << COM1B1); // Clear OC1A/OC1B on Compare Match when upcounting. Set OC1A/OC1B on Compare Match when downcounting.
TCCR1B |= ((1 << CS10) | (1 << WGM13)); // WGM13: Mode 8 - PWM, Phase and Frequency Correct; CS10: clkI/O/1 (No prescaling)
ICR1H = 0x00; // TOP. This sets the PWM frequency: PWM_FREQ=312.500kHz ICR=0x1freq bit_depth=5; PWM_FREQ=156.250kHz ICR=0x3freq bit_depth=6; PWM_FREQ=78.125kHz ICR=0x7freq bit_depth=7; PWM_FREQ=39.250kHz ICR=0xFF bit_depth=8
ICR1L = 0xFF; // Fpwm = F_CPU / (2 * Prescaler * TOP) : PWM_FREQ = 39.25kHz, bit-depth=8
OCR1BH = 0x00;
OCR1BL = 0x00; // PWM duty-cycle (span set by ICR).
//freq= 7096800;
si5351_prev_pll_freq = 0; //enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
si5351_alt_clk2(freq + 1000); // si5351_freq_clk2(freq + 1000);
si5351_SendRegister(SI_CLK_OE, 0b11111000); // CLK2_EN=1, CLK1_EN,CLK0_EN=1
digitalWrite(RX, LOW); // TX
OCR1BL = 0xFF; // Max power to determine scale
delay(200);
float scale = sample_amp(AUDIO2);
wdt_reset();
//OCR1BL = 0x00;
//delay(200);
uint8_t amp[256];
int16_t i,j;
for(i = 127; i >= 0; i--) // determine amp for pwm value i
{
OCR1BL = i;
wdt_reset();
delay(100);
amp[i] = min(255, (float)sample_amp(AUDIO2) * 255.0/scale);
lcd.setCursor(0,1); lcd.print(""); lcd.print(i); lcd.print(" "); lcd.print(amp[i]); lcd.print(" ");
//if(amp[i] == 255) break; // skip rest when peak RF reached
}
OCR1BL = 0xFF; // Max power to determine scale
delay(200);
for(j = 0; j != 256; j++){
for(i = 0; i != 255 && amp[i] < j; i++) ; //lookup pwm i for amp[i] >= j
if(j == 255) i = 255; // do not use PWM for peak RF
lut[j] = i;
}
OCR1BL = 0x00;
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
change=true; //restore original frequency setting
}
/*
byte blackonwhite = true;
static byte canvas_bitmaps[2*4][9]; // 4x2 custom char 6x9 (actual visual char 5x8)
inline void canvas_init()
@ -755,16 +1105,21 @@ void test_samplerate()
i2c_resume(); //prepare for I2C after LCD operation
interrupts();
}
*/
void smeter()
{
float dbm = 20.0 * log10(sample_amp(AUDIO1)) - 102.0;
//float dbm = 20.0 * log10(sample_amp(AUDIO1)) - 102.0;
float rms = ((float)sample_amp(AUDIO1)) * 5.0 / (1024.0*128.0*100.0*120.0); // actual voltage at ADC, minus ADC DR, processing gain, receiver gain, audio gain
float dbm = 10 * log10((rms*rms) / 50.0) + 30.0; //from rmsV to dBM at 50R
lcd.setCursor(10,0); lcd.print((int8_t)dbm); lcd.print("dBm ");
}
void setup()
{
#ifndef RXADC
wdt_enable(WDTO_2S); // Enable watchdog, resolves QCX startup issue
#endif
lcd.begin(16, 2);
lcd.createChar(1, font_run);
@ -783,6 +1138,17 @@ void setup()
delay(1000);
lcd.setCursor(7,0); lcd.print("\001"); lcd.print(blanks); // display initialization complete
// initialize LUT
// Based on amplitude set the PA voltage through PWM. The following voltages at L4 have been measured for various PWM values: 0x00 (0.48V), 0x10 (0.50V), 0x1A (0.54V), 0x1D (0.66V), 0x20 (3.4-7V), 0x30 (6.5-11V), 0x40 (9.96V), 0x60 (11V), 0x80 (11.5V), 0xFF (11.8V)
#define KEY_OUT_PWM_MIN 29 // The PWM (threshold) value where the voltage over L4 just start rising ~0.6V
#define KEY_OUT_PWM_MAX 96 // The PWM value where the maximum voltage over L4 is approximated ~11V
uint16_t i;
for(i=0; i!=256; i++)
//lut[i] = i / (255/(KEY_OUT_PWM_MAX - KEY_OUT_PWM_MIN)) + KEY_OUT_PWM_MIN; // WITH C31 deployed
lut[i] = i; // WITHOUT C31 deployed
numSamples = 0; // DO NOT remove this volatile variable
}
@ -793,15 +1159,19 @@ void loop()
smeter();
if(!digitalRead(DIT)){
lcd.setCursor(15,1); lcd.print("T");
set_tx(true);
adc_start();
lcd.setCursor(15,1); lcd.print("T");
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
adc_start(2, true);
digitalWrite(RX, LOW); // TX
tx = 1;
for(;!digitalRead(DIT);){ //until depressed
//test_samplerate();
wdt_reset();
}
digitalWrite(RX, HIGH); // RX
tx = 0;
adc_stop();
set_tx(false);
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
lcd.setCursor(15,1); lcd.print("R");
}
if(digitalRead(BUTTONS)){ // Left-/Right-/Rotary-button
@ -809,49 +1179,54 @@ void loop()
bool longpress = false;
bool doubleclick = false;
int32_t t0 = millis();
for(;digitalRead(BUTTONS) && !longpress;){ //until depressed or long-press
for(;digitalRead(BUTTONS) && !longpress;){ // until depressed or long-press
longpress = ((millis() - t0) > 300);
wdt_reset();
}
delay(10); //debounce
for(;!longpress && ((millis() - t0) < 500) && !doubleclick;){ //until 2nd press or timeout
for(;!longpress && ((millis() - t0) < 500) && !doubleclick;){ //u ntil 2nd press or timeout
doubleclick = digitalRead(BUTTONS);
wdt_reset();
}
for(;digitalRead(BUTTONS);) wdt_reset(); //until depressed
if(val < 852) { // Left-button ADC=772
if(doubleclick){
//test_linearity();
return;
}
if(longpress){
lcd.setCursor(15,1); lcd.print("V");
vox_enable = true;
set_tx(true);
adc_start();
for(;!digitalRead(BUTTONS);) wdt_reset(); //until 2nd press
adc_start(2, true);
for(;!digitalRead(BUTTONS);) wdt_reset(); // until 2nd press
adc_stop();
set_tx(false);
vox_enable = false;
lcd.setCursor(15,1); lcd.print("R");
for(;digitalRead(BUTTONS);) wdt_reset(); //until depressed
for(;digitalRead(BUTTONS);) wdt_reset(); // until depressed
delay(100);
return;
}
usb = !usb;
si5351_prev_pll_freq = 0; //enforce PLL reset
si5351_prev_pll_freq = 0; // enforce PLL reset
change=true;
} else if(val < 978) { // Right-button ADC=933
if(doubleclick){
test_scope();
//test_scope();
#ifndef RXADC
test_calibrate();
#else
test_phase();
#endif
return;
}
if(longpress){
test_iq();
//test_iq();
test_amp();
return;
}
if(drive == 0) drive = 2;
else drive += 2;
if(drive>16) drive = 0;
if(drive == 0) drive = 1;
else drive += 1;
if(drive>8) drive = 0;
lcd.setCursor(0,1); lcd.print("Drive "); lcd.print(drive); lcd.print(blanks);
} else { // Rotory-button ADC=1023
if(doubleclick){
@ -872,7 +1247,7 @@ void loop()
}
if(change){
change = false;
uint32_t n = freq / 1000000; //lcd.print(f) with commas
uint32_t n = freq / 1000000; // lcd.print(f) with commas
uint32_t n2 = freq % 1000000;
uint32_t scale = 1000000;
char buf[16];

112
README.md
Wyświetl plik

@ -1,14 +1,14 @@
# 🆀🅲🆇-🆂🆂🅱
# **ⓆⒸⓍ-ⓈⓈⒷ**
# QCX-SSB: SSB with your QCX transceiver (modification)
This is a simple and experimental modification that transforms a [QCX] into a (Class-E driven) SSB transceiver. It can be used to make QRP SSB contacts, or (in combination with a PC) used for the digital modes such as FT8. It can be fully-continuous tuned through bands 160m-10m in the LSB/USB-modes with a 2200Hz bandwidth has up to 5W PEP SSB output and features a software-based full Break-In VOX for fast RX/TX switching in voice and digital operations.
The SSB transmit-stage is implemented completely in a digital and software-based manner: at the heart the
ATMEGA328 is sampling the input-audio and reconstructing a SSB-signal by controlling the SI5351 PLL phase (through tiny frequency changes over 800kbit/s I2C) and controlling the PA Power (through PWM on the key-shaping circuit). In this way a highly power-efficient class-E driven SSB-signal can be realized; a PWM driven class-E design keeps the SSB transceiver simple, tiny, cool, power-efficient and low-cost (ie. no need for power-inefficient and complex linear amplifier with bulky heat-sink as often is seen in SSB transceivers).
ATMEGA328P is sampling the input-audio and reconstructing a SSB-signal by controlling the SI5351 PLL phase (through tiny frequency changes over 800kbit/s I2C) and controlling the PA Power (through PWM on the key-shaping circuit). In this way a highly power-efficient class-E driven SSB-signal can be realized; a PWM driven class-E design keeps the SSB transceiver simple, tiny, cool, power-efficient and low-cost (ie. no need for power-inefficient and complex linear amplifier with bulky heat-sink as often is seen in SSB transceivers).
An Open Source Arduino sketch is used as the basis for the firmware, the hardware modification bypasses the QCX CW filter and adds a microphone input in-place of the DVM-circuit; the mod is easy to apply and consist of four wire and four component changes and after applying the transceiver remains compatible with the original QCX (CW) firmware.
This experiment is created to try out what can be done with minimal hardware; a simple ATMEGA processor, a QCX and a software-based SSB processing approach. It would be nice to add more features to the sketch, and try out if the QCX design can be further simplified e.g. by implementing parts of the receiver stage in software. Feel free to experiment with this sketch, let me know your thoughts or contribute here: https://github.com/threeme3/QCX-SSB
This experiment is created to try out what can be done with minimal hardware; a simple ATMEGA processor, a QCX and a software-based SSB processing approach. It would be nice to add more features to the sketch, and try out if the QCX design can be further simplified e.g. by implementing parts of the receiver stage in software. Feel free to experiment with this sketch, let me know your thoughts or contribute here: https://github.com/threeme3/QCX-SSB There is a forum discussion on the topic here: [QRPLabs Forum]
73, Guido
pe1nnz@amsat.org
@ -16,25 +16,27 @@ pe1nnz@amsat.org
![](https://4.bp.blogspot.com/-bYQAutLaijA/XExM0D5YnDI/AAAAAAAABmw/vZMP3G9xXBovKVClV2j1KN3fTPP-9VL1ACLcBGAs/s1600/IMG_8077.jpg])
## List of features:
- **[EER]-alike Class-E** driven SSB transmit-stage
- **[EER]/[Polar-transmitter] Class-E** driven SSB transmit-stage
- Approximately **5W PEP SSB output** (depending on supply voltage, PA voltage regulated through PWM with **36dB dynamic range**)
- supports **USB and LSB** modes up to **2200 Hz bandwidth** (receiver and transmitter)
- Receiver unwanted side-band **rejection up to 20dB**
- Continuously tunable through bands **160m-10m** (anything between 20kHz-99MHz)
- **Multiband** support <sup>[3](#note3)</sup> with the possibility to add I2C filter bank switching in Arduino sketch
- Receiver unwanted side-band **rejection up to -20dB**
- Continuously tunable through bands **80m-10m** (anything between 20kHz-99MHz is tunable but with degraded or loss in performance)
- **Multiband** support <sup>[3](#note3)</sup>
- Software-based **VOX** that can be used as **fast Full Break-In** (QSK operation) or assist in RX/TX switching for operating digital modes (no CAT or PTT interface required)
- **Mod simple to apply** (4 wires, 4 components changes and a firmware change)
- Mod remains **downwards-compatible** with the original firmware (except for the loss of DVM function)
- Firmware is **Open Source** through an Arduino Sketch, it allows experimentation, new features can be easily added, contributions can be shared via Github repository [QCX-SSB].
- Firmware is **Open Source** through an Arduino Sketch, it allows experimentation, new features can be easily added, contributions can be shared via Github repository [QCX-SSB]
- Completely **digital and software-based** SSB transmit-stage (**no additional circuitry needed**, except for the audio-in circuit)
- **ATMEGA328 signal processing:** samples audio-input and reconstruct a SSB-signal by controlling the _phase of the SI5351 PLL_ (through tiny frequency changes over 800kbits/s I2C) and the _amplitude of the PA_ (through PWM of the PA key-shaping circuit).
- **Lean and low-cost SSB transceiver design**: because of the EER class-E stage it is **highly power-efficient** (no bulky heatsinks required), and has a **simple design** (no complex balanced linear power amplifier required)
- **ATMEGA328P signal processing:** samples audio-input and reconstruct a SSB-signal by controlling the _phase of the SI5351 PLL_ (through tiny frequency changes over 800kbits/s I2C) and the _amplitude of the PA_ (through PWM of the PA key-shaping circuit).
- **Lean and low-cost SSB transceiver design**: because of the EER/Polar-transmitter class-E stage it is **highly power-efficient** (no bulky heatsinks required), and has a **simple design** (no complex balanced linear power amplifier required)
- An **pre-distorion** algorithm that cancels out the amplitude errors of non-linearities in the PA voltage regulated PWM supply; a lookup table is used that can be calibrated with an internal PA amplitude measurement
## Revision History:
| Rev. | Author | Description |
| ----- | ---------------- | ------------------------------------------------------------------- |
| R1.00 | pe1nnz@amsat.org | Initial release of prototype |
| R1.01 | pe1nnz@amsat.org | Improved audio quality and IMD3 performance and experimental (amplitude) pre-distortion and calibration. Fixed an issue with spurious transmission for RX-TX-RX transitions. <span style="color:red">**NOTE: When upgrading from R1.00 you need to execute installation step 6 (see below).**</span> |
## Schematic:
@ -50,45 +52,41 @@ RX mod (step 1):
```
Audio-input mod (step 2-5, changes the DVM circuit):
```
5V┌─────────┐
┌────────────┤AVCC 20 │
│ 1.1V│ │
D4│ R57┌───┤AREF │
10K│ 10K│ │21 │
┌┴┐ ┌┴┐ │ │
│ │ │ │ │ATMEGA328│ (Step 7 - only for ISP programming)
│ │ │ │ │ QCX │ ┌─────────┐
└┬┘ └┬┘ │ │ISP-2 │ Arduino │
Paddle/Tip │ || │ │ RESET├───────────┤10 ├────[USB+cable to PC with Arduino
┌──────────+───||───+───┤ADC2 │ISP-3 │ UNO │
│Electret │R58|| │ │25 MOSI├───────────┤11 │
│mic │ 220nF ┌┴┐ │ │ISP-6 │ │
|O ──┴── │ │ │ MISO├───────────┤12 │
│ ──┬── │ │ │ │ISP-4 │ │
│ C42 │ R56└┬┘ │ SCK├───────────┤13 │ <-- runs ArduinoISP
│ 10nF│ 10K │ │22 │ISP-1 │ │
└──────────┴────────┴───┤GND GND├───────────┤GND │
Paddle-jack/Sleeve └─────────┘ └─────────┘
5V┌─────────
┌────────────┤AVCC 20
│ 1.1V│
D4│ R57┌───┤AREF
10K│ 10K│ │21
┌┴┐ ┌┴┐ │
│ │ │ │ │ATMEGA328P│ (Step 7 - only for ISP programming)
│ │ │ │ │ QCX │ ┌─────────┐
└┬┘ └┬┘ │ │ISP-2 │ Arduino │
Paddle/Tip │ || │ │ RESET├───────────┤10 ├────[USB+cable to PC with Arduino
┌──────────+───||───+───┤ADC2 │ISP-3 │ UNO │
│Electret │R58|| │ │25 MOSI├───────────┤11 │
│mic │ 220nF ┌┴┐ │ │ISP-6 │ │
|O ──┴── │ │ │ MISO├───────────┤12 │
│ ──┬── │ │ │ │ISP-4 │ │
│ C42 │ R56└┬┘ │ SCK├───────────┤13 │ <-- runs ArduinoISP
│ 10nF│ 10K │ │22 │ISP-1 │ │
└──────────┴────────┴───┤GND GND├───────────┤GND │
Paddle-jack/Sleeve └─────────┘ └─────────┘ AND... DO NOT FORGET TO CHANGE C31 & C32
```
Original [QCX Schematic]: ![QCX Schematic]
Original QCX Schematic (click to zoom): ![QCX Schematic]
## Installation:
You will need to install 4 wires, change 4 components and upload the firmware of this sketch:
You will need to install 4 wires, change 6 components and upload the firmware of this sketch:
1. Disconnect +/side of C21 from IC9/pin1 and wire +/side of C21 to R27/pin2, you may<sup>[1](#note1)</sup> insert SPDT switch with common to C21 and the throws to IC9/pin1 and R27/pin2 for SSB/CW switching
2. Remove D4, insert resistor 10K in D4
3. Move R58 (10K) to R56, insert capacitor 220nF in R58
4. Move C42 (10nF) to the backside of PCB and place it on the top pads of D4 and C42 that are closest to IC2
5. Wire DVM/pin3-R57 to IC2/pin21 (AREF), wire junction D4-C42-R58 to IC2/pin18 (DAH), wire DVM/pin2 to IC2/pin20 (AVCC)
6. Connect an electret microphone between Tip (DAH) and Sleeve (GND) of Paddle-jack, PTT-switch between Ring (DIT) and Sleeve
(GND) ![X1M-mic]
7. Upload new firmware via an Arduino UNO board; install and start [Arduino] environment, connect Arduino Uno to your PC,
upload QCX-SSB sketch and place the ATMEGA328 into the QCX. If the ATMEGA328 chip cannot be exchanged you may proceed with ISP
programming the QCX via Uno and overwrite the existing firmware: upload the [ArduinoISP] sketch to Uno, connect
Arduino Uno to QCX via [ISP jumper], power on QCX, in Arduino select "Tools > Programmer > Arduino as ISP", select "Tools >
Board > Arduino/Genuino Uno", and upload [QCX-SSB Sketch] to QCX by selecting "Sketch > Upload Using Programmer". Once upload
succeeds the LCD should display "QCX-SSB".
6. Remove C31; and replace C32 with 10uF
7. Connect an electret microphone (+/-) between Tip (DAH) and Sleeve (GND) of Paddle-jack, PTT-switch between Ring (DIT) and Sleeve (GND) (such as a [X1M-mic])
8. Upload new firmware via an Arduino UNO board; install and start [Arduino] environment, connect Arduino Uno to your PC,
upload QCX-SSB sketch and place the ATMEGA328P into the QCX. See also <sup>[5](#note5)</sup>.
## Operation:
@ -103,31 +101,34 @@ Currently, the following functions have been assigned to the buttons:
| CENTER double-press | Select Band |
| CENTER long-press | Select frequency step (reverse-direction) |
| CENTER turn | Tune frequency |
| RIGHT single-press | Set amplitude drive level on (0=constant carrier on TX) |
| RIGHT double-press | Test scope (experimental) |
| RIGHT long-press | Measurement of unwanted side-band (adjust R27 for I-Q balance, R24 for Phase Hi, R17 for Phase Lo) |
| RIGHT single-press | Set amplitude drive level on (8=constant carrier on TX) |
| RIGHT double-press | Internal calibration of PA amplitude |
| RIGHT long-press | Sweep over frequency 0..2550Hz and amplitude 0..100% |
| KEY | Transmitter-keyed (PTT) |
## Technical Description:
For SSB reception the QCX CW filter is too small, therefore the first modification step 1 is to bypass the CW filter, providing a 3dB wideband passthrough of about 2kHz, this has side-effect that we loose 18dB audio-gain of the CW filter. Another way is to modify the CW-filter <sup>[2](#note2)</sup>, but this creates a steep filter-transition band. Insertion of a SPDT switch between the CW filter output, unfiltered output and the audio amplifier input may support CW and SSB mode selection. The phase-network is less efficient for the full SSB bandwidth in attenuating the unwanted side band, but overall a rejection of ~20 dB can still be achieved. LSB/USB mode switching is done by changing the 90 degree phase shift on the CLK1/CLK2 signals of the SI5351 PLL.
For SSB transmission the QCX DVM-circuitry is changed and used as an audio-input circuit (installation steps 2-5). An electret-microphone (with PTT switch) is added to the Paddle jack connecting the DVM-circuitry, whereby the DOT input acts as the PTT and the DASH input acts as the audio-input (installation step 6). The electret microphone is biased with 5V through a 10K resistor. A 10nF blocking capacitor prevents RF leakage into the circuit. The audio is fed into ADC2 input of the ATMEGA328 microprocessor through a 220nF decoupling capacitor. The ADC2 input is biased at 0.55V via a divider network of 10K to a 1.1V analog reference voltage, with 10-bits ADC resolution this means the microphone-input sensitivity is about 1mV (1.1V/1024) which is just sufficient to process unamplified speech.
For SSB transmission the QCX DVM-circuitry is changed and used as an audio-input circuit (installation steps 2-5). An electret-microphone (with PTT switch) is added to the Paddle jack connecting the DVM-circuitry, whereby the DOT input acts as the PTT and the DASH input acts as the audio-input (installation step 7). The electret microphone is biased with 5V through a 10K resistor. A 10nF blocking capacitor prevents RF leakage into the circuit. The audio is fed into ADC2 input of the ATMEGA328P microprocessor through a 220nF decoupling capacitor. The ADC2 input is biased at 0.55V via a divider network of 10K to a 1.1V analog reference voltage, with 10-bits ADC resolution this means the microphone-input sensitivity is about 1mV (1.1V/1024) which is just sufficient to process unamplified speech.
A new QCX-SSB firmware is uploaded to the ATMEGA328 (installation step 7), and facilitates a [digital SSB generation technique] in a completely software-based manner. A DSP algorithm samples the ADC2 audio-input at a rate of 4400 samples/s, performs a Hilbert transformation and determines the phase and amplitude of the complex-signal; the phase-changes are restricted<sup>[4](#note4)</sup> and transformed into either positive (for USB) or negative (for LSB) phase changes which in turn transformed into temporary frequency changes which are sent 4400 times per second over 800kbit/s I2C towards the SI5351 PLL. This result in phase changes on the SSB carrier signal and delivers a SSB-signal with a bandwidth of 2200 Hz whereby spurious in the opposite side-band components is attenuated.
A new QCX-SSB firmware is uploaded to the ATMEGA328P (installation step 8), and facilitates a [digital SSB generation technique] in a completely software-based manner. A DSP algorithm samples the ADC2 audio-input at a rate of 4400 samples/s, performs a Hilbert transformation and determines the phase and amplitude of the complex-signal; the phase-changes are restricted<sup>[4](#note4)</sup> and transformed into either positive (for USB) or negative (for LSB) phase changes which in turn transformed into temporary frequency changes which are sent 4400 times per second over 800kbit/s I2C towards the SI5351 PLL. This result in phase changes on the SSB carrier signal and delivers a SSB-signal with a bandwidth of 2200 Hz whereby spurious in the opposite side-band components is attenuated.
The amplitude of the complex-signal controls the supply-voltage of the PA, and thus the envelope of the SSB-signal. The key-shaping circuit is controlled with a 32kHz PWM signal, which can control the PA voltage from 1 to about 12V in about 64 steps (PWM value range 0x1D to 0x60), providing a dynamic range of (log2(64) * 6 =) 36dB in the SSB signal. Though the amplitude information is not mandatory to make a SSB signal intelligable, adding amplitude information improves quality. The complex-amplitude is also used in VOX-mode to determine when RX and TX transitions are supposed to be made.
The amplitude of the complex-signal controls the supply-voltage of the PA, and thus the envelope of the SSB-signal. The key-shaping circuit is controlled with a 32kHz PWM signal, which can control the PA voltage from 0 to about 12V in 256 steps, providing a dynamic range of (log2(256) * 6 =) 48dB in the SSB signal. C31 is removed (installation step 6) to ensure that Q6 is operating as a digital switch, this improves the efficiency, thermal stability, linearity, dynamic range and response-time. Though the amplitude information is not mandatory to make a SSB signal intelligable, adding amplitude information improves quality. The complex-amplitude is also used in VOX-mode to determine when RX and TX transitions are supposed to be made.
The IMD performance is related dependent on the quality of the system: the linearity (accuracy) of the amplitude and phase response and the precision (dynamic range) of these quantities. Especially the DSP bit-width, the precision used in the DSP algorithms, the PWM and key-shaping circuit that supplies the PA and the PA phase response are critical. The following has been done to improve the quality (since v1.01): A. use a more accurate I/Q amplitude estimation algorithm; B. pre-distort, cancel out PA induced amplitude and (amplitude-dependent) phase-errors, the ssb generation algorithm applies a amplitude correction through a (amplitude) lookup in a predefined table before modulating.
### Notes:
1. <a name="note1"/>on a new kit components IC8, IC9, R28-R35, C13-C20, C53 may be omitted if the CW filter is bypassed permanently
2. <a name="note2"/>optionally (not recommended) a steep 2 kHz SSB filter with gain can be realized by modification of Sallen-Key CW filter: replace C13, C15, C17 with 1nF capacitor and remove C53
3. <a name="note3"/>to support si5351 multi-band operation, the RX BPF can be omitted (C1, C5, C8, secondary 3 of T1), and a switchable
LPF-bank/matching-network may be placed instead of the existing LPF C25-C28, L1-L3 and matching network C29, C30. For external filtering /matching you could bypass the LPF with a wire.
4. <a name="note4"/>The occupied SSB bandwidth can be further reduced by restricting the maximum phase change (set MAX_DP to 180). The sensitivity of the VOX switching can be set with parameter VOX_THRESHOLD. And the working range of the PA supply voltage can be adjusted with parameters KEY_OUT_PWM_MIN and KEY_OUT_PWM_MAX (the PWM values for which the PA supply voltage is just OFF (1V) and ON (11V)). Audio-input can be attenuated by increasing parameter MIC_ATTEN (6dB per step).
3. <a name="note3"/>to support si5351 multi-band operation, the RX BPF can be omitted (C1, C5, C8, secondary 3 of T1), and a switchable LPF-bank/matching-network may be placed instead of the existing LPF C25-C28, L1-L3 and matching network C29, C30, L4. The Arduino sketch may be extended to make I2C controllable filter-bank. When using external filters the on-board LPF may be bypassed with a wire.
4. <a name="note4"/>The occupied SSB bandwidth can be further reduced by restricting the maximum phase change (set MAX_DP to 180). The sensitivity of the VOX switching can be set with parameter VOX_THRESHOLD. Audio-input can be attenuated by increasing parameter MIC_ATTEN (6dB per step).
5. <a name="note5"/>If the ATMEGA328P chip cannot be exchanged you may proceed with ISP programming the QCX via Uno and overwrite the existing firmware: to do so, upload the [ArduinoISP] sketch to Uno, connect Arduino Uno to QCX via [ISP jumper], power on QCX, in Arduino select "Tools > Programmer > Arduino as ISP", select "Tools > Board > Arduino/Genuino Uno", and upload [QCX-SSB Sketch] to QCX by selecting "Sketch > Upload Using Programmer". Once upload succeeds the LCD should display "QCX-SSB".
### Credits
[QCX] (QRP Labs CW Xcvr) is a kit designed by _Hans Summers (G0UPL)_, a high performance, image rejecting DC transceiver; basically a simplified implementation of the [NorCal 2030] by _Dan Tayloe (N7VE)_ designed in 2004 combined with a Hi-Per-Mite Active Audio CW Filter by _David Cripe (NMØS)_ and combined with commonly used components in HAM and Arduino communities such as a Silicon Labs SI5351 Clock Generator, ATMEGA328 AVR microprocessor and a HD44700 LCD display. The [QCX-SSB] modification and its Arduino [QCX-SSB Sketch] is designed by _Guido (PE1NNZ)_; the software-based SSB transmit stage is a derivate of earlier experiments with a [digital SSB generation technique] on a Raspberry Pi in 2013 and is basically a kind of [EER] implemented in software.
[QCX] (QRP Labs CW Xcvr) is a kit designed by _Hans Summers (G0UPL)_, a high performance, image rejecting DC transceiver; basically a simplified implementation of the [NorCal 2030] by _Dan Tayloe (N7VE)_ designed in 2004 combined with a Hi-Per-Mite Active Audio CW Filter by _David Cripe (NMØS)_, Low Pass Filters from _Ed (W3NQN)_ 1983 Articles, a key-shaping circuit by _Donald Huff (W6JL)_, a BS170 switched [CMOS driven MOSFET PA] stage as used by _Steven Weber (KD1JV)_ inspired by _Frank Cathell (W7YAZ)_ in 1988, and combined with popular components such as a Silicon Labs SI5351 Clock Generator, ATMEGA328P microprocessor and a HD44700 LCD display. The [QCX-SSB] modification and its Arduino [QCX-SSB Sketch] is designed by _Guido (PE1NNZ)_; the software-based SSB transmit stage is a derivate of earlier experiments with a [digital SSB generation technique] on a Raspberry Pi in 2013 and is basically a kind of [EER] implemented in software.
[QCX]: https://qrp-labs.com/qcx.html
@ -141,8 +142,6 @@ LPF-bank/matching-network may be placed instead of the existing LPF C25-C28, L1-
[digital SSB generation technique]: http://pe1nnz.nl.eu.org/2013/05/direct-ssb-generation-on-pll.html
[EER]: https://core.ac.uk/download/pdf/148657773.pdf
[QCX-SSB]: https://github.com/threeme3/QCX-SSB
[QCX-SSB Sketch]: https://raw.githubusercontent.com/threeme3/QCX-SSB/master/QCX-SSB.ino
@ -151,4 +150,15 @@ LPF-bank/matching-network may be placed instead of the existing LPF C25-C28, L1-
[Norcal 2030]: http://www.norcalqrp.org/nc2030.htm
[QRPLabs Forum]: https://groups.io/g/QRPLabs/topic/29572792
[CMOS driven MOSFET PA]: http://www.maxmcarter.com/Classexmtr/simplebeacon/mpm_class_e.html
[EER]: https://core.ac.uk/download/pdf/148657773.pdf
[MBF]: https://www.arrl.org/files/file/QEX_Next_Issue/Mar-Apr2017/MBF.pdf
[Polar-transmitter]: https://sigarra.up.pt/fcup/pt/pub_geral.show_file?pi_doc_id=25850
[Intelligibility]: https://g8jnj.webs.com/speechintelligibility.htm