SA/GEN integration tests + bugfixes

This commit is contained in:
Jan Käberich 2022-11-20 20:48:36 +01:00
parent ec6fae5822
commit 0c17288ece
11 changed files with 171 additions and 15 deletions

View File

@ -90,7 +90,7 @@ class libreVNA:
return self.__read_response()
@staticmethod
def parse_trace_data(data):
def parse_VNA_trace_data(data):
ret = []
# Remove brackets (order of data implicitly known)
data = data.replace(']','').replace('[','')
@ -103,5 +103,20 @@ class libreVNA:
real = float(values[i+1])
imag = float(values[i+2])
ret.append((freq, complex(real, imag)))
return ret
return ret
@staticmethod
def parse_SA_trace_data(data):
ret = []
# Remove brackets (order of data implicitly known)
data = data.replace(']','').replace('[','')
values = data.split(',')
if int(len(values) / 2) * 2 != len(values):
# number of values must be a multiple of two (frequency, dBm)
raise Exception("Invalid input data: expected tuples of two values each")
for i in range(0, len(values), 2):
freq = float(values[i])
dBm = float(values[i+1])
ret.append((freq, dBm))
return ret

View File

@ -42,7 +42,7 @@ data = vna.query(":VNA:TRACE:DATA? S11")
# Returned data is just a string containing all the measurement points.
# Parsing the data returns a list containing frequency/complex tuples
S11 = vna.parse_trace_data(data)
S11 = vna.parse_VNA_trace_data(data)
for x in S11:
print(x)

View File

@ -5,6 +5,8 @@ testmodules = [
'tests.TestMode',
'tests.TestVNASweep',
'tests.TestCalibration',
'tests.TestGenerator',
'tests.TestSASweep',
]
suite = unittest.TestSuite()

View File

@ -149,10 +149,10 @@ class TestCalibration(TestBase):
cal.reset()
# grab trace data
S11 = self.vna.parse_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
S12 = self.vna.parse_trace_data(self.vna.query(":VNA:TRACE:DATA? S12"))
S21 = self.vna.parse_trace_data(self.vna.query(":VNA:TRACE:DATA? S21"))
S22 = self.vna.parse_trace_data(self.vna.query(":VNA:TRACE:DATA? S22"))
S11 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
S12 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S12"))
S21 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S21"))
S22 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S22"))
# Attenuation is frequency dependent, use excessively large limits
# TODO: use smaller limits based on frequency

View File

@ -0,0 +1,16 @@
from tests.TestBase import TestBase
class TestGenerator(TestBase):
def test_Generator(self):
self.vna.cmd(":DEV:MODE GEN")
self.assertEqual(self.vna.query(":DEV:MODE?"), "GEN")
self.vna.cmd(":GEN:FREQ 2000000000")
self.assertEqual(float(self.vna.query(":GEN:FREQ?")), 2000000000)
self.vna.cmd(":GEN:LVL -12.34")
self.assertEqual(float(self.vna.query(":GEN:LVL?")), -12.34)
self.assertEqual(self.vna.query(":GEN:PORT?"), "0")
self.vna.cmd(":GEN:PORT 2")
self.assertEqual(self.vna.query(":GEN:PORT?"), "2")

View File

