Skip to content

display_driver: LVGL on one multimer timer that asks LVGL when to come back - #22

Merged
bdbarnett merged 4 commits into
mainfrom
timing-redesign
Sep 26, 2026
Merged

bdbarnett merged 4 commits into
mainfrom
timing-redesign

Conversation

@bdbarnett

@bdbarnett bdbarnett commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

display_driver.py runs LVGL on one multimer ONE_SHOT timer that re-arms itself to what lv.timer_handler() returns, bounded by a 10 ms period so input is read at least that often. A pass that outran what LVGL asked for is followed by at least a period off, and a pass longer than a period by min(pass, max_yield_ms): the rule of #15 and #19, now in one place (event_loop._next_delay) with a test on a virtual clock that reproduces the board's 87 % without it. PARTIAL panels present from the display's frame clock after a flush; DIRECT panels present from flush_is_last as before. The driver claims refresh and device polling from the App while it runs, so the App's own tick no longer eats LVGL's input events. event_loop(asynchronous=...) is still accepted and ignored.

Needs PyDevices/pydevices#101 (the new multimer, appdev and displaydev) on the path. Frame pacing measured against the current driver is in that PR's design doc: 91 frames at 33 ms instead of 75 at 40 ms on the same host, and less CPU on a static screen. The consumers (lvgl-micropython, lvgl-python, lvgl-circuitpython) sync this file with their scripts/sync_from_lvgl_bindings.sh after the merge.

Companions: PyDevices/pydevices-examples#149, PyDevices/micropython-pydevices#24. Draft until the hardware phases are in the ledger.

…o come back

One ONE_SHOT timer runs lv.timer_handler() and re-arms itself to what LVGL
returns, bounded by LVGL_PERIOD_MS and the yield rule from #15 and #19. A
PARTIAL panel presents from the display's own frame clock, only after a
flush; LVGL's refresh timer is set to the display's frame period. The async
mode and the App-timer subscription go: multimer delivers on the host's own
loop where the host owns one. Driver construction runs under
multimer.hold(), so no callback can land while draw buffers are created.
LVGL presents once per frame from the frame clock and reads its indevs
itself; the App's refresh timer and service tick stand down while the
driver runs (pause_refresh, pause_polling) and come back at shutdown.
… ask; tests on the new loop

A pass that took longer than the delay LVGL asked for ("again now" after
any real work counts) is followed by at least one period and, when it was
longer than a period, by min(pass, max_yield_ms). The starvation test runs
the real _on_timer on a virtual clock with a stand-in multimer Timer, keeps
the #15 and #19 cases, adds the calibration (the model reproduces the
board's ~87 % without the hold) and a check that an idle LVGL is run when it
asks, not on a poll. The nesting test gains the same stand-in.
The 10 ms lvgl.host_pump timer drains a host window's event queue for
every driver's virtual devices. A panel with a touch controller has
none, and on the T-Embed the timer fired 100 times a second for an
empty loop (multimer.report() showed it beside the real ones). It is
created only when a driver has virtual devices.
@bdbarnett
bdbarnett marked this pull request as ready for review September 26, 2026 11:16
@bdbarnett
bdbarnett merged commit 85907b7 into main Sep 26, 2026
4 checks passed
@bdbarnett
bdbarnett deleted the timing-redesign branch September 26, 2026 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant