Skip to content

Astro pack: sun / moon / Maidenhead / lat-lon display modes#6

Draft
peterlewis wants to merge 3 commits into
mitxela:masterfrom
peterlewis:astro-pack
Draft

Astro pack: sun / moon / Maidenhead / lat-lon display modes#6
peterlewis wants to merge 3 commits into
mitxela:masterfrom
peterlewis:astro-pack

Conversation

@peterlewis

@peterlewis peterlewis commented Jun 30, 2026

Copy link
Copy Markdown

Draft / proposal. A self-contained, opt-in set of GPS-derived astronomy display modes for the Mk IV. Independent of the timekeeping-fix PR — branched from master, touches no timing code. All five modes — and the astro_page_ms paging — have been built and confirmed working on real hardware; the maths is also verified natively (see Build & test). It stays a draft for your review, not for want of testing.

What this adds

Five new display modes, each disabled by default and enabled individually with a MODE_* key in config.txt, exactly like the existing modes. They follow the MODE_SATVIEW pattern: the read-out shows on the 10-character date row while the live clock keeps running on the time row, so you never lose the time to read the sky.

Mode Date row Example
MODE_SUN sunrise / sunset / solar noon (local), auto-paging (default 5.5 s, set by astro_page_ms); RISE/SET/SOL labels are 4-char-padded so the time columns stay aligned RISE 04.43 · SET 21.21 · SOL 13.02
MODE_SUN_AZEL sun azimuth & elevation now AZ179EL62, AZ083EL-29
MODE_MOON phase index (0–7) + illuminated % MOON 4 100 (full), MOON 7 16
MODE_GRID Maidenhead grid locator IO91xl
MODE_LATLON latitude / longitude, auto-paging LAT 51.48 · LON 0.00

The sun page auto-pages (default 5.5 s, set by astro_page_ms); the labels are padded to 4 characters so the
time digits stay column-aligned (note SET/SOL sit one column right of RISE):

RISE 04.43
SET  21.21
SOL  13.02

With no GPS fix the modes use fake_latitude/fake_longitude if set (handy indoors), otherwise show ----. The moon needs no fix at all.

Why these

They reuse data the firmware already has (position + disciplined UTC) for things the existing modes don't surface, and they suit the device's likely owner — a tinkerer / ham / observer: sun & moon position, sunrise/sunset, your grid square, your coordinates.

Design — keeping the maths out of the ISR

sendDate() runs inside the SysTick ISR (the 0.900 s repaint), and the L4 has only a single-precision FPU, so the double-precision astronomy would be slow soft-float in an interrupt. So the heavy maths runs in the main loopastro_update(), gated on the active mode in exactly the way measure_vbat() is gated for MODE_VBAT — and the small result struct is swapped into a cache under a brief __disable_irq() mask. The sendDate() cases only format cached scalars: no trig, no sun_times, nothing heavy in the ISR. Position is snapshotted once per update so az/el, grid and lat/lon are always mutually consistent.

Everything is parameterised by NUM_DISPLAY_MODES (the enum append grows config.modes_enabled[] automatically), so the modes join the button cycle and config parsing with no other changes.

The maths — Core/Src/astro.c (+ astro.h)

A self-contained C99 + <math.h> unit with no firmware dependencies:

  • sun_az_el() — apparent solar azimuth/elevation (NOAA/Meeus low-precision).
  • sun_times() — sunrise / sunset / solar noon (+ optional civil/nautical/golden), decimal UTC hours.
  • moon_phase() / moon_illuminated_fraction() / moon_phase_index().
  • equation_of_time().
  • maidenhead() — 6-char locator.

Because it has no firmware ties, it unit-tests on a host. test/test_astro.c checks it against 45 reference vectors (four locations spanning hemispheres/seasons incl. a polar-winter case, plus fixed Maidenhead known-answers like Trafalgar Sq → IO91wm, ARRL HQ → FN31pr):

$ cd mk4-time/test
$ cc -std=c99 -O2 -Wall -Wextra -I ../Core/Inc ../Core/Src/astro.c test_astro.c -lm -o test_astro && ./test_astro
...
45/45 passed, 0 failed

