Repository navigation
Build a radar-style G meter as the G-Only primary display #136
Copy link
Copy link
Open
Labels
area:session-uiSession workflow and driver-facing UISession workflow and driver-facing UIenhancementNew feature or requestNew feature or requestpriority:highRequired for the current milestone or a correctness/reliability riskRequired for the current milestone or a correctness/reliability risk
Milestone
Description
Objective
A radar-style graphical G meter that becomes the primary display when G-Only mode
(#132) is selected: a polar plot showing current G, a fading trace of the last few
seconds, and the session maximum held as a marker.
What exists already
More than expected, and it should be reused rather than rewritten:
ui::ImuMeterControlleralready produces everything the display needs — current planar
G, a trail ring buffer, and peaks for acceleration, braking, left, right and total,
including the position at which total peaked. Host-tested.board::ImuSampleis already 6-axis: three accelerometer and three gyroscope axes.ui::GmeterScreenexists from Build G-meter and IMU status views #84.
What is missing
- The existing screen is a cartesian scatter, not a radar. It places 6 px square
markers and a peak marker on a flat panel with a text summary beside it. There is no
polar grid, no G rings, no axis labelling, and no fade along the trace. kImuTrailCapacityis 24 points. At a realistic sample-to-display rate that is
roughly a second, not "the last few seconds". The capacity needs sizing against the
chosen update rate rather than left at its current value.- No sample source: see Bring up QMI8658, RTC, microSD, and board power diagnostics #12 for the QMI8658 driver. The meter is fed by fixtures only.
Scope
- Radar rendering: concentric G rings with labelled magnitudes, cardinal axes marked
for braking, acceleration and left/right cornering - Current-G indicator distinct from the trace
- Trace of the last few seconds, oldest fading, with the capacity sized from the
chosen sample rate and stated in the code rather than implied - Session maximum shown as a held marker at the position it occurred
- Numeric peaks retained alongside the graphic; a driver reviewing afterwards wants
the number, not only the picture - Full-panel layout when G-Only is the active mode, with no timer or lap furniture
- Legible in daylight at 311 PPI; sizes chosen in millimetres, per Driver-facing typography is too small to read on the physical panel #127
- Measure redraw cost. A trace redrawn every frame on a 600x450 panel through a
96,000-byte stripe buffer is the most demanding rendering the device does.
Exiting G-Only
G-Only has no on-screen controls, so leaving it uses the standard gated route: press
and hold opens the menu, then Mode, then choose another mode. This is the same gesture
as everywhere else and needs no special case, but it must be verified that the hold is
reachable when the radar occupies the whole panel — a child object that consumes presses
would block it, which is how the ready dashboard's buttons behaved before #133.
Acceptance criteria
- G-Only renders the radar full-panel with no timer furniture
- The trace covers a stated number of seconds and fades with age
- The session maximum is held and visible, and matches the numeric peak
- Press-and-hold reliably opens the menu from the radar, confirmed on hardware
- Redraw cost is measured and recorded
- Readable in daylight on the physical panel
Notes
- Peaks recorded here must reach the session summary; that is Record maximum G on all axes in every session and surface it in Review #137, and both should use
the sameImuPeakSummaryrather than computing maxima twice.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:session-uiSession workflow and driver-facing UISession workflow and driver-facing UIenhancementNew feature or requestNew feature or requestpriority:highRequired for the current milestone or a correctness/reliability riskRequired for the current milestone or a correctness/reliability risk
Activity
PurpleSentinel commentedon Oct 16, 2026
Status check: the radar is built, but one acceptance item this issue specifically called out is untouched
Done.
GRadarScreen(g_radar_screen.cpp) is the full-panel primary display for G-Only —home_screen()returnsradar_root_forDeviceMode::g_only, with no timer or lap furniture:kRingG = {0.5, 1.0, kRadarFullScaleG}kAxisTextg_radar_screen.cpp:144, floored at 30 so the tail does not vanish)peaks.total_positionACCEL,BRAKE,LEFT,RIGHT,MAX, with--rather than0.00when the IMU is absent, because zero is a legitimate measurementNot done — the trail capacity. This issue said:
It is still 24 (
imu_meter.hpp:16), and the update rate has since been chosen and can now be measured rather than estimated:service_imu()is called from behind the display cadence gate atkDisplayServicePeriodUs = 50'000, so the meter samples at 20 Hz and 24 points is 1.2 seconds of trace. The prediction in the issue was right, and the number it was waiting on now exists.For "the last few seconds" at 20 Hz: 3 s needs 60 points, 5 s needs 100.
PlanarAccelerationis 2 floats plus 2 bools, so 100 points is a few hundred bytes of the controller — cheap in memory. The cost is in rendering, which is the other open item.Not verified — redraw cost. The final box is unticked and I have no measurement for it:
Each trail point is an LVGL object whose opacity is restyled every update. At 24 points that is affordable; at 100 it needs measuring before being assumed, and this is exactly the case the issue flagged. Raising the capacity without that measurement is how the display cadence quietly becomes the bottleneck.
Remaining scope
kImuTrailCapacityfrom the 20 Hz cadence, with the arithmetic stated in the code rather than impliedfont_for(), which Driver-facing typography is too small to read on the physical panel #127 has not reached yetAll bench work; no circuit or road test required.
feat(ui): give the G radar a three-second trace, and measure what dra…
PurpleSentinel commentedon Oct 16, 2026
Correction: the trace increase was merged on a bad measurement and has been reverted
#211 raised the trace to three seconds on the strength of a render-cost measurement I took incorrectly. #212 reverts it. The radar work this issue describes remains delivered; both boxes I claimed to close are open again.
The mistake. I measured while moving the device by hand, reasoning that a moving dot dirties more screen area than a parked one. Motion keeps
GravityCalibrationfrom completing, soresolve()returns invalid, the meter state never reachesready, andGRadarScreen::update()takes the branch that hides every dot without positioning or restyling it. The figures were the cost of drawing nothing — and they scaled cleanly from 1,727 to 2,395 us, which is what made them convincing.Measured properly, device untouched, which is when the trace is actually drawn:
Three times the object work and 14 ms more on the worst repaint. Object cost grows faster than linearly in the dot count, which fits LVGL's invalid-area list saturating and joining rather than a fixed overhead plus a per-dot cost.
What this issue is now blocked on
The remaining scope stands, but the order has changed — the capacity cannot be raised first:
kImuTrailCapacityfrom the cadence, with the arithmetic stated in the codeThe instrumentation that produced these figures went out with the revert, deliberately, to put
mainback to a known state rather than keep the half of the change I still think is right. Re-landing it on its own — no capacity change — is worth doing first, since nothing above can be verified without it.Note to self, recorded because it cost a merge and a revert
Measure the path that actually runs. An early-out that skips the expensive work will produce clean, plausible, well-scaling numbers for the wrong branch, and those are harder to doubt than obviously broken ones.
PurpleSentinel commentedon Oct 16, 2026
Blocked on #213, which carries the repaint measurement: a single repaint costs 38.5-41.1 ms against a 25 ms service period at the current 24 trail points, so the trace cannot grow until the drawing is cheaper.
Drawing the trace as one object rather than 24 individually positioned dots is likely to resolve both: one invalidation instead of 24, and a capacity that stops being the thing that drives render cost.