Compare commits
No commits in common. "18a1b2faa33c347f08fb63022e4b03aaf8b0a601" and "3369bb290a9ad434d3b4f2ce8240dca3015b4169" have entirely different histories.
18a1b2faa3
...
3369bb290a
@ -39,9 +39,7 @@ Without this, you'll be limited to S11 and uncalibrated S21 measurements (with r
|
|||||||
There's nothing special about this particular board, if you want more than 4 ports you can make your own pretty easily. You just need 3 SPxT switches. Note that these switches will see tons of cycles so avoid mechanical switches.
|
There's nothing special about this particular board, if you want more than 4 ports you can make your own pretty easily. You just need 3 SPxT switches. Note that these switches will see tons of cycles so avoid mechanical switches.
|
||||||
- SMA cables
|
- SMA cables
|
||||||
|
|
||||||
### Pluto Configuration
|
### Pluto Modification
|
||||||
|
|
||||||
Most of my testing is with Pluto firmware [v0.39](https://github.com/analogdevicesinc/plutosdr-fw/releases/tag/v0.39) though this may work with other firmware versions. Instructions for upgrading firmware are on the [Analog Devices wiki](https://wiki.analog.com/university/tools/pluto/users/firmware).
|
|
||||||
|
|
||||||
We need two receive channels on the SDR. If you have a Pluto+ that should already be configured and you can skip this step.
|
We need two receive channels on the SDR. If you have a Pluto+ that should already be configured and you can skip this step.
|
||||||
|
|
||||||
|
@ -14,8 +14,6 @@ from numpy import typing as npt
|
|||||||
from PySide6.QtGui import QAction, QKeySequence
|
from PySide6.QtGui import QAction, QKeySequence
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
QFileDialog,
|
|
||||||
QInputDialog,
|
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
QMenu,
|
QMenu,
|
||||||
QProgressBar,
|
QProgressBar,
|
||||||
@ -23,19 +21,18 @@ from PySide6.QtWidgets import (
|
|||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
from skrf import plotting as rf_plt
|
from skrf import plotting as rf_plt
|
||||||
from vna import Charon
|
|
||||||
|
|
||||||
from charon_vna.gui_helpers import FlowLayout
|
from charon_vna.gui_helpers import FlowLayout
|
||||||
from charon_vna.util import db20, s2vswr
|
from charon_vna.util import db20, s2vswr
|
||||||
|
|
||||||
|
# from vna import Charon
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
DEFAULT_CONFIG = dict(
|
DEFAULT_CONFIG = dict(
|
||||||
frequency=np.arange(1e9, 2e9, 11), # Hz
|
frequency=np.arange(1e9, 2e9, 11), # Hz
|
||||||
power=-5, # dB
|
power=-5, # dB
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SUFFIX = ".json"
|
|
||||||
|
|
||||||
|
|
||||||
class PlotWidget(QWidget):
|
class PlotWidget(QWidget):
|
||||||
traces: List[Tuple[int | str]]
|
traces: List[Tuple[int | str]]
|
||||||
@ -160,7 +157,6 @@ class MainWindow(QMainWindow):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.config_path = None
|
self.config_path = None
|
||||||
self._frequency = np.linspace(1e9, 2e9, 101) # TODO: read frequency from config
|
|
||||||
|
|
||||||
# self.device = Charon("ip:192.168.3.1", frequency=DEFAULT_CONFIG["frequency"])
|
# self.device = Charon("ip:192.168.3.1", frequency=DEFAULT_CONFIG["frequency"])
|
||||||
|
|
||||||
@ -173,10 +169,10 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
menu_file = QMenu("&File")
|
menu_file = QMenu("&File")
|
||||||
menubar.addMenu(menu_file)
|
menubar.addMenu(menu_file)
|
||||||
action_open_config = QAction("&Open Configuration", self)
|
action_load_config = QAction("&Open Configuration", self)
|
||||||
menu_file.addAction(action_open_config)
|
menu_file.addAction(action_load_config)
|
||||||
action_open_config.triggered.connect(self.open_config)
|
action_load_config.triggered.connect(self.load_config)
|
||||||
action_open_config.setShortcut(QKeySequence("Ctrl+O"))
|
action_load_config.setShortcut(QKeySequence("Ctrl+O"))
|
||||||
action_save_config = QAction("&Save Configuration", self)
|
action_save_config = QAction("&Save Configuration", self)
|
||||||
menu_file.addAction(action_save_config)
|
menu_file.addAction(action_save_config)
|
||||||
action_save_config.triggered.connect(self.save_config)
|
action_save_config.triggered.connect(self.save_config)
|
||||||
@ -190,7 +186,7 @@ class MainWindow(QMainWindow):
|
|||||||
menubar.addMenu(menu_stimulus)
|
menubar.addMenu(menu_stimulus)
|
||||||
action_set_frequency = QAction("&Frequency", self)
|
action_set_frequency = QAction("&Frequency", self)
|
||||||
menu_stimulus.addAction(action_set_frequency)
|
menu_stimulus.addAction(action_set_frequency)
|
||||||
action_set_frequency.triggered.connect(self.set_frequency)
|
# action_set_frequency.triggered.connect(self.set_frequency)
|
||||||
action_set_power = QAction("&Power", self)
|
action_set_power = QAction("&Power", self)
|
||||||
menu_stimulus.addAction(action_set_power)
|
menu_stimulus.addAction(action_set_power)
|
||||||
# action_set_power.triggered.connect(self.set_power)
|
# action_set_power.triggered.connect(self.set_power)
|
||||||
@ -234,51 +230,24 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def saveas_config(self) -> None:
|
def saveas_config(self) -> None:
|
||||||
print("Prompting for save path...")
|
print("Prompting for save path...")
|
||||||
dialog = QFileDialog(self)
|
# TODO: prompt for config path
|
||||||
dialog.setDefaultSuffix(CONFIG_SUFFIX)
|
self.config_path = Path(__file__).parent / "config.json"
|
||||||
dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
|
|
||||||
if dialog.exec():
|
|
||||||
config_path = Path(dialog.selectedFiles()[0])
|
|
||||||
print(config_path)
|
|
||||||
if config_path.suffix != CONFIG_SUFFIX:
|
|
||||||
raise ValueError(
|
|
||||||
f"{config_path.name} is not a valid configuration file. Must have extension {CONFIG_SUFFIX}"
|
|
||||||
)
|
|
||||||
self.config_path = config_path
|
|
||||||
print(f"Config path is now {self.config_path.resolve()}")
|
print(f"Config path is now {self.config_path.resolve()}")
|
||||||
|
|
||||||
self.save_config()
|
self.save_config()
|
||||||
|
|
||||||
def open_config(self) -> None:
|
|
||||||
print("Prompting for load path...")
|
|
||||||
dialog = QFileDialog(self)
|
|
||||||
dialog.setNameFilter(f"*{CONFIG_SUFFIX}")
|
|
||||||
dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)
|
|
||||||
if dialog.exec():
|
|
||||||
config_path = Path(dialog.selectedFiles()[0])
|
|
||||||
print(config_path)
|
|
||||||
if config_path.suffix != CONFIG_SUFFIX:
|
|
||||||
raise ValueError(
|
|
||||||
f"{config_path.name} is not a valid configuration file. Must have extension {CONFIG_SUFFIX}"
|
|
||||||
)
|
|
||||||
self.config_path = config_path
|
|
||||||
print(f"Config path is now {self.config_path.resolve()}")
|
|
||||||
|
|
||||||
self.load_config(self.config_path)
|
|
||||||
|
|
||||||
def save_config(self) -> None:
|
def save_config(self) -> None:
|
||||||
if self.config_path is None:
|
if self.config_path is None:
|
||||||
self.saveas_config()
|
self.saveas_config()
|
||||||
else:
|
else:
|
||||||
print(f"Saving config to {self.config_path.resolve()}")
|
print(f"saving config to {self.config_path.resolve()}")
|
||||||
# TODO: save config
|
# TODO: save config
|
||||||
|
|
||||||
def load_config(self, path: Path) -> None:
|
def load_config(self) -> None:
|
||||||
print(f"Loading config from {path}...")
|
print("loading config")
|
||||||
# TODO: load config
|
# TODO: load config
|
||||||
|
|
||||||
def generate_sim_data(self) -> None:
|
def generate_sim_data(self) -> None:
|
||||||
coords = {"frequency": self._frequency, "m": [1], "n": [1]}
|
coords = {"frequency": np.linspace(1e9, 2e9, 101), "m": [1], "n": [1]}
|
||||||
shape = tuple(len(v) for v in coords.values())
|
shape = tuple(len(v) for v in coords.values())
|
||||||
data = xr.DataArray(
|
data = xr.DataArray(
|
||||||
((-1 + 2 * np.random.rand(*shape)) + 1j * (-1 + 2 * np.random.rand(*shape))) / np.sqrt(2),
|
((-1 + 2 * np.random.rand(*shape)) + 1j * (-1 + 2 * np.random.rand(*shape))) / np.sqrt(2),
|
||||||
@ -289,20 +258,6 @@ class MainWindow(QMainWindow):
|
|||||||
for plot in self.plots:
|
for plot in self.plots:
|
||||||
plot.update_plot(data)
|
plot.update_plot(data)
|
||||||
|
|
||||||
def set_frequency(self, *, frequency: npt.ArrayLike | None = None):
|
|
||||||
print(frequency)
|
|
||||||
if frequency is None:
|
|
||||||
start, ok = QInputDialog.getDouble(
|
|
||||||
self, "Start Frequency", "Start Frequency", minValue=30e6, maxValue=6e9, value=1e9
|
|
||||||
)
|
|
||||||
stop, ok = QInputDialog.getDouble(
|
|
||||||
self, "Stop Frequency", "Stop Frequency", minValue=30e6, maxValue=6e9, value=2e9
|
|
||||||
)
|
|
||||||
points, ok = QInputDialog.getInt(self, "Points", "Points", minValue=2, value=101)
|
|
||||||
frequency = np.linspace(start, stop, points)
|
|
||||||
# Currently does not support zero span
|
|
||||||
self._frequency = frequency
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
@ -4,8 +4,7 @@ from pathlib import Path
|
|||||||
from typing import Any, Dict, Tuple
|
from typing import Any, Dict, Tuple
|
||||||
|
|
||||||
import adi
|
import adi
|
||||||
|
import iio
|
||||||
# import iio
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import skrf as rf
|
import skrf as rf
|
||||||
import xarray as xr
|
import xarray as xr
|
||||||
@ -141,9 +140,8 @@ class Charon:
|
|||||||
self.set_output_power(power)
|
self.set_output_power(power)
|
||||||
self.sdr.tx_lo = int(frequency - self.FREQUENCY_OFFSET)
|
self.sdr.tx_lo = int(frequency - self.FREQUENCY_OFFSET)
|
||||||
self.sdr.tx_cyclic_buffer = True
|
self.sdr.tx_cyclic_buffer = True
|
||||||
# For some reason the pluto's DDS has truly horrendous phase noise to the point where it looks modulated
|
# self.sdr.tx(generate_tone(f=self.FREQUENCY_OFFSET, fs=self.sdr.sample_rate))
|
||||||
self.sdr.tx(generate_tone(f=self.FREQUENCY_OFFSET, fs=self.sdr.sample_rate))
|
self.sdr.dds_single_tone(self.FREQUENCY_OFFSET, scale=0.9, channel=0)
|
||||||
# self.sdr.dds_single_tone(self.FREQUENCY_OFFSET, scale=0.9, channel=0)
|
|
||||||
|
|
||||||
def _rx(self, count: int = 1, fc: float | None = None) -> npt.ArrayLike:
|
def _rx(self, count: int = 1, fc: float | None = None) -> npt.ArrayLike:
|
||||||
if count < 1:
|
if count < 1:
|
||||||
@ -230,10 +228,6 @@ class Charon:
|
|||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
# %%
|
|
||||||
if __name__ == "__main__":
|
|
||||||
pass
|
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
sdr = Charon("ip:192.168.3.1", frequency=np.linspace(1e9, 1.1e9, 11))
|
sdr = Charon("ip:192.168.3.1", frequency=np.linspace(1e9, 1.1e9, 11))
|
||||||
|
|
||||||
@ -293,6 +287,7 @@ if __name__ == "__main__":
|
|||||||
plt.grid(True)
|
plt.grid(True)
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
s = sdr.vna_capture(frequency=np.linspace(70e6, 200e6, 101))
|
s = sdr.vna_capture(frequency=np.linspace(70e6, 200e6, 101))
|
||||||
|
|
||||||
@ -327,6 +322,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
# %% SOL calibration
|
# %% SOL calibration
|
||||||
cal_frequency = np.linspace(70e6, 600e6, 101)
|
cal_frequency = np.linspace(70e6, 600e6, 101)
|
||||||
ideal_cal_frequency = rf.Frequency(np.min(cal_frequency), np.max(cal_frequency), len(cal_frequency))
|
ideal_cal_frequency = rf.Frequency(np.min(cal_frequency), np.max(cal_frequency), len(cal_frequency))
|
||||||
@ -347,6 +343,7 @@ if __name__ == "__main__":
|
|||||||
[cal_ideal.short(), cal_ideal.open(), cal_ideal.load(0)],
|
[cal_ideal.short(), cal_ideal.open(), cal_ideal.load(0)],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
s = sdr.vna_capture(frequency=cal_frequency)
|
s = sdr.vna_capture(frequency=cal_frequency)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user