Agreement with the reference is ~1e-5 (well under the whole-degree / whole-minute the display shows). astro.c is added under Core/Src, which the .cproject compiles automatically; test/ is not a source path, so the test never enters the firmware build.

Accuracy & legibility (honest notes)

  • Low-precision algorithms: sun position good to a fraction of a degree, event times to a minute or two — appropriate for a 7-seg read-out.
  • 7-seg letters are approximate (S→5, I→1, O→0, Z→2), the same as the existing GPS/bat/ttff/rtc labels. Maidenhead's lowercase subsquare (ax) renders best-effort and is in-bounds for lut_7seg.
  • Moon phase is shown as the standard 8-step index because the names don't spell on 7-seg; the legend (0 new … 4 full … 7 waning crescent) is documented in config.txt.

Config

config.txt gains the five mode keys (disabled by default) with comments and the moon-index legend, plus astro_page_ms — the dwell (ms) each sub-screen of the paged modes (SUN, LATLON) shows before flipping (default 5500, floored at 250):

MODE_SUN = disabled
MODE_SUN_AZEL = disabled
MODE_MOON = disabled
MODE_GRID = disabled
MODE_LATLON = disabled
astro_page_ms = 5500
#fake_latitude  = 51.48
#fake_longitude = -0.01

Build & test

Build mk4-time in Release (Debug links a non-bootloader script, so a Debug fwt.bin won't flash via the bootloader) — no project-file changes are needed (astro.c auto-compiles). All five modes — and the astro_page_ms sub-screen paging, its cadence tuned on the unit itself — have been built and confirmed working on real hardware (from a single location); cross-location and edge-case behaviour — hemispheres, the antimeridian, polar day/night — is covered by the 45 native reference vectors above. It stays a draft for your review, not for want of testing.

Review & verification

Before this PR the change was put through an adversarial review across three independent lenses, each trying to break it rather than confirm it:

  • Firmware integration / ARM build — enum + cycle wiring, config.modes_enabled[NUM_DISPLAY_MODES] sizing, buffer bounds, switch-case correctness, and that the new modes take the normal COUNT_NORMAL live-clock path.
  • Math & port fidelity — re-ran the native test (45/45) and checked edge cases: poles / polar day-night, the antimeridian, azimuth wrap, moon-phase index wrap, maidenhead clamping, fmod sign.
  • 7-seg display strings — every mode's output width across the full value range (≤10 chars, no overflow) and glyph legibility.

Verdict was ship-with-fixes; the findings were addressed in this branch:

  • ISR snapshot bug (fixed)MODE_LATLON formatted the live latitude/longitude inside sendDate() (which runs in the SysTick ISR) instead of the IRQ-mask-swapped astro cache, so the shown coordinates could disagree with the cached have_pos/grid in the same frame. Lat/lon are now snapshotted into the cache with everything else.
  • Negative zero (fixed) — on the prime meridian / equator %.2f printed LON-0.00; switched to integer-hundredths so it reads LON 0.00.
  • Glyph set (confirmed, no change) — the date board's lut_7seg renders the full printable-ASCII set, so the labels and lowercase Maidenhead subsquares display fine; maidenhead() is left faithful.
  • ISR safety (confirmed) — the double-precision trig runs once per second in the main loop (mirroring measure_vbat); only cached scalars are formatted in the ISR, so there's no soft-float in the interrupt.

Possible follow-ups

  • Render sun-event times on the big time row (needs the SHOW_OFFSET-style segment-buffer path) for nicer HH:MM:SS.
  • Civil/nautical twilight and moonrise/moonset (the maths is already present in astro.c).

Files

  • mk4-time/Core/Src/astro.c, mk4-time/Core/Inc/astro.h — new, self-contained maths.
  • mk4-time/test/test_astro.c — new, native test (not built into firmware).
  • mk4-time/Core/Src/main.c, Core/Inc/main.h — enum, cache, astro_update(), 5 sendDate cases, 5 config keys, main-loop hook.
  • qspi/config.txt — documented keys + legend.

🤖 Generated with Claude Code

@peterlewis peterlewis force-pushed the astro-pack branch 2 times, most recently from 39b1e79 to de7fb99 Compare June 30, 2026 15:00
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant