Skip to content

Commit e2b510e

Browse files
committed
Add limit checking to coefficient measurements
1 parent 473404f commit e2b510e

File tree

3 files changed

+190
-4
lines changed

3 files changed

+190
-4
lines changed

Software/Scripts/FactoryCoefficients/VNA_Example_SNA5000A/VNA.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ def checkIfReady() -> bool:
2626
vna.stop_sweep()
2727
vna.set_start_freq(9000)
2828
vna.set_stop_freq(8500000000)
29-
vna.set_points(801)
29+
vna.set_points(1001)
3030
vna.set_source_power(0)
31-
vna.set_IF_bandwidth(10000)
31+
vna.set_IF_bandwidth(1000)
32+
vna.set_excited_ports(range(1, vna.num_ports+1))
3233

3334
return True
3435

Software/Scripts/FactoryCoefficients/createFactoryCoefficients.py

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import serial.tools.list_ports
44
import subprocess
55
import time
6+
import json
7+
import math
8+
import cmath
69
#import VNA
710
#from VNA_Example_LibreVNA import VNA
811
from VNA_Example_SNA5000A import VNA
@@ -19,6 +22,7 @@ def SCPICommand(ser, cmd: str) -> str:
1922

2023
parser = argparse.ArgumentParser(description = "Helps with creation of factory calibration coefficients for a new LibreCAL device")
2124
parser.add_argument('-f', '--flash', help='Flash the firmware file first', metavar="firmware")
25+
parser.add_argument('-l', '--limits', help='Enables limit checking on coefficients with limits from json file', metavar="json_limit_file")
2226

2327
args = parser.parse_args()
2428

@@ -34,7 +38,6 @@ def SCPICommand(ser, cmd: str) -> str:
3438
print("Firmware flashed, waiting for device to boot")
3539
time.sleep(2)
3640

37-
3841
# Try to find the connected LibreCAL
3942
port = None
4043
for p in serial.tools.list_ports.comports():
@@ -47,7 +50,7 @@ def SCPICommand(ser, cmd: str) -> str:
4750

4851
print("Found LibreCAL device on port " + port.device)
4952

50-
ser = serial.Serial(port.device, timeout = 1)
53+
ser = serial.Serial(port.device, timeout = 2)
5154
idn = SCPICommand(ser, "*IDN?").split(",")
5255
if idn[0] != "LibreCAL":
5356
raise Exception("Invalid *IDN response: "+idn)
@@ -214,6 +217,94 @@ def takeMeasurements(portmapping : dict):
214217

215218
print("\r\nMeasurements complete.")
216219

