Merge branch 'plutosdr'

This commit is contained in:
Wouter Bokslag
2026-03-19 15:15:43 +01:00
8 changed files with 360 additions and 236 deletions

View File

@@ -42,13 +42,14 @@ impl StackConfig {
soapy_cfg.io_cfg.iocfg_usrpb2xx.is_some(),
soapy_cfg.io_cfg.iocfg_limesdr.is_some(),
soapy_cfg.io_cfg.iocfg_sxceiver.is_some(),
soapy_cfg.io_cfg.iocfg_pluto.is_some(),
]
.iter()
.filter(|&&x| x)
.count();
if config_count != 1 {
return Err(
"soapysdr backend requires exactly one hardware configuration (iocfg_usrpb2xx, iocfg_limesdr, or iocfg_sxceiver)",
"soapysdr backend requires exactly one hardware configuration (iocfg_usrpb2xx, iocfg_limesdr, iocfg_sxceiver or iocfg_pluto)",
);
}
}

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use serde::Deserialize;
use toml::Value;
use crate::bluestation::{CfgLimeSdr, CfgSoapySdr, CfgSxCeiver, CfgUsrpB2xx, SoapySdrDto, SoapySdrIoCfg};
use crate::bluestation::{CfgLimeSdr, CfgSoapySdr, CfgSxCeiver, CfgUsrpB2xx, CfgPluto, SoapySdrDto, SoapySdrIoCfg};
/// The PHY layer backend type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
@@ -83,6 +83,20 @@ pub fn phy_dto_to_cfg(src: PhyIoDto) -> CfgPhyIo {
});
}
if let Some(pluto_dto) = soapy_dto.iocfg_pluto {
soapy_cfg.io_cfg.iocfg_pluto = Some(CfgPluto {
rx_ant: pluto_dto.rx_ant,
tx_ant: pluto_dto.tx_ant,
rx_gain_pga: pluto_dto.rx_gain_pga,
tx_gain_pga: pluto_dto.tx_gain_pga,
uri: pluto_dto.uri,
loopback: pluto_dto.loopback,
timestamp_every: pluto_dto.timestamp_every,
usb_direct: pluto_dto.usb_direct,
direct: pluto_dto.direct,
});
}
soapy_cfg
});

View File

