diff --git a/Software/Scripts/FactoryCoefficients/VNA.py b/Software/Scripts/FactoryCoefficients/VNA.py index 525a0c3..425f89e 100644 --- a/Software/Scripts/FactoryCoefficients/VNA.py +++ b/Software/Scripts/FactoryCoefficients/VNA.py @@ -33,8 +33,8 @@ def measure(): Measurements are returned as a dictionary: Key: S parameter name (e.g. "S11") - Value: List of tuples: [frequency, real, imaginary] + Value: List of tuples: (frequency, complex S parameter) :return: Measurements """ - return {} \ No newline at end of file + return {} diff --git a/Software/Scripts/FactoryCoefficients/VNA_Example_LibreVNA/VNA.py b/Software/Scripts/FactoryCoefficients/VNA_Example_LibreVNA/VNA.py index c7b81c2..7c317d6 100644 --- a/Software/Scripts/FactoryCoefficients/VNA_Example_LibreVNA/VNA.py +++ b/Software/Scripts/FactoryCoefficients/VNA_Example_LibreVNA/VNA.py @@ -73,7 +73,7 @@ def measure(): Measurements are returned as a dictionary: Key: S parameter name (e.g. "S11") - Value: List of tuples: [frequency, complex] + Value: List of tuples: (frequency, complex) :return: Measurements """ diff --git a/Software/Scripts/FactoryCoefficients/VNA_Example_SNA5000A/SNA5000A.py b/Software/Scripts/FactoryCoefficients/VNA_Example_SNA5000A/SNA5000A.py new file mode 100644 index 0000000..a39472c --- /dev/null +++ b/Software/Scripts/FactoryCoefficients/VNA_Example_SNA5000A/SNA5000A.py @@ -0,0 +1,119 @@ +import pyvisa +from enum import Enum + +class SNA5000A: + # Class to control a siglent SNA5000A vector network analyzer + + def __init__(self, ip="192.168.22.8"): + rm = pyvisa.ResourceManager('@py') + dev_addr = "TCPIP0::"+ip+"::INSTR" + self.inst = rm.open_resource(dev_addr) + # check ID + id = self.inst.query('*IDN?') + if not id.startswith('Siglent Technologies,SNA50'): + raise RuntimeError("Wrong device ID received:"+id) + self.num_ports = int(self.read(":SERVICE:PORT:COUNT?")) + self.excited_ports = self.num_ports + + def set_excited_ports(self, portlist): + if len(portlist) == 0: + # disabling all ports is not allowed + return + # enable traces for the specified ports + for i in range(1, self.num_ports+1): + if i in portlist: + # enable the trace + self.write(":DISP:WIND:TRAC" + str(i) + " 1") + # set the parameter + self.write(":CALC:PAR" + str(i) + ":DEF S" + str(i) + str(i)) + else: + # disable the trace + self.write(":DISP:WIND:TRAC" + str(i) + " 0") + self.excited_ports = len(portlist) + + def set_start_freq(self, freq): + self.write(":SENS:FREQ:STAR "+str(freq)) + + def set_stop_freq(self, freq): + self.write(":SENS:FREQ:STOP "+str(freq)) + + def set_IF_bandwidth(self, bandwidth): + self.write(":SENS:BWID "+str(bandwidth)) + + def set_source_power(self, power): + self.write(":SOUR:POW "+str(power)) + + def set_points(self, points): + self.write(":SENS:SWEEP:POINTS "+str(points)) + + def get_trace_data(self, parameters: set): + # example call: get_trace_data({"S11", "S21"}) + xdata = self.read(":CALC:DATA:XAXIS?") + rawdata = {} + for p in parameters: + rawdata[p] = self.read(":SENS:DATA:CORR? "+p) + # parse received data + freqList = [] + for val in xdata.split(","): + freqList.append(float(val)) + parsedData = {} + for param in rawdata.keys(): + real = [] + imag = [] + doubleValues = rawdata[param].split(",") + for i in range(0, len(doubleValues), 2): + real.append(float(doubleValues[i])) + imag.append(float(doubleValues[i+1])) + if len(real) != len(freqList): + raise Exception("X/Y data vectors with different lengths, unable to parse data") + parsedData[param+"_real"] = real + parsedData[param+"_imag"] = imag + ret = {} + for p in parameters: + trace = [] + for i in range(len(freqList)): + freq = freqList[i] + real = parsedData[p + "_real"][i] + imag = parsedData[p + "_imag"][i] + trace.append((freq, complex(real, imag))) + ret[p] = trace + return ret + + def start_single_sweep(self): + self.start_continuous_sweep() + self.write(":INIT:IMM") + + def blocking_single_sweep(self, data=set()): + self.write(":TRIG:SOUR MAN") + self.start_continuous_sweep() + + # Sweep may take a long time, so we need to adjust the timeout of the visa connection + sweeptime = float(self.read(":SENS:SWE:TIME?")) + timeout = self.excited_ports * sweeptime + 10 + self.write("TRIG:SING") + old_timeout = self.inst.timeout + self.inst.timeout = timeout * 1000 + self.read("*OPC?") + self.inst.timeout = old_timeout + ret = self.get_trace_data(data) + self.write(":TRIG:SOUR INT") + return ret + + def start_continuous_sweep(self): + self.write(":INIT:CONT 1") + + def stop_sweep(self): + self.write(":INIT:CONT 0") + + def sweep_complete(self) -> bool: + print(self.read(":SENS:AVER:COUN?")) + return True + + def write(self, command): + self.inst.write(command) + + def read(self, command): + return self.inst.query(command) + + def reset(self): + self.inst.write("*RST") diff --git a/Software/Scripts/FactoryCoefficients/VNA_Example_SNA5000A/VNA.py b/Software/Scripts/FactoryCoefficients/VNA_Example_SNA5000A/VNA.py new file mode 100644 index 0000000..2548e1d --- /dev/null +++ b/Software/Scripts/FactoryCoefficients/VNA_Example_SNA5000A/VNA.py @@ -0,0 +1,59 @@ +# A VNA is needed to measure and create the factory coefficients. +# This file contains the necessary function to extract data from this VNA. +# Please implement all functions according to the VNA you are using. + +from VNA_Example_SNA5000A.SNA5000A import SNA5000A +import time + +vna = None + +def checkIfReady() -> bool: + """ + checkIfReady checks if the VNA is connected and ready to be used + + This function can also be used for initiliazing the VNA. It is called only + once at the beginning + + :return: True if VNA is ready. False otherwise + """ + global vna + try: + vna = SNA5000A() + except: + print("Failed to detect VNA") + return False + + vna.stop_sweep() + vna.set_start_freq(9000) + vna.set_stop_freq(8500000000) + vna.set_points(801) + vna.set_source_power(0) + vna.set_IF_bandwidth(10000) + + return True + +def getPorts() -> int: + """ + getPorts returns the number of ports the VNA has + + Creation of factory coefficients if faster if more ports are available. + The VNA needs at least 2 ports and at most 4 ports can be utilized. + + :return: Number of ports on the VNA + """ + return vna.num_ports + +def measure(): + """ + measure starts a measurement and returns the S parameter data + + This function should start a new measurement and block until the data + is available. No previous measurements must be returned. + + Measurements are returned as a dictionary: + Key: S parameter name (e.g. "S11") + Value: List of tuples: (frequency, complex) + + :return: Measurements + """ + return vna.blocking_single_sweep({"S11","S12","S13","S14","S21","S22","S23","S24","S31","S32","S33","S34","S41","S42","S43","S44"}) diff --git a/Software/Scripts/FactoryCoefficients/createFactoryCoefficients.py b/Software/Scripts/FactoryCoefficients/createFactoryCoefficients.py index ad06c52..449febe 100644 --- a/Software/Scripts/FactoryCoefficients/createFactoryCoefficients.py +++ b/Software/Scripts/FactoryCoefficients/createFactoryCoefficients.py @@ -4,7 +4,8 @@ import subprocess import time #import VNA -from VNA_Example_LibreVNA import VNA +#from VNA_Example_LibreVNA import VNA +from VNA_Example_SNA5000A import VNA def SCPICommand(ser, cmd: str) -> str: ser.write((cmd+"\r\n").encode()) @@ -47,10 +48,10 @@ def SCPICommand(ser, cmd: str) -> str: print("Found LibreCAL device on port " + port.device) ser = serial.Serial(port.device, timeout = 1) -idn = SCPICommand(ser, "*IDN?").split("_") +idn = SCPICommand(ser, "*IDN?").split(",") if idn[0] != "LibreCAL": - raise Exception("Invalid *IDN response: "+idn[0]) -print("Detected LibreCAL device has serial number "+idn[1]) + raise Exception("Invalid *IDN response: "+idn) +print("Detected LibreCAL device has serial number "+idn[2]) # Enable writing of the factory partition SCPICommand(ser, ":FACT:ENABLEWRITE I_AM_SURE")