diff --git a/QCX-SSB.ino b/QCX-SSB.ino index 2944f42..3bd2ed2 100644 --- a/QCX-SSB.ino +++ b/QCX-SSB.ino @@ -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) + } } } diff --git a/README.md b/README.md index c14e6fe..bf05208 100644 --- a/README.md +++ b/README.md @@ -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. 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. 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. 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. 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 [note 4](#note4)), this maintains the hardware compatibility with the original QCX firmware. Optionally this can be extended with a Arduino based DSP filter stage (see [note 5](#note5)). 5. 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)).