Added 4-step attenuator. Reduced PLL frequency for 80m coverage.

pull/8/head
guido 2019-11-05 16:31:27 +01:00
rodzic c2349ec450
commit 720aea3a51
2 zmienionych plików z 22 dodań i 16 usunięć

Wyświetl plik

@ -2,7 +2,7 @@
//
// https://github.com/threeme3/QCX-SSB
#define VERSION "1.01j"
#define VERSION "1.01k"
// QCX pin defintion
#define LCD_D4 0
@ -181,6 +181,7 @@ public:
#define SI_CLK_SRC_MS 0b00001100
#define SI_CLK_IDRV_8MA 0b00000011
#define SI_CLK_INV 0b00010000
#define SI_PLL_FREQ 400000000 //900000000
#define SI_XTAL_FREQ 27004900 //myqcx1:27003847 myqcx2:27004900 Measured crystal frequency of XTAL2 for CL = 10pF (default), calibrate your QCX 27MHz crystal frequency here
volatile uint8_t prev_divider;
@ -292,11 +293,11 @@ public:
}
void freq(uint32_t freq, uint8_t i, uint8_t q)
{ // 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 divider to be in range
uint8_t r_div = (freq > (SI_PLL_FREQ/1800/1)) ? 1 : (freq > (SI_PLL_FREQ/1800/32)) ? 32 : 128; // helps divider to be in range
freq *= r_div; // take r_div into account, now freq is in the range 1MHz to 150MHz
raw_freq = freq; // cache frequency generated by PLL and MS stages (excluding R divider stage); used by freq_calc_fast()
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)
divider = SI_PLL_FREQ / 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(divider % 2) divider--; // 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( (divider * (freq - 5000) / SI_XTAL_FREQ) != (divider * (freq + 5000) / SI_XTAL_FREQ) ) divider -= 2; // Test if multiplier remains same for freq deviation +/- 5kHz, if not use different divider to make same
/*int32_t*/ pll_freq = divider * freq; // Calculate the pll_freq: the divider * desired output freq
@ -376,7 +377,8 @@ volatile bool vox_enable = false;
enum dsp_cap_t { ANALOG, DSP, SDR };
static uint8_t dsp_cap = 0;
static uint8_t ssb_cap = 0;
volatile bool att_enable = false;
volatile uint8_t att = 0;
const char* att_label[] = { "0dB", "-6dB", "-20dB", "-26dB" };
//#define PROFILING 1
#ifdef PROFILING
volatile uint32_t numSamples = 0;
@ -391,7 +393,7 @@ inline void txen(bool en)
si5351.SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
digitalWrite(RX, LOW); // TX
} else {
digitalWrite(RX, !(att_enable)); // RX (enable RX when attenuator not on)
digitalWrite(RX, !(att == 2)); // RX (enable RX when attenuator not on)
si5351.SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
lcd.setCursor(15, 1); lcd.print((vox_enable) ? "V" : "R");
}
@ -1363,8 +1365,8 @@ public:
numSamples = 0;
func_ptr = sdr_rx; //enable RX DSP/SDR
if(dsp_cap == SDR){
adc_start(0, true, F_ADC_CONV); admux[0] = ADMUX;
adc_start(1, true, F_ADC_CONV); admux[1] = ADMUX;
adc_start(0, !(att == 1)/*true*/, F_ADC_CONV); admux[0] = ADMUX;
adc_start(1, !(att == 1)/*true*/, F_ADC_CONV); admux[1] = ADMUX;
} else { // ANALOG, DSP
adc_start(0, false, F_ADC_CONV); admux[0] = ADMUX; admux[1] = ADMUX;
}
@ -1523,7 +1525,7 @@ volatile uint8_t menumode = 0; // 0=not in menu, 1=selects menu item, 2=selects
volatile int8_t menu = 0; // current parameter id selected in menu
const char* offon_label[] = {"OFF", "ON"};
enum params_t {ALL, VOLUME, MODE, FILTER, BAND, STEP, AGC, NR, SMETER, CWDEC, VOX, DRIVE, PARAM_A, PARAM_B, PARAM_C, FREQ, VERS};
enum params_t {ALL, VOLUME, MODE, FILTER, BAND, STEP, AGC, NR, ATT, SMETER, CWDEC, VOX, DRIVE, PARAM_A, PARAM_B, PARAM_C, FREQ, VERS};
#define N_PARAMS 14 // number of (visible) parameters
#define N_ALL_PARAMS 16 // number of parameters
uint8_t eeprom_version;
@ -1546,7 +1548,8 @@ void paramAction(uint8_t action, uint8_t id = ALL) // list of parameters
case STEP: paramAction(action, qcx.stepsize, F("A5"), F("Tuning step"), /*qcx.*/stepsize_label, 0, sizeof(/*qcx.*/stepsize_label)/sizeof(char*) - 1, false, qcx.STEP_1k); break;
case AGC: paramAction(action, agc, F("A6"), F("AGC"), offon_label, 0, 1, false, true); break;
case NR: paramAction(action, nr, F("A7"), F("NR"), offon_label, 0, 1, false, false); break;
case SMETER: paramAction(action, qcx.smode, F("A8"), F("S-meter"), qcx.smode_label, 0, sizeof(qcx.smode_label)/sizeof(char*) - 1, false, 1); break;
case ATT: paramAction(action, att, F("A8"), F("ATT"), att_label, 0, 3, false, 0); break;
case SMETER: paramAction(action, qcx.smode, F("A9"), F("S-meter"), qcx.smode_label, 0, sizeof(qcx.smode_label)/sizeof(char*) - 1, false, 1); break;
case CWDEC: paramAction(action, cwdec, F("B1"), F("CW Decoder"), offon_label, 0, 1, false, false); break;
case VOX: paramAction(action, vox_enable, F("C1"), F("VOX"), offon_label, 0, 1, false, false); break;
case DRIVE: paramAction(action, drive, F("C2"), F("TX Drive"), NULL, 0, 8, false, 4); break;
@ -1827,12 +1830,6 @@ void loop()
break;
case BL|SC:
//calibrate_iq();
/*
if(dsp_cap){
encoder_val = 1; paramAction(UPDATE, att_enable, NULL, F("Attenuate"), offon_label, 0, sizeof(offon_label)/sizeof(char *), true);
txen(false); // submit attenuator setting
wdt_reset(); delay(1500); wdt_reset(); change = true; // wait & refresh display
}*/
int8_t _menumode;
if(menumode == 0){ _menumode = 1; if(menu == 0) menu = 1; } // short left-click while in default screen: enter menu mode
if(menumode == 1){ _menumode = 2; } // short left-click while in menu: enter value selection screen
@ -1921,13 +1918,20 @@ void loop()
}
if(menumode == 2){
lcd.setCursor(0, 1); lcd.cursor(); delay(10); // edits menu item value; make cursor visible
if(menu == 2){ // post-handling Mode parameter
if(menu == MODE){ // post-handling Mode parameter
change = true;
si5351.prev_pll_freq = 0; // enforce PLL reset
// make more generic:
if(mode != CW) qcx.stepsize = qcx.STEP_1k; else qcx.stepsize = qcx.STEP_100;
if(mode == CW) filt = 4; else filt = 0;
}
if(menu == ATT){ // post-handling ATT parameter
if(dsp_cap == SDR){
adc_start(0, !(att & 0x01), F_ADC_CONV); admux[0] = ADMUX;
adc_start(1, !(att & 0x01), F_ADC_CONV); admux[1] = ADMUX;
}
digitalWrite(RX, !(att & 0x02)); // RX (enable RX when attenuator not on), otherwise keep Q5 off (attenuate)
}
}
}

