Added calibration test with actual measurements
This commit is contained in:
parent
2b9cc78e55
commit
94fc6ab840
@ -1,9 +1,9 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
testmodules = [
|
testmodules = [
|
||||||
'tests.TestConnect',
|
# 'tests.TestConnect',
|
||||||
'tests.TestMode',
|
# 'tests.TestMode',
|
||||||
'tests.TestVNASweep',
|
# 'tests.TestVNASweep',
|
||||||
'tests.TestCalibration',
|
'tests.TestCalibration',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
117652
Software/Integrationtests/LIBRECAL.cal
Normal file
117652
Software/Integrationtests/LIBRECAL.cal
Normal file
File diff suppressed because it is too large
Load Diff
81
Software/Integrationtests/libreCAL.py
Normal file
81
Software/Integrationtests/libreCAL.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import serial
|
||||||
|
import serial.tools.list_ports
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class libreCAL:
|
||||||
|
def __init__(self, serialnum = ''):
|
||||||
|
self.ser = None
|
||||||
|
for p in serial.tools.list_ports.comports():
|
||||||
|
if p.vid == 0x0483 and p.pid == 0x4122:
|
||||||
|
self.ser = serial.Serial(p.device, timeout = 1)
|
||||||
|
idn = self.SCPICommand("*IDN?").split("_")
|
||||||
|
if idn[0] != "LibreCAL":
|
||||||
|
continue
|
||||||
|
self.serial = idn[1]
|
||||||
|
if len(serialnum) > 0:
|
||||||
|
# serial number specified, compare
|
||||||
|
if self.serial != serialnum:
|
||||||
|
self.ser = None
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
if self.ser == None:
|
||||||
|
if len(serialnum) > 0:
|
||||||
|
raise Exception("LibreCAL with serial number '"+serialnum+"' not detected")
|
||||||
|
else:
|
||||||
|
raise Exception("No LibreCAL device detected")
|
||||||
|
|
||||||
|
def getSerial(self) -> str:
|
||||||
|
return self.serial
|
||||||
|
|
||||||
|
class Standard(Enum):
|
||||||
|
NONE = 0,
|
||||||
|
OPEN = 1,
|
||||||
|
SHORT = 2,
|
||||||
|
LOAD = 3,
|
||||||
|
THROUGH = 4
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.SCPICommand(":PORT 1 NONE")
|
||||||
|
self.SCPICommand(":PORT 2 NONE")
|
||||||
|
self.SCPICommand(":PORT 3 NONE")
|
||||||
|
self.SCPICommand(":PORT 4 NONE")
|
||||||
|
|
||||||
|
def setPort(self, s : Standard, port, port2 = -1):
|
||||||
|
if s == self.Standard.THROUGH and port2 == -1:
|
||||||
|
raise Exception("When setting a port to THROUGH, the destination port must also be specified")
|
||||||
|
cmd = ":PORT "+str(port)+" "+s.name
|
||||||
|
if s == self.Standard.THROUGH:
|
||||||
|
cmd += " "+str(port2)
|
||||||
|
if len(self.SCPICommand(cmd)) == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getPort(self, port):
|
||||||
|
resp = self.SCPICommand(":PORT? "+str(port)).split(" ")[0]
|
||||||
|
try:
|
||||||
|
s = self.Standard[resp]
|
||||||
|
return s
|
||||||
|
except:
|
||||||
|
raise Exception("LibreCAL reported unknown standard '"+resp+"'")
|
||||||
|
|
||||||
|
def getTemperature(self):
|
||||||
|
return float(self.SCPICommand(":TEMP?"))
|
||||||
|
|
||||||
|
def isStable(self) -> bool:
|
||||||
|
if self.SCPICommand(":TEMP:STABLE?") == "TRUE":
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getHeaterPower(self):
|
||||||
|
return float(self.SCPICommand(":HEAT:POW?"))
|
||||||
|
|
||||||
|
def SCPICommand(self, cmd: str) -> str:
|
||||||
|
self.ser.write((cmd+"\r\n").encode())
|
||||||
|
resp = self.ser.readline().decode("ascii")
|
||||||
|
if len(resp) == 0:
|
||||||
|
raise Exception("Timeout occurred in communication with LibreCAL")
|
||||||
|
if resp.strip() == "ERROR":
|
||||||
|
raise Exception("LibreCAL returned 'ERROR' for command '"+cmd+"'")
|
||||||
|
return resp.strip()
|
@ -1,13 +1,15 @@
|
|||||||
from tests.TestBase import TestBase
|
from tests.TestBase import TestBase
|
||||||
|
from libreCAL import libreCAL
|
||||||
import time
|
import time
|
||||||
|
import math
|
||||||
|
|
||||||
class TestCalibration(TestBase):
|
class TestCalibration(TestBase):
|
||||||
def cal_measure(self, number):
|
def cal_measure(self, number, timeout = 3):
|
||||||
self.vna.cmd(":VNA:CAL:MEAS "+str(number))
|
self.vna.cmd(":VNA:CAL:MEAS "+str(number))
|
||||||
# wait for the measurement to finish
|
# wait for the measurement to finish
|
||||||
timeout = time.time() + 3
|
stoptime = time.time() + timeout
|
||||||
while self.vna.query(":VNA:CAL:BUSY?") == "TRUE":
|
while self.vna.query(":VNA:CAL:BUSY?") == "TRUE":
|
||||||
if time.time() > timeout:
|
if time.time() > stoptime:
|
||||||
raise AssertionError("Calibration measurement timed out")
|
raise AssertionError("Calibration measurement timed out")
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
@ -80,3 +82,85 @@ class TestCalibration(TestBase):
|
|||||||
self.assertEqual(self.vna.query(":VNA:CAL:ACT SOLT_1"), "")
|
self.assertEqual(self.vna.query(":VNA:CAL:ACT SOLT_1"), "")
|
||||||
self.assertEqual(self.vna.query(":VNA:CAL:ACT SOLT_2"), "")
|
self.assertEqual(self.vna.query(":VNA:CAL:ACT SOLT_2"), "")
|
||||||
self.assertEqual(self.vna.query(":VNA:CAL:ACT SOLT_12"), "")
|
self.assertEqual(self.vna.query(":VNA:CAL:ACT SOLT_12"), "")
|
||||||
|
|
||||||
|
def assertTrace_dB(self, trace, dB_nominal, dB_deviation):
|
||||||
|
for S in trace:
|
||||||
|
dB = 20*math.log10(abs(S[1]))
|
||||||
|
self.assertLessEqual(dB, dB_nominal + dB_deviation)
|
||||||
|
self.assertGreaterEqual(dB, dB_nominal - dB_deviation)
|
||||||
|
|
||||||
|
def test_SOLT_calibration(self):
|
||||||
|
# This test performs a 2-port SOLT calibration with a connected LibreCAL
|
||||||
|
# Afterwqrds, the calibration is checked with a 6dB attenuator
|
||||||
|
# Set up the sweep first
|
||||||
|
self.vna.cmd(":DEV:MODE VNA")
|
||||||
|
self.vna.cmd(":VNA:SWEEP FREQUENCY")
|
||||||
|
self.vna.cmd(":VNA:STIM:LVL -10")
|
||||||
|
self.vna.cmd(":VNA:ACQ:IFBW 1000")
|
||||||
|
self.vna.cmd(":VNA:ACQ:AVG 1")
|
||||||
|
self.vna.cmd(":VNA:ACQ:POINTS 501")
|
||||||
|
self.vna.cmd(":VNA:FREQuency:START 50000000")
|
||||||
|
self.vna.cmd(":VNA:FREQuency:STOP 6000000000")
|
||||||
|
|
||||||
|
self.vna.cmd(":VNA:CAL:RESET")
|
||||||
|
|
||||||
|
# Load calibration file (only for standard and measurement setup, no
|
||||||
|
# measurements are included)
|
||||||
|
self.assertEqual(self.vna.query(":VNA:CAL:LOAD? LIBRECAL.CAL"), "TRUE")
|
||||||
|
|
||||||
|
# Take the measurements
|
||||||
|
|
||||||
|
cal = libreCAL()
|
||||||
|
# Connections:
|
||||||
|
# LibreVNA -> LibreCAL
|
||||||
|
# 1 -> 3
|
||||||
|
# 2 -> 1
|
||||||
|
cal.reset()
|
||||||
|
cal.setPort(cal.Standard.OPEN, 1)
|
||||||
|
cal.setPort(cal.Standard.OPEN, 3)
|
||||||
|
self.cal_measure(0, 15)
|
||||||
|
self.cal_measure(3, 15)
|
||||||
|
|
||||||
|
cal.setPort(cal.Standard.SHORT, 1)
|
||||||
|
cal.setPort(cal.Standard.SHORT, 3)
|
||||||
|
self.cal_measure(1, 15)
|
||||||
|
self.cal_measure(4, 15)
|
||||||
|
|
||||||
|
cal.setPort(cal.Standard.LOAD, 1)
|
||||||
|
cal.setPort(cal.Standard.LOAD, 3)
|
||||||
|
self.cal_measure(2, 15)
|
||||||
|
self.cal_measure(5, 15)
|
||||||
|
|
||||||
|
cal.setPort(cal.Standard.THROUGH, 1, 3)
|
||||||
|
self.cal_measure(6, 15)
|
||||||
|
|
||||||
|
# activate calibration
|
||||||
|
self.assertEqual(self.vna.query(":VNA:CAL:ACT SOLT_12"), "")
|
||||||
|
|
||||||
|
# switch in 6dB attenuator
|
||||||
|
cal.setPort(cal.Standard.THROUGH, 1, 2)
|
||||||
|
cal.setPort(cal.Standard.THROUGH, 3, 4)
|
||||||
|
|
||||||
|
# Start measurement and grab data
|
||||||
|
self.vna.cmd(":VNA:ACQ:SINGLE TRUE")
|
||||||
|
while self.vna.query(":VNA:ACQ:FIN?") == "FALSE":
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
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"))
|
||||||
|
|
||||||
|
# Attenuation is frequency dependent, use excessively large limits
|
||||||
|
# TODO: use smaller limits based on frequency
|
||||||
|
self.assertTrace_dB(S12, -13, 5)
|
||||||
|
self.assertTrace_dB(S21, -13, 5)
|
||||||
|
|
||||||
|
# Reflection should be below -10dB (much lower for most frequencies)
|
||||||
|
self.assertTrace_dB(S11, -100, 90)
|
||||||
|
self.assertTrace_dB(S22, -100, 90)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user