@ -0,0 +1,98 @@
from tests.TestBase import TestBase
import time
class TestSASweep(TestBase):
def waitSweepTimeout(self, timeout = 1):
self.assertEqual(self.vna.query(":SA:ACQ:FIN?"), "FALSE")
stoptime = time.time() + timeout
while self.vna.query(":SA:ACQ:FIN?") == "FALSE":
if time.time() > stoptime:
raise AssertionError("Sweep timed out")
def assertTrace_dB(self, trace, dB_nominal, dB_deviation):
for S in trace:
self.assertLessEqual(S[1], dB_nominal + dB_deviation)
self.assertGreaterEqual(S[1], dB_nominal - dB_deviation)
def test_sweep_frequency(self):
self.vna.cmd(":DEV:MODE SA")
self.vna.cmd(":SA:FREQ:CENT 2000000000")
self.vna.cmd(":SA:FREQ:SPAN 200000")
self.assertEqual(float(self.vna.query(":SA:FREQ:START?")), 1999900000)
self.assertEqual(float(self.vna.query(":SA:FREQ:STOP?")), 2000100000)
self.vna.cmd(":SA:ACQ:RBW 10000")
self.waitSweepTimeout(10)
port1 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT1"))
port2 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT2"))
self.assertEqual(port1[0][0], 1999900000)
self.assertEqual(port1[-1][0], 2000100000)
self.assertEqual(port2[0][0], 1999900000)
self.assertEqual(port2[-1][0], 2000100000)
# No signal present, signal level should be very low
self.assertTrace_dB(port1, -140, 60)
self.assertTrace_dB(port2, -140, 60)
def test_sweep_zerospan(self):
self.vna.cmd(":DEV:MODE SA")
self.vna.cmd(":SA:FREQ:CENT 2000000000")
self.vna.cmd(":SA:FREQ:SPAN 0")
self.assertEqual(float(self.vna.query(":SA:FREQ:START?")), 2000000000)
self.assertEqual(float(self.vna.query(":SA:FREQ:STOP?")), 2000000000)
self.vna.cmd(":SA:ACQ:RBW 10000")
self.waitSweepTimeout(10)
port1 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT1"))
port2 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT2"))
self.assertEqual(port1[0][0], 0.0)
self.assertGreater(port1[-1][0], 4.5)
self.assertLess(port1[-1][0], 5)
self.assertEqual(port2[0][0], 0.0)
self.assertGreater(port2[-1][0], 4.5)
self.assertLess(port2[-1][0], 5)
# No signal present, signal level should be very low
self.assertTrace_dB(port1, -140, 60)
self.assertTrace_dB(port2, -140, 60)
def test_tracking_generator(self):
self.vna.cmd(":DEV:MODE SA")
self.vna.cmd(":SA:FREQ:CENT 2000000000")
self.vna.cmd(":SA:FREQ:SPAN 200000")
self.vna.cmd(":SA:ACQ:RBW 10000")
self.vna.cmd(":SA:TRACK:PORT 1")
self.assertEqual(self.vna.query(":SA:TRACK:PORT?"), "1")
self.vna.cmd(":SA:TRACK:LVL -20")
self.vna.cmd(":SA:TRACK:OFFSET 0")
self.vna.cmd(":SA:TRACK:EN TRUE")
self.waitSweepTimeout(10)
port1 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT1"))
level = port1[0][1]
self.assertGreater(level, -30)
self.assertLess(level, -10)
# check tracking generator signal
self.assertTrace_dB(port1, level, 5)
# Enable normalization
self.vna.cmd(":SA:TRACK:NORM:LVL -10")
self.vna.cmd(":SA:TRACK:NORM:EN TRUE")
# wait for normalization to finish
self.waitSweepTimeout(10)
# trigger the next sweep
self.vna.cmd(":SA:ACQ:SINGLE TRUE")
self.waitSweepTimeout(10)
# Reported level on port1 should match normalization very closely now
port1 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT1"))
self.assertTrace_dB(port1, -10, 1)

View File

@ -20,7 +20,7 @@ class TestVNASweep(TestBase):
self.vna.cmd(":VNA:FREQuency:STOP 6000000000")
self.waitSweepTimeout(2)
S11 = self.vna.parse_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
S11 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
self.assertEqual(S11[0][0], 1000000)
self.assertEqual(S11[-1][0], 6000000000)
@ -36,7 +36,7 @@ class TestVNASweep(TestBase):
self.vna.cmd(":VNA:FREQuency:ZERO 1500000000")
self.waitSweepTimeout(2)
S11 = self.vna.parse_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
S11 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
self.assertEqual(S11[0][0], 0.0)
# Sweep should take about 0.125 seconds
self.assertGreater(S11[-1][0], 0.1)
@ -53,7 +53,7 @@ class TestVNASweep(TestBase):
self.vna.cmd(":VNA:POWER:STOP -10")
self.waitSweepTimeout(2)
S11 = self.vna.parse_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
S11 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
self.assertEqual(S11[0][0], -30)
self.assertEqual(S11[-1][0], -10)

