Changed sdr_rx() with improved efficiency (and cleaner junk section). Changed CW filter into integer logic (faster).

experimental
guido 2019-08-31 17:27:21 +02:00
rodzic 8c67a37c15
commit 21c81482ce
1 zmienionych plików z 296 dodań i 380 usunięć

Wyświetl plik

@ -2,7 +2,7 @@
//
// https://github.com/threeme3/QCX-SSB
#define VERSION "1.01h"
#define VERSION "1.01i"
// QCX pin defintion
#define LCD_D4 0
@ -381,12 +381,11 @@ void si5351_powerDown()
si5351_SendRegister(SI_CLK_OE, 0b11111111); // Disable all CLK outputs
}
const char* mode_label[] = { "LSB", "USB", "CW ", "AM ", "FM " };
enum mode_t { LSB, USB, CW, AM, FM };
volatile uint8_t mode = USB;
const char* mode_label[] = { "LSB", "USB", "CW ", "AM ", "FM " };
volatile bool change = true;
volatile int32_t freq = 7074000;
volatile bool ptt = false;
volatile uint8_t tx = 0;
volatile bool vox_enable = false;
@ -394,6 +393,12 @@ enum dsp_cap_t { ANALOG, DSP, SDR };
static uint8_t dsp_cap = 0;
static uint8_t ssb_cap = 0;
volatile bool att_enable = false;
//#define PROFILING 1
#ifdef PROFILING
volatile uint32_t numSamples = 0;
#else
volatile uint8_t numSamples = 0;
#endif
inline void txen(bool en)
{
@ -491,12 +496,6 @@ inline int16_t ssb(int16_t in)
return dp * (-F_SAMP_TX / _UA);
}
//#define PROFILING 1
#ifdef PROFILING
volatile uint32_t numSamples = 0;
#else
volatile uint16_t numSamples = 0;
#endif
#define MIC_ATTEN 0 // 0*6dB attenuation (note that the LSB bits are quite noisy)
// This is the ADC ISR, issued with sample-rate via timer1 compb interrupt.
@ -577,36 +576,15 @@ char cw(int16_t in)
return ch;
}
inline int16_t filt_cwn(int16_t v)
{ // 2nd Order Bandpass 650-840Hz (SR=8kHz) IIR in Direct Form I
float zx0 = v;
static float za1,za2;
static float zb1,zb2;
zx0=(zx0+-2.0*za1+za2+21.0*zb1+-11.6*zb2)*5.0/64.0; //zx0=(5.0*zx0+-10.0*za1+5.0*za2+105.0*zb1+-58.0*zb2)/64.0;
za2=za1;
za1=v;
zb2=zb1;
zb1=zx0;
static float zc1,zc2;
zx0=(zx0+2.0*zb1+zb2+97.0*zc1+-57.0*zc2)/64.0;
zc2=zc1;
zc1=zx0;
return zx0;
}
static float gain = 1.0;
static char out[] = " ";
volatile bool cw_event = false;
volatile uint8_t volume = 8;
//#define F_SAMP_RX 156250
//#define F_SAMP_RX 78125
//#define F_SAMP_RX 62500 //overrun; sample rate of 55500 can be obtained
#define F_SAMP_RX 52083
#define F_SAMP_RX 62500 //overrun; sample rate of 55500 can be obtained
//#define F_SAMP_RX 52083
//#define F_SAMP_RX 44643
//#define F_SAMP_RX 39062
//#define F_SAMP_RX 34722
@ -614,6 +592,8 @@ volatile uint8_t volume = 8;
//#define F_SAMP_RX 28409
#define F_ADC_CONV 192307
volatile uint8_t volume = 8;
inline int16_t agc(int16_t in)
{ // source: Lyons Understanding Digital Signal Processing 3rd edition 13.30
float out = in * gain;
@ -626,245 +606,204 @@ inline int16_t agc(int16_t in)
#define JUNK 1
#ifdef JUNK
// This is here only for historical purpose and because of code alignment (makes sdr_rx faster)
void dsp_rx()
{ // jitter dependent things first
ADCSRA |= (1 << ADSC); // start next ADC conversion (trigger ADC interrupt if ADIE flag is set)
int16_t adc = (ADCL | (ADCH << 8)) - 512; // ADC sample 10-bits analog input, NOTE: first ADCL, then ADCH
static int16_t dc;
dc += (adc - dc) / 2;
int16_t ac = adc - dc; // DC decoupling
//#define RX_SIMPLE 1
#ifdef RX_SIMPLE
ac = agc(ac);
if(mode == CW) ac = filt_cwn(ac /* *64 */ * 16 );
OCR1AL = ac + 128;
#endif
#define RX_CIC 1
#ifdef RX_CIC
// Decimating 2nd Order CIC filter
#define R 8 // Rate change from 62.5kSPS to 7812.5SPS
static int16_t zi1, zi2, zd1, zd2;
zi2 = zi1 + zi2; // Integrator section
zi1 = ac + zi1;
if((numSamples % R) == 0){
int16_t d1 = zi2 - zd1; // Comb section
ac = d1 - zd2;
zd2 = d1;
zd1 = zi2;
if(mode == CW) filt_cwn(ac >> (16-volume));//filt_cwn(ac * volume/256 ); //ac = filt_cwn(ac /* *64 */ * 4 );
else ac = (volume) ? ac >> (16-volume) : 0;//ac * volume/256;//ac >> drive; //ac >> 3; //ac = agc(ac);
ac=min(max(ac, -128), 127); // clip
OCR1AL = ac + 128;
}
#endif
//#define CW_DECODER 1
#ifdef CW_DECODER
char ch = cw(adc >> 0);
if(ch){
for(int i=0; i!=15;i++) out[i]=out[i+1];
out[15] = ch;
cw_event = true;
}
#endif
numSamples++;
// Having this function included here and referenced makes sdr_rx faster 15% faster (normally including filt_cwn() in sdr_rx() makes the thing slower for an unknown reason)
void junk()
{
filt_cwn(0);
}
#endif
inline int16_t filt_cwn(int16_t v)
{ // 2nd Order Bandpass 650-840Hz (SR=8kHz) IIR in Direct Form I
int16_t zx0 = v;
static int16_t za1,za2;
static int16_t zb1,zb2;
zx0=(5L*(zx0-2*za1+za2)+105L*zb1-58L*zb2)/64L; //zx0=(zx0+-2*za1+za2+21*zb1+-12*zb2); zx0=zx0/16+zx0/64; //zx0=(5.0*zx0+-10.0*za1+5.0*za2+105.0*zb1+-58.0*zb2)/64.0;
za2=za1;
za1=v;
zb2=zb1;
zb1=zx0;
static int16_t zc1,zc2;
zx0=((int32_t)(zx0+2*zb1+zb2)+97L*zc1-57L*zc2)/64L; //zx0=(zx0+2*zb1+zb2)/64 + zc1*3/2 - zc2*9/10; //zx0=(zx0+2.0*zb1+zb2+97.0*zc1+-57.0*zc2)/64.0;
zc2=zc1;
zc1=zx0;
return zx0;
}
/*
iterate through modes always from begin, skipping the current one
code definitions and re-use for comb, integrator, dc decoupling, arctan
add sdr_rx capability to skip ADCMUX changing and Q processing, (while oversampling I branch?)
in func_ptr for different mode types
agc based on rms128
agc based on rms256
skip adc processing while smeter lcd update?
vox by sampling mic port in sdr_rx?
*/
volatile uint8_t admuxi, admuxq;
volatile uint32_t rms128 = 0;
//#define ADC_NR 1 // Stop CPU at ADC conversion, reduces noise but costs 30% CPU performance when ADC prescaler is 8
typedef void (*func_t)(void);
#ifdef JUNK
volatile func_t func_ptr = junk;
#else
volatile func_t func_ptr;
#endif
// sdr_rx() is sampling the ADC0 (I) and ADC1 (Q) inputs in alternating fashion, and generating a PWM output at OC1A. For both
// I and Q samples, a DC-decoupling and CIC down-sampling is performed by integrating the samples and for each R samples performing
// a comb and post-processing step where the I and Q results are demodulated into a single signal which is CIC upsampled again via
// a comb; the upsampled signal is integrated and ouput via PWM.
//
// The ADC sampling is governed by Timer2 compare match A interrupt. When the rate is too high for the CPU, sdr_rx() is still performing
// well: the integration of each sample takes longer than the sample-period, the next interrupt is scheduled later (once the current interrupt
// finishes) or is missed due to a delay that has been build-up and wwhere an interrupt happens while an interrupt flag was already
// set. As a result, the actual sample rate is a fraction of the timer rate, and the CPU is freed for the moments an interrupt
// is missed. Since the time for sampling and integrating is roughly the same processing time for both the I and Q channel,
// a constant sample-rate is obtained when next interrupts are delayed and scheduled. Missing samples however disturbs the
// continuous sampling, and therefore cause a (random) phase shift errors for on the next series of samples. But, since these phase-shift
// are instantly and and effective on both the I/Q inputs and PWM output, the resulting output do contain a constant average of
// these phase-shift errors, and hence are not harmful. Hence, predicted sample rate = F_SAMP_RX / CPU_load_rx_avg
// [1] ATMEGA328P datasheet, chapter 6.7, "Reset and Interrupt Handling". Current performance figures:
// CPU load figures for numSamples[4..12]={250, 125, 125, 125, 125, 125, 125, 225} at R=4, Fs=62.5k, DUC on
// CPU load figures for numSamples[4..12]={187, 104, 104, 104, 104, 104, 104, 187} at R=4, Fs=52k, DUC on
volatile uint32_t absavg256 = 0;
volatile uint8_t admux[2];
volatile bool _init;
volatile int16_t ocomb, i, q, qh;
#undef R // Decimating 2nd Order CIC filter
#define R 4 // Rate change from 52.083/2 kSPS to 6510.4SPS, providing 12dB gain
void sdr_rx()
{
static int16_t ozi1, ozi2, ozd1, ozd2, ocomb;
static int16_t i, q, qh;
#undef R // Decimating 2nd Order CIC filter
//#define R 8 // Rate change from 62.5/2 kSPS to 3906.25SPS, providing 18dB gain
//#define R 4 // Rate change from 62.5/2 kSPS to 7812.5SPS, providing 12dB gain
#define R 4 // Rate change from 52.083/2 kSPS to 6510.4SPS, providing 12dB gain
static int16_t dc, zi1, zi2, zd1, zd2;
// process I for even samples
if((numSamples % 2) == 0){ // [75% CPU@R=4;Fs=62.5k] (excluding the Comb branch and output stage)
ADMUX = admuxq; //1 | (1 << REFS1) | (1 << REFS0); // prepare next Q conversion, 1v1 ref enabled
#ifdef ADC_NR
interrupts();
sleep_cpu();
// noInterrupts();
#else
ADCSRA |= (1 << ADSC); // start next ADC conversion (trigger ADC interrupt if ADIE flag is set)
#endif
int16_t adc = (ADCL | (ADCH << 8)) - 512; // current ADC sample 10-bits analog input, NOTE: first ADCL, then ADCH
// process I for even samples [75% CPU@R=4;Fs=62.5k] (excluding the Comb branch and output stage)
ADMUX = admux[1]; // set MUX for next conversion
ADCSRA |= (1 << ADSC); // start next ADC conversion
func_ptr = sdr_rx_2; // processing function for next conversion
int16_t adc = (ADCL | (ADCH << 8)) - 512; // current ADC sample 10-bits analog input, NOTE: first ADCL, then ADCH
sdr_rx_common();
// Correct I/Q sample delay by means of linear interpolation
static int16_t prev_adc;
int16_t corr_adc = (prev_adc + adc) / 2;
prev_adc = adc;
// Only for I: correct I/Q sample delay by means of linear interpolation
static int16_t prev_adc;
int16_t corr_adc = (prev_adc + adc) / 2;
prev_adc = adc;
adc = corr_adc;
static int16_t dc;
dc += (corr_adc - dc) / 2;
int16_t ac = corr_adc - dc; // DC decoupling
dc += (adc - dc) / 2;
int16_t ac = adc - dc; // DC decoupling
zi2 = zi1 + zi2; // Integrator section
zi1 = ac + zi1;
static int16_t zi1, zi2, zd1, zd2;
zi2 = zi1 + zi2; // Integrator section
zi1 = ac + zi1;
if((numSamples % (R*2)) == 0){ // I-Comb branch [175% CPU@R=4;Fs=62.5k]
int16_t d1 = zi2 - zd1; // Comb section
static int16_t v[9];
v[8] = d1 - zd2;
zd2 = d1;
zd1 = zi2;
//if((numSamples % (R*2)) == 0){
if(numSamples == 0){
int16_t d1 = zi2 - zd1; // Comb section
int16_t d2 = d1 - zd2;
zd2 = d1;
zd1 = zi2;
for(uint8_t j = 0; j != 8; j++) v[j] = v[j + 1]; // (25%CPU)
i = v[0]; // Delay to match Hilbert transform on Q branch
// post processing I and Q results
static int16_t v[9];
v[8] = d2;
//for(uint8_t j = 0; j != 8; j++) v[j] = v[j + 1]; // (25%CPU)
v[0] = v[1]; v[1] = v[2]; v[2] = v[3]; v[3] = v[4]; v[4] = v[5]; v[5] = v[6]; v[6] = v[7]; v[7] = v[8];
i = v[0]; // Delay to match Hilbert transform on Q branch
//i = i>>(drive-4);
//q = q>>(drive-4);
int16_t ac = i + qh;
static uint8_t absavg256cnt;
if(!(absavg256cnt--)){ absavg256 = 0; } else absavg256 += abs(ac);
// post processing I and Q results
ac = i + qh;
if((numSamples % (R*2*128)) == 0) rms128 = 0; else rms128 += abs(ac);
if(mode == AM) { // (12%CPU for the mode selection etc)
{ static int16_t dc;
dc += (i - dc) / 2;
i = i - dc; } // DC decoupling
{ static int16_t dc;
dc += (q - dc) / 2;
q = q - dc; } // DC decoupling
ac = magn(i, q); //(25%CPU)
{ static int16_t dc;
dc += (ac - dc) / 2;
ac = ac - dc; } // DC decoupling
} else if(mode == FM){
static int16_t z1;
int16_t z0 = arctan3(q, i);
ac = z0 - z1;
z1 = z0;
} // needs: p.12 https://www.veron.nl/wp-content/uploads/2014/01/FmDemodulator.pdf
else { ; } // USB, LSB, CW
if(1)//(mode != LSB && mode != USB)
ac >>= (16-volume);
else
ac = agc(ac);
if(mode == CW){
ac = filt_cwn(ac << 4);
if(mode == AM) { // (12%CPU for the mode selection etc)
{ static int16_t dc;
dc += (i - dc) / 2;
i = i - dc; } // DC decoupling
{ static int16_t dc;
dc += (q - dc) / 2;
q = q - dc; } // DC decoupling
ac = magn(i, q); //(25%CPU)
{ static int16_t dc;
dc += (ac - dc) / 2;
ac = ac - dc; } // DC decoupling
} else if(mode == FM){
static int16_t z1;
int16_t z0 = arctan3(q, i);
ac = z0 - z1; // Differentiator
z1 = z0;
//ac = ac * (F_SAMP_RX/R) / _UA; // =ac*3.5 -> skip
} // needs: p.12 https://www.veron.nl/wp-content/uploads/2014/01/FmDemodulator.pdf
else { ; } // USB, LSB, CW
if(1)//(mode != LSB && mode != USB)
ac >>= (16-volume);
else
ac = agc(ac);
if(mode == CW){
ac = filt_cwn(ac << 6);
//#define CW_DECODER 1
#ifdef CW_DECODER
char ch = cw(ac >> 0);
if(ch){
for(int i=0; i!=15;i++) out[i]=out[i+1];
out[15] = ch;
cw_event = true;
}
#endif
char ch = cw(ac >> 0);
if(ch){
for(int i=0; i!=15;i++) out[i]=out[i+1];
out[15] = ch;
cw_event = true;
}
//static int16_t dc;
//dc += (ac - dc) / 2;
//ac = ac - dc; // DC decoupling
#define DUC 1
#ifdef DUC
// Output stage
if(numSamples == 0){ ozd1= 0; ozd2 = 0; ozi1 = 0; ozi2 = 0; } // hack: on first sample init accumlators of further stages (to prevent instability)
int16_t od1 = ac - ozd1; // Comb section
ocomb = od1 - ozd2;
ozd2 = od1;
ozd1 = ac;
#else
#ifndef PROFILING
OCR1AL = min(max(ac + ICR1L/2, 0), ICR1L); // center and clip wrt PWM working range
#endif
#endif
}
}
// process Q for odd samples
else { // [75% CPU@R=4;Fs=62.5k] (excluding the Comb branch and output stage)
ADMUX = admuxi; //0 | (1 << REFS1) | (1 << REFS0); // prepare next I conversion, 1v1 ref enabled
#ifdef ADC_NR
interrupts();
sleep_cpu();
#else
ADCSRA |= (1 << ADSC); // start next ADC conversion (trigger ADC interrupt if ADIE flag is set)
#endif
int16_t adc = (ADCL | (ADCH << 8)) - 512; // current ADC sample 10-bits analog input, NOTE: first ADCL, then ADCH
//static int16_t dc;
//dc += (ac - dc) / 2;
//ac = ac - dc; // DC decoupling
static int16_t dc;
dc += (adc - dc) / 2;
int16_t ac = adc - dc; // DC decoupling
static int16_t zi1, zi2, zd1, zd2;
zi2 = zi1 + zi2; // Integrator section
zi1 = ac + zi1;
if((numSamples % (R*2)) == ((R*2)-1) ){ // Q-Comb branch: [125% CPU@R=4;Fs=62.5k] - executed just 1 sample before executing I-Comb (and final) branch
//interrupts();
int16_t d1 = zi2 - zd1; // Comb section
static int16_t v[16];
v[15] = d1 - zd2;
zd2 = d1;
zd1 = zi2;
for(uint8_t j = 0; j != 15; j++) v[j] = v[j + 1];
q = v[7];
qh = ((v[0] - v[14]) * 2 + (v[2] - v[12]) * 8 + (v[4] - v[10]) * 21 + (v[6] - v[8]) * 15) / 128 + (v[6] - v[8]) / 2; // Hilbert transform, 40dB side-band rejection in 400..1900Hz (@4kSPS) when used in image-rejection scenario; (Hilbert transform require 5 additional bits)
}
// Output stage
static int16_t ozd1, ozd2;
if(_init){ ozd1= 0; ozd2 = 0; _init = false; } // hack: on first sample init accumlators of further stages (to prevent instability)
int16_t od1 = ac - ozd1; // Comb section
ocomb = od1 - ozd2;
ozd2 = od1;
ozd1 = ac;
}
#ifdef DUC
numSamples++;
}
void sdr_rx_2()
{
static int16_t dc, zi1, zi2, zd1, zd2;
// process Q for odd samples [75% CPU@R=4;Fs=62.5k] (excluding the Comb branch and output stage)
ADMUX = admux[0]; // set MUX for next conversion
ADCSRA |= (1 << ADSC); // start next ADC conversion
func_ptr = sdr_rx; // processing function for next conversion
int16_t adc = (ADCL | (ADCH << 8)) - 512; // current ADC sample 10-bits analog input, NOTE: first ADCL, then ADCH
dc += (adc - dc) / 2;
int16_t ac = adc - dc; // DC decoupling
zi2 = zi1 + zi2; // Integrator section
zi1 = ac + zi1;
//if((numSamples % (R*2)) == ((R*2)-1) ){
if(numSamples == (R*2)-1){
int16_t d1 = zi2 - zd1; // Comb section
int16_t d2 = d1 - zd2;
zd2 = d1;
zd1 = zi2;
// Process Q samples
static int16_t v[16];
v[15] = d2;
for(uint8_t j = 0; j != 15; j++) v[j] = v[j + 1];
q = v[7];
qh = ((v[0] - v[14]) * 2 + (v[2] - v[12]) * 8 + (v[4] - v[10]) * 21 + (v[6] - v[8]) * 15) / 128 + (v[6] - v[8]) / 2; // Hilbert transform, 40dB side-band rejection in 400..1900Hz (@4kSPS) when used in image-rejection scenario; (Hilbert transform require 5 additional bits)
#ifndef PROFILING
numSamples = 0;
} else
numSamples++;
#else
}
numSamples++;
#endif
}
inline void sdr_rx_common()
{
static int16_t ozi1, ozi2;
if(_init){ ozi1 = 0; ozi2 = 0; } // hack
// Output stage [25% CPU@R=4;Fs=62.5k]
ozi2 = ozi1 + ozi2; // Integrator section
ozi1 = ocomb + ozi1;
#ifndef PROFILING
if(volume) OCR1AL = min(max((ozi2>>5) + ICR1L/2, 0), ICR1L); // center and clip wrt PWM working range
numSamples++;
#endif
#endif
//
#endif
}
typedef void (*func_t)(void);
static volatile func_t func_ptr = dsp_rx; //referencing dsp_rx means it is used, and because it is there sdr_rx runs faster!
ISR(TIMER2_COMPA_vect) // Timer2 COMPA interrupt
{
func_ptr();
#ifdef PROFILING
numSamples++;
#endif
}
void adc_start(uint8_t adcpin, bool ref1v1, uint32_t fs)
@ -1039,30 +978,11 @@ void customDelay(uint32_t _micros) //_micros=100000 is 132052us delay
uint32_t i; for(i = 0; i != _micros * 3; i++) wdt_reset();
}
uint32_t sample_amp(uint8_t pin, uint16_t osr)
float smeter(float ref = 5.1) //= 10*log(F_SAMP_RX/R/2400) ref to 2.4kHz BW.
{
uint16_t avg = 1024 / 2;
uint32_t rms = 0;
uint16_t i;
for(i = 0; i != 16; i++){
uint16_t adc = analogRead(pin);
avg = (avg + adc) / 2;
}
for(i = 0; i != osr; 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
wdt_reset();
}
return rms;
}
float smeter(float ref = 0.0)
{
float rms;
if(dsp_cap == ANALOG) rms = ((float)sample_amp(AUDIO1, 128)) * 5.0 / (1024.0 * 128.0 * 100.0 * 120.0 / 1.750); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * audio gain]
if(dsp_cap == DSP) rms = (float)rms128 * 5.0 / (1024.0 * 128.0 * 100.0 * 120.0 / 1.750); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * audio gain]
if(dsp_cap == SDR) rms = (float)rms128 * 1.1 / (1024.0 * (float)R * 128.0 * 100.0 * 50.0); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * audio gain]
float rms = absavg256 / 256; //sqrt(256.0);
if(dsp_cap == SDR) rms = (float)rms * 1.1 / (1024.0 * (float)R * 100.0 * 50.0); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * audio gain]
else rms = (float)rms * 5.0 / (1024.0 * (float)R * 100.0 * 120.0 / 1.750);
float dbm = (10.0 * log10((rms * rms) / 50.0) + 30.0) - ref; //from rmsV to dBm at 50R
static float dbm_max;
dbm_max = max(dbm_max, dbm);
@ -1081,6 +1001,24 @@ float smeter(float ref = 0.0)
return dbm;
}
uint32_t sample_amp(uint8_t pin, uint16_t osr)
{
uint16_t avg = 1024 / 2;
uint32_t rms = 0;
uint16_t i;
for(i = 0; i != 16; i++){
uint16_t adc = analogRead(pin);
avg = (avg + adc) / 2;
}
for(i = 0; i != osr; 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
wdt_reset();
}
return rms;
}
float dbmeter(float ref = 0.0)
{
float rms = ((float)sample_amp(AUDIO1, 128)) * 5.0 / (1024.0 * 128.0 * 100.0); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * audio gain]
@ -1090,6 +1028,75 @@ float dbmeter(float ref = 0.0)
return dbm;
}
// RX I/Q calibration procedure: terminate with 50 ohm, enable CW filter, adjust R27, R24, R17 subsequently to its minimum side-band rejection value in dB
void calibrate_iq()
{
lcd.setCursor(9, 0); lcd.print(blanks);
digitalWrite(SIG_OUT, true); // loopback on
si5351_prev_pll_freq = 0; //enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
float dbc;
si5351_alt_clk2(freq + 700);
dbc = dbmeter();
si5351_alt_clk2(freq - 700);
lcd.setCursor(0, 1); lcd.print("I-Q bal. (700 Hz)"); lcd.print(blanks);
for(; !digitalRead(BUTTONS);){ wdt_reset(); dbmeter(dbc); } for(; digitalRead(BUTTONS);) wdt_reset();
si5351_alt_clk2(freq + 600);
dbc = dbmeter();
si5351_alt_clk2(freq - 600);
lcd.setCursor(0, 1); lcd.print("Phase Lo (600 Hz)"); lcd.print(blanks);
for(; !digitalRead(BUTTONS);){ wdt_reset(); dbmeter(dbc); } for(; digitalRead(BUTTONS);) wdt_reset();
si5351_alt_clk2(freq + 800);
dbc = dbmeter();
si5351_alt_clk2(freq - 800);
lcd.setCursor(0, 1); lcd.print("Phase Hi (800 Hz)"); lcd.print(blanks);
for(; !digitalRead(BUTTONS);){ wdt_reset(); dbmeter(dbc); } for(; digitalRead(BUTTONS);) wdt_reset();
/*
for(uint32_t offset = 0; offset < 3000; offset += 100){
si5351_alt_clk2(freq + offset);
dbc = dbmeter();
si5351_alt_clk2(freq - offset);
lcd.setCursor(0, 1); lcd.print(offset); lcd.print(" Hz"); lcd.print(blanks);
wdt_reset(); dbmeter(dbc); delay(500); wdt_reset();
}
*/
lcd.setCursor(9, 0); lcd.print(blanks); // cleanup dbmeter
digitalWrite(SIG_OUT, false); // loopback off
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
change = true; //restore original frequency setting
}
void powermeter()
{
lcd.setCursor(9, 0); lcd.print(blanks);
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(KEY_OUT, HIGH); //OCR1BL = 0xFF;
digitalWrite(RX, LOW); // TX
//if(dsp_cap != SDR){ adc_start(1, false, F_ADC_CONV); admux[0] = ADMUX; admux[1] = ADMUX; }
wdt_reset(); delay(100);
float rms;
rms = ((float)sample_amp(AUDIO2, 128)) * 5.0 / (1024.0 * 128.0 * 100.0 / 10000.0); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * rx/tx switch attenuation]
//if(dsp_cap == SDR) rms = (float)rms256 * 1.1 / (1024.0 * (float)R * 256.0 * 100.0 * 50.0 / 10000); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * audio gain]
//else rms = (float)rms256 * 5.0 / (1024.0 * (float)R * 256.0 * 100.0 / 10000);
float w = (rms * rms) / 50.0;
float dbm = 10.0 * log10(w) + 30.0; //from rmsV to dBm at 50R
lcd.setCursor(9, 0); lcd.print(" "); lcd.print(w); lcd.print("W ");
//lcd.setCursor(9, 0); lcd.print(" +"); lcd.print((int16_t)dbm ); lcd.print("dBm ");
digitalWrite(KEY_OUT, LOW); //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
delay(1000);
}
void test_tx_amp()
{
lcd.setCursor(9, 0); lcd.print(blanks);
@ -1170,88 +1177,25 @@ void calibrate_predistortion()
change = true; //restore original frequency setting
}
// RX I/Q calibration procedure: terminate with 50 ohm, enable CW filter, adjust R27, R24, R17 subsequently to its minimum side-band rejection value in dB
void calibrate_iq()
{
lcd.setCursor(9, 0); lcd.print(blanks);
digitalWrite(SIG_OUT, true); // loopback on
si5351_prev_pll_freq = 0; //enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
float dbc;
si5351_alt_clk2(freq + 700);
dbc = dbmeter();
si5351_alt_clk2(freq - 700);
lcd.setCursor(0, 1); lcd.print("I-Q bal. (700 Hz)"); lcd.print(blanks);
for(; !digitalRead(BUTTONS);){ wdt_reset(); dbmeter(dbc); } for(; digitalRead(BUTTONS);) wdt_reset();
si5351_alt_clk2(freq + 600);
dbc = dbmeter();
si5351_alt_clk2(freq - 600);
lcd.setCursor(0, 1); lcd.print("Phase Lo (600 Hz)"); lcd.print(blanks);
for(; !digitalRead(BUTTONS);){ wdt_reset(); dbmeter(dbc); } for(; digitalRead(BUTTONS);) wdt_reset();
si5351_alt_clk2(freq + 800);
dbc = dbmeter();
si5351_alt_clk2(freq - 800);
lcd.setCursor(0, 1); lcd.print("Phase Hi (800 Hz)"); lcd.print(blanks);
for(; !digitalRead(BUTTONS);){ wdt_reset(); dbmeter(dbc); } for(; digitalRead(BUTTONS);) wdt_reset();
/*
for(uint32_t offset = 0; offset < 3000; offset += 100){
si5351_alt_clk2(freq + offset);
dbc = dbmeter();
si5351_alt_clk2(freq - offset);
lcd.setCursor(0, 1); lcd.print(offset); lcd.print(" Hz"); lcd.print(blanks);
wdt_reset(); dbmeter(dbc); delay(500); wdt_reset();
}
*/
lcd.setCursor(9, 0); lcd.print(blanks); // cleanup dbmeter
digitalWrite(SIG_OUT, false); // loopback off
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
change = true; //restore original frequency setting
}
void powermeter()
{
lcd.setCursor(9, 0); lcd.print(blanks);
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(KEY_OUT, HIGH); //OCR1BL = 0xFF;
digitalWrite(RX, LOW); // TX
delay(100);
float rms = ((float)sample_amp(AUDIO2, 128)) * 5.0 / (1024.0 * 128.0 * 100.0 * 50.0 / 100000.0); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * rx/tx switch attenuation]
float dbm = 10.0 * log10((rms * rms) / 50.0) + 30.0; //from rmsV to dBm at 50R
lcd.setCursor(9, 0); lcd.print(" +"); lcd.print((int16_t)dbm); lcd.print("dBm ");
digitalWrite(KEY_OUT, LOW); //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
delay(1000);
}
void start_rx()
{
//if(!vox_enable) txen(false);
timer2_stop();
timer1_stop();
adc_stop();
if(dsp_cap){
tx = 1;
func_ptr = sdr_rx; //enable RX DSP/SDR
numSamples = 0;
if(dsp_cap == SDR){
adc_start(0, true, F_ADC_CONV); admuxi = ADMUX;
adc_start(1, true, F_ADC_CONV); admuxq = ADMUX;
timer2_start(F_SAMP_RX);
timer1_start(F_SAMP_RX);
} else { // ANALOG, DSP
adc_start(0, false, F_ADC_CONV); admuxi = ADMUX; admuxq = ADMUX;
timer2_start(F_SAMP_RX);
timer1_start(F_SAMP_RX);
}
tx = 1; // transit from TX to RX
_init = true;
func_ptr = sdr_rx; //enable RX DSP/SDR
numSamples = 0;
if(dsp_cap == SDR){
adc_start(0, true, F_ADC_CONV); admux[0] = ADMUX;
adc_start(1, true, F_ADC_CONV); admux[1] = ADMUX;
timer2_start(F_SAMP_RX);
timer1_start(F_SAMP_RX);
} else { // ANALOG, DSP
adc_start(0, false, F_ADC_CONV); admux[0] = ADMUX; admux[1] = ADMUX;
timer2_start(F_SAMP_RX);
timer1_start(F_SAMP_RX);
}
}
@ -1260,7 +1204,8 @@ void start_tx()
timer2_stop();
timer1_stop();
adc_stop(); //disable RX DSP
tx = 0;
tx = 0; // transit from RX to TX
_init = true;
func_ptr = dsp_tx;
numSamples = 0;
amp = 0; // initialize
@ -1286,9 +1231,9 @@ int analogSafeRead(uint8_t pin)
return val;
}
static uint8_t bandval = 4; //2
#define N_BANDS 14 //7
uint32_t band[] = { 472000, 1840000, 3573000, 5357000, 7074000, 10136000, 14074000, 18100000, 21074000, 24915000, 28074000, 50313000, 70101000, 144125000 }; // { 3573000, 5357000, 7074000, 10136000, 14074000, 18100000, 21074000 };
static uint8_t bandval = 2;
#define N_BANDS 11
uint32_t band[] = { /*472000, 1840000,*/ 3573000, 5357000, 7074000, 10136000, 14074000, 18100000, 21074000, 24915000, 28074000, 50313000, 70101000/*, 144125000*/ }; // { 3573000, 5357000, 7074000, 10136000, 14074000, 18100000, 21074000 };
enum step_t { STEP_10M, STEP_1M, STEP_500k, STEP_100k, STEP_10k, STEP_1k, STEP_500, STEP_100, STEP_10, STEP_1 };
int32_t stepsizes[] = { 10000000, 1000000, 500000, 100000, 10000, 1000, 500, 100, 10, 1 };
volatile int8_t stepsize = STEP_1k;
@ -1362,35 +1307,6 @@ void powerDown()
resetFunc();
}
/*
volatile boolean WDTalarm = false;
ISR(WDT_vect)
{
wdt_disable(); // disable watchdog so the system does not restart
WDTalarm = true; // flag the event
}
float getTemp()
{
WDTalarm = false;
// Set the Watchdog timer from: https://www.gammon.com.au/power
byte interval = 0b000110; // 1s=0b000110, 2s=0b000111, 4s=0b100000, 8s=0b10000
//64ms= 0b000010, 128ms = 0b000011, 256ms= 0b000100, 512ms= 0b000101
noInterrupts ();
MCUSR = 0;
WDTCSR |= 0b00011000; // set WDCE, WDE
WDTCSR = 0b01000000 | interval; // set WDIE & delay interval
wdt_reset(); // pat the dog
interrupts ();
unsigned long startTime = micros();
while(!WDTalarm) { //sleep while waiting for the WDT
set_sleep_mode (SLEEP_MODE_IDLE);
noInterrupts (); sleep_enable(); interrupts (); sleep_cpu ();
sleep_disable(); //processor starts here when any interrupt occurs
}
unsigned long WDTmicrosTime = micros() - startTime; // this is your measurement!
return (float)WDTmicrosTime * 0.0026 - 3668.1; //calibrate here
}
*/
#define SAFE 1
void setup()
@ -1442,10 +1358,6 @@ void setup()
lcd.begin(16, 2);
lcd.createChar(1, font_run);
//PCICR |= (1 << PCIE0);
//PCMSK0 |= (1 << PCINT5) | (1 << PCINT4) | (1 << PCINT3);
//interrupts();
// 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:
@ -1488,9 +1400,6 @@ void setup()
lcd.setCursor(8, 0); lcd.print("R"); lcd.print(VERSION); lcd.print(blanks);
//lcd.setCursor(0, 0); lcd.print(String("QCX-") + (String[]){"SSB", "DSP", "SDR" }[dsp_cap] + F(" R") + F(VERSION) + blanks );
//lcd.setCursor(0, 1); lcd.print("temp="); lcd.print(getTemp()); lcd.print("C");
//for(;!digitalRead(BUTTONS);) wdt_reset();
#ifdef SAFE
// Measure CPU loads
if(!(load_tx < 100.0))
@ -1498,17 +1407,23 @@ void setup()
lcd.setCursor(0, 1); lcd.print("!!CPU_tx="); lcd.print(load_tx); lcd.print("%"); lcd.print(blanks);
delay(1500); wdt_reset();
}
for(i = 0; i != 8; i++){
if(!(load_rx[i] < 100.0))
{
lcd.setCursor(0, 1); lcd.print("!!CPU_rx"); lcd.print(i); lcd.print("="); lcd.print(load_rx[i]); lcd.print("%"); lcd.print(blanks);
delay(1500); wdt_reset();
}
{
lcd.setCursor(0, 1); lcd.print("!!CPU_rx"); lcd.print(0); lcd.print("="); lcd.print(load_rx[0]); lcd.print("%"); lcd.print(blanks);
delay(1500); wdt_reset();
}
//if(!(load_rx_avg < 100.0))
if(!(load_rx_avg < 100.0))
{
lcd.setCursor(0, 1); lcd.print("!!CPU_rx"); lcd.print("="); lcd.print(load_rx_avg); lcd.print("%"); lcd.print(blanks);
delay(1500); wdt_reset();
// and specify indivual timings for each of the eight alternating processing functions:
for(i = 1; i != 8; i++){
if(!(load_rx[i] < 100.0))
{
lcd.setCursor(0, 1); lcd.print("!!CPU_rx"); lcd.print(i); lcd.print("="); lcd.print(load_rx[i]); lcd.print("%"); lcd.print(blanks);
delay(1500); wdt_reset();
}
}
}
// Measure VDD (+5V); should be ~5V
@ -1582,8 +1497,7 @@ void setup()
}
#endif
// Display banner
//delay(800); wdt_reset();
//delay(800); wdt_reset(); // Display banner
lcd.setCursor(7, 0); lcd.print("\001"); lcd.print(blanks);
volume = (dsp_cap) ? ((dsp_cap == SDR) ? 8 : 14) : 0;
@ -1637,13 +1551,15 @@ void loop()
att_enable = !att_enable;
lcd.setCursor(0, 1); lcd.print("Attenuate: "); lcd.print(att_enable ? "ON" : "OFF"); lcd.print(blanks);
txen(false); // submit attenuator setting
wdt_reset(); delay(1500); wdt_reset();
change = true; // refresh display
}
break;
case BL|DC: powerDown(); break;
case BL|PL:
//calibrate_predistortion();
//powermeter();
test_tx_amp();
powermeter();
//test_tx_amp();
break;
case BL|PT: break;
case BR|SC: