Skip to main content

Crate gnssdo

Crate gnssdo 

Source
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 via DisciplinedClock::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 bundling PpsTracker + DisciplinedClock under 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§

AlphaBetaBoost
Fixed-gain α-β output-phase observer with a residual-triggered transient boost. See the module docs. Integer-only / no_std. Telemetry state: 0 = steady, 1 = boosting.
AlphaBetaBoostConfig
Tuning for AlphaBetaBoost. Gains are inverse (a gain of 1/x is stored as x), 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).
ControlDebug
Per-controller debug/telemetry. Logged for analysis; the caller’s actuation uses only ControlOutput::trim_mppb and ControlOutput::pcorr_ns.
ControlInit
Fair start-of-segment seed for switching controllers mid-run (same-boot cycling).
ControlInput
One output edge’s input to a PhaseController.
ControlOutput
Result of PhaseController::step. The caller forms the output period from trim_mppb (added to the crystal feedforward) and pcorr_ns; the rest is telemetry.
DisciplinedClock
The PPS-disciplined clock model.
DisciplinedClockConfig
Tuning configuration for DisciplinedClock.
Gnssdo
Turn-key GNSS-disciplined clock — the easy tier: a PpsTracker + a DisciplinedClock wired with the recommended default policy (discipline the frequency only on Locked edges and quarantine the estimate for a few edges after recovering from a gap). For a different policy, drive PpsTracker and DisciplinedClock yourself (the fine tier) — this bundles one good default.
GnssdoStep
Result of Gnssdo::on_pps.
IntegralRework
Flag-free continuous-integral PID + Smith. See the module docs and IntegralReworkConfig.
IntegralReworkConfig
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_ns so a big transient cannot wind it up, and bleed back any actuator-trim saturation with kbc_inv (back-calculation). Same P/D as the Smith production loop.
OpenLoopFf
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.
PhaseLockLoop
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.
PhaseLockLoopConfig
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.
PhaseLockLoopUpdate
Result of PhaseLockLoop::update. The caller forms the output period from freq_trim_mppb (added to the crystal estimate) and phase_corr_ns; the rest is for logging.
PpsTracker
State machine over the PPS edge stream.
PpsTrackerConfig
Configuration for PpsTracker. Made configurable to support non-1 Hz PPS (e.g. 10 Hz) and receiver/capture-specific tolerances. Default is 1 Hz / ±50 ms.
QErrCorrector
連続 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 (vs Box<dyn>, which needs an allocator the firmware lacks).
FreqUpdate
Result of update_freq. Used by the caller (e.g. pps_task) for logging/state management.
LoopMode
Which control terms are active. PidSmith is 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§

PhaseController
A pluggable output-phase discipline strategy. Implementors are integer-only / no_std and 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).