Skip to content

Commit

Permalink
Update factory coefficient script with SNA5000A example
Browse files Browse the repository at this point in the history
  • Loading branch information
jankae committed Nov 26, 2023
1 parent 44ac6cd commit aa0865e
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 7 deletions.
4 changes: 2 additions & 2 deletions Software/Scripts/FactoryCoefficients/VNA.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
return {}
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
Expand Down
119 changes: 119 additions & 0 deletions Software/Scripts/FactoryCoefficients/VNA_Example_SNA5000A/SNA5000A.py
Original file line number Diff line number Diff line change
@@ -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")
59 changes: 59 additions & 0 deletions Software/Scripts/FactoryCoefficients/VNA_Example_SNA5000A/VNA.py
Original file line number Diff line number Diff line change
@@ -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"})
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit aa0865e

Please sign in to comment.