Skip to content

task_handler: fix LVGL time running ~1.8x fast (double tick_inc) - #13

Open
bitcoin3us wants to merge 1 commit into
MicroPythonOS:integrationfrom
bitcoin3us:fix/lvgl-tick-single-source
Open

bitcoin3us wants to merge 1 commit into
MicroPythonOS:integrationfrom
bitcoin3us:fix/lvgl-tick-single-source

Conversation

@bitcoin3us

Copy link
Copy Markdown

Problem

LVGL time runs about 1.8x faster than wall clock on ESP32 builds. task_handler.py feeds lv.tick_inc() from two places at once:

  • _timer_cb adds the nominal period on every machine.Timer tick (2 ms on MicroPythonOS, which calls change_task_handler(period_ms=2)), and
  • _task_handler adds the elapsed time since its previous pass on every scheduled run, plus a third increment covering the TASK_HANDLER_FINISHED callbacks.

Every lv.timer, animation, scroll throw and long-press threshold therefore fires early. It was noticed because a video player's lv.timer_create(cb, 1000 // 12) produced 14 frames per second.

Measurements (Waveshare ESP32-S3-Touch-LCD-3.5, MicroPythonOS 0.18)

lv.tick_get() advance per time.ticks_ms() advance:

Condition Stock This PR
Idle 1.834 1.000
Under flash-read load ~1.07 1.000
TaskHandler.disable() (timer path only) 1.000 n/a
LVGL ms per 500 ms sleep 1026 500
Handler timer deinit()'d 0 (confirms no other tick source)

The stock timer path alone loses ~20% of ticks under load because timer callbacks are delivered through the scheduler and coalesce or drop; the handler-side increment was masking that loss by double counting.

Fix

Make _timer_cb the only place ticks are added, and have it add the real elapsed milliseconds since its previous run instead of the nominal period. LVGL time then equals wall time regardless of load, and a stalled scheduler (long C call, raw-REPL transfer) catches up afterwards instead of losing time. Net diff: 8 insertions, 14 deletions in one file.

Verification

  • Hot-loaded on the badge via mpos.ui.change_task_handler(): ratio 1.000 idle and under load, exactly 500 ms per 500 ms sleep.
  • Rebuilt and flashed (scripts/build_mpos.sh esp32s3, headroom unchanged at 50288 bytes): ratio 1.000 in every settled window across two boots; a 12 fps lv.timer now paces at 11.4 fps instead of running free at 14.2.
  • A second session on the same board independently measured 1026 ms per 500 ms on the stock firmware before the flash.

Notes

  • The same three tick_inc calls exist in upstream lvgl-micropython/lvgl_micropython, so the bug is inherited, not local.
  • The web build's asyncio replacement (scripts/web_port/staged_lib/task_handler.py in the MicroPythonOS repo) has no timer-side increment and is not affected by the 1.8x error; it only carries the minor FINISHED-callback double count.
  • User-visible effect after the fix: transitions, long-press delay, scroll momentum and app timers run at their configured real-time durations, roughly 1.8x longer than before.

🤖 Generated with Claude Code

With thanks to the scientists and engineers who did the hard, unglamorous work that got us here.

LVGL time ran ~1.8x faster than wall clock on ESP32 builds: _timer_cb
added the nominal period on every machine.Timer tick while
_task_handler also added the elapsed time on every scheduled pass (plus
a third increment covering the FINISHED callbacks). Every lv timer,
animation, scroll throw and long-press threshold therefore fired early;
a 12 fps lv.timer produced 14 frames per second.

Measured on a Waveshare ESP32-S3-Touch-LCD-3.5 (MicroPythonOS 0.18):
lv.tick_get() advanced 1.834 ms per wall-clock ms when idle. With
TaskHandler.disable() (timer path only) the ratio was exactly 1.000,
and under load the timer path alone lost ~20% of ticks because
scheduled callbacks coalesce.

Make _timer_cb the only place ticks are added, and have it add the real
elapsed milliseconds since its previous run instead of the nominal
period, so LVGL time equals wall time regardless of load. Verified
1.000 idle and under flash-read load; a stalled scheduler now catches
up instead of losing time.

Co-Authored-By: Claude Fable 5.1 <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