pub mod boc;
pub mod cn0;
pub mod design;
pub mod discrim;
pub mod filter;
mod bank;
mod channel;
pub mod lock;
pub mod sink;
pub use bank::{replay, ReplayResult, TrackingBank};
pub use boc::{BocKind, BocTracking, BumpJump, BumpJumpConfig, BumpJumpEvent};
pub use channel::{Channel, ChannelInit, EpochOutput, MAX_EXTRA_TAPS, MAX_TAP_OFFSET_CHIPS};
pub use lock::{LockEvent, LockState, SessionChannel, TrackSession};
use self::cn0::BitSyncConfig;
use self::discrim::{DllDiscriminator, FllDiscriminator, PllDiscriminator};
use self::filter::LoopFilter;
use super::Cf64;
use std::f64::consts::TAU;
pub fn commensurate_samples_per_chip(fs_hz: f64, chip_rate_hz: f64) -> Option<f64> {
if !(fs_hz > 0.0 && chip_rate_hz > 0.0) {
return None;
}
let r = fs_hz / chip_rate_hz;
let nearest = (2.0 * r).round() / 2.0;
(nearest > 0.0 && (r - nearest).abs() <= 1e-6 * r).then_some(r)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FllAssist {
PullIn(FllGate),
Always,
}
impl Default for FllAssist {
fn default() -> Self {
FllAssist::PullIn(FllGate::default())
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FllGate {
pub off_pli: f64,
pub on_pli: f64,
pub dwell_s: f64,
}
impl Default for FllGate {
fn default() -> Self {
Self {
off_pli: 0.8,
on_pli: 0.6,
dwell_s: 0.1,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CarrierLoop {
Pll {
order: u8,
bn_hz: f64,
},
Fll {
order: u8,
bn_hz: f64,
},
FllAssistedPll {
pll_order: u8,
pll_bn_hz: f64,
fll_order: u8,
fll_bn_hz: f64,
},
}
impl CarrierLoop {
pub fn filter(&self) -> Result<LoopFilter, String> {
match *self {
CarrierLoop::Pll { order, bn_hz } => LoopFilter::pll(order, bn_hz),
CarrierLoop::Fll { order, bn_hz } => LoopFilter::fll(order, bn_hz),
CarrierLoop::FllAssistedPll {
pll_order,
pll_bn_hz,
fll_order,
fll_bn_hz,
} => LoopFilter::new(Some((pll_order, pll_bn_hz)), Some((fll_order, fll_bn_hz))),
}
}
pub fn has_pll(&self) -> bool {
!matches!(self, CarrierLoop::Fll { .. })
}
pub fn has_fll(&self) -> bool {
!matches!(self, CarrierLoop::Pll { .. })
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct LoopConfig {
pub label: String,
pub coherent_periods: usize,
pub spacing_chips: f64,
pub extra_taps_chips: Vec<f64>,
pub dll: DllDiscriminator,
pub dll_order: u8,
pub dll_bn_hz: f64,
pub carrier_aiding: bool,
pub carrier: CarrierLoop,
pub pll_discriminator: PllDiscriminator,
pub fll_discriminator: FllDiscriminator,
pub fll_assist: FllAssist,
pub pli_threshold: f64,
pub code_lock_cn0_dbhz: f64,
pub cn0_windows: usize,
pub cn0_window_periods: usize,
pub bit_sync: BitSyncConfig,
pub boc: Option<BocTracking>,
pub fll_pull_in_max_s: Option<f64>,
}
impl Default for LoopConfig {
fn default() -> Self {
Self {
label: "default".into(),
coherent_periods: 1,
spacing_chips: 0.5,
extra_taps_chips: Vec::new(),
dll: DllDiscriminator::EarlyMinusLatePower,
dll_order: 1,
dll_bn_hz: 2.0,
carrier_aiding: true,
carrier: CarrierLoop::FllAssistedPll {
pll_order: 2,
pll_bn_hz: 15.0,
fll_order: 1,
fll_bn_hz: 10.0,
},
pll_discriminator: PllDiscriminator::CostasAtan,
fll_discriminator: FllDiscriminator::Atan2,
fll_assist: FllAssist::default(),
pli_threshold: 0.8,
code_lock_cn0_dbhz: 26.0,
cn0_windows: 50,
cn0_window_periods: 20,
bit_sync: BitSyncConfig::default(),
boc: None,
fll_pull_in_max_s: None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Discriminators {
pub pll_rad: f64,
pub fll_hz: f64,
pub dll_chips: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct LoopCore {
cfg: LoopConfig,
carrier: LoopFilter,
code: LoopFilter,
chip_rate_hz: f64,
carrier_hz: f64,
init_doppler_hz: f64,
doppler_hz: f64,
code_rate_hz: f64,
prev_prompt: Option<Cf64>,
fll_enabled: bool,
}
impl LoopCore {
pub fn new(
cfg: &LoopConfig,
chip_rate_hz: f64,
carrier_hz: f64,
init_doppler_hz: f64,
) -> Result<Self, String> {
if !(cfg.spacing_chips > 0.0 && cfg.spacing_chips <= 2.0) {
return Err(format!(
"early-late spacing must lie in (0, 2] chips (got {})",
cfg.spacing_chips
));
}
if cfg.dll_order == 0 || cfg.dll_order > 2 {
return Err(format!("DLL order must be 1 or 2 (got {})", cfg.dll_order));
}
if !(chip_rate_hz > 0.0 && carrier_hz > 0.0) {
return Err("chip rate and carrier frequency must be positive".into());
}
if let Some(b) = &cfg.boc {
b.validate(cfg.spacing_chips)?;
}
let mut s = Self {
cfg: cfg.clone(),
carrier: cfg.carrier.filter()?,
code: LoopFilter::pll(cfg.dll_order, cfg.dll_bn_hz)?,
chip_rate_hz,
carrier_hz,
init_doppler_hz,
doppler_hz: init_doppler_hz,
code_rate_hz: 0.0,
prev_prompt: None,
fll_enabled: true,
};
s.code_rate_hz = s.base_code_rate();
Ok(s)
}
fn base_code_rate(&self) -> f64 {
let d = if self.cfg.carrier_aiding {
self.doppler_hz
} else {
self.init_doppler_hz
};
self.chip_rate_hz * (1.0 + d / self.carrier_hz)
}
pub fn update(&mut self, e: Cf64, p: Cf64, l: Cf64, t_s: f64) -> Discriminators {
let pll_rad = if self.cfg.carrier.has_pll() {
self.cfg.pll_discriminator.discriminate(p)
} else {
0.0
};
let fll_hz = match self.prev_prompt {
Some(prev) => self.cfg.fll_discriminator.discriminate(prev, p, t_s),
None => 0.0,
};
let fe = if self.cfg.carrier.has_fll() && self.fll_enabled {
TAU * fll_hz
} else {
0.0
};
let w = self.carrier.update(pll_rad, fe, t_s);
self.doppler_hz = self.init_doppler_hz + w / TAU;
let mut dll_chips = self.cfg.dll.discriminate(e, p, l, self.cfg.spacing_chips);
if let Some(b) = &self.cfg.boc {
match b.kind {
BocKind::SineBoc11 => {
dll_chips *= boc::boc11_dll_scale(self.cfg.dll, self.cfg.spacing_chips)
}
}
}
let corr = self.code.update(dll_chips, 0.0, t_s);
self.code_rate_hz = self.base_code_rate() + corr;
self.prev_prompt = Some(p);
Discriminators {
pll_rad,
fll_hz,
dll_chips,
}
}
pub fn set_fll_enabled(&mut self, on: bool) {
self.fll_enabled = on || !self.cfg.carrier.has_pll();
}
pub fn fll_enabled(&self) -> bool {
self.cfg.carrier.has_fll() && self.fll_enabled
}
pub fn doppler_hz(&self) -> f64 {
self.doppler_hz
}
pub fn code_rate_hz(&self) -> f64 {
self.code_rate_hz
}
pub fn config(&self) -> &LoopConfig {
&self.cfg
}
}