220+
if args.limits:
221+
jlimits = None
222+
try:
223+
f = open(args.limits)
224+
jlimits = json.load(f)
225+
except Exception as e:
226+
raise Exception("Failed to parse limit file")
227+
228+
229+
for i in jlimits:
230+
# grab the specified measurement
231+
measurements = {}
232+
if i == "OPEN":
233+
measurements = Opens
234+
elif i == "SHORT":
235+
measurements = Shorts
236+
elif i == "LOAD":
237+
measurements = Loads
238+
elif i == "THRU REFLECTION":
239+
for through in Throughs:
240+
measurements[through+"_S11"] = Throughs[through]["S11"]
241+
measurements[through+"_S22"] = Throughs[through]["S22"]
242+
elif i == "THRU TRANSMISSION":
243+
for through in Throughs:
244+
measurements[through+"_S12"] = Throughs[through]["S12"]
245+
measurements[through+"_S21"] = Throughs[through]["S21"]
246+
elif i == "OPEN SHORT PHASE":
247+
for key in Opens.keys():
248+
if key not in Shorts:
249+
# should not happen
250+
raise RuntimeError("Got an open measurement without corresponding short measurement at port "+str(key))
251+
samples = max(len(Opens[key]), len(Shorts[key]))
252+
open_vs_short = []
253+
for j in range(samples):
254+
if Opens[key][j][0] == Shorts[key][j][0]:
255+
# this sample uses the same frequency (should always be the case)
256+
open_vs_short.append((Opens[key][j][0], Opens[key][j][1] / Shorts[key][j][1]))
257+
else:
258+
raise RuntimeError("Open and short measurements have difference frequencies at port "+str(key))
259+
measurements[key] = open_vs_short
260+
261+
if len(measurements) == 0:
262+
# unknown limit keyword, nothing to check
263+
continue
264+
265+
# iterate over and check the specified limits
266+
for limit in jlimits[i]:
267+
# iterate over the measurements we need to check
268+
for key in measurements.keys():
269+
# check every sample in the measurement
270+
for sample in measurements[key]:
271+
if sample[0] < limit["x1"] or sample[0] > limit["x2"]:
272+
# Sample not covered by this limit
273+
continue
274+
# calculate limit value for this sample
275+
alpha = (sample[0] - limit["x1"]) / (limit["x2"] - limit["x1"])
276+
limval = limit["y1"] + alpha * (limit["y2"] - limit["y1"])
277+
278+
# transform y value according to limit type
279+
yval = None
280+
if limit["type"] == "dB":
281+
yval = 20*math.log10(abs(sample[1]))
282+
elif limit["type"] == "phase":
283+
yval = math.degrees(cmath.polar(sample[1])[1])
284+
# contrain to [0, 360)
285+
while yval < 0:
286+
yval += 360
287+
while yval >= 360:
288+
yval -= 360
289+
else:
290+
# unknown limit type
291+
raise Exception("Unknown limit type: "+str(limit["type"]))
292+
293+
# perform the actual limit check
294+
success = True
295+
if limit["limit"] == "max":
296+
if yval > limval:
297+
success = False
298+
elif limit["limit"] == "min":
299+
if yval < limval:
300+
success = False
301+
else:
302+
# unknown limit
303+
raise Exception("Unknown limit: "+str(limit["limit"]))
304+
if not success:
305+
# this limit failed
306+
raise Exception("Limit check failed for type "+str(i)+" in measurement "+str(key)+" at frequency "+str(sample[0])+": limit is "+str(limval)+", measured value is "+str(yval))
307+
217308
rCoeffs = {}
218309
tCoeffs = {}
219310

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
{
2+
"OPEN": [
3+
{
4+
"x1": 9000,
5+
"y1": 1.0,
6+
"x2": 6000000000,
7+
"y2": -2.0,
8+
"limit": "max",
9+
"type": "dB"
10+
},
11+
{
12+
"x1": 9000,
13+
"y1": -2.0,
14+
"x2": 6000000000,
15+
"y2": -6.0,
16+
"limit": "min",
17+
"type": "dB"
18+
}
19+
],
20+
"SHORT": [
21+
{
22+
"x1": 9000,
23+
"y1": 1.0,
24+
"x2": 6000000000,
25+
"y2": -2.0,
26+
"limit": "max",
27+
"type": "dB"
28+
},
29+
{
30+
"x1": 9000,
31+
"y1": -4.5,
32+
"x2": 6000000000,
33+
"y2": -6.0,
34+
"limit": "min",
35+
"type": "dB"
36+
}
37+
],
38+
"LOAD": [
39+
{
40+
"x1": 9000,
41+
"y1": -20.0,
42+
"x2": 6000000000,
43+
"y2": -12.0,
44+
"limit": "max",
45+
"type": "dB"
46+
}
47+
],
48+
"THRU REFLECTION": [
49+
{
50+
"x1": 9000,
51+
"y1": -10.0,
52+
"x2": 6000000000,
53+
"y2": -10.0,
54+
"limit": "max",
55+
"type": "dB"
56+
}
57+
],
58+
"THRU TRANSMISSION": [
59+
{
60+
"x1": 9000,
61+
"y1": -1.0,
62+
"x2": 6000000000,
63+
"y2": -3.0,
64+
"limit": "max",
65+
"type": "dB"
66+
},
67+
{
68+
"x1": 9000,
69+
"y1": -3.0,
70+
"x2": 6000000000,
71+
"y2": -8.0,
72+
"limit": "min",
73+
"type": "dB"
74+
}
75+
],
76+
"OPEN SHORT PHASE": [
77+
{
78+
"x1": 9000,
79+
"y1": 195.0,
80+
"x2": 6000000000,
81+
"y2": 195.0,
82+
"limit": "max",
83+
"type": "phase"
84+
},
85+
{
86+
"x1": 9000,
87+
"y1": 165.0,
88+
"x2": 6000000000,
89+
"y2": 165.0,
90+
"limit": "min",
91+
"type": "phase"
92+
}
93+
]
94+
}

0 commit comments

Comments
 (0)