Repair project layout and fix Lab024B spectrum detection
This commit is contained in:
172
tests/lab024a_pluto_rx_capture.py
Normal file
172
tests/lab024a_pluto_rx_capture.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""
|
||||
Lab024A. Первый приём реальных IQ-сэмплов с Pluto+.
|
||||
|
||||
Схема:
|
||||
антенна 40–860 МГц -> RX1
|
||||
|
||||
Передатчики TX1 и TX2 не используются.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
import adi
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Параметры приёмника
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
PLUTO_URI = "ip:192.168.2.1"
|
||||
|
||||
CENTER_FREQUENCY_HZ = 100_000_000
|
||||
SAMPLE_RATE_HZ = 2_400_000
|
||||
RX_BANDWIDTH_HZ = 2_000_000
|
||||
|
||||
RX_BUFFER_SIZE = 262_144
|
||||
|
||||
OUTPUT_DIRECTORY = Path("data/raw")
|
||||
IQ_FILE_PATH = OUTPUT_DIRECTORY / "lab024a_pluto_rx_100mhz.npy"
|
||||
METADATA_FILE_PATH = OUTPUT_DIRECTORY / "lab024a_pluto_rx_100mhz.json"
|
||||
SPECTRUM_FILE_PATH = OUTPUT_DIRECTORY / "lab024a_pluto_rx_100mhz_spectrum.png"
|
||||
|
||||
|
||||
def calculate_spectrum(
|
||||
samples: np.ndarray,
|
||||
sample_rate_hz: float,
|
||||
center_frequency_hz: float,
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
Рассчитывает спектр принятого комплексного IQ-сигнала.
|
||||
|
||||
Возвращает:
|
||||
frequencies_hz — абсолютные радиочастоты;
|
||||
power_db — относительная мощность спектра в дБ.
|
||||
"""
|
||||
|
||||
sample_count = len(samples)
|
||||
|
||||
window = np.hanning(sample_count)
|
||||
windowed_samples = samples * window
|
||||
|
||||
spectrum = np.fft.fftshift(np.fft.fft(windowed_samples))
|
||||
power = np.abs(spectrum) ** 2
|
||||
|
||||
power_db = 10.0 * np.log10(power + 1e-12)
|
||||
power_db -= np.max(power_db)
|
||||
|
||||
baseband_frequencies_hz = np.fft.fftshift(
|
||||
np.fft.fftfreq(sample_count, d=1.0 / sample_rate_hz)
|
||||
)
|
||||
|
||||
absolute_frequencies_hz = (
|
||||
center_frequency_hz + baseband_frequencies_hz
|
||||
)
|
||||
|
||||
return absolute_frequencies_hz, power_db
|
||||
|
||||
|
||||
def main() -> None:
|
||||
OUTPUT_DIRECTORY.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print("Подключение к Pluto+...")
|
||||
sdr = adi.Pluto(uri=PLUTO_URI)
|
||||
|
||||
# Используем только первый приёмный канал RX1.
|
||||
sdr.rx_enabled_channels = [0]
|
||||
|
||||
sdr.sample_rate = SAMPLE_RATE_HZ
|
||||
sdr.rx_lo = CENTER_FREQUENCY_HZ
|
||||
sdr.rx_rf_bandwidth = RX_BANDWIDTH_HZ
|
||||
|
||||
# Автоматическая регулировка усиления.
|
||||
sdr.gain_control_mode_chan0 = "slow_attack"
|
||||
|
||||
sdr.rx_buffer_size = RX_BUFFER_SIZE
|
||||
|
||||
print()
|
||||
print("Параметры приёмника:")
|
||||
print(f" URI: {PLUTO_URI}")
|
||||
print(f" Центральная частота: {sdr.rx_lo / 1e6:.3f} МГц")
|
||||
print(f" Частота дискретизации: {sdr.sample_rate / 1e6:.3f} Мвыб/с")
|
||||
print(f" Полоса RX: {sdr.rx_rf_bandwidth / 1e6:.3f} МГц")
|
||||
print(f" Режим усиления: {sdr.gain_control_mode_chan0}")
|
||||
print(f" Размер буфера: {sdr.rx_buffer_size} отсчётов")
|
||||
|
||||
print()
|
||||
print("Получение IQ-сэмплов...")
|
||||
|
||||
# Первый буфер после перенастройки иногда содержит переходный процесс.
|
||||
_ = sdr.rx()
|
||||
|
||||
# Второй буфер сохраняем и анализируем.
|
||||
samples = np.asarray(sdr.rx(), dtype=np.complex64)
|
||||
|
||||
print("IQ-сэмплы получены.")
|
||||
|
||||
np.save(IQ_FILE_PATH, samples)
|
||||
|
||||
mean_value = np.mean(samples)
|
||||
rms_value = np.sqrt(np.mean(np.abs(samples) ** 2))
|
||||
peak_value = np.max(np.abs(samples))
|
||||
|
||||
metadata = {
|
||||
"pluto_uri": PLUTO_URI,
|
||||
"center_frequency_hz": int(sdr.rx_lo),
|
||||
"sample_rate_hz": int(sdr.sample_rate),
|
||||
"rx_bandwidth_hz": int(sdr.rx_rf_bandwidth),
|
||||
"gain_control_mode": sdr.gain_control_mode_chan0,
|
||||
"sample_count": int(len(samples)),
|
||||
"sample_dtype": str(samples.dtype),
|
||||
"mean_i": float(np.real(mean_value)),
|
||||
"mean_q": float(np.imag(mean_value)),
|
||||
"rms": float(rms_value),
|
||||
"peak": float(peak_value),
|
||||
}
|
||||
|
||||
with METADATA_FILE_PATH.open("w", encoding="utf-8") as metadata_file:
|
||||
json.dump(metadata, metadata_file, ensure_ascii=False, indent=4)
|
||||
|
||||
frequencies_hz, power_db = calculate_spectrum(
|
||||
samples=samples,
|
||||
sample_rate_hz=float(sdr.sample_rate),
|
||||
center_frequency_hz=float(sdr.rx_lo),
|
||||
)
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.plot(frequencies_hz / 1e6, power_db)
|
||||
|
||||
plt.title("Lab024A. Спектр сигнала, принятого Pluto+")
|
||||
plt.xlabel("Частота, МГц")
|
||||
plt.ylabel("Относительная мощность, дБ")
|
||||
plt.grid(True)
|
||||
plt.ylim(-100, 5)
|
||||
plt.tight_layout()
|
||||
|
||||
plt.savefig(SPECTRUM_FILE_PATH, dpi=150)
|
||||
plt.show()
|
||||
|
||||
print()
|
||||
print("Статистика:")
|
||||
print(f" Количество сэмплов: {len(samples)}")
|
||||
print(f" Тип данных: {samples.dtype}")
|
||||
print(f" Среднее I: {np.real(mean_value):.3f}")
|
||||
print(f" Среднее Q: {np.imag(mean_value):.3f}")
|
||||
print(f" RMS: {rms_value:.3f}")
|
||||
print(f" Пиковая амплитуда: {peak_value:.3f}")
|
||||
|
||||
print()
|
||||
print("Созданы файлы:")
|
||||
print(f" IQ: {IQ_FILE_PATH}")
|
||||
print(f" Метаданные:{METADATA_FILE_PATH}")
|
||||
print(f" Спектр: {SPECTRUM_FILE_PATH}")
|
||||
|
||||
print()
|
||||
print("Lab024A выполнена успешно.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user