functional 1 port calibration

This commit is contained in:
2025-07-07 23:22:36 -06:00
parent 3c02a4b388
commit 452dddc19c
2 changed files with 79 additions and 4 deletions

View File

@ -1,5 +1,6 @@
# %% imports # %% imports
import copy import copy
import pickle
from enum import IntEnum, unique from enum import IntEnum, unique
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Dict, List, Literal, Tuple from typing import Any, Callable, Dict, List, Literal, Tuple
@ -360,6 +361,51 @@ class Charon:
return s return s
def calibrate_sol(self, prompt: Callable[[str], None] | None = None, **kwargs) -> rf.calibration.Calibration:
if len(self.ports) != 1:
raise ValueError(
f"SOL calibration needs only one port but {len(self.ports)} ports are enabled. "
"Did you mean to use SOLT?"
)
if prompt is None:
prompt = lambda s: input(f"{s}\nENTER to continue...")
ideal = rf.media.DefinedGammaZ0(frequency=rf.media.Frequency.from_f(self.frequency, unit="Hz"))
ideals = [ideal.short(), ideal.open(), ideal.load(0)]
names = ["short", "open", "load"]
measured = list()
for name in names:
prompt(f"Connect standard: {name} to port {self.ports[0]}")
measured.append(self.capture(**kwargs))
cal = rf.OnePort(ideals=ideals, measured=[s2net(m) for m in measured])
self.calibration = cal
return cal
def save_calibration(self, path: Path | str):
path = Path(path)
if path.suffix.lower() == ".pkl":
with open(str(path), "wb") as f:
pickle.dump(self.calibration, f)
else:
raise NotImplementedError(f"Unknown calibration file extension: {path.suffix}")
def load_calibration(self, path: Path | str):
path = Path(path)
if path.suffix.lower() == ".pkl":
with open(str(path), "rb") as f:
cal = pickle.load(f)
if not isinstance(cal, rf.calibration.Calibration):
raise ValueError(f"Expected {rf.calibration.Calibration}, got {type(cal)}")
self.calibration = cal
else:
raise NotImplementedError(f"Unknown calibration file extension: {path.suffix}")
# %% # %%
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -2,23 +2,52 @@
import numpy as np import numpy as np
from matplotlib import pyplot as plt from matplotlib import pyplot as plt
from charon_vna.util import db20, net2s, s2net
from charon_vna.vna import Charon from charon_vna.vna import Charon
# %% # %%
frequency = np.linspace(80e6, 280e6, 31) frequency = np.linspace(80e6, 280e6, 301)
# %% # %%
vna = Charon(frequency=frequency, ports=2) vna = Charon(frequency=frequency, ports=1)
# %% # %%
s = vna.capture(measurements=[(2, 1), (2, 2)]) s = vna.capture()
# %% # %%
for m in s.m.data: for m in s.m.data:
for n in s.n.data: for n in s.n.data:
plt.plot(s.frequency, 20 * np.log10(np.abs(s.sel(m=m, n=n))), label="$S_{" + str(m) + str(n) + "}$") plt.plot(s.frequency, db20(s.sel(m=m, n=n)), label="$S_{" + str(m) + str(n) + "}$")
plt.grid(True) plt.grid(True)
plt.legend() plt.legend()
plt.show()
# %%
vna.calibrate_sol()
# %%
vna.load_calibration("./calibration.pkl")
# %%
s2 = net2s(vna.calibration.apply_cal(s2net(s)))
for m in s.m.data:
for n in s.n.data:
plt.plot(s.frequency, db20(s.sel(m=m, n=n)), label="$S_{" + str(m) + str(n) + "}$ (uncalibrated)")
plt.plot(s2.frequency, db20(s2.sel(m=m, n=n)), label="$S_{" + str(m) + str(n) + "}$ (calibrated)")
plt.grid(True)
plt.legend()
plt.ylabel("Magnitude [dB]")
plt.show()
for m in s.m.data:
for n in s.n.data:
plt.plot(s.frequency, np.angle(s.sel(m=m, n=n), deg=True), label="$S_{" + str(m) + str(n) + "}$ (uncalibrated)")
plt.plot(s2.frequency, np.angle(s2.sel(m=m, n=n), deg=True), label="$S_{" + str(m) + str(n) + "}$ (calibrated)")
plt.grid(True)
plt.legend()
plt.ylabel("Phase [deg]")
plt.show()
# %% # %%