diff --git a/charon_vna/vna.py b/charon_vna/vna.py index d1c77b8..0a6bcf5 100644 --- a/charon_vna/vna.py +++ b/charon_vna/vna.py @@ -18,6 +18,20 @@ dir_ = Path(__file__).parent # %% connection + + +def generate_tone(f: float, fs: float, N: int = 1024, scale: int = 2**14): + fs = int(fs) + fc = int(f / (fs / N)) * (fs / N) + ts = 1 / float(fs) + t = np.arange(0, N * ts, ts) + i = np.cos(2 * np.pi * t * fc) * scale + q = np.sin(2 * np.pi * t * fc) * scale + iq = i + 1j * q + + return iq + + class Charon: FREQUENCY_OFFSET = 1e6 @@ -58,19 +72,19 @@ class Charon: self.sdr.loopback = 0 self.sdr.gain_control_mode_chan0 = "manual" self.sdr.gain_control_mode_chan1 = "manual" - self.sdr.rx_hardwaregain_chan0 = 40 - self.sdr.rx_hardwaregain_chan1 = 40 + self.sdr.rx_hardwaregain_chan0 = 10 + self.sdr.rx_hardwaregain_chan1 = 10 self.sdr.tx_hardwaregain_chan0 = -10 - # switch control - ctx = iio.Context(uri) - self.ctrl = ctx.find_device("ad9361-phy") - # raw ad9361 register accesss: - # https://ez.analog.com/linux-software-drivers/f/q-a/120853/control-fmcomms3-s-gpo-with-python - # https://www.analog.com/media/cn/technical-documentation/user-guides/ad9364_register_map_reference_manual_ug-672.pdf - self.ctrl.reg_write(0x26, 0x90) # bit 7: AuxDAC Manual, bit 4: GPO Manual - self._set_gpo(self.ports[0] - 1) - # TODO: init AuxDAC + # # switch control + # ctx = iio.Context(uri) + # self.ctrl = ctx.find_device("ad9361-phy") + # # raw ad9361 register accesss: + # # https://ez.analog.com/linux-software-drivers/f/q-a/120853/control-fmcomms3-s-gpo-with-python + # # https://www.analog.com/media/cn/technical-documentation/user-guides/ad9364_register_map_reference_manual_ug-672.pdf + # self.ctrl.reg_write(0x26, 0x90) # bit 7: AuxDAC Manual, bit 4: GPO Manual + # self._set_gpo(self.ports[0] - 1) + # # TODO: init AuxDAC def get_config(self) -> Dict[str, Any]: config = dict() @@ -119,17 +133,6 @@ class Charon: # tx_gain = pout.coords["tx_gain"][tx_gain_idx] self.sdr.tx_hardwaregain_chan0 = float(tx_gain) - def generate_tone(self, f: float, fs: float, N: int = 1024, scale: int = 2**14): - fs = int(fs) - fc = int(f / (fs / N)) * (fs / N) - ts = 1 / float(fs) - t = np.arange(0, N * ts, ts) - i = np.cos(2 * np.pi * t * fc) * scale - q = np.sin(2 * np.pi * t * fc) * scale - iq = i + 1j * q - - return iq - def set_output(self, frequency: float, power: float): # TODO: switch to DDS in Pluto @@ -137,7 +140,7 @@ class Charon: self.set_output_power(power) self.sdr.tx_lo = int(frequency - self.FREQUENCY_OFFSET) self.sdr.tx_cyclic_buffer = True - # self.sdr.tx(self.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) def _rx(self, count: int = 1, fc: float | None = None) -> npt.ArrayLike: @@ -158,7 +161,7 @@ class Charon: self.set_output(frequency=frequency, power=-5) data = self._rx(1, fc=frequency - self.FREQUENCY_OFFSET) - ddc_tone = self.generate_tone(f=-self.FREQUENCY_OFFSET, fs=self.sdr.sample_rate, scale=1) + ddc_tone = generate_tone(f=-self.FREQUENCY_OFFSET, fs=self.sdr.sample_rate, scale=1) ddc_data = data * ddc_tone ddc_rel = ddc_data[1] / ddc_data[0] @@ -202,14 +205,36 @@ class Charon: s.loc[dict(frequency=frequency)] = self.get_b_over_a(frequency=frequency) return s + def vna_capture(self, frequency: npt.ArrayLike): + s = xr.DataArray( + np.empty(len(frequency), dtype=np.complex128), + dims=["frequency"], + coords=dict( + frequency=frequency, + ), + ) + for freq in s.frequency.data: + self.set_output(frequency=freq, power=-5) + self.sdr.rx_destroy_buffer() + self.sdr.rx_lo = int(freq) + self.sdr.rx_enabled_channels = [0, 1] + self.sdr.gain_control_mode_chan0 = "manual" + self.sdr.gain_control_mode_chan1 = "manual" + self.sdr.rx_hardwaregain_chan0 = 40 + self.sdr.rx_hardwaregain_chan1 = 40 + rx = self.sdr.rx() + s.loc[dict(frequency=freq)] = np.mean(rx[1] / rx[0]) + + return s + # %% sdr = Charon("ip:192.168.3.1", frequency=np.linspace(1e9, 1.1e9, 11)) # %% initialization config = sdr.get_config() -print(sdr.ctrl.debug_attrs["adi,rx-rf-port-input-select"].value) -print(sdr.ctrl.debug_attrs["adi,tx-rf-port-input-select"].value) +# print(sdr.ctrl.debug_attrs["adi,rx-rf-port-input-select"].value) +# print(sdr.ctrl.debug_attrs["adi,tx-rf-port-input-select"].value) config # %% generate tone @@ -264,31 +289,7 @@ plt.show() # %% -def vna_capture(frequency: npt.ArrayLike): - s = xr.DataArray( - np.empty(len(frequency), dtype=np.complex128), - dims=["frequency"], - coords=dict( - frequency=frequency, - ), - ) - for freq in s.frequency.data: - set_output(frequency=freq, power=-5) - sdr.rx_destroy_buffer() - sdr.rx_lo = int(freq) - sdr.rx_enabled_channels = [0, 1] - sdr.gain_control_mode_chan0 = "manual" - sdr.gain_control_mode_chan1 = "manual" - sdr.rx_hardwaregain_chan0 = 40 - sdr.rx_hardwaregain_chan1 = 40 - rx = sdr.rx() - s.loc[dict(frequency=freq)] = np.mean(rx[1] / rx[0]) - - return s - - -# %% -s = vna_capture(frequency=np.linspace(70e6, 200e6, 101)) +s = sdr.vna_capture(frequency=np.linspace(70e6, 200e6, 101)) # %% Plot Logmag fig, axs = plt.subplots(2, 1, sharex=True, tight_layout=True)