On-device two-button menu with persistent settings (final: stack = validated rollup)#13
Draft
peterlewis wants to merge 85 commits into
Draft
On-device two-button menu with persistent settings (final: stack = validated rollup)#13peterlewis wants to merge 85 commits into
peterlewis wants to merge 85 commits into
Conversation
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>
Five GPS-derived astronomy read-outs, each opt-in via a MODE_* config key and shown on the 10-char date row while the live clock keeps running on the time row (SATVIEW-style, COUNT_NORMAL): MODE_SUN sunrise / sunset / solar noon (local), auto-paged MODE_SUN_AZEL sun azimuth & elevation, now MODE_MOON moon phase index (0-7) + illuminated % MODE_GRID Maidenhead grid locator MODE_LATLON latitude / longitude, auto-paged The astronomy maths is a self-contained Core/Src/astro.c (+ astro.h): low- precision NOAA/Meeus sun alt-az and event times, moon phase, equation of time, and Maidenhead. It has no firmware dependencies, so it unit-tests natively (test/test_astro.c, 45 reference vectors) and was validated to ~1e-5 before touching the firmware. The L4 has only a single-precision FPU, so the double maths would be slow soft- float. It is therefore computed in the main loop (astro_update(), gated on the active mode exactly as measure_vbat() is for MODE_VBAT) and swapped into a cache under a brief __disable_irq() mask; the sendDate() cases that run inside the SysTick ISR only format the cached scalars -- no trig in the interrupt. Modes are disabled by default; with no GPS fix they use fake_latitude/longitude if set, else show "----". config.txt documents the keys and the moon-phase index legend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"LAT 51.48" vs "LAT-51.48" let the minus swallow the separator. Add a sign
slot after the label space — "LAT 51.48" / "LAT -51.48" — so the digits
start in the same column either way, matching the RISE/SET layout. A 3-digit
longitude can't fit both the separator and the slot in 10 characters, so the
separator alone is dropped there ("LON-179.99").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The paged modes (SUN, LATLON) previously flipped sub-screen every 2 s hard-coded. Make the dwell a config key (page_ms, ms; unset -> 5500, floored at 250 so a tiny value can't flood the date-board UART), driven off uwTick, and repaint the moment a page flips rather than waiting for the 1 Hz date-row refresh (guarded by decisec!=9 to avoid racing the SysTick sendDate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- loadRules: reject untrusted rowLength/numEntries from TZRULES.BIN (RAM overflow) - MODE_TEXT: clamp snprintf untruncated-return index (uart2_tx_buffer OOB) - mk4-date latchDisplay: bound dp_pos per orientation (buffer_a/b OOB) - readConfigFile: don't treat a zero FAT timestamp as a cache hit (first-boot hang) - config-over-USB: defer postConfigCleanup out of the USB ISR (sendDate/nextMode races) - firmwareCheckOnEject: fatfs_busy guard for FATFS reentrancy on USB eject Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two further pre-existing bugs from the same read-through. - mk4-time NMEA-over-CDC: CDC_Copy_Transmit() / CDC_Transmit_FS() dereference hUsbDeviceFS.pClassDataCDC unconditionally, but it is NULL until a USB host enumerates. The default nmea_cdc_level = NMEA_ALL forwards every GPS sentence to CDC, so on charger-only power the first forwarded sentence faults in ISR context. NULL guard on both, plus a length clamp before the memcpy. - mk4-date CMD_LOAD_TEXT: 'if (text_idx > MAX_TEXT_LEN) return;' passes at text_idx == MAX_TEXT_LEN and writes text[32], one past the 32-byte array. '>' -> '>='. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MODE_LST and MODE_SOLAR turn the big HH:MM:SS digits into a live ticking Local Sidereal Time or apparent-solar clock. Both reuse the MODE_COUNTDOWN takeover pattern: the heavy GMST/EoT double maths runs once per second in the main loop, staging pre-rendered digits that the SysTick handlers latch at the true GPS PPS boundary -- so accuracy and the P3..P0 sub-second digit-hiding are inherited from the disciplined civil second, and civil timekeeping (currentTime, PPS, the date row) is untouched. The display is quantised to civil second boundaries and reseeded every second, so sidereal's 1.00273791x rate makes the seconds double-step once every ~6 min -- the honest signature of a GPS-disciplined sidereal clock. A dedicated colon animation (alt_colon_mode, default alt_sawtooth, kept distinct from the civil colon) marks the mode so it can never be mistaken for civil time; apparent solar especially can sit within minutes of it. With no position the row shows dashes -- never GMST-as-LST. astro.c regains local_sidereal_time()/local_solar_time() with the GMST series factored into one helper (gmst_hours, +T^2 term); native suite 53/53 incl. the IAU J2000 anchor and Meeus example 12.b. All default-off: stock behaviour unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sun_subsolar(t, &lat, &lon) returns the point where the sun is at the zenith: latitude = solar declination, longitude where the local hour angle is zero (lon = alpha - gmst*15). It reuses the same gmst_hours series as sun_az_el and local_sidereal_time, so the three can never drift apart. Library + tests only — no display mode consumes it yet (a companion app plots it). Tests: the sun must sit at elevation 90 deg over its own subsolar point at every existing test instant (self-consistent but not circular — az/el and subsolar take different paths to the sky), plus declination anchors at the solstices and the absolute GMST anchors (IAU J2000, Meeus ex. 12.b) recorded in comments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Builds on the $PMTXTS telemetry: while GPS-locked the clock learns each oscillator's frequency error vs die temperature (binned, per-oscillator), then during a GPS-loss holdover it steers the SysTick timebase from the HSE model to cancel temperature-driven drift, and optionally trims RTC->CALR from the LSE model so the battery RTC hands over better time across a power loss. - tc_learn / tc_apply / tc_rtc config keys (all default off = stock behaviour) - steering is a Bresenham SysTick->LOAD stretch inside the tick ISR (integer only; all model maths is main-loop); origin-free HSE model so only the temperature DIFFERENCE since GPS loss is applied, and the per-edge PPS phase snap makes re-lock instantly neutral - 'tc_dump = on' over serial prints the learned coefficients as paste-ready config lines (+ a $PMTXTC sentence); pasting them back freezes the model - MODE_TEMPCOMP diagnostic display: die temp / model offset / sample count - ticks-per-ppm derived from the runtime SysTick period (no core-clock assumption) Hardware-validated on an Mk IV: the LSE model learned 18.68 ppm vs 18.9 ppm measured directly (~1%). Survived an adversarial review pass (ISR-safety, integer math, sign conventions, re-lock neutrality, off-means-identical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two coupled additions on top of the self-learning tempcomp, both opt-in and byte-identical to stock behaviour when off. significance_fade: during GPS-loss holdover, retire each sub-second digit by its remaining SIGNIFICANCE rather than by the fixed dash timeouts. A live 3-sigma time-interval-error bound U(tau) = k*sigma*tau (sigma = RSS of the calibration, the MEASURED temp-model residual, and aging terms) drives setPrecision's P3/P2/P1/P0 ladder: a digit dashes the moment U passes its place value — the same dash glyph as the Tolerance_time_* ladder, but driven by measured significance instead of fixed timeouts. digit_bright[] carries the per-digit intensity for a future smooth-dimming display change (and for emulators). tc_fit now also reports the weighted-RMS fit residual, mean-centred for HSE whose model origin is arbitrary. tc_seed (warm start): reload a previously-learned model -- the last tc_dump, persisted in config.txt by the host -- as an EVOLVING prior rather than a freeze. The clock is temperature-compensated from the first second and keeps refining: tc_fit_one returns the achieved model order and tc_fit preserves the seed until real data supports a fit at least as rich, then hands over. New keys tc_seed / tc_seed_lo / tc_seed_hi (documented in qspi/config.txt); tc_dump emits the coverage so a paste round-trips. Over serial the "tc_seed = on" line is the trigger -- it arms tc_seed_pending (a single-word ISR write) and tc_housekeeping applies the seed from the MAIN LOOP, serialized with tc_fit/tc_governor, after the whole paste has parsed. The learned state keeps its main-loop-only contract; the USB ISR never touches the model. Survived two adversarial review passes (the feature, then this port), with fixes re-verified in the emulator against this exact tree: boot-time tc_nom_load capture (ticks-per-ppm is never 0), origin-invariant HSE residual, main-loop-serialized serial seeding with a whole-paste trigger, tc_reset clears the held prior, significance_fade drives the dash ladder (no regression vs stock when enabled), and measure_temp's cadence gate includes the fade so die_temp_c is always real. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, not FADE_MAX
On a warm boot with significance_fade on, digit_bright[] defaulted to
{FADE_MAX,...} ("certainly significant"), so setPrecision() at boot (and each
PendSV second) showed the ms digit ticking — a ~1 s flash of numbers — until the
first per-second computeHoldoverFade() saw the huge holdover age (last_pps_time=0,
no PPS yet) and dashed them. Default to 0 (not significant): the sub-second digits
start dashed and only light once computeHoldoverFade proves significance after
lock (the same PendSV setPrecision path applies it, symmetric). Pre-existing PR mitxela#9
issue, not the SOF change. Builds clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ing arrives with seg_balance) PR mitxela#9 computes each sub-second digit's live significance (digit_bright, 0..FADE_MAX, from the 3-sigma holdover uncertainty) but the display driver could only show-or-dash. PR mitxela#10 built the missing per-digit dimmer. This connects them: - segbal_poll scales the ds/cs/ms columns' mirror duty by their digit_bright, and the seconds column's decimal point by digit_bright[3] (it dies with the 0.1 s digit). While any digit is mid-fade the mirror runs even with seg_balance off (identity duty), so the fade works standalone; with balance on the two multiply. - setPrecision's significance_fade branch now retires digits to BLANK instead of a dash glyph — the digit fades to black, as the feature's own comment always intended. - Stock behaviour (significance_fade off) unchanged: the Tolerance dash ladder remains. Verified in the WASM build of this source: partial fade tracked exactly (holdover age 11 s -> ms digit at 14/16 significance -> lit in exactly 14 of 16 cycles, DP tracking), zero-significance digits blank in the masters, relock restores full digits with auto balance resumed. ARM Release 0 errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The MODE_TEMPCOMP date-row pages honour the configurable page_ms dwell instead of a hardcoded 5.5 s, and the mode's enum entry moves to its final position (after the astro read-outs, before the alternate-timebase modes) so later tiers append ordinals without reshuffling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…efault off) The display is voltage-driven (buffer chips + 10R per segment; the DAC sets the rail), so a digit's lit segments share the drop across its common return path — FEWER segments leaves more voltage per LED, and '-', '1', '7' glow visibly brighter than '8', worst at low brightness. There is no per-digit analog knob, but the scan buffers are sized [80] with the DMA circular: replay the 5-step scan as 16 cycles of 5 slots. Slots 0..4 remain the live masters every existing writer already targets (206 write sites, none changed). segbal_poll() — main loop, at most 1 kHz — mirrors them into slots 5..79 with each digit lit in s of its 16 cycles, s = 16 - strength*(16-2N)/100: at full strength s = 2N and every lit segment averages the same duty (1/N * 2N/16 = 1/8), uniform across the display; dense digits are untouched, sparse ones dim to match. The cycle mask (k*s)%16<s spreads the lit cycles evenly (worst refresh ~7.8 kHz) and always lights cycle 0 — the master — so masters count toward the duty exactly. GPIOB mirror slots keep the bCat column-select bits in every cycle; buffer_c[].high is GPIOC's one-cold column select + enables (NOT segment data — an earlier draft duty-cycled it and adversarial review caught the mis-addressing) and is carried verbatim except cSegDP, the digit's decimal point, which is counted in the digit's N and rides its duty. Config: seg_balance = on (=100) or 0..100 percent; default 0 = stock scan, stock timing. display_scan_len shadows the DMA count so the poll steers 5<->80 across standby (guarded: never restarts DMA in MODE_STANDBY) and the date-board chainloader's 40-slot takeover. Verified: WASM build of this source — exact duty counts at strength 100/50, GPIOC addressing byte verbatim in all 16 cycles, DP synced to its digit, live enable/disable/ re-enable clean (20/20 checks); ARM Release build 0 errors; independent line-by-line review verdict SOUND. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hout the diode knee Hardware A/B at low brightness showed strength 100 (s = 2N, the linear map) still leaving sparse digits visibly brighter. That matches the drive physics: near the LED forward-voltage knee, segment current is exponential in per-segment voltage, so a dense digit's shared-path droop collapses its current by MORE than the linear 1/N model — the true bloom ratio can exceed the 8x a linear duty map can counter. segbal_duty(): 0..100 keeps the exact linear blend (100 -> s = 2N, unchanged behaviour); 101..300 becomes s = 16*(N/8)^(strength/100) — continuous at 100 (gamma 1), floor 1 for lit digits. At 200 a 1-segment '-' runs 1/16 duty against the 8-segment '8.''s 16/16: a 16x range on a knee-shaped curve. seg_balance widens to uint16 (atomic on M4), parse clamps to 300. Tune live over serial against the real display. Verified: WASM slot checks green at 100/50/200/300 incl. the 999->300 clamp (segbal_check); ARM Release 0 errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… anchors Hardware A/B confirmed the bloom is rail-dependent: one fixed strength over-dims sparse digits at full brightness while under-correcting at 5%. The firmware already knows the rail (dac_target, 4095 = dimmest), so interpolate the effective strength between two live-tunable anchors: seg_balance (dim end) and seg_balance_bright (full brightness). Unset (default) keeps constant strength — prior behaviour unchanged. -1 unsets. Tune each anchor at its own operating point over serial; the clock then compensates correctly across the whole auto-brightness range. Verified: WASM slot checks green at both rail ends (dac 4095 -> dim anchor's curve, dac 0 -> bright anchor's curve) + unset fallback; ARM Release 0 errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r depth
The date row blooms identically to the time row (same shared-rail electronics), so
equalising one row alone made the display look worse, not better. The date board is a
UART slave with no config store, so the time board forwards the compensation:
- mk4-time: segbal_forward() sends CMD_SET_SEG_BALANCE (0x94) + 9 duty bytes (the 0..16
scale for digits with 0..8 lit segments) whenever the effective strength changes — from
the main loop, only when the UART is idle and outside the date board's latch window
(RX disabled there), with a ~2 s re-assert so a lost frame or a date-board reboot
self-heals. One config key now drives both rows.
- mk4-date (-> v0.0.3): staged atomic table commit in the RX parser (an interrupted frame
leaves the previous table intact); duty recompute at every buffer-write site; the TIM2
scan ISR applies a D-cycle duty mask with per-port segment masks (port A: segments
bits 4..10 + DP bit 14, mask 0x47F0; port B: segments bits 4..10 + DP bit 0, mask
0x07F1 — cathode selects verbatim in every cycle, verified against all ten cathode
words). Identity table (boot default) is bit-identical to stock.
- BOTH boards: adaptive dither depth. display_frequency (user config, 1..100 kHz) retunes
the scan timers, and the previous fixed 16-cycle dither would strobe at slow scans:
depth now picks the deepest of {16,8,4} keeping the dither >= ~200 Hz, off below that.
The forwarded table stays depth-agnostic; each board rescales to its own depth.
Verified: both ARM Release builds 0 errors; WASM slot checks all green (time board);
adversarial review (bit-layout / protocol+concurrency / ISR-timing lenses + adversarial
verification) confirmed zero defects, including the ISR cycle budget at the fastest
commandable scan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ry brightness Hardware calibration (eyeball-matched digits at four rail levels, with the anchor confound guarded out) measured the required strength as a VALLEY, not a monotone: dac 0 614 2048 4095 strength 50 35 30 100 Strong at the dim end (LED knee: current exponential in voltage), moderate again at full rail (maximum-current IR drop in the shared return), mild between. The curve is baked as a piecewise-linear table and interpolated by the live rail every refill. Interface simplified to what a user should ever see: seg_balance = on -> AUTO, the measured curve (the intended setting) seg_balance = 0/off -> stock scan, stock timing (default) seg_balance = 2..300 -> fixed manual strength, kept for tuning experiments seg_balance_bright is retired (the two-anchor scheme is superseded by the table). The forwarded duty table keeps the date board in step automatically — auto included. Verified: WASM slot checks green at all four breakpoints plus interpolated points (dac 1331 -> 33, C truncation semantics mirrored in the test); ARM Release 0 errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e's to bump Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…olons with the rail) seg_balance AUTO: full eyeballed rail sweep (2026-07-11) landed the even point on K = 10*9^(dac/4096) — the LED knee. Replaced the mis-calibrated 4-point valley LUT with a 9-point sample on power-of-two dac breakpoints; hits the measured anchors 10/30/90 exactly. segbal_check.mjs ALL PASS, GPIOC addressing verbatim. colon_balance (NEW): colons are TIM2 PWM, not scanned, not coupled to dac_target — held fixed brightness while the digits dimmed. New key ties the colon PWM to the rail: 0/off, on/1 (AUTO rail curve), 2..256 (fixed manual scale for eyeball calibration). Folded into loadColonAnimation, re-applied by colon_balance_poll() from the main loop. Adversarial review (workflow): only real finding was an ISR race — loadColonAnimation() also runs in the USART2 button ISR (nextMode), so the main-loop reload masks JUST USART2 (deliberately not PPS/EXTI9_5, to protect timing); cosmetic + self-healing, no fault possible. Also from review: standby guard (match segbal_poll) + colonForce so an explicit colon_balance set lands even inside the AUTO hysteresis (smooth sweeps). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… not 4096 The 12-bit DAC full-scale code is 4095 (dac_target maxes there), matching the original firmware — so anchor the exponential rail curves there instead of 4096. Top LUT breakpoint 4096 -> 4095 for both SEGBAL_AUTO_DAC and COLON_AUTO_DAC; formula K = 10*9^(dac/4095). The 9 anchor values round identically at either divisor, so the strength/scale LUTs are unchanged — this only removes the one-code overshoot at the dim end and squares the maths with the hardware. Also fixed the stale parseBrightness "0 to 4096" comment (code checks <=4095). Refreshed fwt/fwd images. No behaviour change beyond the dimmest code; no version bump. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bake the measured VTT9812FH (R11=470K) brightness curve as the compiled-in brightnessCurve[] default, so the clock needs no config.txt BS lines (a BS line still overrides its stop). config.txt marks the BS lines optional. (Split out of the tempcomp auto-persist commit 3218270 — this is its brightness-curve-bake half; the persist half lives on stack/tempcomp.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Colon AUTO curve reworked from the 9-point starter LUT to the straight line a full production-Mk IV eyeball sweep (2026-07-11) actually landed: scale = clamp( 256*(4095-dac) / (4095-colon_full_at), colon_floor, 256 ) Two per-hardware anchors — colon_full_at (dac at which colons hit full scale, default 2048) and colon_floor (scale at the dimmest rail, default 20) — are baked in and overridable from config.txt, exactly like the BS brightness curve. Passes through the measured points (dac 3276->102, 2867->153); ~ (4095-dac)/8 at the defaults. (Split out of 0f0634c — this is its colon-curve engine half; the single BALANCE menu toggle half lives on stack/menu.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The per-digit dimmer significance_fade anticipated: while any sub-second digit is mid-fade, run the duty mirror even with seg_balance off (identity duty) so digit_bright (0..FADE_MAX, recomputed each second from the live U(tau)) scales each digit's cycles — the real display renders the partial fade the emulator always could. The decimal point rides its digit's fade. Also document the display-balance controls (seg_balance / colon_balance) in the golden config. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The honest oscillator-stability signal is the FREE-RUNNING HSE phase from DWT->CYCCNT, not the SysTick residual (capturePPS re-pins SysTick every second -> infinite-gain reset -> the residual's ADEV rolls off at tau>=2s and measures the loop, not the crystal). Per locked second: phase += DWT delta - 80e6 (frac-freq error), integrated to cumulative phase (int32 ticks, 12.5ns) in a 16 KB ring placed in the previously-unused RAM2 bank (new .ram2 NOLOAD section, off the app-CRC/reflash region). Overlapping ADEV (IEEE-1139) computed on demand: int64 second-difference kernel, one float sqrt per octave. Gap-restart on missed PPS (needs contiguous 1s samples). adev_reset() clears the un-zeroed RAM2 at boot. Core verified in the emulator (adev_check.mjs): white FM -> tau^-1/2, random-walk FM -> tau^+1/2, and the MCU kernel matches a double-precision reference to 5e-8 across all tau. Display (MODE_ADEV paging) + $PMADEV serial dump are the next increment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Display: sigma_y(tau) paged one octave per page_ms dwell (tau=2^page s) on the date row while the time row keeps live GPS time — the satview/tempcomp pattern. Compact scientific that always fits 10 chars: "1s 3.2e-11" / "64s 3e-11" / "1024s3e-11" (mantissa digit dropped as the tau label grows). "Adev ----" until the ring holds 3 contiguous 1 s samples. Main loop recomputes the octave cache + repaints on each page flip (thread context, sub-ms), 0xFFFFFFFF sentinel forces an immediate paint on entry. MODE_ADEV config key gates it like any mode. Serial: "adev_dump = on" (serial-only, origin-guarded) emits ONE checksummed $PMADEV,<valid>,<noct>,<sigma_0..noct-1>*CC sentence — sigma_k at tau=2^k s, %.2e, fresh cache so it works in any display mode. Same NMEA framing/CDC submit/BUSY-retry path as $PMTXTS and tc_dump. Verified in the emulator: "Adev ----" fallback, a well-formed <=10-char octave row, and the real adev_dump_step() sentence — framing + XOR checksum valid, 9 octaves, every dumped sigma matches the on-MCU cache it read. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
§3c — the value scrubber. A run of same-direction STEP taps within 350 ms grows the increment: fine for the first couple (precise nudge), then doubling every 2 taps, capped at range/16, so page_ms (250..60000) and matrix (1000..100000) sweep in a few seconds. Accelerated steps SNAP to the coarse grid so you can't sail past a round target; a pause (>350 ms) or a direction reversal drops back to 1x. Run-state is main-loop-only statics, reset on enter-edit and on exit/idle — never ISR-touched. §3b — the ring never hides a value. STEP items get a compact unit form (page_ms "5.5S", matrix "20KHZ") + label-trim; ENUM/TOGGLE keep the LABEL recognisable (you scroll by label) and truncate the value, or fall back to label-only when there's no room. Verified: menu_accel_check.mjs (single tap = one step; held run hits the floor in <=12 taps not ~21; grid-snap; reversal + pause both reset to fine; ring shows "5.5S"). All emu suites green; ARM 81%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exposes the multiplex refresh rate (setDisplayFreq) as an editable menu item under SYS, 8000..100000 Hz step 1000. Live-applies while editing via the clamping setter (never ARR-direct from the menu path, so a stray value can't stall the display), reverts on CANCEL, persists on SAVE. Persistence: ovr.matrix_freq (u32) stored at record bytes 33..36 (was reserved padding) — EE_SCHEMA stays 1, old records round-trip with matrix_freq=0 which the clamping setter treats as a no-op. config.txt MATRIX_FREQUENCY still wins under the existing mtime-precedence merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…TC VIEW Adds a DIAG toggle (KID_TEMPCOMP) that arms the self-learning oscillator compensator as a unit: tc_learn (sample ppm-vs-die-temp while GPS-locked) AND tc_apply (steer SysTick from the model in holdover). Previously the menu only exposed MODE_TEMPCOMP, which is just the diagnostic *view* — so the mode row is relabelled TC VIEW to end the ambiguity (enabling the compensator vs. looking at it). config.txt keeps the finer tc_learn / tc_apply keys for asymmetric setups; either one marks KID_TEMPCOMP defined so the mtime-precedence merge treats config as authoritative. Persisted at record byte 37 (was reserved), EE_SCHEMA stays 1 — old records read tc=0 (disarmed), matching prior default behaviour. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DISP gains one BALANCE toggle (KID_BALANCE) that arms BOTH brightness- uniformity systems at their baked AUTO curves — per-segment duty equalisation (seg_balance) and rail-tied colon dimming (colon_balance) — the 'brightness uniformity' control the menu was missing. config.txt keeps the finer seg_balance / colon_balance keys (incl. manual strengths) for calibration; either marks KID_BALANCE defined so config precedence holds. Persisted at record byte 38 (was reserved), EE_SCHEMA stays 1. (Split out of 0f0634c — this is its BALANCE menu-row half; the 2-anchor colon-curve engine half lives on stack/segment-balance.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Editing ALTCOLON picks the colon animation the sidereal/solar faces use, but you edit it from a civil face — so applyColonForMode() kept rendering the civil colon and the choice was invisible. Add a colon_preview override: while a colon-animation item is being edited, the value under the cursor is forced onto the real colons regardless of display context, tracks live as you step, and is cleared + restored to the context colon on SAVE / CANCEL / idle-exit (menu_to_L0 clears it too, so an idle timeout can't leave the preview stuck). COLON editing already previewed via the civil context; this makes ALTCOLON behave identically. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Renames the alt-colon control from ALTCOLON to COLONALT (reads as a COLON variant, groups with COLON in DISP). The stable KID_COLON_ALT id (3) is unchanged, so persisted overrides survive. (Split out of 633cc06 — this is its menu-label half; the config-key rename alt_colon_mode -> colon_alt_mode half lives on stack/sidereal-modes.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The bootloader reflashes only when the image CRC differs from the loaded one. Two builds of the same version on the same day could be byte-identical (the pre-build only did 'rm -f version.o', which is skipped on direct-target / incremental builds, so __DATE__/__TIME__ went stale) — so the second flash was silently treated as 'already loaded', and the reported version still read a bare 0.0.5. gen-build-id.sh (new pre-build hook, wired from the .cproject prebuildStep and the generated Release makefile) regenerates Core/Src/build_id.h with a fresh epoch-seconds BUILD_ID and removes version.o every build, so version.c recompiles with a fresh id + timestamp. The version string becomes 'Version 0.0.5+<epoch>' — every image is byte-unique (proven: identical source, two builds -> different CRC -> reflashes), and the detected string distinguishes them. Kept at 0.0.5 per request; build_id.h is a gitignored generated artifact, guarded by __has_include so bare checkouts + the emulator build (never compiles this TU) fall back to 'dev'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All low/medium (no brick risk found — the EE record layout, schema-1 round-trip, CRC coverage and precedence merge all held up): 1. (medium) idle-timeout mid-edit now abandons like CANCEL. menu_to_L0 (the only path that reaches an L3 editor via the 15 s idle check) neither restored the pre-edit value nor ran the KID_MATRIX_FREQ setDisplayFreq() flush that commit/cancel do — so a scrubbed MATRIX rate could leave the date board multiplexing at a stale frequency, and any abandoned edit kept its live-preview value until reboot. Now the idle check calls menu_cancel_edit() before menu_to_L0 when in L3_EDIT. 2. (low) BALANCE toggle no longer collapses a config manual strength. ON now enables AUTO only where a sub-system is OFF (both s_bal and the boot OVR_S), so seg_balance=150 / colon_balance=128 from config.txt survive a BALANCE=on override instead of being silently overwritten with AUTO(1). 3. (low) matrix_freq_hz is seeded from the real TIM1->ARR right after MX_TIM1_Init, so g_matrix can't report a rate the hardware isn't running (and a no-edit SAVE can't persist a wrong baseline) if config.txt omits MATRIX_FREQUENCY. Tests: menu_accel_check gains an idle-abandon-reverts case; menu_bal_check gains manual-strength-preserved-on-boot + off-forces-both cases. All 11 emu suites + ARM green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lashes The date board (mk4-date, STM32L010) had the same staleness as the time board: its pre-build only did 'rm -f version.o', so on incremental/direct builds __DATE__/__TIME__ went stale and a rebuilt fwd.bin was byte-identical -> the time board's CRC compare treated it as 'already loaded' and skipped the reflash. (This is why the date board didn't flash even though the time board did — its image hadn't changed.) Same fix as mk4-time: gen-build-id.sh pre-build hook (wired from both .cproject prebuildSteps + the Release makefile) regenerates a gitignored Core/Src/build_id.h with a fresh epoch and removes version.o every build. Version string is now 'Version 0.0.2+<epoch>'. Proven: fwd.bin rebuilt -> new CRC (e6b48928) != the loaded image -> the date-board updater reflashes. __has_include fallback to 'dev' for bare checkouts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…anner
On real hardware, entering a section showed the section name as a one-shot
breadcrumb (e.g. ENTER ASTRO -> 'ASTRO'), which looked like nothing had
happened ('just in astro again'). Worse, an EDIT chord under the banner ran
menu_enter_edit() on the section's first item WITHOUT ever showing it — so
you'd edit SUN blind and toggling it on enabled SUN unexpectedly.
Removed the banner mechanism entirely (menu_banner + its render/dispatch/
fire_stage branches): ENTER now lands directly on the section's first item,
so you always see what you're on ('SUN off') and EDIT edits the visible
item. Fewer taps, no blind edit, no ambiguous 'same name' screen. Chosen
over keeping the banner + a reveal-before-edit step (user call).
Tests updated: menu_check asserts ENTER lands on BRIGHT (no dismiss tap);
the goto helpers in accel/tc/bal/colon_preview drop their banner-dismiss
tap. All 11 emu suites + ARM green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MATRIX rendered its value as '20KHZ', but K and Z have no clean 7-segment glyph so on hardware it read as garbled 'ghz'. Show it in kHz as a bare number instead: the L2 item reads 'MATRIX 20' (the full label now fits) and the L3 editor reads '20', stepping by 1 kHz. The stored value stays in Hz (1000 Hz = 1 kHz step), so setDisplayFreq / config MATRIX_FREQUENCY / persistence are unchanged — display-only. menu_accel_check guards it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… fire on release) The chord was far too unforgiving: a lone button emitted its single press at btn_debounce = 2 ticks (~40ms at TIM21's 50Hz), so unless both buttons landed within ~40ms the first registered as a stray single (mode cycle / scroll) instead of starting the chord. Defer the single-press decision to chord_grace = 6 ticks (~120ms): if the other button overlaps within that window (b*_chorded) the pair becomes a chord and no single is sent. Raising the threshold can't drop quick taps, because a lone tap released before the window now fires on RELEASE instead of being lost. Auto-repeat (btn_delay/btn_repeat) and the both-tapped-too- brief-emits-nothing behaviour are preserved. Not emulated (mk4-date isn't in the WASM twin) — ARM-built, verify on hardware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Simple on/off items (modes + BALANCE/TEMPCOMP/PPS/SIG-FADE) now flip AND record in a single EDIT press at L2 — no editor, no SAVE step. This fixes the surprise where enabling a mode applied live but silently reverted on reboot because it was never committed. Values (BRIGHT/PAGE MS/MATRIX) and enums (COLON/NMEA) keep the editor + SAVE/CANCEL. The last enabled display mode is still refused (LASt guard). Because a toggle now has no editor, its state must be readable at L2, so toggle rows show a compact ON/OF and trim the label if tight (8-char labels lose their last char, e.g. TEMPCOMP -> 'TEMPCOM ON') rather than dropping the state entirely as before. Enums still keep the full label. Tests updated for the one-press flow + the visible-state render. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The two were conflated: the L0 chord's 'RESET' stage actually did NVIC reset (a reboot), and the real factory reset (menu_reset: wipe the on-device store -> config.txt defaults) was serial-only. Now: - L0 deep-hold chord relabelled RESET -> REBOOT (unchanged action = reboot). - New SYS > RESET item (MIT_ACTION, a new item type): EDIT opens a 'SURE?' confirm, SAVE fires the factory reset (menu_reset_pending), CANCEL aborts. It is deliberately NOT a one-press toggle. Fires menu_reset_step (erase the store + re-read config.txt, no reboot). MIT_ACTION renders label-only at L2 and 'SURE?' at L3; menu_edit_step no-ops it; menu_commit_edit runs its set hook instead of recording. New menu_reset_check.mjs covers the confirm gate + CANCEL-safe + SAVE-wipes. All 12 emu suites + ARM green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the one-press toggle (EDIT flips in place) with the enter->change-> exit model the user asked for: EDIT opens the item's editor (shows the current ON/OFF, does NOT flip), a single tap toggles it live, and exiting saves — the L3 chord for a toggle commits on EITHER release (labelled DONE instead of SAVE/CANCEL) so there's no separate SAVE step and no save-vs-cancel decision. Idle still abandons (reverts). Values (BRIGHT/PAGE MS/MATRIX) + enums (COLON/NMEA) keep SAVE/CANCEL; the SYS>RESET action keeps SAVE=confirm/CANCEL=abort. L2 still shows the live state (LABEL ON/OF) so you can glance without entering. Tests reworked for the editor flow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…or RAM-only? Settings not surviving reboot points at the override store being RAM-only, not a commit-timing bug. The store lives in the two flash pages above the app-CRC region (top-2*PAGE). On RG silicon (1 MB) there is room and ee_avail=1 (persists); on RC silicon (256 KB) the flash ends exactly at the app boundary 0x08040000, so ee_avail=0 and every setting is lost on power-off. tempcomp (ee2) shares the same gate, one page lower, so it is RAM-only too on RC (its real persistence is the config.txt seed). The emulator always has ee_avail=1, which masked this in tests. "menu_dump = on" emits one human-readable line: # menu store: avail=<0|1> flash=<kb>KB page_a=<hex> gen=<n> ovr=<0|1> -- <verdict> so the hardware tells us which case it is instead of guessing at the die. Mirrors the star_dump/tc_dump CDC/BUSY-retry contract; forward-declared, defined below the ee_* globals. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gn C) The on-device settings store (menu overrides ee_* + learned tempcomp model ee2_*) lived in the top two internal-flash pages. On RC silicon (256 KB) flash ends exactly at the app-CRC boundary, so ee_avail=0 and EVERY setting was RAM-only — confirmed on hardware via menu_dump (avail=0 flash=256KB). Fix: relocate the store into /SETTINGS.BIN on the QSPI FAT16 volume, chosen from a 3-design adversarial review as the only option that changes neither the MSC geometry nor the bootloader: - 16 KiB contiguous host-visible file = 4 QSPI sectors: menu A/B + tempcomp A/B. Boot resolves file->physical via the read-only FATFS fastseek CLMT (single-fragment REQUIRED; fragmented/missing -> RAM-only fallback, warned). Record format/CRC16/generation/A-B ping-pong unchanged; slots per page 32->64 (4 KB sectors). RG silicon keeps the internal-flash store untouched (EE_BK_INTERNAL); the QSPI path engages only where internal has no room. - Firmware writes the file RAW through qspi_drv (new QSPI_Program, 8-byte doublewords so CRC still lands last) — never FAT metadata, size, or dirent. - Safety set from the design review: (1) settings_map_gen bumped by every STORAGE_Write_FS; commits re-resolve the mapping when stale, so a host that moves/rewrites the file can never cause a blind write (worst-case target was fwt.bin/the FAT); (2) content sentinel before erase/program — active page must read our magic or 0xFF, else re-init INSIDE the file; (3) fatfs_busy across the whole commit (eject-reset atomicity); (4) NOR skip-to-blank cursor + first-use erase of non-blank pages (a 0x00-provisioned file would otherwise AND every record into permanent CRC failure); (5) commits defer while host MSC I/O is in flight; (6) menu_dump now reports backing (INT/QSPI/NONE) + the RAM-only reason. Known cost: a rollover erase makes the USB drive NOT_READY for <=800 ms once per 64 commits. - flash.sh provisions SETTINGS.BIN FIRST (contiguity) filled 0xFF; qspi.md documents the file contract. - Emu now models the real medium: NOR AND-program + 4 KB erase + attach/ fragment/host-write hooks. The old memcpy shim with ee_avail hard-coded 1 is gone — it let every persistence test pass while hardware lost settings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ements From the 24-finding adversarial review. Bugs: (1) releasing an L0 chord on an unlabeled stage stranded the DATE row on "----" (now every unhandled stage restores/repaints); (2) a toggle SAVED when released on a stage labeled "----"/CANCEL (now shallow stages = DONE save, deep stage = CANCEL revert); (3) CANCEL/idle out of the alt-colon editor leaked colonAltExplicit=1 via the restore write, defeating the auto-distinguish on the next config load (snapshot + restore); (4) disabling FW CRC while viewing the CRC-on-date mode stranded the display on a disabled mode (mirror menuSetMode\x27s advance guard); (5) a no-op editor visit burned a flash record (skip when value unchanged); (6) a staggered both-press leaked a nextMode before SETUP fired (snapshot the mode on an L0 single tap; SETUP within 2.5 s undoes it — the ~120 ms merge window itself is untouched). Grammar/labels (7-seg glyph reality: S=5, O=0, I=1, Z=2, no ? glyph): APPLY/----/CANCEL replaces SAVE/CANCEL adjacency (overshooting a commit can no longer silently discard the edit); deep hold = CLOCK bail-out from L1/L2; REBOOT arms only on the SECOND stage cycle (~4.3 s) so an exploratory over- hold of the menu-entry gesture cannot reset the clock, and the label only shows once armed; "SURE?" (rendered 5URE-) -> "DELETE ALL"; toggle state "OF" (0F, reads as the word of) -> full "OFF" both layers; PAGE MS -> PAGE with seconds in BOTH layers and no unit-S (5.5S read as 5.55); SOLID -> FULL (50L1D); COLONALT -> ACOLON so the live value fits at L2; PPS OUT -> PPS MSG (it gates the $PMTXTS sentence, not a pulse output); TC VIEW -> TC DATA. Tests: stage remap applied across the suites + new regressions for every bug (L0 unlabeled-stage release, REBOOT two-cycle arming, mode-leak undo, toggle deep-CANCEL, no-op-visit no-record). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t DONE flash New read-only MIT_INFO row type (no editor, never persisted): DIAG "PPS LOCK"/"HOLD <s>"/"PPS ----" answers "am I disciplined right now?"; ASTRO "STARS <n>" / "STARS b<n>" (baked fallback tagged) answers "did my catalogue load?" — the tell that would have caught a failed card a session early. The chord offers no EDIT on INFO rows. SYS>RESET now flashes "DONE" on completion (a destructive action no longer returns to an identical screen). (Split out of f7ba9c4 'opportunity bundle' — menu hunks only; ADEV hunks live on stack/adev-display, star hunks on stack/star-transit.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nt, mechanism A) The signed-off §7 spec, mechanism A (time-board only — works unchanged on a stock date board; the per-digit brightness overlay B stays a documented future layer gated on the next date-board protocol change): - EDITOR BLINK: a resting L3 editor value blinks at 1 Hz — the digital-watch "you are setting this" idiom. It stays SOLID while actively scrubbing (any event within 600 ms) so the number never flickers under your thumb, starts on the visible phase, and ACTION confirms stay steady. Chord stage labels always win. - IDLE WARNING: the whole row flickers once (300 ms) at T-3 s before the 15 s idle-abandon reverts an open edit — "act or lose it". Any press resets both behaviours\x27 clocks. - VBAT -> DIAG: coin-cell health joins the menu as a normal persisted mode toggle (was config-only). menu_check regressions: solid-while-fresh, blank/visible blink phases, solid-while-scrubbing, T-3 s flicker + restore, abandon still fires, VBAT row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ig cleanup) Factory reset now clears EVERYTHING learned: SYS>RESET (and menu_reset) also fires tc_forget + tc_reset, wiping the retained tempcomp model and the live learned state; the config.txt re-read then re-seeds from the file's tc_* block — the clock returns to exactly what the file says, compensation included. tc_forget_step gains the same stale-mapping (settings_mapping_ok) + eject (fatfs_busy) guards the other QSPI erasers use, and the internal-flash unlock is now backing-gated like everywhere else. The ASTRO info row reads plain "STARS <n>" (0 = nothing loaded) now that provenance is single-source. Cleanup (docs review findings): golden config.txt's stale "#alt_colon_mode" comment corrected to the parser's colon_alt_mode spelling; a worked refined- model example block (real learned values: t0 40, die coverage 26..36) added. (Split out of dc83c30 — menu/factory-reset/config half; the star-core no-fallback half lives on stack/star-transit.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
…rsist The DIAG toggle armed tc_learn+tc_apply but left tc_persist config/serial-only — a split that made sense before the settings store existed on 256K silicon and doesn't now: enabling the compensator from the front panel obviously means wanting its learned model to survive power-off. Same bundling pattern as BALANCE (seg+colon). A stored TEMPCOMP override now arms all three on boot, so existing menu-armed units gain persistence with no user action; a config.txt that explicitly defines tc_persist claims the bundle key and wins, exactly like tc_learn/tc_apply. Docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…om the tree
Fine-tooth-comb chain-audit fixes (all text, no behaviour): docs — TEMPCOMP
menu line now says the learn+apply+persist trio (matching s_tc and its own
config section); star catalogue consistently 93 / HYG v4.1; the three astro
render examples now show the firmware\x27s exact padding ("SET 21.14",
"MOON 4 99", the LON sign-slot note). config.txt — the MODE_STAR and ADEV
example strings now match the real renders ("SIRI 0 45"; no unit-s).
generate-stars.py — CLOCK-drive terminology, v4.1, real star count. main.c —
the last stale "card or baked default" comment corrected. And
mk4-time/.settings/language.settings.xml reverted to upstream\x27s blob: a
machine-specific IDE environment hash has no business in a PR chain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The exact residue between the pure union of the nine stacked PR tiers and the emulator-verified + hardware-run rollup tree @ 8a36c89. After the round-5 re-homes (MODE_TEMPCOMP cadence/enum -> tempcomp tier, segbal fade mirror -> segment-balance tier, golden-config mode docs -> their feature tiers, subsolar helper -> sidereal tier) what remains is comment wording, comment placement and whitespace, plus one default-off config key line: - evil-merge comment wording from the original merge commits (10c8bf6 / 15567d1), not cherry-pickable as tier commits: tc_seed trigger comments (x3 sites), page_ms comment wording (x4 sites), postConfigCleanup / applyColonForMode 'sidereal colon' wording, delayedPostConfigCleanup and astro repaint notes, a comment-block move around write_rtc, and two whitespace-only line tweaks. - 16ece45/8515aa7 astro comment refinements (gmst_hours shared-series comment, local_solar_time wrap note, LST/solar test-anchor comments + one blank line) that postdate the relocated subsolar commit's content. - config.txt: the significance_fade ladder paragraph incl. the 'significance_fade = off' key line (default-off; absence is behaviourally identical), the mode_tempcomp wording, one trailing-space line, one blank line. No functional code differs: all firmware logic, data tables, binaries and build inputs are byte-identical before this commit except the lines above. After this commit the stack tip is BYTE-IDENTICAL to rollup @ 8a36c89 while the linear ancestry contains exactly the nine tiers' assigned commits. 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.
The capstone: a sectioned on-device menu (CAL / ASTRO / DISP / DIAG / SYS) driven by the two date-
board buttons through a rolling-chord grammar, live-preview editors, and settings that persist
across power loss — in the top two internal-flash pages on 1 MB parts, or a host-visible
SETTINGS.BINon the QSPI volume on 256 KB parts (which have no spare internal flash). Includes thecompanion date-board firmware (chord protocol), per-build version tags, and the documentation for
the whole series.
Stack: based on
star-transit(#12) — the FINAL PR. Merging #5 → … → this PR in order is a chain offast-forwards whose result is byte-identical to the
rollupbranch (the tree this series wasbuilt, emulator-tested, and hardware-validated on; the last commit here is an explicit integration-
delta aligning the union to that validated tree).
Try it now (ready to flash):
fwt.bin (time board) ·
fwd.bin (date board — needed
for the menu buttons; everything else works on a stock date board). Copy to the CLOCK drive and
power-cycle. On 256 KB clocks also create
SETTINGS.BINfor persistence (16 KiB of0xFF, firstfile on a fresh volume — see docs).
Docs: this PR ships
docs/firmware-additions.md— a complete write-up of every feature in theseries (modes, config keys, serial sentences field-by-field, the menu, flash-drive file formats), written
to slot into the project's documentation page.
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.
Retargeting offer: the nine PRs form a linear stack against
master. If you'd prefer to take theseries onto an integration branch (e.g.
pcc-series), create it and say the word — I'll retarget allnine in one pass; each merge then becomes a fast-forward and the per-PR diffs collapse to just their
own commits as the stack lands.
Highlights
(
APPLY) and discard (CANCEL) are separated by a buffer stage; toggles save on exit (DONE);a deep hold bails to the clock from anywhere;
REBOOTarms only on the second cycle so the entrygesture can't reset the clock.
grid snap, blink-at-rest (the digital-watch "you are setting this" idiom), a T-3 s warning flicker
before the 15 s idle-abandon, and a last-enabled-mode guard (
LASt).an mtime stamp (editing config.txt on the drive reclaims any key it defines). On 256 KB silicon the
store rides inside
SETTINGS.BIN— resolved through the read-only FAT layer at boot, rewrittenraw in place (never touching FAT metadata), host-clobber-safe (mapping re-verified before every
write; content sentinel; graceful RAM-only fallback if the file is absent or fragmented).
PPS LOCK/HOLD nand star-catalogue count rows;menu_dumpserial verdict; factory reset with a real confirm (DELETE ALL) and completion tell.Known issues
026-07-12for a single second, self-healing at thenext 1 Hz repaint). Root cause is in the stock date-board UART configuration: reception is POLLED
from the main loop with overrun detection disabled (
USART_CR3_OVRDIS), so if the loop is delayedmore than ~2 byte-times (~174 µs at 115200) while a frame streams in, one byte is silently
dropped. The features in this series add date-row traffic and display-scan ISR load, which raises
the (still small) odds of hitting the window. It cannot corrupt state or stick — command bytes
have bit 7 set, so the parser resyncs on every frame and the next repaint heals the row. Candidate
fixes (deliberately not bundled here): trim the scan-ISR worst-case path, or enable ORE handling
with a careful drain path; a drop-counter diagnostic would quantify the real-world rate first.