@@ -13,6 +13,9 @@ pub struct SoapySdrIoCfg {
/// SXceiver configuration
pub iocfg_sxceiver: Option<CfgSxCeiver>,
/// Pluto timestamp configuration
pub iocfg_pluto: Option<CfgPluto>,
}
impl SoapySdrIoCfg {
@@ -23,6 +26,8 @@ impl SoapySdrIoCfg {
"lime"
} else if self.iocfg_sxceiver.is_some() {
"sx"
} else if self.iocfg_pluto.is_some() {
"plutosdr"
} else {
"unknown"
}
@@ -35,12 +40,13 @@ impl Default for SoapySdrIoCfg {
iocfg_usrpb2xx: None,
iocfg_limesdr: None,
iocfg_sxceiver: None,
iocfg_pluto: None,
}
}
}
/// Configuration for Ettus USRP B2xx series
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Default)]
pub struct CfgUsrpB2xx {
pub rx_ant: Option<String>,
pub tx_ant: Option<String>,
@@ -49,7 +55,7 @@ pub struct CfgUsrpB2xx {
}
/// Configuration for LimeSDR
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Default)]
pub struct CfgLimeSdr {
pub rx_ant: Option<String>,
pub tx_ant: Option<String>,
@@ -61,7 +67,7 @@ pub struct CfgLimeSdr {
}
/// Configuration for SXceiver
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Default)]
pub struct CfgSxCeiver {
pub rx_ant: Option<String>,
pub tx_ant: Option<String>,
@@ -71,6 +77,20 @@ pub struct CfgSxCeiver {
pub tx_gain_mixer: Option<f64>,
}
/// Configuration for Pluto timestamp
#[derive(Debug, Clone, Deserialize, Default)]
pub struct CfgPluto {
pub rx_ant: Option<String>,
pub tx_ant: Option<String>,
pub rx_gain_pga: Option<f64>,
pub tx_gain_pga: Option<f64>,
pub uri: Option<String>,
pub usb_direct: Option<bool>,
pub direct: Option<bool>,
pub timestamp_every: Option<usize>,
pub loopback: Option<bool>,
}
/// SoapySDR configuration
#[derive(Debug, Clone)]
pub struct CfgSoapySdr {
@@ -109,6 +129,7 @@ pub struct SoapySdrDto {
pub iocfg_usrpb2xx: Option<UsrpB2xxDto>,
pub iocfg_limesdr: Option<LimeSdrDto>,
pub iocfg_sxceiver: Option<SXceiverDto>,
pub iocfg_pluto: Option<PlutoDto>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
@@ -142,3 +163,17 @@ pub struct SXceiverDto {
pub tx_gain_dac: Option<f64>,
pub tx_gain_mixer: Option<f64>,
}
#[derive(Deserialize)]
pub struct PlutoDto {
pub rx_ant: Option<String>,
pub tx_ant: Option<String>,
pub rx_gain_pga: Option<f64>,
pub tx_gain_pga: Option<f64>,
pub uri: Option<String>,
pub loopback: Option<bool>,
pub timestamp_every: Option<usize>,
pub usb_direct: Option<bool>,
pub direct: Option<bool>,
}

View File

@@ -8,7 +8,7 @@ pub mod fir;
pub mod history;
pub mod modem_common;
pub mod modulator;
pub mod soapy_defaults;
pub mod soapy_settings;
pub mod soapy_time;
pub mod soapyio;

View File

@@ -1,92 +0,0 @@
#[derive(Clone, Debug)]
pub struct SdrSettings {
/// Name used to print which SDR was detected
pub name: String,
/// Receive and transmit sample rate for TMO BS.
pub fs_bs: f64,
/// Receive and transmit sample rate for TMO monitor.
/// The sample rate needs to be high enough to receive
/// both downlink and uplink at the same time.
pub fs_monitor: f64,
/// Receive antenna
pub rx_ant: Option<String>,
/// Transmit antenna
pub tx_ant: Option<String>,
/// Receive gains
pub rx_gain: Vec<(String, f64)>,
/// Transmit gains
pub tx_gain: Vec<(String, f64)>,
}
impl SdrSettings {
/// Get default settings based on SDR type
pub fn get_defaults(driver_key: &str, hardware_key: &str) -> Self {
match (driver_key, hardware_key) {
(_, "LimeSDR-USB") => Self::defaults_limesdr(),
(_, "LimeSDR-Mini_v2") => Self::defaults_limesdr_mini_v2(),
("sx", _) => Self::defaults_sxceiver(),
("uhd", _) | ("b200", _) => Self::defaults_usrp_b2x0(),
_ => Self::unknown(),
}
}
fn unknown() -> Self {
SdrSettings {
name: "Unknown SDR device".to_string(),
fs_bs: 512e3,
fs_monitor: 16384e3,
rx_ant: None,
tx_ant: None,
rx_gain: vec![],
tx_gain: vec![],
}
}
pub fn defaults_limesdr() -> Self {
SdrSettings {
name: "LimeSDR".to_string(),
fs_bs: 512e3,
fs_monitor: 16384e3,
rx_ant: Some("LNAL".to_string()),
tx_ant: Some("BAND1".to_string()),
rx_gain: vec![("LNA".to_string(), 20.0), ("TIA".to_string(), 10.0), ("PGA".to_string(), 10.0)],
tx_gain: vec![("PAD".to_string(), 52.0), ("IAMP".to_string(), 3.0)],
}
}
pub fn defaults_limesdr_mini_v2() -> Self {
SdrSettings {
name: "LimeSDR Mini v2".to_string(),
fs_bs: 512e3,
fs_monitor: 16384e3,
rx_ant: Some("LNAW".to_string()),
tx_ant: Some("BAND2".to_string()),
rx_gain: vec![("TIA".to_string(), 6.0), ("LNA".to_string(), 18.0), ("PGA".to_string(), 0.0)],
tx_gain: vec![("PAD".to_string(), 30.0), ("IAMP".to_string(), 6.0)],
}
}
pub fn defaults_sxceiver() -> Self {
SdrSettings {
name: "SXceiver".to_string(),
fs_bs: 600e3,
fs_monitor: 600e3, // monitoring is not really possible with SXceiver
rx_ant: Some("RX".to_string()),
tx_ant: Some("TX".to_string()),
rx_gain: vec![("LNA".to_string(), 42.0), ("PGA".to_string(), 16.0)],
tx_gain: vec![("DAC".to_string(), 9.0), ("MIXER".to_string(), 30.0)],
}
}
pub fn defaults_usrp_b2x0() -> Self {
SdrSettings {
name: "USRP B200/B210".to_string(),
fs_bs: 512e3,
fs_monitor: 16384e3,
rx_ant: Some("TX/RX".to_string()),
tx_ant: Some("TX/RX".to_string()),
rx_gain: vec![("PGA".to_string(), 50.0)],
tx_gain: vec![("PGA".to_string(), 35.0)],
}
}
}

View File

@@ -0,0 +1,248 @@
//! Device-specific SoapySDR settings
use tetra_config::bluestation::sec_phy_soapy::*;
/// Performance for some SDRs can be optimized by using different settings
/// for different operating modes of the stack.
/// For BS or MS mode, we want low latency at a fairly low sample rate.
/// For monitor mode, a high sample rate is needed,
/// so we want to maximize throughput, but latency is not critical.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Mode {
Bs,
Ms,
Mon,
}
#[derive(Clone, Debug)]
pub struct SdrSettings {
/// Name used to print which SDR was detected
pub name: String,
/// If false, timestamp of latest RX read is used to estimate
/// current hardware time. This is used in case get_hardware_time
/// is unacceptably slow or not supported.
pub use_get_hardware_time: bool,
/// Receive and transmit sample rate.
pub fs: f64,
/// Receive antenna
pub rx_ant: Option<String>,
/// Transmit antenna
pub tx_ant: Option<String>,
/// Receive gains
pub rx_gain: Vec<(String, f64)>,
/// Transmit gains
pub tx_gain: Vec<(String, f64)>,
/// Receive stream arguments
pub rx_args: Vec<(String, String)>,
/// Transmit stream arguments
pub tx_args: Vec<(String, String)>,
}
/// Get device arguments based on IO configuration.
///
/// This is separate from SdrSettings because device arguments
/// must be known before opening the device,
/// whereas SdrSettings may depend on information
/// that is obtained after the device has been opened.
pub fn get_device_arguments(io_cfg: &SoapySdrIoCfg, _mode: Mode) -> Vec<(String, String)> {
let mut args = Vec::<(String, String)>::new();
let driver = io_cfg.get_soapy_driver_name();
args.push(("driver".to_string(), driver.to_string()));
// Additional device arguments for devices that need them
match driver {
"plutosdr" => {
let cfg: &Option<CfgPluto> = &io_cfg.iocfg_pluto;
// If cfg is None, use default which sets all optional fields to None.
let cfg_pluto = if let Some(cfg) = cfg { &cfg } else { &CfgPluto::default() };
args.push(("direct".to_string(), cfg_pluto.direct.map_or("1", |v| if v {"1"} else {"0"}).to_string()));
args.push(("timestamp_every".to_string(), cfg_pluto.timestamp_every.unwrap_or(1500).to_string()));
if let Some(ref uri) = cfg_pluto.uri {
args.push(("uri".to_string(), uri.to_string()));
}
if let Some(loopback) = cfg_pluto.loopback {
args.push(("loopback".to_string(), (if loopback {"1"} else {"0"}).to_string()));
}
},
_ => { },
}
args
}
impl SdrSettings {
/// Get settings based on SDR type
pub fn get_settings(
io_cfg: &SoapySdrIoCfg,
driver_key: &str,
hardware_key: &str,
mode: Mode,
) -> Self {
match (driver_key, hardware_key) {
(_, "LimeSDR-USB") =>
Self::settings_limesdr(&io_cfg.iocfg_limesdr, mode, LimeSDRModel::LimeSDR),
(_, "LimeSDR-Mini_v2") =>
Self::settings_limesdr(&io_cfg.iocfg_limesdr, mode, LimeSDRModel::LimeSDRMini),
("sx", _) =>
Self::settings_sxceiver(&io_cfg.iocfg_sxceiver),
("uhd", _) | ("b200", _) =>
Self::settings_usrp_b2x0(&io_cfg.iocfg_usrpb2xx, mode),
("PlutoSDR", _) => Self::settings_pluto(&io_cfg.iocfg_pluto, mode),
_ => Self::unknown(mode),
}
}
fn unknown(mode: Mode) -> Self {
SdrSettings {
name: "Unknown SDR device".to_string(),
use_get_hardware_time: true,
fs: if mode == Mode::Mon { 16384e3 } else { 512e3 },
rx_ant: None,
tx_ant: None,
rx_gain: vec![],
tx_gain: vec![],
rx_args: vec![],
tx_args: vec![],
}
}
fn settings_limesdr(cfg: &Option<CfgLimeSdr>, mode: Mode, model: LimeSDRModel) -> Self {
// If cfg is None, use default which sets all optional fields to None.
let cfg = if let Some(cfg) = cfg { &cfg } else { &CfgLimeSdr::default() };
SdrSettings {
name: format!("{:?}", model),
use_get_hardware_time: true,
fs: if mode == Mode::Mon { 16384e3 } else { 512e3 },
rx_ant: Some(cfg.rx_ant.clone().unwrap_or(match model {
LimeSDRModel::LimeSDR => "LNAL",
LimeSDRModel::LimeSDRMini => "LNAW",
}.to_string())),
tx_ant: Some(cfg.tx_ant.clone().unwrap_or(match model {
LimeSDRModel::LimeSDR => "BAND1",
LimeSDRModel::LimeSDRMini => "BAND2",
}.to_string())),
rx_gain: vec![
("LNA".to_string(), cfg.rx_gain_lna.unwrap_or(18.0)),
("TIA".to_string(), cfg.rx_gain_tia.unwrap_or(6.0)),
("PGA".to_string(), cfg.rx_gain_pga.unwrap_or(0.0)),
],
tx_gain: vec![
("PAD".to_string(), cfg.tx_gain_pad.unwrap_or(30.0)),
("IAMP".to_string(), cfg.tx_gain_iamp.unwrap_or(6.0)),
],
// Minimum latency for BS/MS, maximum throughput for monitor
rx_args: vec![
("latency".to_string(), if mode == Mode::Mon { "1" } else { "0" }.to_string()),
],
tx_args: vec![
("latency".to_string(), if mode == Mode::Mon { "1" } else { "0" }.to_string()),
],
}
}
fn settings_sxceiver(cfg: &Option<CfgSxCeiver>) -> Self {
// If cfg is None, use default which sets all optional fields to None.
let cfg = if let Some(cfg) = cfg { &cfg } else { &CfgSxCeiver::default() };
let fs = 600e3;
SdrSettings {
name: "SXceiver".to_string(),
use_get_hardware_time: true,
fs: fs,
rx_ant: Some(cfg.rx_ant.clone().unwrap_or("RX".to_string())),
tx_ant: Some(cfg.tx_ant.clone().unwrap_or("TX".to_string())),
rx_gain: vec![
("LNA".to_string(), cfg.rx_gain_lna.unwrap_or(42.0)),
("PGA".to_string(), cfg.rx_gain_pga.unwrap_or(16.0)),
],
tx_gain: vec![
("DAC".to_string(), cfg.tx_gain_dac.unwrap_or(9.0)),
("MIXER".to_string(), cfg.tx_gain_mixer.unwrap_or(30.0)),
],
rx_args: vec![
("period".to_string(), block_size(fs).to_string()),
],
tx_args: vec![
("period".to_string(), block_size(fs).to_string()),
],
}
}
fn settings_usrp_b2x0(cfg: &Option<CfgUsrpB2xx>, mode: Mode) -> Self {
// If cfg is None, use default which sets all optional fields to None.
let cfg = if let Some(cfg) = cfg { &cfg } else { &CfgUsrpB2xx::default() };
SdrSettings {
name: "USRP B200/B210".to_string(),
use_get_hardware_time: true,
fs: if mode == Mode::Mon { 16384e3 } else { 512e3 },
rx_ant: Some(cfg.rx_ant.clone().unwrap_or("TX/RX".to_string())),
tx_ant: Some(cfg.tx_ant.clone().unwrap_or("TX/RX".to_string())),
rx_gain: vec![
("PGA".to_string(), cfg.rx_gain_pga.unwrap_or(50.0)),
],
tx_gain: vec![
("PGA".to_string(), cfg.tx_gain_pga.unwrap_or(35.0)),
],
rx_args: vec![],
tx_args: vec![],
}
}
fn settings_pluto(cfg: &Option<CfgPluto>, mode: Mode) -> Self {
// If cfg is None, use default which sets all optional fields to None.
let cfg = if let Some(cfg) = cfg { &cfg } else { &CfgPluto::default() };
SdrSettings {
name: "Pluto".to_string(),
use_get_hardware_time: false,
fs: if mode == Mode::Mon { 1e6 } else { 1e6 },
rx_ant: Some(cfg.rx_ant.clone().unwrap_or("A_BALANCED".to_string())),
tx_ant: Some(cfg.tx_ant.clone().unwrap_or("A".to_string())),
rx_gain: vec![
("PGA".to_string(), cfg.rx_gain_pga.unwrap_or(20.0)),
],
tx_gain: vec![
("PGA".to_string(), cfg.tx_gain_pga.unwrap_or(89.0)),
],
rx_args: vec![],
tx_args: vec![],
}
}
}
#[derive(Debug, PartialEq)]
enum LimeSDRModel {
LimeSDR,
LimeSDRMini,
}
/// Get processing block size in samples for a given sample rate.
/// This can be used to optimize performance for some SDRs.
pub fn block_size(fs: f64) -> usize {
// With current FCFB parameters processing blocks are 1.5 ms long.
// It is a bit bug prone to have it here in case
// FCFB parameters are changed, but it makes things simpler for now.
(fs * 1.5e-3).round() as usize
}

View File

@@ -3,25 +3,19 @@ use tetra_config::bluestation::SharedConfig;
use tetra_pdus::phy::traits::rxtx_dev::RxTxDevError;
use super::dsp_types;
use super::dsp_types::*;
use super::soapy_defaults::SdrSettings;
use super::soapy_settings;
use super::soapy_settings::SdrSettings;
pub use super::soapy_settings::Mode;
use super::soapy_time::{ticks_to_time_ns, time_ns_to_ticks};
type StreamType = ComplexSample;
#[derive(Debug)]
pub enum Mode {
Bs,
Ms,
Mon,
}
pub struct RxResult {
/// Number of samples read
pub len: usize,
/// Sample counter for the first sample read
pub count: dsp_types::SampleCount,
pub count: SampleCount,
}
pub struct SoapyIo {
@@ -37,7 +31,7 @@ pub struct SoapyIo {
/// If false, timestamp of latest RX read is used to estimate
/// current hardware time. This is used in case get_hardware_time
/// is unacceptably slow, particularly with SoapyRemote.
/// is unacceptably slow or not supported.
use_get_hardware_time: bool,
dev: soapysdr::Device,
@@ -66,24 +60,9 @@ macro_rules! soapycheck {
}
impl SoapyIo {
/// Get gain value from config or use default value from SdrSettings
fn get_gain_or_default(gain_name: &str, cfg_val: Option<f64>, defaults: &SdrSettings) -> (String, f64) {
if let Some(val) = cfg_val {
(gain_name.to_string(), val)
} else {
defaults
.rx_gain
.iter()
.find(|(name, _)| name == gain_name)
.cloned()
.unwrap_or_else(|| (gain_name.to_string(), 0.0))
}
}
pub fn new(cfg: &SharedConfig, mode: Mode) -> Result<Self, soapysdr::Error> {
let rx_ch = 0;
let tx_ch = 0;
let mut use_get_hardware_time = true;
let binding = cfg.config();
let soapy_cfg = binding
@@ -91,8 +70,6 @@ impl SoapyIo {
.soapysdr
.as_ref()
.expect("SoapySdr config must be set for SoapySdr PhyIo");
let driver = soapy_cfg.io_cfg.get_soapy_driver_name();
let dev_args_str = &[("driver", driver)];
// Get PPM corrected freqs
let (dl_corrected, _) = soapy_cfg.dl_freq_corrected();
@@ -112,18 +89,12 @@ impl SoapyIo {
}
};
let dev_args_str = soapy_settings::get_device_arguments(&soapy_cfg.io_cfg, mode);
tracing::info!("Using device arguments: {:?}", dev_args_str);
let mut dev_args = soapysdr::Args::new();
for (key, value) in dev_args_str {
dev_args.set(*key, *value);
// get_hardware_time tends to be unacceptably slow
// over SoapyRemote, so do not use it.
// Maybe this is not a reliably way to detect use of SoapyRemote
// in case SoapySDR selects it by default, but I do not know
// a better way to detect it.
if *key == "driver" && *value == "remote" {
use_get_hardware_time = false;
}
dev_args.set(key, value);
}
let dev = soapycheck!("open SoapySDR device", soapysdr::Device::new(dev_args));
@@ -134,107 +105,31 @@ impl SoapyIo {
// Get default settings based on detected hardware
let driver_key = dev.driver_key().unwrap_or_default();
let hardware_key = dev.hardware_key().unwrap_or_default();
let mut sdr_settings = SdrSettings::get_defaults(&driver_key, &hardware_key);
// Apply user configuration overrides based on driver type
let driver = soapy_cfg.io_cfg.get_soapy_driver_name();
match driver {
"uhd" => {
if let Some(cfg) = &soapy_cfg.io_cfg.iocfg_usrpb2xx {
// Override antenna settings if specified
if let Some(ref ant) = cfg.rx_ant {
sdr_settings.rx_ant = Some(ant.clone());
}
if let Some(ref ant) = cfg.tx_ant {
sdr_settings.tx_ant = Some(ant.clone());
}
// Override gain settings
let mut rx_gains = Vec::new();
rx_gains.push(Self::get_gain_or_default("PGA", cfg.rx_gain_pga, &sdr_settings));
sdr_settings.rx_gain = rx_gains;
let mut tx_gains = Vec::new();
tx_gains.push(Self::get_gain_or_default("PGA", cfg.tx_gain_pga, &sdr_settings));
sdr_settings.tx_gain = tx_gains;
}
}
"lime" => {
if let Some(cfg) = &soapy_cfg.io_cfg.iocfg_limesdr {
// Override antenna settings if specified
if let Some(ref ant) = cfg.rx_ant {
sdr_settings.rx_ant = Some(ant.clone());
}
if let Some(ref ant) = cfg.tx_ant {
sdr_settings.tx_ant = Some(ant.clone());
}
// Override gain settings
let mut rx_gains = Vec::new();
rx_gains.push(Self::get_gain_or_default("LNA", cfg.rx_gain_lna, &sdr_settings));
rx_gains.push(Self::get_gain_or_default("TIA", cfg.rx_gain_tia, &sdr_settings));
rx_gains.push(Self::get_gain_or_default("PGA", cfg.rx_gain_pga, &sdr_settings));
sdr_settings.rx_gain = rx_gains;
let mut tx_gains = Vec::new();
tx_gains.push(Self::get_gain_or_default("PAD", cfg.tx_gain_pad, &sdr_settings));
tx_gains.push(Self::get_gain_or_default("IAMP", cfg.tx_gain_iamp, &sdr_settings));
sdr_settings.tx_gain = tx_gains;
}
}
"sx" => {
if let Some(cfg) = &soapy_cfg.io_cfg.iocfg_sxceiver {
// Override antenna settings if specified
if let Some(ref ant) = cfg.rx_ant {
sdr_settings.rx_ant = Some(ant.clone());
}
if let Some(ref ant) = cfg.tx_ant {
sdr_settings.tx_ant = Some(ant.clone());
}
// Override gain settings
let mut rx_gains = Vec::new();
rx_gains.push(Self::get_gain_or_default("LNA", cfg.rx_gain_lna, &sdr_settings));
rx_gains.push(Self::get_gain_or_default("PGA", cfg.rx_gain_pga, &sdr_settings));
sdr_settings.rx_gain = rx_gains;
let mut tx_gains = Vec::new();
tx_gains.push(Self::get_gain_or_default("DAC", cfg.tx_gain_dac, &sdr_settings));
tx_gains.push(Self::get_gain_or_default("MIXER", cfg.tx_gain_mixer, &sdr_settings));
sdr_settings.tx_gain = tx_gains;
}
}
_ => {
tracing::warn!("Unknown SoapySDR driver '{}', using default settings", driver);
}
}
let sdr_settings = SdrSettings::get_settings(&soapy_cfg.io_cfg, &driver_key, &hardware_key, mode);
tracing::info!(
"Got driver key '{}' hardware_key '{}', using settings for {}",
driver_key,
hardware_key,
sdr_settings.name
sdr_settings.name,
);
tracing::info!("Using SDR settings: {:?}", sdr_settings);
let samp_rate = match mode {
Mode::Bs | Mode::Ms => sdr_settings.fs_bs,
Mode::Mon => sdr_settings.fs_monitor,
};
let mut rx_fs: f64 = 0.0;
if rx_enabled {
soapycheck!("set RX sample rate", dev.set_sample_rate(soapysdr::Direction::Rx, rx_ch, samp_rate));
soapycheck!("set RX sample rate", dev.set_sample_rate(soapysdr::Direction::Rx, rx_ch, sdr_settings.fs));
// Read the actual sample rate obtained and store it
// to avoid having to read it again every time it is needed.
rx_fs = soapycheck!("get RX sample rate", dev.sample_rate(soapysdr::Direction::Rx, rx_ch));
}
let mut tx_fs: f64 = 0.0;
if tx_enabled {
soapycheck!("set TX sample rate", dev.set_sample_rate(soapysdr::Direction::Tx, tx_ch, samp_rate));
soapycheck!("set TX sample rate", dev.set_sample_rate(soapysdr::Direction::Tx, tx_ch, sdr_settings.fs));
tx_fs = soapycheck!("get TX sample rate", dev.sample_rate(soapysdr::Direction::Tx, tx_ch));
}
if rx_enabled {
// If rx_enabled is true, we already know sdr_rx_freq is not None,
// If rx_enabled is true, we already know rx_freq is not None,
// so unwrap is fine here.
soapycheck!(
"set RX center frequency",
@@ -271,24 +166,15 @@ impl SoapyIo {
}
}
// TODO: add stream arguments to SdrSettings.
// Maybe they should be different for BS and monitor modes.
// For example, the latency argument with LimeSDR should probably
// be set for minimum latency for TMO BS
// but for maximum throughput for TMO monitor.
let mut rx_args = soapysdr::Args::new();
let tx_args = soapysdr::Args::new();
// hack to test the idea above, TODO properly
match mode {
Mode::Bs | Mode::Ms => {
// Minimize latency
rx_args.set("latency", "0");
}
Mode::Mon => {
// Maximize throughput with high sample rates
rx_args.set("latency", "1");
}
};
for (key, value) in sdr_settings.rx_args {
rx_args.set(key, value);
}
let mut tx_args = soapysdr::Args::new();
for (key, value) in sdr_settings.tx_args {
tx_args.set(key, value);
}
let mut rx = if rx_enabled {
Some(soapycheck!("setup RX stream", dev.rx_stream_args(&[rx_ch], rx_args)))
@@ -313,7 +199,12 @@ impl SoapyIo {
tx_fs,
initial_time: None,
rx_next_count: 0,
use_get_hardware_time,
// TODO: if SoapyRemote support is added back,
// always set use_get_hardware_time to false when SoapyRemote is used.
// The setting was originally added to deal with unacceptably slow
// get_hardware_time over SoapyRemote but turns out it is needed
// for some SDR devices as well, so it now a part of sdr_settings.
use_get_hardware_time: sdr_settings.use_get_hardware_time,
dev,
rx,
tx,

View File

@@ -66,6 +66,33 @@ tx_ant = "TX/RX"
rx_gain_pga = 50.0
tx_gain_pga = 35.0
# Sane defaults for Original Adalm Pluto and Pluto+
# Don't use the low quality USB cables delivered with Pluto+!
# Firmware with timestamping needed:
# https://github.com/pgreenland/plutosdr-fw/releases (Pluto)
# https://github.com/wahlm/plutosdr-fw-timestamp/releases (Pluto+)
# and specific Soapy driver needed:
# https://github.com/pgreenland/SoapyPlutoSDR/tree/sdr_gadget_timestamping
# Other Pluto "clones" are not supported!
# [phy_io.soapysdr.iocfg_pluto]
# rx_ant = "A_BALANCED"
# tx_ant = "A"
# rx_gain_pga = 20.0
# tx_gain_pga = 89.0
# optional "uri" to select connection used for Pluto+ (e.g. IP/ethernet),
# don't uncomment and use for Pluto!
# (check availability with "SoapySDRUtil --find")
# examples:
# uri = "usb:1.3.5"
# uri = "ip:pluto.local"
# uri = "ip:plutoplus.your.domain"
# uri = "ip:192.168.42.42"
# configurable Soapy driver parameters with default values follow,
# don't uncomment and change values, unless you know what you are doing
# direct = true
# timestamp_every = 1500
# loopback = false
###############################################################################
# Network Information