diff --git a/Documentation/UserManual/SCPI_Examples/libreVNA.py b/Documentation/UserManual/SCPI_Examples/libreVNA.py index b06334e..9607183 100644 --- a/Documentation/UserManual/SCPI_Examples/libreVNA.py +++ b/Documentation/UserManual/SCPI_Examples/libreVNA.py @@ -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 diff --git a/Documentation/UserManual/SCPI_Examples/retrieve_trace_data.py b/Documentation/UserManual/SCPI_Examples/retrieve_trace_data.py index dd043a9..147defd 100644 --- a/Documentation/UserManual/SCPI_Examples/retrieve_trace_data.py +++ b/Documentation/UserManual/SCPI_Examples/retrieve_trace_data.py @@ -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) diff --git a/Software/Integrationtests/Integrationtest.py b/Software/Integrationtests/Integrationtest.py index b7987f9..b7c0af2 100644 --- a/Software/Integrationtests/Integrationtest.py +++ b/Software/Integrationtests/Integrationtest.py @@ -5,6 +5,8 @@ testmodules = [ 'tests.TestMode', 'tests.TestVNASweep', 'tests.TestCalibration', + 'tests.TestGenerator', + 'tests.TestSASweep', ] suite = unittest.TestSuite() diff --git a/Software/Integrationtests/tests/TestCalibration.py b/Software/Integrationtests/tests/TestCalibration.py index 1640d2b..234613f 100644 --- a/Software/Integrationtests/tests/TestCalibration.py +++ b/Software/Integrationtests/tests/TestCalibration.py @@ -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 diff --git a/Software/Integrationtests/tests/TestGenerator.py b/Software/Integrationtests/tests/TestGenerator.py new file mode 100644 index 0000000..463e974 --- /dev/null +++ b/Software/Integrationtests/tests/TestGenerator.py @@ -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") diff --git a/Software/Integrationtests/tests/TestSASweep.py b/Software/Integrationtests/tests/TestSASweep.py new file mode 100644 index 0000000..730e8f9 --- /dev/null +++ b/Software/Integrationtests/tests/TestSASweep.py @@ -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) + \ No newline at end of file diff --git a/Software/Integrationtests/tests/TestVNASweep.py b/Software/Integrationtests/tests/TestVNASweep.py index 4de1568..b8f6fca 100644 --- a/Software/Integrationtests/tests/TestVNASweep.py +++ b/Software/Integrationtests/tests/TestVNASweep.py @@ -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) diff --git a/Software/PC_Application/LibreVNA-GUI/Device/virtualdevice.h b/Software/PC_Application/LibreVNA-GUI/Device/virtualdevice.h index 236711f..fada186 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/virtualdevice.h +++ b/Software/PC_Application/LibreVNA-GUI/Device/virtualdevice.h @@ -132,7 +132,7 @@ public: Detector detector; bool signalID; bool trackingGenerator; - int trackingPort; + int trackingPort; // counting starts at zero double trackingOffset; double trackingPower; }; diff --git a/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp index ec6087e..7a1283a 100644 --- a/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -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; diff --git a/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.h b/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.h index 0b49ad2..42dd25e 100644 --- a/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.h +++ b/Software/PC_Application/LibreVNA-GUI/SpectrumAnalyzer/spectrumanalyzer.h @@ -74,6 +74,7 @@ private slots: void Run(); void Stop(); void ConfigureDevice(); + void ResetLiveTraces(); private: void SetupSCPI(); diff --git a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp index 152ac1a..4943086 100644 --- a/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp +++ b/Software/VNA_embedded/Application/SpectrumAnalyzer.cpp @@ -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(); }