Wyświetl plik

@ -33,6 +33,7 @@ pe1nnz@amsat.org
- Possibility to extend the QCX analog phasing stage with a **DSP stage**
- Could replace the QCX analog phasing stage completely with a **digital SDR receiver stage**, taking away the need for the manual side-band rejection adjustment procedure and delivering DSP features such as the joy of having a **AGC, adjustable CW/SSB filters**.
- A theoretical **digital receiver dynamic range of 83dB** at 2.4kHz BW. (1 dB) Compression point (at -126dBm sensitivity): -44dBm/1mV (for in-band signal); -4dBm/160mV (for signal at 15kHz offset); 19dBm/2V (for signal at 100kHz offset or more).
- Switchable attenuator steps: 0dB, -13dB, -20dB, -33dB
- SDR implementation **simplifies** the receiver heaviliy and **shaves off roughly 30% of the components** from the original QCX design while adding new and improving existing features. On a new QCX build: 46 components less to be installed, 8 component design changes, 9 additional wires.
- Can be used as alternate firmware on an unmodified QCX.
@ -134,6 +135,7 @@ The following performance measurements were made with QCX-SSB R1.01, a modified
1. <a name="note1"/>To support multi-band operation, the RX BPF can be omitted (C1,C5,C8, secondary 3 of T1). To improve RX sensitivity on higher bands, T1 and R64 should be removed and on the original wire-endings of T1 (see [original Assembly instruction] chapter 3.56 for PC Board pattern) the following components should be installed: resistor 1K over 6-8 and 3-4; capacitor 10nF over 4-8. A switchable LPF-bank could replace the existing LPF C25-28,L1-L3, or a wire may bypass if external LPFs are present; the matching network C30,L4 should be set to 30pF and 1uH (16 turns). A switchable filter-bank could potentialy be controlled via I2C I/O port.
2. <a name="note2"/>The QCX-SSB firmware can be uploaded to ATMEGA328P chip placed in the QCX via ISP programming on an Arduino Uno board. To do so, istall an [Arduino] environment, connect an Arduino Uno board to PC, upload this [ArduinoISP] sketch to Uno, install a new ATMEGA328P chip in QCX, connect Arduino Uno to QCX via [ISP jumper] wiring, power on QCX, in Arduino select "Tools > Programmer > Arduino as ISP", select "Tools > Board > Arduino/Genuino Uno", select "Tools > Port > /dev/ttyUSB0 or ttyACM0", select "Tools > Burn Bootloader", upload [QCX-SSB Sketch] by opening and selecting "Sketch > Upload Using Programmer". Once upload succeeds the LCD should display "QCX-SSB". Make sure that the Microphone is not connected during programming.
Alternatively a firmware hex file can be uploaded with avrdude: avrdude -c avrisp -b 19200 -P /dev/ttyUSB0 -p m328p -F -U flash:w:QCX-SSB.ino.with_bootloader.standard.hex
Suitable fuse settings are: avrdude -c avrisp -b 19200 -P /dev/ttyUSB0 -p m328p -U efuse:w:0xfd:m -U hfuse:w:0xd1:m -U lfuse:w:0xF7:m
3. <a name="note3"/>The occupied SSB bandwidth can be further reduced by restricting the maximum phase change (set MAX_DP to half a unit-circle _UA/2 (equivalent to 180 degrees)). 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).
4. <a name="note4"/>To implement the SDR stage, the 17 component changes of installation step 1 are easiest to be implemented on a newly to be build QCX. Alternatively, on an already built QCX it is easier to bypass the CW filter (see <sup>[note 4](#note4)</sup>), this maintains the hardware compatibility with the original QCX firmware. Optionally this can be extended with a Arduino based DSP filter stage (see <sup>[note 5](#note5)</sup>).
5. <a name="note5"/>To implement SSB receiver via CW filter bypass: disconnect C21(+) and wire to common of a SPDT switch; wire R27(pin2) and IC9(pin1) both to each throw of SPDT switch. (see here the corresponding [schematic](https://raw.githubusercontent.com/threeme3/QCX-SSB/26c4e97a034d367e1325c5587a56a7c2a43c69f3/schematic.png) and [layout](https://raw.githubusercontent.com/threeme3/QCX-SSB/26c4e97a034d367e1325c5587a56a7c2a43c69f3/layout.png)).