Quisk version 4.2.44
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
Quisk Version 4.2.44 September 2025
|
||||
====================================
|
||||
Quisk now has Pure Signal for the Hermes Lite2. This is baseband predistortion to reduce IMD products.
|
||||
To use it, press "PsCal" for calibration, and "PS" to enable Pure Signal. There are no adjustments needed.
|
||||
More details are in the documentation.
|
||||
|
||||
Quisk Version 4.2.43 September 2025
|
||||
====================================
|
||||
I fixed a bug in the WDSP library NR2 noise reduction feature. I added the "Trained" method of NR2.
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#Quisk version 4.2.43
|
||||
#Quisk version 4.2.44
|
||||
from .quisk import main
|
||||
|
||||
@@ -75,6 +75,7 @@ h3 {font-size:1.6rem;}
|
||||
<li><a href="#wsjtx">WSJT-X</a></li>
|
||||
<li><a href="#freedv">FreeDV</a></li>
|
||||
<li><a href="#wdsp">NR2 and WDSP</a></li>
|
||||
<li><a href="#pure">Pure Signal</a></li>
|
||||
<li><a href="#VNA">VNA</a></li>
|
||||
<li><a href="#Remote">Remote Operation</a></li>
|
||||
</ul>
|
||||
@@ -1627,6 +1628,24 @@ quisk/wdsp directory and entering "make". The library is used in-place and no in
|
||||
The new "trained" method of NR2 is included. Press the NR2 button twice to select "NR2t".
|
||||
</p>
|
||||
<br><br>
|
||||
<h2 id="pure">Pure Signal</h2>
|
||||
<p>
|
||||
Pure Signal is a term used to describe baseband predistortion, a technique used to reduce IMD products
|
||||
caused by a non-linear power amp. Pure Signal is available for the Hermes Lite2.
|
||||
To use it first press the PsCal button to create a calibration. This will key a two second CW carrier.
|
||||
Then press PS to enable corrections. You can view the result by using the "FDX" button to see the Tx output.
|
||||
The feature is automatic and no settings are required. You can re-calibrate if you change bands, but this is not
|
||||
really necessary.
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
You need to supply a sample of the amplifier output so Pure Signal can correct it. First set the "LNA during Tx dB" to
|
||||
the minimum gain of -12 on the Hermes Hardware screen. This minimizes the preamp gain and the noise picked up from the HL2 board.
|
||||
Then provide a Tx sample much above the remaining board noise but below clipping. Use the FDX button to see the Tx output
|
||||
with and without the Tx sample connected. Twenty or thirty dB over board noise should be enough. To learn how to provide a Tx
|
||||
signal sample, see <a href="https://github.com/softerhardware/Hermes-Lite2/wiki/PureSignal">the HL2 Pure Signal wiki</a>.
|
||||
</p>
|
||||
<br><br>
|
||||
<h2 id="VNA">Vector Network Analyzer</h2>
|
||||
<p>
|
||||
If you have my transceiver hardware from 2010 QEX, or the newer HiQSDR
|
||||
|
||||
+30
-3
@@ -8,6 +8,7 @@ from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import math, wx, time
|
||||
import _quisk as QS
|
||||
from configure import ComboCtrl
|
||||
|
||||
|
||||
@@ -24,8 +25,14 @@ class BottomWidgets: # Add extra widgets to the bottom of the screen
|
||||
def Widgets_0x06(self, app, hardware, conf, frame, gbs, vertBox):
|
||||
self.num_rows_added = 1
|
||||
start_row = self.start_row
|
||||
self.btnPS = app.QuiskCheckbutton(frame, self.OnBtnPS, text='PS')
|
||||
self.btnPS.Enable(False)
|
||||
gbs.Add(self.btnPS, (start_row, self.start_col), flag=wx.EXPAND)
|
||||
self.btnPsCal = app.QuiskPushbutton(frame, self.OnBtnPsCal, 'PS Cal')
|
||||
self.btnPsCal.active = False
|
||||
gbs.Add(self.btnPsCal, (start_row, self.start_col + 1), flag=wx.EXPAND)
|
||||
self.atu_ctrl = ComboCtrl(frame, "ATU", ["Tune", "Bypass"], True)
|
||||
gbs.Add(self.atu_ctrl, (start_row, self.start_col), (1, 2), flag=wx.EXPAND)
|
||||
gbs.Add(self.atu_ctrl, (start_row, self.start_col + 2), (1, 2), flag=wx.EXPAND)
|
||||
bw, bh = self.atu_ctrl.GetMinSize()
|
||||
frame.Bind(wx.EVT_COMBOBOX_CLOSEUP, self.OnAtu)
|
||||
init = app.hermes_LNA_dB
|
||||
@@ -33,7 +40,7 @@ class BottomWidgets: # Add extra widgets to the bottom of the screen
|
||||
self.sliderLNA.idName = "RfLna"
|
||||
app.midiControls["RfLna"] = (self.sliderLNA, self.OnLNA)
|
||||
hardware.ChangeLNA(init)
|
||||
gbs.Add(self.sliderLNA, (start_row, self.start_col + 2), (1, 8), flag=wx.EXPAND)
|
||||
gbs.Add(self.sliderLNA, (start_row, self.start_col + 4), (1, 6), flag=wx.EXPAND)
|
||||
if conf.button_layout == "Small screen":
|
||||
# Display four data items in a single window
|
||||
self.text_temperature = app.QuiskText1(frame, '', bh)
|
||||
@@ -93,6 +100,20 @@ class BottomWidgets: # Add extra widgets to the bottom of the screen
|
||||
value = self.sliderLNA.GetValue()
|
||||
self.hardware.ChangeLNA(value)
|
||||
self.application.hermes_LNA_dB = value
|
||||
def OnBtnPS(self, event):
|
||||
if self.btnPS.GetValue():
|
||||
QS.set_tx_audio(PsEnable=1)
|
||||
self.btnPsCal.Enable(False)
|
||||
else:
|
||||
QS.set_tx_audio(PsEnable=0)
|
||||
self.btnPsCal.Enable(True)
|
||||
def OnBtnPsCal(self, event):
|
||||
if len(self.application.multi_rx_screen.receiver_list) == 0:
|
||||
self.application.multi_rx_screen.OnAddReceiver(event=None, hide_it=True)
|
||||
self.btnPS.Enable(False)
|
||||
self.btnPsCal.active = True
|
||||
self.btnPsCal.SetColor(self.config.color_check_btn)
|
||||
QS.set_tx_audio(PsCal=1)
|
||||
def Code2Temp(self): # Convert the HermesLite temperature code to the temperature
|
||||
temp = self.hardware.hermes_temperature
|
||||
# For best accuracy, 3.26 should be a user's measured 3.3V supply voltage.
|
||||
@@ -117,7 +138,13 @@ class BottomWidgets: # Add extra widgets to the bottom of the screen
|
||||
return fwd, rev
|
||||
else:
|
||||
return rev, fwd
|
||||
def UpdateText(self):
|
||||
def UpdateText(self): # Called at intervals
|
||||
# Button state
|
||||
if self.btnPsCal.active:
|
||||
if not QS.get_tx_audio("PsCal"):
|
||||
self.btnPsCal.active = False
|
||||
self.btnPsCal.SetColorNormal()
|
||||
self.btnPS.Enable(True)
|
||||
# Temperature
|
||||
temp = self.Code2Temp()
|
||||
temp = (" Temp %3.0f" % temp) + u'\u2103'
|
||||
|
||||
+613
-11
@@ -896,6 +896,8 @@ static void quisk_hermes_tx_add(complex double * cSamples, int tx_count, int key
|
||||
if (hermes_read_index >= HERMES_TX_BUF_SHORTS)
|
||||
hermes_read_index -= HERMES_TX_BUF_SHORTS;
|
||||
}
|
||||
// Send the transmit samples to the predistortion logic. Modify the samples in place.
|
||||
PreDistort(NULL, NULL, 0, cSamples, tx_count);
|
||||
hermes_num_samples += tx_count;
|
||||
for (i = 0; i < tx_count; i++) { // Put transmit mic samples into the buffer
|
||||
hermes_buf[hermes_write_index++] = (short)cimag(cSamples[i]);
|
||||
@@ -911,6 +913,7 @@ void quisk_hermes_tx_send(int tx_socket, int * tx_records)
|
||||
{ // Send one UDP block of mic samples using the Metis-Hermes protocol. Timing is from blocks received, rate is 48k.
|
||||
// If the key is up we send samples anyway, but the samples are zero.
|
||||
int i, j, offset, sent, ratio;
|
||||
int PsInUse;
|
||||
short s;
|
||||
unsigned char sendbuf[1032];
|
||||
unsigned char * pt_buf;
|
||||
@@ -918,6 +921,7 @@ void quisk_hermes_tx_send(int tx_socket, int * tx_records)
|
||||
static unsigned char C0_index = 0;
|
||||
complex double cw_samples[63 * 2];
|
||||
static double writequeue_time0 = 0;
|
||||
static unsigned char rx1_freq[4]; // frequency of the first receiver
|
||||
|
||||
//printf("hermes_tx_send start 1: hermes_num_samples %d\n", hermes_num_samples);
|
||||
if (tx_records == NULL) {
|
||||
@@ -930,6 +934,7 @@ void quisk_hermes_tx_send(int tx_socket, int * tx_records)
|
||||
last_play_state = quisk_play_state;
|
||||
serial_key_samples(NULL, 0);
|
||||
}
|
||||
PsInUse = PsEnable || PsCal;
|
||||
//printf("hermes_tx_send start 2: hermes_num_samples %d\n", hermes_num_samples);
|
||||
ratio = quisk_sound_state.sample_rate / 48000; // send rate is 48 ksps
|
||||
//printf ("quisk_hermes_tx_send ratio %d count %d\n", ratio, *tx_records);
|
||||
@@ -973,15 +978,33 @@ void quisk_hermes_tx_send(int tx_socket, int * tx_records)
|
||||
sendbuf[13] = quisk_pc_to_hermes[offset++]; // C2
|
||||
sendbuf[14] = quisk_pc_to_hermes[offset++]; // C3
|
||||
sendbuf[15] = quisk_pc_to_hermes[offset++]; // C4
|
||||
if (C0_index == 0) { // Do not change receiver count without stopping Hermes and restarting
|
||||
switch (C0_index) {
|
||||
case 0: // Do not change receiver count without stopping Hermes and restarting
|
||||
sendbuf[15] = quisk_multirx_count << 3 | 0x04; // Send the old count, not the changed count
|
||||
if (hermes_mox_bit) // send filter selection on J16
|
||||
sendbuf[13] = hermes_filter_tx << 1;
|
||||
else
|
||||
sendbuf[13] = hermes_filter_rx << 1;
|
||||
break;
|
||||
case 2: // Rx1 frequency
|
||||
rx1_freq[0] = sendbuf[12];
|
||||
rx1_freq[1] = sendbuf[13];
|
||||
rx1_freq[2] = sendbuf[14];
|
||||
rx1_freq[3] = sendbuf[15];
|
||||
break;
|
||||
case 3: // Rx2 frequency
|
||||
if (PsInUse && hermes_mox_bit) {
|
||||
sendbuf[12] = rx1_freq[0];
|
||||
sendbuf[13] = rx1_freq[1];
|
||||
sendbuf[14] = rx1_freq[2];
|
||||
sendbuf[15] = rx1_freq[3];
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
if (PsInUse && hermes_mox_bit)
|
||||
sendbuf[13] |= 0x40; // assert pure signal
|
||||
break;
|
||||
}
|
||||
else if ( ! quisk_is_vna && C0_index == 9) {
|
||||
}
|
||||
if (++C0_index > 16)
|
||||
C0_index = 0;
|
||||
pt_buf = sendbuf + 16;
|
||||
@@ -1030,15 +1053,33 @@ void quisk_hermes_tx_send(int tx_socket, int * tx_records)
|
||||
sendbuf[525] = quisk_pc_to_hermes[offset++]; // C2
|
||||
sendbuf[526] = quisk_pc_to_hermes[offset++]; // C3
|
||||
sendbuf[527] = quisk_pc_to_hermes[offset++]; // C4
|
||||
if (C0_index == 0) {
|
||||
sendbuf[527] = quisk_multirx_count << 3 | 0x04; // Send the old count, not the changed count
|
||||
switch (C0_index) {
|
||||
case 0: // Do not change receiver count without stopping Hermes and restarting
|
||||
sendbuf[527] = quisk_multirx_count << 3 | 0x04; // Send the old count, not the changed count
|
||||
if (hermes_mox_bit) // send filter selection on J16
|
||||
sendbuf[525] = hermes_filter_tx << 1;
|
||||
else
|
||||
sendbuf[525] = hermes_filter_rx << 1;
|
||||
break;
|
||||
case 2: // Rx1 frequency
|
||||
rx1_freq[0] = sendbuf[524];
|
||||
rx1_freq[1] = sendbuf[525];
|
||||
rx1_freq[2] = sendbuf[526];
|
||||
rx1_freq[3] = sendbuf[527];
|
||||
break;
|
||||
case 3: // Rx2 frequency
|
||||
if (PsInUse && hermes_mox_bit) {
|
||||
sendbuf[524] = rx1_freq[0];
|
||||
sendbuf[525] = rx1_freq[1];
|
||||
sendbuf[526] = rx1_freq[2];
|
||||
sendbuf[527] = rx1_freq[3];
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
if (PsInUse && hermes_mox_bit)
|
||||
sendbuf[525] |= 0x40; // assert pure signal
|
||||
break;
|
||||
}
|
||||
else if ( ! quisk_is_vna && C0_index == 9) {
|
||||
}
|
||||
if (++C0_index > 16)
|
||||
C0_index = 0;
|
||||
}
|
||||
@@ -1104,12 +1145,47 @@ static void transmit_udp(complex double * cSamples, int count)
|
||||
|
||||
static void transmit_mic_carrier(complex double * cSamples, int count, double level)
|
||||
{ // send a CW carrier instead of mic samples
|
||||
#if 0
|
||||
// transmit a saw tooth
|
||||
static int up = 1;
|
||||
static int ramp = 0;
|
||||
int i;
|
||||
int max = 5000;
|
||||
static int pause = 5000 / 10;
|
||||
int half_max = (int)(0.1 / 0.2339 * max);
|
||||
for (i = 0; i < count; i++) {
|
||||
cSamples[i] = (double)ramp / max * CLIP16;
|
||||
if (ramp == half_max && pause) {
|
||||
pause--;
|
||||
}
|
||||
else if (up) {
|
||||
if (ramp == max) {
|
||||
up = 0;
|
||||
pause = max / 10;
|
||||
}
|
||||
else {
|
||||
ramp += 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ramp == 0) {
|
||||
up = 1;
|
||||
pause = max / 10;
|
||||
}
|
||||
else {
|
||||
ramp -= 1;
|
||||
}
|
||||
}
|
||||
//printf("Ramp %d\n", ramp);
|
||||
}
|
||||
#endif
|
||||
#if 1
|
||||
// transmit a carrier equal to the number of samples
|
||||
int i;
|
||||
for (i = 0; i < count; i++)
|
||||
cSamples[i] = level * CLIP16;
|
||||
#else
|
||||
#endif
|
||||
#if 0
|
||||
// replace mic samples with a sin wave
|
||||
int i;
|
||||
double freq;
|
||||
@@ -1464,14 +1540,16 @@ PyObject * quisk_get_tx_audio(PyObject * self, PyObject * args)
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
|
||||
PyObject * quisk_set_tx_audio(PyObject * self, PyObject * args, PyObject * keywds)
|
||||
{ /* Call with keyword arguments ONLY; change Tx audio parameters */
|
||||
static char * kwlist[] = {"vox_level", "vox_time", "mic_clip", "mic_preemphasis", "tx_sample_rate",
|
||||
"reverse_tx_sideband", NULL} ;
|
||||
"reverse_tx_sideband", "PsEnable", "PsCal", NULL} ;
|
||||
int vlevel = -9999, clevel = -9999;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiidii", kwlist,
|
||||
&vlevel, &timeVOX, &clevel, &quisk_mic_preemphasis, &tx_sample_rate, &reverse_tx_sideband))
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiidiiii", kwlist,
|
||||
&vlevel, &timeVOX, &clevel, &quisk_mic_preemphasis, &tx_sample_rate, &reverse_tx_sideband,
|
||||
&PsEnable, &PsCal))
|
||||
return NULL;
|
||||
if (vlevel != -9999)
|
||||
vox_level = (int)(pow(10.0, vlevel / 20.0) * CLIP16); // Convert dB to 16-bit sample
|
||||
@@ -1641,3 +1719,527 @@ static void serial_key_samples(complex double * cSamples, int count) // called f
|
||||
cSamples[i] = ampl * CLIP16;
|
||||
}
|
||||
}
|
||||
|
||||
static void spline(double x[], double y[], int n, double yp1, double ypn, double y2[])
|
||||
{
|
||||
int i,k;
|
||||
double p,qn,sig,un,*u;
|
||||
|
||||
u = (double *)malloc(n * sizeof(double));
|
||||
if (yp1 > 0.99e30)
|
||||
y2[1]=u[1]=0.0;
|
||||
else {
|
||||
y2[1] = -0.5;
|
||||
u[1]=(3.0/(x[2]-x[1]))*((y[2]-y[1])/(x[2]-x[1])-yp1);
|
||||
}
|
||||
for (i=2;i<=n-1;i++) {
|
||||
sig=(x[i]-x[i-1])/(x[i+1]-x[i-1]);
|
||||
p=sig*y2[i-1]+2.0;
|
||||
y2[i]=(sig-1.0)/p;
|
||||
u[i]=(y[i+1]-y[i])/(x[i+1]-x[i]) - (y[i]-y[i-1])/(x[i]-x[i-1]);
|
||||
u[i]=(6.0*u[i]/(x[i+1]-x[i-1])-sig*u[i-1])/p;
|
||||
}
|
||||
if (ypn > 0.99e30)
|
||||
qn=un=0.0;
|
||||
else {
|
||||
qn=0.5;
|
||||
un=(3.0/(x[n]-x[n-1]))*(ypn-(y[n]-y[n-1])/(x[n]-x[n-1]));
|
||||
}
|
||||
y2[n]=(un-qn*u[n-1])/(qn*y2[n-1]+1.0);
|
||||
for (k=n-1;k>=1;k--)
|
||||
y2[k]=y2[k]*y2[k+1]+u[k];
|
||||
free(u);
|
||||
}
|
||||
|
||||
static void polint(double xa[], double ya[], double x, double *y)
|
||||
{
|
||||
int i,m,ns=1;
|
||||
double den,dif,dift,ho,hp,w, dy;
|
||||
double c[4], d[4];
|
||||
|
||||
dif=fabs(x-xa[1]);
|
||||
for (i=1;i<=3;i++) {
|
||||
if ( (dift=fabs(x-xa[i])) < dif) {
|
||||
ns=i;
|
||||
dif=dift;
|
||||
}
|
||||
c[i]=ya[i];
|
||||
d[i]=ya[i];
|
||||
}
|
||||
*y=ya[ns--];
|
||||
for (m=1;m<3;m++) {
|
||||
for (i=1;i<=3-m;i++) {
|
||||
ho=xa[i]-x;
|
||||
hp=xa[i+m]-x;
|
||||
w=c[i+1]-d[i];
|
||||
den = ho - hp;
|
||||
if ( (den=ho-hp) == 0.0)
|
||||
printf("Error in routine polint\n");
|
||||
den=w/den;
|
||||
d[i]=hp*den;
|
||||
c[i]=ho*den;
|
||||
}
|
||||
*y += (dy=(2*ns < (3-m) ? c[ns+1] : d[ns--]));
|
||||
}
|
||||
}
|
||||
|
||||
static void splint(double xa[], double ya[], double y2a[], int n, double x, double *y)
|
||||
{
|
||||
static int klo = 1;
|
||||
int khi,k;
|
||||
double h,b,a;
|
||||
|
||||
khi = klo + 1;
|
||||
if (x < xa[klo] || x > xa[khi]) {
|
||||
klo=1;
|
||||
khi=n;
|
||||
while (khi-klo > 1) {
|
||||
k=(khi+klo) >> 1;
|
||||
if (xa[k] > x) khi=k;
|
||||
else klo=k;
|
||||
}
|
||||
}
|
||||
h=xa[khi]-xa[klo];
|
||||
if (h == 0.0)
|
||||
printf("Bad xa input to routine splint\n");
|
||||
a=(xa[khi]-x)/h;
|
||||
b=(x-xa[klo])/h;
|
||||
*y=a*ya[klo]+b*ya[khi]+((a*a*a-a)*y2a[klo]+(b*b*b-b)*y2a[khi])*(h*h)/6.0;
|
||||
}
|
||||
|
||||
#define NUM_BINS 21
|
||||
#define BIN_SPACING (1.0 / (NUM_BINS - 1))
|
||||
#define BIN_TOL (BIN_SPACING * 0.20)
|
||||
#define BLOCK_MSECS 2000 // Time of one data block.
|
||||
#define MILLISECS(x) (48 * (x)) // Convert milliseconds to samples at 48000 samples per second.
|
||||
#define DEBUG_PS 0
|
||||
#define HOLD 0 // Make calibration run permanent
|
||||
#define RX1_GAIN 0.238
|
||||
void PreDistort(complex double * amp_in_samples, complex double * amp_out_samples, int nSamples, complex double * tx_samples, int num_tx)
|
||||
{
|
||||
int i, bin, num_bin_data;
|
||||
double G0, phase, mag_in, mag_out, new_mag, aaa, ppp;
|
||||
double error, xp, xpg, xp0, xpg0;
|
||||
double quadX[4], quadY[4], quadZ[4];
|
||||
complex double in_sample, out_sample;
|
||||
static int state = 0;
|
||||
static int Fault;
|
||||
static int isCalibrated = 0;
|
||||
static int ramp_counter, ramp_count0;
|
||||
static int input_samples = 0;
|
||||
static double tx_to_RX1_gain = 0;
|
||||
static double max_out_mag, starting_phase;
|
||||
#if DEBUG_PS
|
||||
static double clip_tx, clip_mag_in, clip_aaa;
|
||||
#endif
|
||||
#if DEBUG_PS > 2
|
||||
static int old_state;
|
||||
static double time0;
|
||||
#endif
|
||||
static int spline_P_size;
|
||||
static double spline_P_X[NUM_BINS + 1];
|
||||
static double spline_P_mag[NUM_BINS + 1];
|
||||
static double spline_P_mag2[NUM_BINS + 1];
|
||||
static double spline_P_phase[NUM_BINS + 1];
|
||||
static double spline_P_phase2[NUM_BINS + 1];
|
||||
|
||||
struct BPD {
|
||||
int XP_count;
|
||||
double table_XP;
|
||||
double XP_mag;
|
||||
double mag_X;
|
||||
double mag_P;
|
||||
double phase_P;
|
||||
complex double XP_gain;
|
||||
double XP_gain_mag;
|
||||
double XP_gain_phase;
|
||||
};
|
||||
static struct BPD Bins[NUM_BINS];
|
||||
|
||||
static enum {
|
||||
NothingHappens,
|
||||
SendCW,
|
||||
StopCW
|
||||
} tx_state = NothingHappens;
|
||||
|
||||
if (quisk_multirx_count == 0) {
|
||||
state = 0;
|
||||
return;
|
||||
}
|
||||
if (PsCal) {
|
||||
if (state == 0) {
|
||||
isCalibrated = 0;
|
||||
state = 10;
|
||||
#if DEBUG_PS > 2
|
||||
time0 = QuiskTimeSec();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if ( ! PsEnable) {
|
||||
state = 0;
|
||||
return;
|
||||
}
|
||||
else if ( ! hermes_mox_bit) {
|
||||
state = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (num_tx) {
|
||||
input_samples += num_tx;
|
||||
if (isCalibrated) { // perform predistortion of Tx samples; multiply input X by P
|
||||
for (i = 0; i < num_tx; i++) {
|
||||
mag_in = cabs(tx_samples[i]) / CLIP16;
|
||||
splint(spline_P_X, spline_P_mag, spline_P_mag2, spline_P_size, mag_in, &aaa);
|
||||
splint(spline_P_X, spline_P_phase, spline_P_phase2, spline_P_size, mag_in, &ppp);
|
||||
tx_samples[i] *= aaa * cexp(I * ppp);
|
||||
new_mag = mag_in * aaa;
|
||||
if (new_mag > 1.0) {
|
||||
tx_samples[i] /= new_mag;
|
||||
#if DEBUG_PS
|
||||
if (new_mag > 1.0001 && new_mag > clip_tx) {
|
||||
clip_tx = new_mag;
|
||||
clip_mag_in = mag_in;
|
||||
clip_aaa = aaa;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (tx_state) {
|
||||
case NothingHappens:
|
||||
default:
|
||||
break;
|
||||
case SendCW: // Start sending a CW carrier.
|
||||
for (i = 0; i < num_tx; i++) {
|
||||
if (ramp_counter > 0) // slow increase
|
||||
tx_samples[i] = CLIP16 * (1.0 - (double)ramp_counter-- / ramp_count0);
|
||||
else
|
||||
tx_samples[i] = CLIP16;
|
||||
}
|
||||
break;
|
||||
case StopCW: // Stop sending a CW carrier.
|
||||
for (i = 0; i < num_tx; i++) {
|
||||
if (ramp_counter > 0) // slow decrease
|
||||
tx_samples[i] = (double)CLIP16 * ramp_counter-- / ramp_count0;
|
||||
else
|
||||
tx_samples[i] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nSamples <= 0)
|
||||
return;
|
||||
|
||||
#if DEBUG_PS > 2
|
||||
if (state != old_state) {
|
||||
aaa = cabs(amp_in_samples[0] / CLIP32);
|
||||
printf("State %2d time %6.3lf sample %12.6f\n", old_state, QuiskTimeSec() - time0, aaa);
|
||||
old_state = state;
|
||||
time0 = QuiskTimeSec();
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (state) {
|
||||
case 0: // Start a timer
|
||||
tx_state = NothingHappens;
|
||||
input_samples = 0;
|
||||
starting_phase = 0;
|
||||
if ( ! HOLD)
|
||||
state++;
|
||||
break;
|
||||
case 1:
|
||||
if (input_samples >= MILLISECS(200)) {
|
||||
#if DEBUG_PS
|
||||
clip_tx = 0.0;
|
||||
#endif
|
||||
for (bin = 0; bin < NUM_BINS; bin++) { // initialize
|
||||
Bins[bin].XP_count = 0;
|
||||
Bins[bin].XP_mag = 0;
|
||||
Bins[bin].XP_gain = 0;
|
||||
}
|
||||
input_samples = 0;
|
||||
state++;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
case 15:
|
||||
if (input_samples >= MILLISECS(BLOCK_MSECS)) {
|
||||
num_bin_data = 0;
|
||||
Fault = 0;
|
||||
for (bin = 0; bin < NUM_BINS; bin++) {
|
||||
if (Bins[bin].XP_count) {
|
||||
num_bin_data++;
|
||||
Bins[bin].XP_mag /= Bins[bin].XP_count;
|
||||
Bins[bin].XP_gain /= Bins[bin].XP_count;
|
||||
if (starting_phase == 0 && bin >= NUM_BINS / 2) {
|
||||
starting_phase = carg(Bins[bin].XP_gain);
|
||||
#if DEBUG_PS
|
||||
printf("starting_phase %.3f\n", starting_phase);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PsCal && num_bin_data < NUM_BINS - 4) {
|
||||
printf("PS calibration failed to fill all bins ");
|
||||
for (bin = 0; bin < NUM_BINS; bin++)
|
||||
if (Bins[bin].XP_count)
|
||||
printf("1");
|
||||
else
|
||||
printf("0");
|
||||
printf("\n");
|
||||
Fault = 1;
|
||||
}
|
||||
state++;
|
||||
}
|
||||
else {
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
in_sample = amp_in_samples[i] / CLIP32 / RX1_GAIN;
|
||||
mag_in = cabs(in_sample);
|
||||
if (tx_to_RX1_gain < mag_in) {
|
||||
tx_to_RX1_gain = mag_in;
|
||||
#if DEBUG_PS > 2
|
||||
printf("tx RX1 gain %.6f, max_out_mag %.6f\n", tx_to_RX1_gain, max_out_mag);
|
||||
#endif
|
||||
}
|
||||
bin = (int)(mag_in / BIN_SPACING + 0.5);
|
||||
if ( ! bin)
|
||||
continue;
|
||||
error = fabs(mag_in - Bins[bin].table_XP);
|
||||
if (error < BIN_TOL) {
|
||||
out_sample = amp_out_samples[i] / CLIP32;
|
||||
Bins[bin].XP_gain += out_sample / in_sample;
|
||||
Bins[bin].XP_mag += mag_in;
|
||||
Bins[bin].XP_count++;
|
||||
}
|
||||
}
|
||||
if (state == 15) {
|
||||
if (tx_state != StopCW) {
|
||||
ramp_counter = ramp_count0 = MILLISECS(BLOCK_MSECS - 500);
|
||||
tx_state = StopCW;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
case 16:
|
||||
#if DEBUG_PS
|
||||
if (clip_tx != 0.0) {
|
||||
printf ("clip output sample %.6f, mag_in %.6f, predistort %.6f\n", clip_tx, clip_mag_in, clip_aaa);
|
||||
clip_tx = 0.0;
|
||||
}
|
||||
#endif
|
||||
for (bin = 1; bin < NUM_BINS - 1; bin++) {
|
||||
if (Bins[bin].XP_count > 0) { // This bin has data.
|
||||
Bins[bin].XP_gain_mag = cabs(Bins[bin].XP_gain);
|
||||
phase = carg(Bins[bin].XP_gain) - starting_phase;
|
||||
if (phase < - M_PI)
|
||||
phase += 2 * M_PI;
|
||||
else if (phase > M_PI)
|
||||
phase -= 2 * M_PI;
|
||||
Bins[bin].XP_gain_phase = phase;
|
||||
}
|
||||
else {
|
||||
Bins[bin].XP_gain_mag = 1.0;
|
||||
Bins[bin].XP_gain_phase = 0.0;
|
||||
}
|
||||
}
|
||||
Bins[0].XP_count = 1;
|
||||
Bins[0].XP_mag = 0.0;
|
||||
i = 1;
|
||||
for (bin = 1; bin < 6; bin++) {
|
||||
if (Bins[bin].XP_count) {
|
||||
quadX[i] = Bins[bin].XP_mag;
|
||||
quadY[i] = Bins[bin].XP_gain_mag;
|
||||
quadZ[i] = Bins[bin].XP_gain_phase;
|
||||
if (i == 3)
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (i == 3) {
|
||||
polint(quadX, quadY, 0.0, &Bins[0].XP_gain_mag);
|
||||
polint(quadX, quadZ, 0.0, &Bins[0].XP_gain_phase);
|
||||
}
|
||||
else {
|
||||
Bins[0].XP_gain_mag = 1.0;
|
||||
Bins[0].XP_gain_phase = 0.0;
|
||||
Fault = 1;
|
||||
if (DEBUG_PS || PsCal)
|
||||
printf("Failure to find G0 at zero\n");
|
||||
}
|
||||
|
||||
Bins[NUM_BINS - 1].XP_count = 1;
|
||||
Bins[NUM_BINS - 1].XP_mag = 1.0;
|
||||
i = 3;
|
||||
for (bin = NUM_BINS - 2; bin >= NUM_BINS - 6; bin--) {
|
||||
if (Bins[bin].XP_count) {
|
||||
quadX[i] = Bins[bin].XP_mag;
|
||||
quadY[i] = Bins[bin].XP_gain_mag;
|
||||
quadZ[i] = Bins[bin].XP_gain_phase;
|
||||
if (--i == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == 0) {
|
||||
polint(quadX, quadY, 1.0, &Bins[NUM_BINS - 1].XP_gain_mag);
|
||||
polint(quadX, quadZ, 1.0, &Bins[NUM_BINS - 1].XP_gain_phase);
|
||||
}
|
||||
else {
|
||||
Bins[NUM_BINS - 1].XP_gain_mag = 1.0;
|
||||
Bins[NUM_BINS - 1].XP_gain_phase = 0.0;
|
||||
Fault = 1;
|
||||
if (DEBUG_PS || PsCal)
|
||||
printf("Failure to find G0 at one\n");
|
||||
}
|
||||
state++;
|
||||
break;
|
||||
case 4:
|
||||
case 17:
|
||||
G0 = Bins[NUM_BINS - 1].XP_gain_mag;
|
||||
num_bin_data = 0;
|
||||
xp0 = -1.0;
|
||||
xpg0 = -1.0;
|
||||
for (bin = 0; bin < NUM_BINS; bin++) {
|
||||
if (bin == NUM_BINS - 1) {
|
||||
num_bin_data++;
|
||||
Bins[NUM_BINS - 1].mag_P = 1.0;
|
||||
Bins[NUM_BINS - 1].phase_P = - Bins[NUM_BINS - 1].XP_gain_phase;
|
||||
Bins[NUM_BINS - 1].mag_X = 1.0;
|
||||
}
|
||||
else if (Bins[bin].XP_count) {
|
||||
num_bin_data++;
|
||||
Bins[bin].mag_P = G0 / Bins[bin].XP_gain_mag;
|
||||
Bins[bin].phase_P = - Bins[bin].XP_gain_phase;
|
||||
Bins[bin].mag_X = Bins[bin].XP_mag / Bins[bin].mag_P;
|
||||
xp = Bins[bin].mag_X * Bins[bin].mag_P;
|
||||
if (xp < xp0) {
|
||||
if (DEBUG_PS || PsCal)
|
||||
printf("xp is not monotonic at bin %d\n", bin);
|
||||
Fault = 1;
|
||||
}
|
||||
xpg = xp * Bins[bin].XP_gain_mag;
|
||||
if (xpg < xpg0) {
|
||||
if (DEBUG_PS || PsCal)
|
||||
printf("xpg is not monotonic at bin %d\n", bin);
|
||||
Fault = 1;
|
||||
}
|
||||
xp0 = xp;
|
||||
xpg0 = xpg;
|
||||
}
|
||||
else {
|
||||
Bins[bin].mag_P = 0.0;
|
||||
Bins[bin].phase_P = 0.0;
|
||||
Bins[bin].mag_X = 0.0;
|
||||
}
|
||||
}
|
||||
if (num_bin_data < NUM_BINS * 7 / 10) {
|
||||
if (DEBUG_PS || state == 17)
|
||||
printf("Not enough data for corrections %d\n", num_bin_data);
|
||||
Fault = 1;
|
||||
}
|
||||
if ( ! Fault) {
|
||||
#if DEBUG_PS
|
||||
printf("Create a new predistortion\n");
|
||||
#endif
|
||||
spline_P_size = 0;
|
||||
for (bin = 0; bin < NUM_BINS; bin++) {
|
||||
if (Bins[bin].XP_count) {
|
||||
spline_P_size++;
|
||||
spline_P_X[spline_P_size] = Bins[bin].mag_X;
|
||||
spline_P_mag[spline_P_size] = Bins[bin].mag_P;
|
||||
spline_P_phase[spline_P_size] = Bins[bin].phase_P;
|
||||
}
|
||||
}
|
||||
|
||||
spline(spline_P_X, spline_P_mag, spline_P_size, 2E30, 2E30, spline_P_mag2);
|
||||
spline(spline_P_X, spline_P_phase, spline_P_size, 2E30, 2E30, spline_P_phase2);
|
||||
}
|
||||
state++;
|
||||
break;
|
||||
case 5:
|
||||
case 18:
|
||||
#if DEBUG_PS
|
||||
if (Fault)
|
||||
printf("Data for predistortion was rejected\n");
|
||||
printf("\n");
|
||||
#endif
|
||||
input_samples = 0;
|
||||
state++;
|
||||
break;
|
||||
case 6:
|
||||
if ( ! HOLD)
|
||||
state = 1;
|
||||
input_samples = 0;
|
||||
break;
|
||||
case 10:
|
||||
isCalibrated = 0;
|
||||
input_samples = 0;
|
||||
quisk_set_key_down(1);
|
||||
max_out_mag = 0;
|
||||
tx_to_RX1_gain = 0;
|
||||
starting_phase = 0;
|
||||
ramp_counter = ramp_count0 = MILLISECS(10);
|
||||
#if DEBUG_PS
|
||||
clip_tx = 0.0;
|
||||
#endif
|
||||
for (bin = 0; bin < NUM_BINS; bin++) {
|
||||
Bins[bin].table_XP = BIN_SPACING * bin;
|
||||
Bins[bin].XP_mag = 0;
|
||||
Bins[bin].XP_count = 0;
|
||||
Bins[bin].XP_gain = 0;
|
||||
}
|
||||
tx_state = SendCW;
|
||||
state++;
|
||||
break;
|
||||
case 11:
|
||||
if (input_samples >= MILLISECS(400)) {
|
||||
input_samples = 0;
|
||||
state++;
|
||||
}
|
||||
if (input_samples >= MILLISECS(50) && cabs(amp_in_samples[0]) / CLIP32 / RX1_GAIN > 0.1) {
|
||||
input_samples = 0;
|
||||
state++;
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
if (input_samples >= MILLISECS(200)) {
|
||||
state++;
|
||||
}
|
||||
else {
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
in_sample = amp_in_samples[i] / CLIP32;
|
||||
mag_in = cabs(in_sample);
|
||||
if (tx_to_RX1_gain < mag_in)
|
||||
tx_to_RX1_gain = mag_in;
|
||||
out_sample = amp_out_samples[i] / CLIP32;
|
||||
mag_out = cabs(out_sample);
|
||||
if (max_out_mag < mag_out)
|
||||
max_out_mag = mag_out;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 13:
|
||||
#if DEBUG_PS
|
||||
printf("tx RX1 gain %.5f, max_out_mag %.6f\n", tx_to_RX1_gain, max_out_mag);
|
||||
#endif
|
||||
if (fabs(tx_to_RX1_gain - RX1_GAIN) / RX1_GAIN > 1E-2)
|
||||
printf("Measured %.6f and assumed %.6f RX1 gain differ\n", tx_to_RX1_gain, RX1_GAIN);
|
||||
input_samples = 0;
|
||||
state++;
|
||||
break;
|
||||
case 14:
|
||||
input_samples = 0;
|
||||
state++;
|
||||
break;
|
||||
case 19:
|
||||
quisk_set_key_down(0);
|
||||
tx_state = NothingHappens;
|
||||
PsCal = 0;
|
||||
if ( ! Fault)
|
||||
isCalibrated = 1;
|
||||
input_samples = 0;
|
||||
state = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3851,7 +3851,7 @@ static int read_rx_udp10(complex double * samp) // Read samples from UDP using t
|
||||
}
|
||||
}
|
||||
}
|
||||
//PreDistort(multirx_cSamples[0], samp, nSamples, NULL, 0); //amp in samples, amp out samples, count
|
||||
PreDistort(multirx_cSamples[0], samp, nSamples, NULL, 0); //amp in samples, amp out samples, count
|
||||
#if 0
|
||||
float mag_in, mag_out;
|
||||
complex float gain;
|
||||
|
||||
@@ -7,7 +7,7 @@ import os
|
||||
# You must define the version here. A title string including
|
||||
# the version will be written to __init__.py and read by quisk.py.
|
||||
|
||||
Version = '4.2.43'
|
||||
Version = '4.2.44'
|
||||
|
||||
fp = open("__init__.py", "w") # write title string
|
||||
fp.write("#Quisk version %s\n" % Version)
|
||||
|
||||
Reference in New Issue
Block a user