pounder_test/dsp/src/pll.rs

114 lines
4.3 KiB
Rust
Raw Normal View History

2020-12-04 02:09:54 +08:00
use serde::{Deserialize, Serialize};
/// Type-II, sampled phase, discrete time PLL
///
/// This PLL tracks the frequency and phase of an input signal with respect to the sampling clock.
/// The transfer function is I^2,I from input phase to output phase and P,I from input phase to
/// output frequency.
///
/// The PLL locks to any frequency (i.e. it locks to the alias in the first Nyquist zone) and is
/// stable for any gain (1 <= shift <= 30). It has a single parameter that determines the loop
/// bandwidth in octave steps. The gain can be changed freely between updates.
///
2020-12-14 05:24:40 +08:00
/// The frequency and phase settling time constants for a frequency/phase jump are `1 << shift`
2020-12-05 20:05:59 +08:00
/// update cycles. The loop bandwidth is about `1/(2*pi*(1 << shift))` in units of the sample rate.
2020-12-14 05:24:40 +08:00
/// While the phase is being settled within one turn, there is a typically very small frequency
/// overshoot.
2020-12-04 02:09:54 +08:00
///
/// All math is naturally wrapping 32 bit integer. Phase and frequency are understood modulo that
/// overflow in the first Nyquist zone. Expressing the IIR equations in other ways (e.g. single
2020-12-14 05:24:40 +08:00
/// (T)-DF-{I,II} biquad/IIR) would break on overflow (i.e. every cycle).
2020-12-04 02:09:54 +08:00
///
/// There are no floating point rounding errors here. But there is integer quantization/truncation
/// error of the `shift` lowest bits leading to a phase offset for very low gains. Truncation
2020-12-05 20:05:59 +08:00
/// bias is applied. Rounding is "half up". The phase truncation error can be removed very
/// efficiently by dithering.
2020-12-04 02:09:54 +08:00
///
/// This PLL does not unwrap phase slips during lock acquisition. This can and should be
2020-12-14 05:24:40 +08:00
/// implemented elsewhere by unwrapping and scaling the input phase and un-scaling
/// and wrapping output phase and frequency. This affects dynamic range, gain, and noise accordingly.
2020-12-04 02:09:54 +08:00
///
/// The extension to I^3,I^2,I behavior to track chirps phase-accurately or to i64 data to
/// increase resolution for extremely narrowband applications is obvious.
#[derive(Copy, Clone, Default, Deserialize, Serialize)]
2020-12-05 15:12:07 +08:00
pub struct PLL {
2020-12-04 02:09:54 +08:00
// last input phase
x: i32,
// filtered frequency
f: i32,
// filtered output phase
y: i32,
}
2020-12-05 15:12:07 +08:00
impl PLL {
2020-12-14 05:24:40 +08:00
/// Update the PLL with a new phase sample. This needs to be called (sampled) periodically.
/// The signal's phase/frequency is reconstructed relative to the sampling period.
2020-12-04 02:09:54 +08:00
///
/// Args:
/// * `input`: New input phase sample.
2020-12-14 05:24:40 +08:00
/// * `shift_frequency`: Frequency error scaling. The frequency gain per update is
/// `1/(1 << shift_frequency)`.
/// * `shift_phase`: Phase error scaling. The phase gain is `1/(1 << shift_phase)`
/// per update. A good value is typically `shift_frequency - 1`.
2020-12-04 02:09:54 +08:00
///
/// Returns:
/// A tuple of instantaneous phase and frequency (the current phase increment).
2020-12-14 05:24:40 +08:00
pub fn update(
&mut self,
x: i32,
shift_frequency: u8,
shift_phase: u8,
) -> (i32, i32) {
debug_assert!((1..=30).contains(&shift_frequency));
debug_assert!((1..=30).contains(&shift_phase));
2020-12-04 02:09:54 +08:00
let e = x.wrapping_sub(self.f);
self.f = self.f.wrapping_add(
2020-12-14 05:24:40 +08:00
(1i32 << (shift_frequency - 1))
.wrapping_add(e)
.wrapping_sub(self.x)
>> shift_frequency,
2020-12-04 02:09:54 +08:00
);
self.x = x;
let f = self.f.wrapping_add(
2020-12-14 05:24:40 +08:00
(1i32 << (shift_phase - 1))
.wrapping_add(e)
.wrapping_sub(self.y)
>> shift_phase,
2020-12-04 02:09:54 +08:00
);
self.y = self.y.wrapping_add(f);
(self.y, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mini() {
2020-12-05 15:12:07 +08:00
let mut p = PLL::default();
2020-12-14 05:24:40 +08:00
let (y, f) = p.update(0x10000, 8, 4);
assert_eq!(y, 0x1100);
2020-12-04 02:09:54 +08:00
assert_eq!(f, y);
}
2020-12-05 06:05:04 +08:00
#[test]
fn converge() {
2020-12-05 15:12:07 +08:00
let mut p = PLL::default();
2020-12-05 06:05:04 +08:00
let f0 = 0x71f63049_i32;
2020-12-14 05:24:40 +08:00
let shift = (10, 9);
let n = 31 << shift.0 + 2;
2020-12-05 06:05:04 +08:00
let mut x = 0i32;
for i in 0..n {
x = x.wrapping_add(f0);
2020-12-14 05:24:40 +08:00
let (y, f) = p.update(x, shift.0, shift.1);
2020-12-05 06:05:04 +08:00
if i > n / 4 {
assert_eq!(f.wrapping_sub(f0).abs() <= 1, true);
}
if i > n / 2 {
2020-12-14 05:24:40 +08:00
// The remaining error would be removed by dithering.
2020-12-05 06:05:04 +08:00
assert_eq!(y.wrapping_sub(x).abs() < 1 << 18, true);
}
}
}
2020-12-04 02:09:54 +08:00
}