View File

@ -132,7 +132,7 @@ public:
Detector detector;
bool signalID;
bool trackingGenerator;
int trackingPort;
int trackingPort; // counting starts at zero
double trackingOffset;
double trackingPower;
};

View File

@ -549,6 +549,7 @@ void SpectrumAnalyzer::NewDatapoint(VirtualDevice::SAMeasurement m)
void SpectrumAnalyzer::SettingsChanged()
{
configurationTimer.start(100);
ResetLiveTraces();
}
void SpectrumAnalyzer::SetStartFreq(double freq)
@ -909,6 +910,13 @@ void SpectrumAnalyzer::ConfigureDevice()
}
}
void SpectrumAnalyzer::ResetLiveTraces()
{
average.reset(settings.points);
traceModel.clearLiveData();
UpdateAverageCount();
}
void SpectrumAnalyzer::SetupSCPI()
{
auto scpi_freq = new SCPINode("FREQuency");
@ -1113,8 +1121,8 @@ void SpectrumAnalyzer::SetupSCPI()
unsigned long long newval;
if(!SCPI::paramToULongLong(params, 0, newval)) {
return SCPI::getResultName(SCPI::Result::Error);
} else if(newval <= VirtualDevice::getInfo(window->getDevice()).ports){
SetTGPort(newval);
} else if(newval > 0 && newval <= VirtualDevice::getInfo(window->getDevice()).ports){
SetTGPort(newval-1);
return SCPI::getResultName(SCPI::Result::Empty);
} else {
// invalid port number
@ -1122,7 +1130,7 @@ void SpectrumAnalyzer::SetupSCPI()
}
return SCPI::getResultName(SCPI::Result::Empty);
}, [=](QStringList) -> QString {
return QString::number(settings.trackingPort);
return QString::number(settings.trackingPort+1);
}));
scpi_tg->add(new SCPICommand("LVL", [=](QStringList params) -> QString {
double newval;

View File

@ -74,6 +74,7 @@ private slots:
void Run();
void Stop();
void ConfigureDevice();
void ResetLiveTraces();
private:
void SetupSCPI();

View File

@ -37,8 +37,11 @@ static uint8_t attenuator;
static int64_t trackingFreq;
static bool trackingLowband;
static bool firstSample;
// Zero span variables
static uint64_t firstPointTime;
static bool firstPoint;
static bool firstPoint; // indicates the first point to be transmitted to the GUI, sets the firstPointTime
static bool zerospan;
static void StartNextSample() {
@ -175,6 +178,17 @@ static void StartNextSample() {
trackingFreq, FPGA::SettlingTime::us60, FPGA::Samples::SPPRegister, 0,
FPGA::LowpassFilter::Auto);
if(firstSample && (signalIDstep == 0)) {
firstSample = false;
// configure source/LO to allow for early settling
FPGA::SetMode(FPGA::Mode::SourcePLL);
Source.Update();
FPGA::SetMode(FPGA::Mode::LOPLL);
LO1.Update();
FPGA::SetMode(FPGA::Mode::FPGA);
HAL_Delay(20);
}
FPGA::StartSweep();
}
@ -214,6 +228,7 @@ void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) {
zerospan = (s.f_start == s.f_stop);
// set initial state
pointCnt = 0;
signalIDstep = 0;
// enable the required hardware resources
Si5351.Enable(SiChannel::Port1LO2);
Si5351.Enable(SiChannel::Port2LO2);
@ -265,6 +280,7 @@ void SA::Setup(Protocol::SpectrumAnalyzerSettings settings) {
lastLO2 = 0;
firstPoint = true;
firstSample = true;
active = true;
StartNextSample();
}