kopia lustrzana https://github.com/threeme3/usdx
Formatting style
rodzic
3fb5110b80
commit
221ae59a64
271
QCX-SSB.ino
271
QCX-SSB.ino
|
@ -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();
|
||||
|
|
14
README.md
14
README.md
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue