add RPLL port
This commit is contained in:
parent
1c54151f31
commit
d95f780674
|
@ -0,0 +1,103 @@
|
|||
#include <cstdint>
|
||||
#include <tuple>
|
||||
#include <optional>
|
||||
#include <cassert>
|
||||
|
||||
// C++ port of RPLL from https://github.com/quartiq/idsp
|
||||
// MIT or Apache-2.0 license
|
||||
|
||||
/// Reciprocal PLL.
|
||||
///
|
||||
/// Consumes noisy, quantized timestamps of a reference signal and reconstructs
|
||||
/// the phase and frequency of the update() invocations with respect to (and in units of
|
||||
/// 1 << 32 of) that reference.
|
||||
/// In other words, `update()` rate ralative to reference frequency,
|
||||
/// `u32::MAX` corresponding to both being equal.
|
||||
class RPLL {
|
||||
private:
|
||||
uint32_t dt2; // 1 << dt2 is the counter rate to update() rate ratio
|
||||
int32_t x; // previous timestamp
|
||||
uint32_t ff; // current frequency estimate from frequency loop
|
||||
uint32_t f; // current frequency estimate from both frequency and phase loop
|
||||
int32_t y; // current phase estimate
|
||||
public:
|
||||
RPLL(uint32_t dt2);
|
||||
std::tuple<int32_t, uint32_t> update(std::optional<int32_t>, uint32_t, uint32_t);
|
||||
int32_t phase();
|
||||
uint32_t frequency();
|
||||
};
|
||||
|
||||
/// Create a new RPLL instance.
|
||||
///
|
||||
/// Args:
|
||||
/// * dt2: inverse update() rate. 1 << dt2 is the counter rate to update() rate ratio.
|
||||
///
|
||||
/// Returns:
|
||||
/// Initialized RPLL instance.
|
||||
RPLL::RPLL(uint32_t dt2):
|
||||
dt2(dt2),
|
||||
x(0),
|
||||
ff(0),
|
||||
f(0),
|
||||
y(0)
|
||||
{}
|
||||
|
||||
/// Advance the RPLL and optionally supply a new timestamp.
|
||||
///
|
||||
/// Args:
|
||||
/// * input: Optional new timestamp (wrapping around at the i32 boundary).
|
||||
/// There can be at most one timestamp per `update()` cycle (1 << dt2 counter cycles).
|
||||
/// * shift_frequency: Frequency lock settling time. 1 << shift_frequency is
|
||||
/// frequency lock settling time in counter periods. The settling time must be larger
|
||||
/// than the signal period to lock to.
|
||||
/// * shift_phase: Phase lock settling time. Usually one less than
|
||||
/// `shift_frequency` (see there).
|
||||
///
|
||||
/// Returns:
|
||||
/// A tuple containing the current phase (wrapping at the i32 boundary, pi) and
|
||||
/// frequency.
|
||||
std::tuple<int32_t, uint32_t> RPLL::update(std::optional<int32_t> input,
|
||||
uint32_t shift_frequency,
|
||||
uint32_t shift_phase)
|
||||
{
|
||||
assert(shift_frequency >= dt2);
|
||||
assert(shift_phase >= dt2);
|
||||
// Advance phase
|
||||
y += f;
|
||||
if(input.has_value()) {
|
||||
// Reference period in counter cycles
|
||||
int32_t dx = *input - x;
|
||||
// Store timestamp for next time
|
||||
x = *input;
|
||||
// Phase using the current frequency estimate
|
||||
uint64_t p_sig_64 = (uint64_t)ff * (uint64_t)dx;
|
||||
// Add half-up rounding bias and apply gain/attenuation
|
||||
uint32_t p_sig =
|
||||
(p_sig_64 + (1 << (shift_frequency - 1))) >> shift_frequency;
|
||||
// Reference phase (1 << dt2 full turns) with gain/attenuation applied
|
||||
uint32_t p_ref = 1 << (32 + dt2 - shift_frequency);
|
||||
// Update frequency lock
|
||||
ff += p_ref - p_sig;
|
||||
// Time in counter cycles between timestamp and "now"
|
||||
uint32_t dt = -x & ((1 << dt2) - 1);
|
||||
// Reference phase estimate "now"
|
||||
int32_t y_ref = (f >> dt2)*dt;
|
||||
// Phase error with gain
|
||||
int32_t dy = (y_ref - y) >> (shift_phase - dt2);
|
||||
// Current frequency estimate from frequency lock and phase error
|
||||
f = ff + dy;
|
||||
}
|
||||
return { y, f };
|
||||
}
|
||||
|
||||
/// Return the current phase estimate
|
||||
int32_t RPLL::phase()
|
||||
{
|
||||
return y;
|
||||
}
|
||||
|
||||
/// Return the current frequency estimate
|
||||
uint32_t RPLL::frequency()
|
||||
{
|
||||
return f;
|
||||
}
|
Loading…
Reference in New Issue