Skip to main content

<alp/gui.h> — LVGL Re-export

Re-exports the upstream LVGL library with Alp defaults applied so LVGL widgets work out of the box on any E1M target, and provides alp_gui_lvgl_attach() — the bridge that binds an alp_display_t to LVGL's display driver.

No custom widgets ship: the widget vocabulary is upstream LVGL's, and this header is the portable binding to Alp's display surface.

#include <alp/gui.h> // pulls in <lvgl.h> with Alp defaults

Including <alp/gui.h> requires the LVGL package to be on the include path — this is a build option, not bundled source.

Enabling LVGL

BuildHow
ZephyrSet CONFIG_LVGL=y. CONFIG_ALP_SDK_HAS_LVGL then defaults on and the build defines ALP_HAS_LVGL automatically. The lvgl board.yaml library knob selects the module.
Plain CMake / YoctoSet -DALP_HAS_LVGL=ON and supply the LVGL include path yourself.
libraries:
- lvgl

The SDK ships a profile header that pre-tunes LVGL for embedded display sizes and 16-bit colour. Override by dropping your own profile at the app's include root.

When the build has no LVGL wired in, the symbol still links — alp_gui_lvgl_attach() degrades to ALP_ERR_INVAL on a NULL display and ALP_ERR_NOSUPPORT otherwise, so portable code compiles either way.

Attach a display

The app owns the whole sequence: open the panel → lv_init() → wire the tick → attach.

#include <alp/display.h>
#include <alp/gui.h>

// 1. Open the panel through the portable display surface.
alp_display_config_t display_cfg = ALP_DISPLAY_CONFIG_DEFAULT(0);
alp_display_t *display = alp_display_open(&display_cfg);
if (display == NULL) {
return (int)alp_last_error();
}

// 2. Allocate LVGL's global state (styles, theme, timer list).
lv_init();

// 3. LVGL needs a monotonic millisecond tick for animations and timers.
lv_tick_set_cb(k_uptime_get_32);

// 4. Bind LVGL's renderer to the panel.
if (alp_gui_lvgl_attach(display) != ALP_OK) {
return -1;
}

lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Hello, E1M!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

while (1) {
lv_task_handler();
k_msleep(5);
}

alp_gui_lvgl_attach() allocates an lv_display_t (LVGL v9) sized to the display's reported geometry, wires its flush callback to alp_display_blit(), and allocates a persistent partial-refresh draw buffer. Call lv_init() once before attaching any display.

:::warning Turn Zephyr's LVGL auto-init off Set CONFIG_LV_Z_AUTO_INIT=n in prj.conf. Left on, Zephyr's own lvgl module creates its own lv_display_t bound straight to the zephyr,display chosen node the moment CONFIG_LVGL=y is set — a second writer racing alp_gui_lvgl_attach()'s display for the same panel.

With auto-init off, that module's SYS_INIT hook never runs, which is also why the app must call lv_init() and lv_tick_set_cb() itself — same work, just moved to the app side of the auto-init boundary. :::

ReturnsWhen
ALP_OKBridge installed.
ALP_ERR_INVALdisplay is NULL.
ALP_ERR_NOSUPPORTBuild not compiled with ALP_HAS_LVGL, or the display's pixel format has no LVGL v9 equivalent.
ALP_ERR_NOMEMlv_display_create or the draw-buffer allocation failed.
(propagated)Any error alp_display_get_caps returns.

Pixel formats

alp_pixfmt_tLVGL v9
ALP_PIXFMT_RGB565Supported
ALP_PIXFMT_RGB888Supported
ALP_PIXFMT_ARGB8888Supported
ALP_PIXFMT_MONO_VLSBALP_ERR_NOSUPPORT
ALP_PIXFMT_YUV420_PLANARALP_ERR_NOSUPPORT
ALP_PIXFMT_NV12ALP_ERR_NOSUPPORT

ALP_PIXFMT_YUV420_PLANAR (4) and ALP_PIXFMT_NV12 (5) were appended to alp_pixfmt_t for <alp/jpeg.h>; they are encoder input formats, not framebuffer formats, and the LVGL bridge does not accept them.

MONO_VLSB has no LVGL match — LVGL's closest analogue is not the SSD1306-style vertical-byte packing that ALP_PIXFMT_MONO_VLSB denotes, so the bridge degrades to ALP_ERR_NOSUPPORT rather than claim a binding it would render incorrectly.

Draw-buffer sizing

The bridge allocates a persistent partial-refresh draw buffer (LV_DISPLAY_RENDER_MODE_PARTIAL). Its height in display lines is CONFIG_ALP_GUI_LVGL_BUF_LINES on Zephyr, and a compile-time default of 16 lines elsewhere (plain CMake / Yocto have no Kconfig).

Buffer size = panel width × lines × bytes-per-pixel, clamped to the panel's actual height. A bigger buffer amortises LVGL's per-flush overhead at the cost of heap; 16 lines is a conservative default (≤ 10 KB for a 320px-wide RGB565 panel).

Status

:::caution Not yet bench-proven on a real panel alp_gui_lvgl_attach() became a real LVGL v9 bridge in v0.10 — it was previously a stub that always returned ALP_ERR_NOSUPPORT. The bridge is code-complete and tested under native_sim, but validation against a real display panel is still pending. Treat it as functional-but-unproven on silicon until that bench run lands. :::

ABI status

[ABI-STABLE] — the re-export shim dates to v0.2 and the alp_gui_lvgl_attach() signature is unchanged. v0.10 changes the implementation from a stub to a working bridge; a build that previously got ALP_ERR_NOSUPPORT on a supported pixel format will now get ALP_OK.

See also

Questions about this page? Discuss in Community Forum