mirror of
https://github.com/MidnightBlueLabs/tetra-bluestation.git
synced 2026-03-29 05:09:51 +00:00
Merge branch 'plutosdr'
This commit is contained in:
@@ -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)",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)],
|
||||
}
|
||||
}
|
||||
}
|
||||
248
crates/tetra-entities/src/phy/components/soapy_settings.rs
Normal file
248
crates/tetra-entities/src/phy/components/soapy_settings.rs
Normal 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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user