Formatting style

pull/8/head
guido 2019-04-12 17:31:09 +02:00
rodzic 3fb5110b80
commit 221ae59a64
2 zmienionych plików z 145 dodań i 140 usunięć

Wyświetl plik

@ -1,6 +1,6 @@
// Arduino Sketch of the QCX-SSB: SSB with your QCX transceiver (modification)
//
// https://github.com/threeme3/QCX-SSB
// https://github.com/threeme3/QCX-SSB
#define VERSION "1.01"
@ -40,9 +40,9 @@ LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
#define I2C_SCL (1 << 5) // PC5 (Pin 19)
#define I2C_SDA_HI() I2C_DDR &= ~I2C_SDA;
#define I2C_SDA_LO() I2C_DDR |= I2C_SDA;
#define I2C_SCL_HI() I2C_DDR &= ~I2C_SCL; asm("nop"); asm("nop");
#define I2C_SCL_LO() I2C_DDR |= I2C_SCL; asm("nop"); asm("nop");
#define I2C_SDA_LO() I2C_DDR |= I2C_SDA;
#define I2C_SCL_HI() I2C_DDR &= ~I2C_SCL; asm("nop"); asm("nop"); // needs 1 additional nop
#define I2C_SCL_LO() I2C_DDR |= I2C_SCL; asm("nop"); asm("nop"); // 2 nops necessary here?
inline void i2c_start()
{
@ -72,16 +72,16 @@ inline void i2c_SendBit(uint8_t data, uint8_t mask)
inline void i2c_SendByte(uint8_t data)
{
i2c_SendBit(data, 1<<7);
i2c_SendBit(data, 1<<6);
i2c_SendBit(data, 1<<5);
i2c_SendBit(data, 1<<4);
i2c_SendBit(data, 1<<3);
i2c_SendBit(data, 1<<2);
i2c_SendBit(data, 1<<1);
i2c_SendBit(data, 1<<0);
i2c_SendBit(data, 1 << 7);
i2c_SendBit(data, 1 << 6);
i2c_SendBit(data, 1 << 5);
i2c_SendBit(data, 1 << 4);
i2c_SendBit(data, 1 << 3);
i2c_SendBit(data, 1 << 2);
i2c_SendBit(data, 1 << 1);
i2c_SendBit(data, 1 << 0);
I2C_SDA_HI(); //ack
asm("nop"); asm("nop"); //delay
asm("nop"); asm("nop"); //delay // needs 1 additional nop?
I2C_SCL_HI();
I2C_SCL_LO();
}
@ -95,7 +95,7 @@ void i2c_init()
}
void i2c_deinit()
{
{
I2C_PORT &= ~( I2C_SDA | I2C_SCL );
I2C_DDR &= ~( I2C_SDA | I2C_SCL );
}
@ -197,7 +197,7 @@ void si5351_SetupMultisynth(uint8_t reg, uint8_t divider, uint32_t num, uint32_t
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;
// Above definition (for SI_XTAL_FREQ=27.00491M) can be optimized by pre-calculating factor (0xFFFFF*128)/SI_XTAL_FREQ (=4.97) as integer constant (5) and
// Above definition (for SI_XTAL_FREQ=27.00491M) can be optimized by pre-calculating factor (0xFFFFF*128)/SI_XTAL_FREQ (=4.97) as integer constant (5) and
// substracting the rest error factor (0.03). Note that the latter is shifted left (0.03<<6)=2, while the other term is shifted right (>>6)
register int32_t z = ((si5351_divider * (si5351_raw_freq + freq_offset)) % SI_XTAL_FREQ);
register int32_t z2 = -(z >> 5);
@ -221,7 +221,7 @@ uint16_t si5351_div(uint32_t num, uint32_t denom, uint32_t* b, uint32_t* c)
uint16_t a = num / denom;
if(b && c){
uint64_t l = num % denom;
l <<= 20; l--; // l *= 1048575;
l <<= 20; l--; // l *= 1048575;
l /= denom; // normalize
*b = l;
*c = 0xFFFFF; // for simplicity set c to the maximum 1048575
@ -238,12 +238,12 @@ void si5351_freq(uint32_t freq, uint8_t i, uint8_t q)
si5351_raw_freq = freq; // cache frequency generated by PLL and MS stages (excluding R divider stage); used by si5351_freq_calc_fast()
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
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
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)
// 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);
@ -256,17 +256,17 @@ void si5351_freq(uint32_t freq, uint8_t i, uint8_t q)
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
// 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 (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
// Switch on the CLK2 output to be PLL B and set si5351_multiSynth2 input
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) {
if((abs(pll_freq - si5351_prev_pll_freq) > 16000000L) || si5351_divider != si5351_prev_divider){
si5351_prev_pll_freq = pll_freq;
si5351_prev_divider = si5351_divider;
si5351_SendRegister(SI_PLL_RESET, 0xA0);
@ -278,12 +278,12 @@ 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
// 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
@ -303,7 +303,7 @@ inline void vox(bool trigger)
{
if(trigger){
if(!tx){
lcd.setCursor(15,1); lcd.print("T");
lcd.setCursor(15, 1); lcd.print("T");
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
digitalWrite(RX, LOW); // TX
}
@ -314,7 +314,7 @@ inline void vox(bool trigger)
if(!tx){
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
lcd.setCursor(15,1); lcd.print("V");
lcd.setCursor(15, 1); lcd.print("V");
}
}
}
@ -327,61 +327,59 @@ volatile uint8_t drive = 4;
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/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)
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)
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];
volatile uint8_t amp;
inline int16_t ssb(int16_t in)
{
static int16_t prev_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];
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
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
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
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) + abs(q) / 4 : abs(q) + abs(i) / 4; // approximation of: amp = sqrt(i*i + q*q); error 0.95dB
#define VOX_THRESHOLD (1 << 1) // 1*6=6dB above ADC noise level
if(vox_enable) vox((_amp > VOX_THRESHOLD));
_amp = _amp << drive;
_amp = ((_amp > 255) || (drive == 8)) ? 255 : _amp; // clip or when drive=8 use max output
amp = (tx) ? lut[_amp] : 0;
OCR1BL = amp; // submit amplitude to PWM register
#define VOX_THRESHOLD (1 << 1) // 1*6=6dB above ADC noise level
if(vox_enable) vox((_amp > VOX_THRESHOLD));
static int16_t prev_phase;
int16_t phase = arctan3(q, i);
int16_t dp = phase - prev_phase; // phase difference and restriction
prev_phase = phase;
_amp = _amp << drive;
_amp = ((_amp > 255) || (drive == 8)) ? 255 : _amp; // clip or when drive=8 use max output
OCR1BL = (tx) ? lut[_amp] : 0; // submit amplitude to PWM register
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;
}
static int16_t prev_phase;
int16_t phase = arctan3(q, i);
int16_t dp = phase - prev_phase; // phase difference and restriction
prev_phase = phase;
if(usb)
return dp * ( F_SAMP/_UA); // calculate frequency-difference based on phase-difference
else
return dp * (-F_SAMP/_UA);
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 / _UA); // calculate frequency-difference based on phase-difference
else
return dp * (-F_SAMP / _UA);
}
volatile uint16_t numSamples = 0;
@ -452,23 +450,23 @@ void adc_start(uint8_t adcpin, uint8_t ref1v1)
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 = 0;
OCR1BL = 0; // PWM duty-cycle (span set by ICR).
OCR1BL = 0; // PWM duty-cycle (span set by ICR).
}
void adc_stop()
{
{
// restore Timer 0, is shared with delay(), micros(), etc.
TCCR0A = old_TCCR0A;
TCCR0B = old_TCCR0B;
TCNT0 = old_TCNT0;
TIMSK0 = old_TIMSK0;
// Stop Timer 0 interrupt
//TIMSK0 &= ~(1 << OCIE0B); // disable timer compare interrupt TIMER0_COMPB_vect
//TCCR0A |= (1 << WGM00); // for some reason WGM00 must be 1 in normal operation
OCR1BL = 0x00;
//ADCSRA &= ~(1 << ADATE); // disable auto trigger
ADCSRA &= ~(1 << ADIE); // disable interrupts when measurement complete
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // 128 prescaler for 9.6kHz
@ -498,7 +496,7 @@ void encoder_vect(int8_t sign)
case STEP_10: stepval = 10; break;
case STEP_1: stepval = 1; break;
}
if(stepval < STEP_100) freq %= 1000; // when tuned and stepsize > 100Hz then forget fine-tuning details
if(stepval < STEP_100) freq %= 1000; // when tuned and stepsize > 100Hz then forget fine-tuning details
freq += sign * stepval;
freq = max(1, min(999999999, freq));
change = true;
@ -506,8 +504,8 @@ void encoder_vect(int8_t sign)
void stepsize_showcursor()
{
lcd.setCursor(stepsize, 1); // display stepsize with cursor
lcd.cursor();
lcd.setCursor(stepsize, 1); // display stepsize with cursor
lcd.cursor();
}
void stepsize_change(int8_t val)
@ -518,7 +516,7 @@ void stepsize_change(int8_t val)
stepsize_showcursor();
}
ISR(PCINT2_vect) { // Interrupt on rotary encoder turn
ISR(PCINT2_vect){ // Interrupt on rotary encoder turn
static uint8_t last_state;
noInterrupts();
uint8_t curr_state = (digitalRead(ROT_B) << 1) | digitalRead(ROT_A);
@ -576,14 +574,14 @@ byte font_run[] = {
uint32_t sample_amp(uint8_t pin)
{
uint16_t avg = 1024/2;
uint16_t avg = 1024 / 2;
uint32_t rms = 0;
uint16_t i;
for(i=0; i!=16; i++){
for(i = 0; i != 16; i++){
uint16_t adc = analogRead(pin);
avg = (avg + adc) / 2;
}
for(i=0; i!=128; i++){ // 128 overampling is 42dB gain => with 10-bit ADC resulting in a total of 102dB DR
for(i = 0; i != 128; i++){ // 128 overampling is 42dB gain => with 10-bit ADC resulting in a total of 102dB DR
uint16_t adc = analogRead(pin);
avg = (avg + adc) / 2; // average
rms += ((adc > avg) ? 1 : -1) * (adc - avg); // rectify based
@ -593,7 +591,7 @@ uint32_t sample_amp(uint8_t pin)
void test_amp()
{
lcd.setCursor(9,0); lcd.print(" ");
lcd.setCursor(9, 0); lcd.print(" ");
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.
@ -601,7 +599,7 @@ void test_amp()
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).
OCR1BL = 0x00; // PWM duty-cycle (span set by ICR).
si5351_prev_pll_freq = 0; // enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
@ -612,14 +610,14 @@ void test_amp()
for(i = 0; i != 256; i++)
{
OCR1BL = lut[i];
si5351_alt_clk2(freq + i*10);
si5351_alt_clk2(freq + i * 10);
wdt_reset();
lcd.setCursor(0,1); lcd.print("SWEEP("); lcd.print(i); lcd.print(")="); lcd.print(lut[i]); lcd.print(" ");
lcd.setCursor(0, 1); lcd.print("SWEEP("); lcd.print(i); lcd.print(")="); lcd.print(lut[i]); lcd.print(" ");
delay(200);
}
OCR1BL = 0;
delay(500);
delay(500);
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
change = true; //restore original frequency setting
@ -627,7 +625,7 @@ void test_amp()
void test_calibrate()
{
lcd.setCursor(9,0); lcd.print(" ");
lcd.setCursor(9, 0); lcd.print(" ");
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.
@ -635,7 +633,7 @@ void test_calibrate()
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).
OCR1BL = 0x00; // PWM duty-cycle (span set by ICR).
si5351_prev_pll_freq = 0; //enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
@ -647,15 +645,15 @@ void test_calibrate()
float scale = sample_amp(AUDIO2);
wdt_reset();
uint8_t amp[256];
int16_t i,j;
for(i = 127; i >= 0; i--) // determine amp for pwm value i
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("CALIB("); lcd.print(i); lcd.print(")="); lcd.print(amp[i]); lcd.print(" ");
amp[i] = min(255, (float)sample_amp(AUDIO2) * 255.0 / scale);
lcd.setCursor(0, 1); lcd.print("CALIB("); lcd.print(i); lcd.print(")="); lcd.print(amp[i]); lcd.print(" ");
}
OCR1BL = 0xFF; // Max power to determine scale
delay(200);
@ -673,19 +671,19 @@ void test_calibrate()
void customDelay(uint32_t _micros) //_micros=100000 is 132052us delay
{
uint32_t i; for(i=0;i!=_micros*3;i++) wdt_reset();
uint32_t i; for(i = 0; i != _micros * 3; i++) wdt_reset();
}
void smeter()
{
float rms = ((float)sample_amp(AUDIO1)) * 5.0 / (1024.0 * 128.0 * 100.0 * 120.0/1.750); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * audio gain]
float dbm = 10.0*log10((rms*rms) / 50.0) + 30.0; //from rmsV to dBm at 50R
float rms = ((float)sample_amp(AUDIO1)) * 5.0 / (1024.0 * 128.0 * 100.0 * 120.0 / 1.750); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * audio gain]
float dbm = 10.0 * log10((rms * rms) / 50.0) + 30.0; //from rmsV to dBm at 50R
static float dbm_max;
dbm_max = max(dbm_max, dbm);
static uint8_t cnt;
cnt++;
if((cnt % 8) == 0){
lcd.setCursor(9,0); lcd.print((int16_t)dbm_max); lcd.print("dBm ");
lcd.setCursor(9, 0); lcd.print((int16_t)dbm_max); lcd.print("dBm ");
dbm_max = -174.0 + 34.0;
}
}
@ -696,7 +694,7 @@ void setup()
lcd.begin(16, 2);
lcd.createChar(1, font_run);
lcd.setCursor(0,0); lcd.print("QCX-SSB R"); lcd.print(VERSION); lcd.print(blanks);
lcd.setCursor(0, 0); lcd.print("QCX-SSB R"); lcd.print(VERSION); lcd.print(blanks);
i2c_init();
@ -711,16 +709,16 @@ void setup()
// initialize LUT
//#define C31_IS_INSTALLED 1 // Uncomment this line when C31 is installed (shaping circuit will be driven with analog signal instead of being switched digitally with PWM signal)
#ifdef C31_IS_INSTALLED // In case of analog driven shaping circuit:
#define PWM_MIN 29 // The PWM value where the voltage over L4 is approaching its minimum (~0.6V)
#define PWM_MAX 96 // The PWM value where the voltage over L4 is approaching its maximum (~11V)
#else // In case of digital driven shaping circuit:
#define PWM_MIN 0 // The PWM value where the voltage over L4 is its minimum (0V)
#define PWM_MAX 255 // The PWM value where the voltage over L4 is its maximum (12V)
#endif
#ifdef C31_IS_INSTALLED // In case of analog driven shaping circuit:
#define PWM_MIN 29 // The PWM value where the voltage over L4 is approaching its minimum (~0.6V)
#define PWM_MAX 96 // The PWM value where the voltage over L4 is approaching its maximum (~11V)
#else // In case of digital driven shaping circuit:
#define PWM_MIN 0 // The PWM value where the voltage over L4 is its minimum (0V)
#define PWM_MAX 255 // The PWM value where the voltage over L4 is its maximum (12V)
#endif
uint16_t i;
for(i=0; i!=256; i++)
lut[i] = (float)i / ((float)255/((float)PWM_MAX - (float)PWM_MIN)) + PWM_MIN;
for(i = 0; i != 256; i++)
lut[i] = (float)i / ((float)255 / ((float)PWM_MAX - (float)PWM_MIN)) + PWM_MIN;
// Benchmark ADC_vect() ISR
uint32_t t0, t1;
@ -728,29 +726,29 @@ void setup()
TIMER0_COMPB_vect();
ADC_vect();
t1 = micros();
float load = (t1-t0) * F_SAMP * 100.0 / 1000000.0;
if(load > 99.9){ lcd.setCursor(0,1); lcd.print("CPU_tx="); lcd.print(load); lcd.print("%"); delay(3000); return; } // CPU overload in ADC_vect() ISR
float load = (t1 - t0) * F_SAMP * 100.0 / 1000000.0;
//if(load > 99.9) // CPU overload
{ lcd.setCursor(0, 1); lcd.print("CPU_tx="); lcd.print(load); lcd.print("%"); }
delay(1000);
lcd.setCursor(7,0); lcd.print("\001"); lcd.print(blanks); // Ready: display initialization complete
lcd.setCursor(15,1); lcd.print("R");
lcd.setCursor(7, 0); lcd.print("\001"); lcd.print(blanks); // Ready: display initialization complete normally
}
void loop()
{
delay(100);
smeter();
if(!digitalRead(DIT)){
lcd.setCursor(15,1); lcd.print("T");
lcd.setCursor(9,0); lcd.print(" ");
lcd.setCursor(15, 1); lcd.print("T");
lcd.setCursor(9, 0); lcd.print(" ");
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
adc_start(2, true);
customDelay(1000); //allow setup time
digitalWrite(RX, LOW); // TX
tx = 1;
for(;!digitalRead(DIT);){ //until depressed
for(; !digitalRead(DIT);){ //until depressed
wdt_reset();
}
digitalWrite(RX, HIGH); // RX
@ -758,46 +756,46 @@ void loop()
tx = 0;
adc_stop();
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
lcd.setCursor(15,1); lcd.print("R");
lcd.setCursor(15, 1); lcd.print("R");
}
if(digitalRead(BUTTONS)){ // Left-/Right-/Rotary-button
if(digitalRead(BUTTONS)){ // Left-/Right-/Rotary-button
uint16_t val = analogRead(BUTTONS);
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;){ //until 2nd press or timeout
doubleclick = digitalRead(BUTTONS);
wdt_reset();
}
for(;digitalRead(BUTTONS);) wdt_reset(); // until depressed
if(val < 852) { // Left-button ADC=772
for(; digitalRead(BUTTONS);) wdt_reset(); // until depressed
if(val < 852){ // Left-button ADC=772
if(doubleclick){
return;
}
if(longpress){
lcd.setCursor(15,1); lcd.print("V");
lcd.setCursor(9,0); lcd.print(" ");
lcd.setCursor(15, 1); lcd.print("V");
lcd.setCursor(9, 0); lcd.print(" ");
vox_enable = true;
adc_start(2, true);
for(;!digitalRead(BUTTONS);){ // while in VOX mode
for(; !digitalRead(BUTTONS);){ // while in VOX mode
wdt_reset(); // until 2nd press
}
adc_stop();
vox_enable = false;
lcd.setCursor(15,1); lcd.print("R");
for(;digitalRead(BUTTONS);) wdt_reset(); // until depressed
lcd.setCursor(15, 1); lcd.print("R");
for(; digitalRead(BUTTONS);) wdt_reset(); // until depressed
delay(100);
return;
}
usb = !usb;
si5351_prev_pll_freq = 0; // enforce PLL reset
change=true;
} else if(val < 978) { // Right-button ADC=933
change = true;
} else if(val < 978){ // Right-button ADC=933
if(doubleclick){
test_calibrate();
return;
@ -809,15 +807,15 @@ void loop()
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
lcd.setCursor(0, 1); lcd.print("Drive "); lcd.print(drive); lcd.print(blanks);
} else { // Rotory-button ADC=1023
if(doubleclick){
delay(100);
bandval++;
if(bandval > N_BANDS) bandval=0;
freq=band[bandval];
if(bandval > N_BANDS) bandval = 0;
freq = band[bandval];
stepsize = STEP_1k;
change=true;
change = true;
return;
}
if(longpress){
@ -825,27 +823,28 @@ void loop()
return;
}
stepsize_change(+1);
}
}
}
if(change){
change = false;
uint32_t n = freq / 1000000; // lcd.print(f) with commas
uint32_t n2 = freq % 1000000;
uint32_t scale = 1000000;
char buf[16];
sprintf(buf, "%2u", n); lcd.setCursor(0,1); lcd.print(buf);
while (scale != 1) {
scale /= 1000;
n = n2 / scale;
n2 = n2 % scale;
sprintf(buf, ",%03u", n); lcd.print(buf);
char buf[16];
sprintf(buf, "%2u", n); lcd.setCursor(0, 1); lcd.print(buf);
while(scale != 1){
scale /= 1000;
n = n2 / scale;
n2 = n2 % scale;
sprintf(buf, ",%03u", n); lcd.print(buf);
}
lcd.print((usb) ? " USB" : " LSB");
lcd.setCursor(15, 1); lcd.print("R");
if(usb)
si5351_freq(freq, 0, 90);
else
si5351_freq(freq, 90, 0);
si5351_freq(freq, 90, 0);
}
wdt_reset();
stepsize_showcursor();

Wyświetl plik

@ -103,10 +103,6 @@ The IMD performance is related dependent on the quality of the system: the linea
## Results
Several OMs reported a successful QCX-SSB modification and were able to make SSB QRP DX contacts over thousands of kilometers on the 20m and 40m bands. During CQ WW contest I was able to make 34 random QSOs on 40m with 5W and an inverted-V over the house in just a few hours with CN3A as my furthest contact, I could observe the benefits of using SSB with constant-envelope in cases where my signal was weak; for FT8 a Raspberry Pi 3B+ with JTDX was used to make FT8 contacts all the way up to NA.
Known issues:
- R1.00: Audio quality especially in a local QSO setup with this release was a bit of a challenge due to analog operation of Q6, resulting in degraded IMD and thermal instability; this issue has been resolved by changing C31/C32 and together with the more accurate signal processing of the new firmware, the IMD performance, carrier+side-band rejection and spectral purity has been improved considerably. **(RESOLVED)**
- R1.01: When transmitting, some distorted audio can be heard from the headphones. This is because the audio op-amps do share same 12V supply as the PA, resulting in serious amplitude RFI. Adding a (>>100uF/16V) capacitor from the Emitter of Q6 to GND alleviate the issue but does not completely resolve. There is also a possibility that RFI causes leakage into the microphone input, in this case it is recommended to increase MIC_ATTEN value in the code which attenuates the ADC input-gain with 6dB per increment.
Measurements:
The following performance measurements were made with QCX-SSB R1.01, a modified RTL-SDR, Spektrum-SVmod-v0.19, Sweex 5.0 USB Audio device and Audicity player. It is recognized that this measurement setup has its own limitations, hence the dynamic range of the measurements is somewhat limited by the RTL-SDR as this device goes easily into overload. Measurements were made with the following setttings: USB modulation, no pre-distortion, two-tone input 1000Hz/1200Hz where audio level is set just before the point where compression starts. Results:
- Intermodulation distortion products (two-tone; SSB with varying envelope) IMD3, IMD5, IMD7: respectively -30dBc; -33dBc; -36dBc
@ -117,6 +113,16 @@ The following performance measurements were made with QCX-SSB R1.01, a modified
- 3dB bandwidth (sweep): 400..2130Hz
![twotone](https://raw.githubusercontent.com/threeme3/QCX-SSB/master/twotone.png)
Known issues:
| Rev. | Issue | Cause | Resolution |
| ----- | ----- | ----- | ---------- |
| R1.00 | in some cases degraded audio quality especially in local QSOs | analog operation of Q6 causes challenges with biasing, dynamic range, linearity and thermal-drift | (fixed in R1.01) change C31/C32 so that Q6 operates in digital mode and together with the more accurate signal processing of the new firmware, the IMD performance, carrier+side-band rejection and spectral purity has been improved considerably |
| R1.00 | crackling sounds and noise on TX | ATMEGA ADC is sensitive for noise and in some cases RF feedback worsen this | (not fixed yet) dynamic noise gating algorithm could be an effective way of mitigating the issue, adding additional inductor in series with mic in could help preventig RF feedback, increasing MIC_ATTEN value in code attentuates the audio input can put the noise below a threshold at the cost of audio sensitivity |
| R1.00 | in VOX mode TX constantly on when soundcard is connected to mic input | VOX is too sensitive and hence responds to the noise of the external device | (not fixed yet) reduce gain on audio input, e.g. by reducing the output level of the external device, adding a resistive divider in the audio line, increase the MIC_ATTEN value in code to attenuate the signal in software or increase VOX_THRESHOLD to make the VOX algorithm less sensitive, dynamic noise gating algorithm could be an effective way of mitigating the issue |
| R1.01 | RFI on the headphones during TX | audio opamp share the same 12V supply as the PA | (not fixed yet) adding 100uF capacitor from emitter of Q6 to GND alleviates the issue, issue does not occur with constant amplitude SSB |
| R1.01 | after pressing PTT or while tuning RX stops working, audio quality on TX alsoimpacted | unknown, likely caused by overclocking I2C signalling of si5351 | (not fixed yet) probably adding a few asm("nop"); statements could reduce the speed to an acceptable level (while tweaking this make sure CPU_tx < 100%) |
### 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