Skip to content

A single LVGL repaint costs 40 ms against a 25 ms service period, on the task that also polls the receiver #213

Open

Description

@PurpleSentinel

Measured

On hardware, main at 3f314e9, G-ONLY on screen, device untouched. Instrumentation that timed lv_timer_handler() in panel_esp.cpp's lvgl_task reported, in steady state:

draw: 11745 calls in 10025 ms, 152 redraws (151 per 10 s), mean 380 us, worst 40604 us (25000 us service period, worst 162%)
draw: 23618 calls in 10028 ms, 148 redraws (147 per 10 s), mean 170 us, worst 38532 us (25000 us service period, worst 154%)
draw: 11667 calls in 10025 ms, 152 redraws (151 per 10 s), mean 396 us, worst 41095 us (25000 us service period, worst 164%)

A repaint takes 38.5–41.1 ms. The service period on that task is 25 ms. It happens roughly 150 times per 10 seconds, so about a 60% duty cycle spent drawing.

The mean is low because most handler calls find nothing dirty and return in microseconds. The separation is stark — no-op calls are single digits, a repaint is tens of thousands — so these are not averages hiding a tail, they are two distinct populations.

Why it matters

lvgl_task dispatches the UI service callback and the GNSS poll, in that order, before each lv_timer_handler():

if (now_us - last_service_us >= kServicePeriodUs) {
    last_service_us = now_us;
    service_callback();
}
delay_ms = lv_timer_handler();

So a 40 ms repaint pushes the next service dispatch 40 ms late. At 25 Hz a fix is due every 40 ms. The margin is gone.

The codebase already says this is the wrong arrangement, in screen_router.cpp:596:

Polled from the LVGL service tick for now, which is deliberately the arrangement most exposed to the failure the gap counter exists to catch: this is the task that has starved twice (#133, #141). That makes it a good place to watch the counter work on real hardware, and the wrong place to leave it — the receiver belongs on its own task before any vehicle testing, which is part of what #18 still has open.

This issue is the measurement behind that comment. The arrangement is not merely theoretically exposed; the drawing genuinely overruns the period by 60%.

What is measured and what is not

Measured: the repaint duration, its frequency, the task structure, and that the trace's cost grows faster than linearly in the number of trail dots — raising kImuTrailCapacity from 24 to 60 took the worst repaint from ~40 ms to ~54 ms and the object work from ~5.6 ms to ~16.5 ms (#211, reverted by #212).

Not measured, and deliberately not asserted:

  • What area is actually being invalidated. 40 ms is consistent with a near-full-screen repaint of a 600x450 panel through a 96,000-byte stripe buffer, but I have not confirmed the area. 26 scattered small objects may be joining into one large region, or the invalid-area list may be saturating and marking the screen dirty wholesale. These have different fixes, so the area needs instrumenting before anything is rewritten.
  • Whether fixes are actually being lost. GnssPipeline already counts gap_events and missed_fixes, but neither is in the periodic log line, so a boot capture cannot answer it. The observed rate did dip to 22.7 Hz in one 10-second window against a configured 25, which is suggestive and not conclusive.

Suggested order

  1. Put gap_events and missed_fixes in the GNSS log line. Cheapest possible step, and it converts "there is no margin" into "fixes are / are not being lost". Everything below is sized differently depending on that answer.
  2. Instrument the invalidated area so the 40 ms is attributed rather than guessed.
  3. Then fix the cause. Candidates, in rough order of appeal:

Notes

Pre-existing: present at the current 24-point trail, not introduced by any recent change. Found while measuring #136, which is now blocked on it — the trace cannot grow until the drawing is cheaper.

All bench work. No circuit or road test required.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:foundationRepository, toolchain, CI, and architecture foundationarea:session-uiSession workflow and driver-facing UIbugSomething isn't workingpriority:highRequired for the current milestone or a correctness/reliability risk

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions