Add $PMTXTS PPS timestamping (opt-in host timing telemetry)#5
Draft
peterlewis wants to merge 3 commits into
Draft
Add $PMTXTS PPS timestamping (opt-in host timing telemetry)#5peterlewis wants to merge 3 commits into
peterlewis wants to merge 3 commits into
Conversation
4f901ab to
02671b2
Compare
This was referenced Jul 5, 2026
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 6, 2026
The exact residue between the pure union of the five draft PRs (mitxela#5 $PMTXTS, mitxela#6 astro pack, mitxela#7 hardening, mitxela#8 sidereal/solar, mitxela#9 tempcomp + significance-fade + tc_seed — mitxela#5 and mitxela#6 arrive as ancestors of mitxela#9 and mitxela#8) and the rollup tree that has been emulator-verified and hardware-run: - astro.c/astro.h/test_astro.c: rollup astro refinements not yet folded back into the refreshed PR mitxela#6/mitxela#8 heads (candidates for a future PR update) - version.c: rollup version string - qspi/output/*.bin: rollup's built firmware images - .settings IDE noise After this commit the megapack tree is BYTE-IDENTICAL to the previously validated rollup (feaf970) while the merge ancestry proves containment of all five PR heads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 7, 2026
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 7, 2026
…xela#9) The source said 0.0.5 (misidentifying a rollup build as stock) while the committed image embedded 0.0.6 from an earlier build lineage. Source and freshly built, CRC-gated image now both say 0.0.6. No cuckoo — that work is parked on its own branch and versions later. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With `pps = on` in config.txt, emit one proprietary NMEA sentence per PPS edge over the existing CDC stream, carrying the sub-second phase captured at the edge plus calibration / holdover / die-temperature telemetry. Time-critical fields are snapshotted in the PPS ISR; formatting and CDC submission happen in the main loop, serialised against the NMEA passthrough. With no enumerated host the record is dropped before any formatting. mk4-time/Core/Src/main.c: capturePPS(), emitPPSTimestamp(), measure_temp(), a `pps` config key and one main-loop hook. qspi/config.txt documents the key. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ver USB Experimental attack on the ~6ms host-driven USB read jitter that swamps the clock's true ~180us precision — without a hardware PPS wire. The OTG_FS core rules out mitxela's last-microsecond FIFO injection, so instead we let the host anchor each PPS to a USB Start-Of-Frame, whose own arrival time the host can read in hardware (macOS IOKit GetBusFrameNumberWithTime). - Enable DWT->CYCCNT (free-running 12.5ns core-cycle counter) as the timebase both the PPS edge and each SOF are latched against. Unaffected by tempcomp SysTick steering (counts raw core clock), so it's a stable monotonic ruler. - Enable the USB SOF interrupt; PCD_SOFCallback latches (DWT, 11-bit frame from DSTS[13:8]) every 1ms, as its first act for minimal latency. - capturePPS() latches DWT at the edge; emitPPSTimestamp() appends the tail ,dwt_pps,sof_frame,dwt_sof to $PMTXTS (read under __disable_irq, no torn read). - Bump NMEA_BUF_SIZE 90->128 for the longer sentence. Host places the edge at hostTime(sof_frame) + (dwt_pps-dwt_sof)/f_dwt, immune to delivery lateness; dwt_pps deltas self-calibrate f_dwt (no core-clock assumption). Backward-compatible: lenient $PMTXTS parsers read the first 9 fields. Bench-only; +1kHz SOF ISR (~0.05% CPU) is the cost to watch. Builds clean (0 errors). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adversarial audit of the SOF-correlation change surfaced two real issues: - Stale first anchor: pps_sof_dwt/frame init to 0, so the first PPS emitted after enumeration (or with pps just toggled on, or on the emulator which has no USB SOF) carried a (0,0) anchor → a ~53s/−729ms host error on that record. Now a pps_sof_valid flag gates the tail: emit the plain 9-field sentence until a real SOF has latched. Backward-compatible; also makes the emulator correct. - SOF work ran unconditionally. Gate the latch on pps_ts_enabled (the SOF IRQ still fires — cheap, below the priority-0 display DMA — but does nothing when the feature is off); clear valid when off so a re-enable can't reuse a stale anchor. config.txt documents that pps=on enables the SOF tail. Builds clean (0 errors). Refuted findings (torn read, 16-bit atomicity, buffer truncation, display preemption, scope) left as-is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
Comprehensive documentation for every feature in PRs mitxela#5-mitxela#10 and the three follow-ons (ADEV, star transits, the menu), written to slot into the project's documentation page: all new display modes with exact row renders, every config.txt key (menu-settable ones marked, precedence explained), every serial command and emitted sentence field-by-field ($PMTXTS/$PMTXTC/$PMADEV/ $PMHDEV/$PMSTAR), a full user guide for the on-device menu, the SETTINGS.BIN and STARS.BIN file contracts, and the 0.0.5/0.0.2 version train with the per-build-tag rationale. Describes the ROLLUP build (the tested union this tree IS), with a real unit's learned tempcomp model as the worked example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
…xela#9) The source said 0.0.5 (misidentifying a rollup build as stock) while the committed image embedded 0.0.6 from an earlier build lineage. Source and freshly built, CRC-gated image now both say 0.0.6. No cuckoo — that work is parked on its own branch and versions later. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
Comprehensive documentation for every feature in PRs mitxela#5-mitxela#10 and the three follow-ons (ADEV, star transits, the menu), written to slot into the project's documentation page: all new display modes with exact row renders, every config.txt key (menu-settable ones marked, precedence explained), every serial command and emitted sentence field-by-field ($PMTXTS/$PMTXTC/$PMADEV/ $PMHDEV/$PMSTAR), a full user guide for the on-device menu, the SETTINGS.BIN and STARS.BIN file contracts, and the 0.0.5/0.0.2 version train with the per-build-tag rationale. Describes the ROLLUP build (the tested union this tree IS), with a real unit's learned tempcomp model as the worked example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
199a215 to
1fdbf33
Compare
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
…xela#9) The source said 0.0.5 (misidentifying a rollup build as stock) while the committed image embedded 0.0.6 from an earlier build lineage. Source and freshly built, CRC-gated image now both say 0.0.6. No cuckoo — that work is parked on its own branch and versions later. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
Comprehensive documentation for every feature in PRs mitxela#5-mitxela#10 and the three follow-ons (ADEV, star transits, the menu), written to slot into the project's documentation page: all new display modes with exact row renders, every config.txt key (menu-settable ones marked, precedence explained), every serial command and emitted sentence field-by-field ($PMTXTS/$PMTXTC/$PMADEV/ $PMHDEV/$PMSTAR), a full user guide for the on-device menu, the SETTINGS.BIN and STARS.BIN file contracts, and the 0.0.5/0.0.2 version train with the per-build-tag rationale. Describes the ROLLUP build (the tested union this tree IS), with a real unit's learned tempcomp model as the worked example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an opt-in (
pps = on)$PMTXTSsentence emitted on each GPS PPS edge, carrying the sub-millisecond phase measurement plus a USB SOF-correlation tail for microsecond-class host timestamping.Stack: base of the series (targets upstream
master). The nine PRs form a linear stack; if you'd rather take them against an integration branch, create one and I'll retarget them all.Try it now: fwt.bin · fwd.bin — the union of this PR stack, byte-identical to the merged result.
Docs: every feature in the stack is documented in
docs/firmware-additions.md, shipped in the final (menu) PR.Note
You can try this PR without hardware: the PCC web app runs the real clock4 firmware compiled to WebAssembly, and its clock is built from a rollup of the whole PR series together, so this PR's behaviour is live there alongside the others. The app's DEVICE → UPDATES panel shows the exact commit it was built from. Fair warning: the app around the firmware is beta and still has bugs — the firmware behaviour is the real thing, the app chrome less so.
Draft. One feature for the Mk IV: opt-in per-PPS host timestamping, found while building a host companion. Source-only —
mk4-time/Core/Src/main.cand one documentedconfig.txtkey. Built and flashed on an STM32L476 time board and left running; it stays a draft because theqspi/output/*.binrelease images still need rebuilding from the diff (see To do).The two unrelated firmware fixes this branch used to carry — the charger-power hard-fault and the date-board text OOB — have moved to the hardening PR (#7), so this one is just the feature.
$PMTXTSper-PPS host timestamping (opt-in)The clock disciplines to the PPS edge to sub-µs, but a host over CDC only ever sees NMEA, which arrives a few hundred ms after the second and is smeared by USB framing — so there is no host-visible timing-stability metric. (The stale
cdc-ppsbranch approached this by toggling the CDC DCD line, which is capped at the ~1 ms USB framing floor.)With
pps = oninconfig.txt, one proprietary sentence per PPS edge over the existing CDC stream:seqepochcurrentTimeat the edge (Unix s, UTC)subms/systick/loadcalerrcalibrateRTC()already computes → ppmsincecaltempflagsdata_valid/had_pps/rtc_goodTime-critical fields are snapshotted in the PPS ISR (a handful of register reads); the sentence is
snprintf-formatted and submitted to CDC from the main loop, never the ISR, serialised against the existing NMEA passthrough. With no enumerated host the record is dropped before any formatting, so the feature costs nothing when nobody is listening. The host's arrival time is USB-jittery, but the measurement is captured in firmware at the edge and carried in the payload, so transport jitter does not corrupt it. Absolute offset to UTC is not recoverable over USB — a host derives jitter, stability and drift, not absolute offset.mk4-time/Core/Src/main.c:capturePPS(),emitPPSTimestamp(),measure_temp(), theppskey, one main-loop hook.USB SOF correlation — a good host timestamp despite USB jitter
$PMTXTScarries the phase measured at the edge, so USB jitter can't corrupt the value — but a host that wants to place the edge on its own clock is still limited by when the packet arrives, and USB is host-polled (arrival jitter ~6–110 ms here, depending on the host's serial buffering). This optional extension anchors each edge to a USB Start-Of-Frame so the host recovers the timing regardless of when it reads the packet:DWT->CYCCNT(free-running, 12.5 ns) as a shared timebase;(frame number, DWT)in the SOF interrupt every 1 ms;…,<dwt_pps>,<sof_frame>,<dwt_sof>.A host that can read each USB frame's own hardware arrival time (e.g. macOS
IOUSBDeviceInterface::GetBusFrameNumberWithTime) then places the edge athostTime(sof_frame) + (dwt_pps − dwt_sof)/f_dwt, immune to delivery lateness.f_dwtself-calibrates from consecutivedwt_ppsdeltas, so no core-clock constant is assumed.Measured (STM32L476 time board): ~6–110 ms raw arrival jitter → ~100–175 µs SOF-corrected — down at the clock's own PPS floor, i.e. the USB transport is no longer the limiting factor. This is where the stale
cdc-ppsDCD approach wanted to go but couldn't: a modem-status bit has no payload to carry the frame, so it can't beat the ~1 ms framing floor.Design notes
ppsjust toggled on, or a build with no USB SOF (the WASM emulator) sends the plain nine-field sentence, never a stale(0,0)anchor.pps = on; the 1 kHz SOF interrupt is cheap and sits below the priority-0 display DMA, so it can't perturb the display.validresets when off, so a re-enable can't reuse a stale anchor.*CCchecksum still covers the whole body.NMEA_BUF_SIZE90→128 for the longer sentence.Considered and ruled out: last-microsecond injection into the outgoing packet (the L476's USB-OTG core has no addressable packet memory to rewrite), and a DCD/serial-state pulse (one bit, no payload → can't carry the frame). Edge cases checked: torn reads (the anchor is read under
__disable_irqin thread context), 16-bit atomicity (single aligned access on M4), DWT wrap (the PPS→SOF gap is always ≪ the 53.7 s wrap).usbd_conf.c:PCD_SOFCallback+Sof_enable;main.c: the DWT enable, thecapturePPS()latch, the emit tail.Temperature
The die-temperature field made it cheap to characterise the oscillators against temperature on the unit. The 10 MHz HSE — which holds time through a running GPS-loss holdover — measured flat, ≈ 0.011 ppm/°C (R² ≈ 0.37) over a ~6 °C sweep, i.e. running-holdover drift from temperature is sub-millisecond over any realistic outage, so no compensation is included. The 32 kHz LSE has a real tempco but it only bites across power-loss recovery, where the MCU is off and cannot compensate. The die-temp telemetry is kept because it is useful and proven; the steering code is not. (The sweep is narrow — see To do.)
Data (STM32L476 time board, GPS-locked)
$PMTXTSstreams at 1 Hz interleaved with NMEA; checksums valid;seqcontiguoussystick85 ± 2 counts)flags = 7)To do
Before this leaves draft:
qspi/output/*.binrelease images from the source diff. The diff is source-only; the committed binaries are unchanged and would need regenerating to match.calerrandsincecalgive a live time-uncertainty, so the display can show only the digits it can still stand behind, degrading like an analogue needle. — since realized downstream assignificance_fadein PR Self-learning holdover temperature compensation #9 (gradual sub-second dimming from the live holdover uncertainty).