Expand description
gnssdo: a GNSS-PPS-disciplined clock & holdover core (a GPSDO building block).
HAL-agnostic, integer-only, no_std, zero-dependency pure logic. It disciplines a local
oscillator against a GNSS 1PPS reference: estimate the crystal’s frequency offset, hold over
through PPS loss, keep disciplined UTC, and phase-lock a generated output. It runs on any MCU
or host by just passing integer-nanosecond timestamps and a UTC epoch (on the RP2040’s PIO
capture, an STM32 timer input-capture, or a host’s /dev/pps — all the same).
DisciplinedClock: EMA-estimates the crystal frequency offset (ppb) from PPS intervals and keeps disciplined UTC, extrapolating through holdover while PPS is lost. Works with two timebases (capture/query); fed an epoch viaDisciplinedClock::update_epoch.PpsTracker: classifies a PPS edge stream (lock / missed / non-monotonic).PhaseLockLoop: a type-II output-phase servo (P/I/D + Smith predictor) for disciplining a generated 1PPS edge to the reference.Gnssdo: an all-in-one easy tier bundlingPpsTracker+DisciplinedClockunder one good default discipline policy.
§Scope
The core is agnostic to where absolute time comes from: it consumes an epoch
(capture_ns ↔ unix_ns) via DisciplinedClock::update_epoch / Gnssdo::on_utc. Decoding
the time source (NMEA framing/parsing) and pairing a PPS edge with its UTC second are a separate
responsibility and live in the sibling rp-pps crate, not here.
Structs§
- Alpha
Beta Boost - Fixed-gain α-β output-phase observer with a residual-triggered transient boost. See the
module docs. Integer-only /
no_std. Telemetrystate: 0 = steady, 1 = boosting. - Alpha
Beta Boost Config - Tuning for
AlphaBetaBoost. Gains are inverse (a gain of1/xis stored asx), keeping the math integer and the “larger = gentler” reading. Defaults lock and stay bounded on the host plant model; the harness sweeps them (do not over-fit the model — hardware PRBS decides). - Control
Debug - Per-controller debug/telemetry. Logged for analysis; the caller’s actuation uses only
ControlOutput::trim_mppbandControlOutput::pcorr_ns. - Control
Init - Fair start-of-segment seed for switching controllers mid-run (same-boot cycling).
- Control
Input - One output edge’s input to a
PhaseController. - Control
Output - Result of
PhaseController::step. The caller forms the output period fromtrim_mppb(added to the crystal feedforward) andpcorr_ns; the rest is telemetry. - Disciplined
Clock - The PPS-disciplined clock model.
- Disciplined
Clock Config - Tuning configuration for
DisciplinedClock. - Gnssdo
- Turn-key GNSS-disciplined clock — the easy tier: a
PpsTracker+ aDisciplinedClockwired with the recommended default policy (discipline the frequency only onLockededges and quarantine the estimate for a few edges after recovering from a gap). For a different policy, drivePpsTrackerandDisciplinedClockyourself (the fine tier) — this bundles one good default. - Gnssdo
Step - Result of
Gnssdo::on_pps. - Integral
Rework - Flag-free continuous-integral PID + Smith. See the module docs and
IntegralReworkConfig. - Integral
Rework Config - Tuning for
IntegralRework. Mirrors the production loop’s P/D/Smith path but reworks the integral to be continuous (no I-enable gate): always integrate, but clamp the integral input to±e_i_nsso a big transient cannot wind it up, and bleed back any actuator-trim saturation withkbc_inv(back-calculation). Same P/D as the Smith production loop. - Open
Loop Ff - Open-loop feedforward: no phase servo. Models the loopback-less build (
docs/report/REPORT.md’s “ループバック無しの構成”), where without the hardware loopback there is no honest output phase to close, so the output rides the crystal feedforward and any fixed offset is burned, not measured. - Phase
Lock Loop - A type-II output-phase PLL. Feed it a measured phase error each output edge; it integrates a frequency trim and emits an immediate phase correction. See the module docs.
- Phase
Lock Loop Config - Tuning for
PhaseLockLoop.Default(=PhaseLockLoopConfig::DEFAULT) is the σ≈35 ns production tuning. The right values depend on the loop delay, the capture resolution, and the reference PPS quality, so they are configurable. - Phase
Lock Loop Update - Result of
PhaseLockLoop::update. The caller forms the output period fromfreq_trim_mppb(added to the crystal estimate) andphase_corr_ns; the rest is for logging. - PpsTracker
- State machine over the PPS edge stream.
- PpsTracker
Config - Configuration for
PpsTracker. Made configurable to support non-1 Hz PPS (e.g. 10 Hz) and receiver/capture-specific tolerances.Defaultis 1 Hz / ±50 ms. - QErr
Corrector - 連続 PPS エッジ間隔 (ns) を qErr で補正する小さな状態 (直前エッジの qErr を保持する)。
Enums§
- Controller
- A runtime-selectable phase controller for same-boot cycling:
no_std/no-alloc enum dispatch over the library’s strategies (vsBox<dyn>, which needs an allocator the firmware lacks). - Freq
Update - Result of
update_freq. Used by the caller (e.g. pps_task) for logging/state management. - Loop
Mode - Which control terms are active.
PidSmithis production; the others exist to compare terms (the firmware’s experiment harness switches between them). - PpsEvent
- Result of recording one edge.
Constants§
- NOMINAL_
US - Nominal PPS interval (1 second = 1_000_000 us). Default for a 1 Hz PPS.
- TOLERANCE_
US - Default lock tolerance (±50 ms). Beyond this is treated as a missed pulse or a glitch.
Traits§
- Phase
Controller - A pluggable output-phase discipline strategy. Implementors are integer-only /
no_stdand behave as a pure phase servo on top of the shared crystal feedforward (see the module docs).
Functions§
- correct_
interval_ ns - 連続 PPS エッジ間隔 (ns) を、前回と今回の qErr (ps) で補正する純粋関数。状態を持たない building block。
両エッジが「補正後 = 測定 − qErr」を受けるので、間隔からは qErr の差
now − prevを引く。 差は ps で取ってから ns へ丸める (各 qErr を先に丸めるより精度が良い)。i32 差を i64 で取り overflow を避ける。 - correct_
phase_ ns - PPS の生の位相/オフセット (ns) を qErr (ps) で補正する。補正後 = 生 − qErr。
- snap_
to_ second_ ns - Remove the integer-second offset and keep only the sub-second residual. The corrected prediction residual (err) can be a huge ~Ns value on recovery after PPS was lost for several seconds, because the PPS↔RMC pairing can be off by an integer second. Snapping to the nearest integer second recovers the true holdover residual (sub-second) buried inside it (e.g. 25_000_000_360 → 360ns = the error of a 25s holdover).