sweep_b_over_a working
All checks were successful
Publish Python 🐍 distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Successful in -11s
Publish Python 🐍 distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been skipped

This commit is contained in:
Brendan Haines 2024-12-04 19:26:44 -07:00
parent 958d1f96d1
commit a20217967f

View File

@ -1,7 +1,7 @@
# %% imports # %% imports
import copy import copy
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional, Tuple from typing import Any, Dict, Tuple
import adi import adi
import iio import iio
@ -94,13 +94,24 @@ class Charon:
return config return config
def _get_gpo(self) -> int:
return (self.ctrl.reg_read(0x27) >> 4) & 0x0F
def _set_gpo(self, value: int) -> None: def _set_gpo(self, value: int) -> None:
self.ctrl.reg_write(0x27, (value & 0x0F) << 4) # bits 7-4: GPO3-0 self.ctrl.reg_write(0x27, (value & 0x0F) << 4) # bits 7-4: GPO3-0
def set_output_power(self, power: float): def set_output_power(self, power: float):
if power == -5:
# FIXME: this is a hack because I don't want to go through re-calibration # FIXME: this is a hack because I don't want to go through re-calibration
tx_gain = -8 if power == 5:
tx_gain = -1
elif power == 0:
tx_gain = -7
elif power == -5:
tx_gain = -12
elif power == -10:
tx_gain = -17
elif power == -15:
tx_gain = -22
else: else:
raise NotImplementedError() raise NotImplementedError()
# # TODO: correct over frequency # # TODO: correct over frequency
@ -108,15 +119,13 @@ class Charon:
# tx_gain = pout.coords["tx_gain"][tx_gain_idx] # tx_gain = pout.coords["tx_gain"][tx_gain_idx]
self.sdr.tx_hardwaregain_chan0 = float(tx_gain) self.sdr.tx_hardwaregain_chan0 = float(tx_gain)
def generate_tone(self, f: float, N: int = 1024, fs: Optional[float] = None): def generate_tone(self, f: float, fs: float, N: int = 1024, scale: int = 2**14):
if fs is None:
fs = self.sdr.sample_rate
fs = int(fs) fs = int(fs)
fc = int(f / (fs / N)) * (fs / N) fc = int(f / (fs / N)) * (fs / N)
ts = 1 / float(fs) ts = 1 / float(fs)
t = np.arange(0, N * ts, ts) t = np.arange(0, N * ts, ts)
i = np.cos(2 * np.pi * t * fc) * 2**14 i = np.cos(2 * np.pi * t * fc) * scale
q = np.sin(2 * np.pi * t * fc) * 2**14 q = np.sin(2 * np.pi * t * fc) * scale
iq = i + 1j * q iq = i + 1j * q
return iq return iq
@ -128,73 +137,87 @@ 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
self.sdr.tx(self.generate_tone(self.FREQUENCY_OFFSET)) # self.sdr.tx(self.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) -> npt.ArrayLike: def _rx(self, count: int = 1, fc: float | None = None) -> npt.ArrayLike:
if count < 1: if count < 1:
raise ValueError raise ValueError
self.sdr.rx_destroy_buffer() self.sdr.rx_destroy_buffer()
if fc is not None:
self.sdr.rx_lo = int(fc)
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 = 30
self.sdr.rx_hardwaregain_chan1 = 30
return np.concatenate([np.array(self.sdr.rx()) for _ in range(count)], axis=-1) return np.concatenate([np.array(self.sdr.rx()) for _ in range(count)], axis=-1)
def get_b_over_a(self, frequency: float):
self.set_output(frequency=frequency, power=-5)
# %% data = self._rx(1, fc=frequency - self.FREQUENCY_OFFSET)
sdr = Charon("ip:192.168.3.1") ddc_tone = self.generate_tone(f=-self.FREQUENCY_OFFSET, fs=self.sdr.sample_rate, scale=1)
# %% initialization
config = sdr.get_config()
config
# %% generate tone
sdr.set_output(frequency=1e9 + sdr.FREQUENCY_OFFSET, power=-5)
# %% capture data
data = sdr._rx(1)
# %%
fig, axs = plt.subplots(2, 2, sharex=True, tight_layout=True)
# ddc_tone = np.exp(
# -1j * 2 * np.pi * (sdr.FREQUENCY_OFFSET / sdr.sdr.sample_rate) * np.arange(data.shape[-1]), dtype=np.complex128
# )
ddc_tone = sdr.generate_tone(-sdr.FREQUENCY_OFFSET) * 2**-14
ddc_data = data * ddc_tone ddc_data = data * ddc_tone
axs[0][0].plot(np.real(ddc_data).T, label="DDC")
axs[1][0].plot(np.imag(ddc_data).T, label="DDC")
ddc_rel = ddc_data[1] / ddc_data[0] ddc_rel = ddc_data[1] / ddc_data[0]
axs[0][1].plot(np.real(ddc_rel).T, label="DDC")
axs[1][1].plot(np.imag(ddc_rel).T, label="DDC") # plt.figure()
# plt.plot(
# np.fft.fftshift(np.fft.fftfreq(ddc_data.shape[-1], 1 / self.sdr.sample_rate)),
# np.abs(np.fft.fftshift(np.fft.fft(ddc_data, axis=-1))).T,
# )
# plt.show()
# TODO: calculate sos only once
n, wn = signal.buttord( n, wn = signal.buttord(
wp=0.3 * sdr.FREQUENCY_OFFSET, wp=0.3 * sdr.FREQUENCY_OFFSET,
ws=0.8 * sdr.FREQUENCY_OFFSET, ws=0.8 * sdr.FREQUENCY_OFFSET,
gpass=1, gpass=1,
gstop=40, gstop=40,
analog=False, analog=False,
fs=sdr.sdr.sample_rate, fs=self.sdr.sample_rate,
) )
sos = signal.butter(n, wn, "lowpass", analog=False, output="sos", fs=sdr.sdr.sample_rate) sos = signal.butter(n, wn, "lowpass", analog=False, output="sos", fs=self.sdr.sample_rate)
# TODO: figure out why filt sucks. Introduces SO much phase noise (out to several MHz)
filt_data = signal.sosfiltfilt(sos, ddc_data, axis=-1) filt_data = signal.sosfiltfilt(sos, ddc_data, axis=-1)
axs[0][0].plot(np.real(filt_data).T, label="FILT")
axs[1][0].plot(np.imag(filt_data).T, label="FILT")
filt_rel = filt_data[1] / filt_data[0] filt_rel = filt_data[1] / filt_data[0]
axs[0][1].plot(np.real(filt_rel).T, label="FILT")
axs[1][1].plot(np.imag(filt_rel).T, label="FILT")
s = np.mean(filt_rel)
axs[0][1].axhline(np.real(s), color="k")
axs[1][1].axhline(np.imag(s), color="k")
axs[0][0].grid(True) return np.mean(data[1] / data[0])
axs[1][0].grid(True)
axs[0][1].grid(True)
axs[1][1].grid(True)
axs[0][0].legend(loc="lower right") def sweep_b_over_a(self):
axs[1][0].legend(loc="lower right") s = xr.DataArray(
axs[0][1].legend(loc="lower right") np.zeros(
axs[1][1].legend(loc="lower right") len(self.frequency),
dtype=np.complex128,
),
dims=["frequency"],
coords=dict(
frequency=self.frequency,
),
)
for frequency in self.frequency:
s.loc[dict(frequency=frequency)] = self.get_b_over_a(frequency=frequency)
return s
axs[0][0].set_ylabel("Real")
axs[1][0].set_ylabel("Imag") # %%
axs[0][0].set_title("By Channel") sdr = Charon("ip:192.168.3.1", frequency=np.linspace(1e9, 1.1e9, 11))
axs[0][1].set_title("Relative")
# %% 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)
config
# %% generate tone
fc = 1e9
sdr.set_output(frequency=fc + sdr.FREQUENCY_OFFSET, power=-5)
# %% capture data
data = sdr._rx(1, fc=fc)
# %% Plot in time # %% Plot in time
fig, axs = plt.subplots(2, 1, sharex=True, tight_layout=True) fig, axs = plt.subplots(2, 1, sharex=True, tight_layout=True)
@ -221,35 +244,16 @@ fig.show()
# %% Plot in frequency # %% Plot in frequency
f = np.fft.fftfreq(data.shape[-1], 1 / sdr.sdr.sample_rate) f = np.fft.fftfreq(data.shape[-1], 1 / sdr.sdr.sample_rate)
RX_BITS = 10 RX_BITS = 12 # for each of i, q (including sign bit)
Pxx_den = np.fft.fft(data, axis=-1) / (len(data) * 2 ** (2 * RX_BITS)) fft_data = np.fft.fft(data, axis=-1, norm="forward") / (2 ** (RX_BITS - 1))
Pxx_den_ddc = np.fft.fft(ddc_data, axis=-1) / (len(ddc_data) * 2 ** (2 * RX_BITS))
Pxx_den_filt = np.fft.fft(filt_data, axis=-1) / (len(filt_data) * 2 ** (2 * RX_BITS))
fft_ddc_tone = np.fft.fft(ddc_tone, axis=-1) / (len(ddc_tone))
plt.figure() plt.figure()
for cc, chan in enumerate(sdr.sdr.rx_enabled_channels): for cc, chan in enumerate(sdr.sdr.rx_enabled_channels):
# plt.plot(
# np.fft.fftshift(f),
# db20(np.fft.fftshift(Pxx_den[cc])),
# label=f"Channel {chan}",
# )
plt.plot( plt.plot(
np.fft.fftshift(f), np.fft.fftshift(f),
db20(np.fft.fftshift(Pxx_den_ddc[cc])), db20(np.fft.fftshift(fft_data[cc])),
label=f"Channel {chan}", label=f"Channel {chan}",
) )
plt.plot(
np.fft.fftshift(f),
db20(np.fft.fftshift(Pxx_den_filt[cc])),
label=f"Channel {chan}",
)
# plt.plot(
# np.fft.fftshift(f),
# db20(np.fft.fftshift(fft_ddc_tone)),
# label="DDC Tone",
# )
plt.legend() plt.legend()
# plt.ylim(1e-7, 1e2)
plt.ylim(-100, 0) plt.ylim(-100, 0)
plt.xlabel("Frequency [Hz]") plt.xlabel("Frequency [Hz]")
plt.ylabel("Power [dBfs]") plt.ylabel("Power [dBfs]")