sweep_b_over_a working
This commit is contained in:
parent
958d1f96d1
commit
a20217967f
|
@ -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
|
if power == 5:
|
||||||
tx_gain = -8
|
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)
|
||||||
|
ddc_tone = self.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]
|
||||||
|
|
||||||
|
# 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(
|
||||||
|
wp=0.3 * sdr.FREQUENCY_OFFSET,
|
||||||
|
ws=0.8 * sdr.FREQUENCY_OFFSET,
|
||||||
|
gpass=1,
|
||||||
|
gstop=40,
|
||||||
|
analog=False,
|
||||||
|
fs=self.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_rel = filt_data[1] / filt_data[0]
|
||||||
|
|
||||||
|
return np.mean(data[1] / data[0])
|
||||||
|
|
||||||
|
def sweep_b_over_a(self):
|
||||||
|
s = xr.DataArray(
|
||||||
|
np.zeros(
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
sdr = Charon("ip:192.168.3.1")
|
sdr = Charon("ip:192.168.3.1", frequency=np.linspace(1e9, 1.1e9, 11))
|
||||||
|
|
||||||
# %% initialization
|
# %% initialization
|
||||||
config = sdr.get_config()
|
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
|
config
|
||||||
|
|
||||||
# %% generate tone
|
# %% generate tone
|
||||||
sdr.set_output(frequency=1e9 + sdr.FREQUENCY_OFFSET, power=-5)
|
fc = 1e9
|
||||||
|
sdr.set_output(frequency=fc + sdr.FREQUENCY_OFFSET, power=-5)
|
||||||
|
|
||||||
# %% capture data
|
# %% capture data
|
||||||
data = sdr._rx(1)
|
data = sdr._rx(1, fc=fc)
|
||||||
|
|
||||||
# %%
|
|
||||||
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
|
|
||||||
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]
|
|
||||||
axs[0][1].plot(np.real(ddc_rel).T, label="DDC")
|
|
||||||
axs[1][1].plot(np.imag(ddc_rel).T, label="DDC")
|
|
||||||
n, wn = signal.buttord(
|
|
||||||
wp=0.3 * sdr.FREQUENCY_OFFSET,
|
|
||||||
ws=0.8 * sdr.FREQUENCY_OFFSET,
|
|
||||||
gpass=1,
|
|
||||||
gstop=40,
|
|
||||||
analog=False,
|
|
||||||
fs=sdr.sdr.sample_rate,
|
|
||||||
)
|
|
||||||
sos = signal.butter(n, wn, "lowpass", analog=False, output="sos", fs=sdr.sdr.sample_rate)
|
|
||||||
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]
|
|
||||||
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)
|
|
||||||
axs[1][0].grid(True)
|
|
||||||
axs[0][1].grid(True)
|
|
||||||
axs[1][1].grid(True)
|
|
||||||
|
|
||||||
axs[0][0].legend(loc="lower right")
|
|
||||||
axs[1][0].legend(loc="lower right")
|
|
||||||
axs[0][1].legend(loc="lower right")
|
|
||||||
axs[1][1].legend(loc="lower right")
|
|
||||||
|
|
||||||
axs[0][0].set_ylabel("Real")
|
|
||||||
axs[1][0].set_ylabel("Imag")
|
|
||||||
axs[0][0].set_title("By Channel")
|
|
||||||
axs[0][1].set_title("Relative")
|
|
||||||
|
|
||||||
# %% 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]")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user