Skip to content

Commit 0be9445

Browse files
JarmouniAkartben
authored andcommitted
modules: lvgl: add multi-display support in Zephyr
Add multi-display support in Zephyr ontop of LVGL which already supports it. This change allows for creating buffers and structures for each display statically and automatically from deviceTree info, and call LV init routines for each display Signed-off-by: Abderrahmane JARMOUNI <git@jarmouni.me>
1 parent cf30cfa commit 0be9445

File tree

2 files changed

+122
-64
lines changed

2 files changed

+122
-64
lines changed

modules/lvgl/include/lvgl_zephyr.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright (c) 2024 Fabian Blatz <fabianblatz@gmail.com>
3+
* Copyright (c) 2025 Abderrahmane JARMOUNI
34
*
45
* SPDX-License-Identifier: Apache-2.0
56
*/
@@ -9,6 +10,14 @@
910

1011
#include <zephyr/kernel.h>
1112

13+
#if DT_ZEPHYR_DISPLAYS_COUNT == 0
14+
#error Could not find "zephyr,display" chosen property, or "zephyr,displays" compatible node in DT
15+
#endif /* DT_ZEPHYR_DISPLAYS_COUNT == 0 */
16+
17+
#define LV_DISPLAY_IDX_MACRO(i, _) _##i
18+
19+
#define LV_DISPLAYS_IDX_LIST LISTIFY(DT_ZEPHYR_DISPLAYS_COUNT, LV_DISPLAY_IDX_MACRO, (,))
20+
1221
#ifdef __cplusplus
1322
extern "C" {
1423
#endif

modules/lvgl/lvgl.c

Lines changed: 113 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright (c) 2018-2019 Jan Van Winkel <jan.van_winkel@dxplore.eu>
3+
* Copyright (c) 2025 Abderrahmane JARMOUNI
34
*
45
* SPDX-License-Identifier: Apache-2.0
56
*/
@@ -21,58 +22,86 @@
2122
#include <zephyr/logging/log.h>
2223
LOG_MODULE_REGISTER(lvgl, CONFIG_LV_Z_LOG_LEVEL);
2324

24-
static lv_display_t *display;
25-
struct lvgl_disp_data disp_data = {
25+
static lv_display_t *lv_displays[DT_ZEPHYR_DISPLAYS_COUNT];
26+
struct lvgl_disp_data disp_data[DT_ZEPHYR_DISPLAYS_COUNT] = {{
2627
.blanking_on = false,
27-
};
28+
}};
29+
30+
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_displays)
31+
#define DISPLAY_NODE(n) DT_ZEPHYR_DISPLAY(n)
32+
#elif DT_HAS_CHOSEN(zephyr_display)
33+
#define DISPLAY_NODE(n) DT_CHOSEN(zephyr_display)
34+
#else
35+
#error Could not find "zephyr,display" chosen property, or a "zephyr,displays" compatible node in DT
36+
#define DISPLAY_NODE(n) DT_INVALID_NODE
37+
#endif
38+
39+
#define IS_MONOCHROME_DISPLAY \
40+
UTIL_OR(IS_EQ(CONFIG_LV_Z_BITS_PER_PIXEL, 1), IS_EQ(CONFIG_LV_COLOR_DEPTH_1, 1))
2841

29-
#define DISPLAY_NODE DT_CHOSEN(zephyr_display)
30-
#define IS_MONOCHROME_DISPLAY ((CONFIG_LV_Z_BITS_PER_PIXEL == 1) || (CONFIG_LV_COLOR_DEPTH_1 == 1))
3142
#define ALLOC_MONOCHROME_CONV_BUFFER \
32-
((IS_MONOCHROME_DISPLAY == 1) && (CONFIG_LV_Z_MONOCHROME_CONVERSION_BUFFER == 1))
43+
UTIL_AND(IS_EQ(IS_MONOCHROME_DISPLAY, 1), \
44+
IS_EQ(CONFIG_LV_Z_MONOCHROME_CONVERSION_BUFFER, 1))
3345

3446
#ifdef CONFIG_LV_Z_BUFFER_ALLOC_STATIC
3547

36-
#define DISPLAY_WIDTH DT_PROP(DISPLAY_NODE, width)
37-
#define DISPLAY_HEIGHT DT_PROP(DISPLAY_NODE, height)
48+
#define DISPLAY_WIDTH(n) DT_PROP(DISPLAY_NODE(n), width)
49+
#define DISPLAY_HEIGHT(n) DT_PROP(DISPLAY_NODE(n), height)
3850

3951
#if IS_MONOCHROME_DISPLAY
4052
/* monochrome buffers are expected to have 8 preceding bytes for the color palette */
41-
#define BUFFER_SIZE \
42-
(((CONFIG_LV_Z_VDB_SIZE * ROUND_UP(DISPLAY_WIDTH, 8) * ROUND_UP(DISPLAY_HEIGHT, 8)) / \
53+
#define BUFFER_SIZE(n) \
54+
(((CONFIG_LV_Z_VDB_SIZE * ROUND_UP(DISPLAY_WIDTH(n), 8) * \
55+
ROUND_UP(DISPLAY_HEIGHT(n), 8)) / \
4356
100) / 8 + \
4457
8)
4558
#else
46-
#define BUFFER_SIZE \
59+
#define BUFFER_SIZE(n) \
4760
(CONFIG_LV_Z_BITS_PER_PIXEL * \
48-
((CONFIG_LV_Z_VDB_SIZE * DISPLAY_WIDTH * DISPLAY_HEIGHT) / 100) / 8)
61+
((CONFIG_LV_Z_VDB_SIZE * DISPLAY_WIDTH(n) * DISPLAY_HEIGHT(n)) / 100) / 8)
4962
#endif /* IS_MONOCHROME_DISPLAY */
5063

51-
/* NOTE: depending on chosen color depth buffer may be accessed using uint8_t *,
52-
* uint16_t * or uint32_t *, therefore buffer needs to be aligned accordingly to
53-
* prevent unaligned memory accesses.
54-
*/
55-
static uint8_t buf0[BUFFER_SIZE]
56-
#ifdef CONFIG_LV_Z_VDB_CUSTOM_SECTION
57-
Z_GENERIC_SECTION(.lvgl_buf)
58-
#endif
59-
__aligned(CONFIG_LV_Z_VDB_ALIGN);
64+
static uint32_t disp_buf_size[DT_ZEPHYR_DISPLAYS_COUNT] = {0};
65+
static uint8_t *buf0_p[DT_ZEPHYR_DISPLAYS_COUNT] = {NULL};
6066

6167
#ifdef CONFIG_LV_Z_DOUBLE_VDB
62-
static uint8_t buf1[BUFFER_SIZE]
63-
#ifdef CONFIG_LV_Z_VDB_CUSTOM_SECTION
64-
Z_GENERIC_SECTION(.lvgl_buf)
68+
static uint8_t *buf1_p[DT_ZEPHYR_DISPLAYS_COUNT] = {NULL};
6569
#endif
66-
__aligned(CONFIG_LV_Z_VDB_ALIGN);
67-
#endif /* CONFIG_LV_Z_DOUBLE_VDB */
6870

6971
#if ALLOC_MONOCHROME_CONV_BUFFER
70-
static uint8_t mono_vtile_buf[BUFFER_SIZE]
71-
#ifdef CONFIG_LV_Z_VDB_CUSTOM_SECTION
72-
Z_GENERIC_SECTION(.lvgl_buf)
72+
static uint8_t *mono_vtile_buf_p[DT_ZEPHYR_DISPLAYS_COUNT] = {NULL};
7373
#endif
74-
__aligned(CONFIG_LV_Z_VDB_ALIGN);
75-
#endif /* ALLOC_MONOCHROME_CONV_BUFFER */
74+
75+
/* NOTE: depending on chosen color depth, buffers may be accessed using uint8_t *,*/
76+
/* uint16_t * or uint32_t *, therefore buffer needs to be aligned accordingly to */
77+
/* prevent unaligned memory accesses. */
78+
79+
/* clang-format off */
80+
#define LV_BUFFERS_DEFINE(n) \
81+
static uint8_t buf0_##n[BUFFER_SIZE(n)] \
82+
IF_ENABLED(CONFIG_LV_Z_VDB_CUSTOM_SECTION, (Z_GENERIC_SECTION(.lvgl_buf))) \
83+
__aligned(CONFIG_LV_Z_VDB_ALIGN); \
84+
\
85+
IF_ENABLED(CONFIG_LV_Z_DOUBLE_VDB, ( \
86+
static uint8_t buf1_##n[BUFFER_SIZE(n)] \
87+
IF_ENABLED(CONFIG_LV_Z_VDB_CUSTOM_SECTION, (Z_GENERIC_SECTION(.lvgl_buf))) \
88+
__aligned(CONFIG_LV_Z_VDB_ALIGN); \
89+
)) \
90+
\
91+
IF_ENABLED(ALLOC_MONOCHROME_CONV_BUFFER, ( \
92+
static uint8_t mono_vtile_buf_##n[BUFFER_SIZE(n)] \
93+
IF_ENABLED(CONFIG_LV_Z_VDB_CUSTOM_SECTION, (Z_GENERIC_SECTION(.lvgl_buf))) \
94+
__aligned(CONFIG_LV_Z_VDB_ALIGN); \
95+
))
96+
97+
FOR_EACH(LV_BUFFERS_DEFINE, (), LV_DISPLAYS_IDX_LIST);
98+
99+
#define LV_BUFFERS_REFERENCES(n) \
100+
disp_buf_size[n] = (uint32_t)BUFFER_SIZE(n); \
101+
buf0_p[n] = buf0_##n; \
102+
IF_ENABLED(CONFIG_LV_Z_DOUBLE_VDB, (buf1_p[n] = buf1_##n;)) \
103+
IF_ENABLED(ALLOC_MONOCHROME_CONV_BUFFER, (mono_vtile_buf_p[n] = mono_vtile_buf_##n;))
104+
/* clang-format on */
76105

77106
#endif /* CONFIG_LV_Z_BUFFER_ALLOC_STATIC */
78107

@@ -101,21 +130,19 @@ static void lvgl_log(lv_log_level_t level, const char *buf)
101130

102131
#ifdef CONFIG_LV_Z_BUFFER_ALLOC_STATIC
103132

104-
static int lvgl_allocate_rendering_buffers(lv_display_t *display)
133+
static void lvgl_allocate_rendering_buffers_static(lv_display_t *display, int disp_idx)
105134
{
106-
int err = 0;
107-
108135
#ifdef CONFIG_LV_Z_DOUBLE_VDB
109-
lv_display_set_buffers(display, &buf0, &buf1, BUFFER_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);
136+
lv_display_set_buffers(display, buf0_p[disp_idx], buf1_p[disp_idx], disp_buf_size[disp_idx],
137+
LV_DISPLAY_RENDER_MODE_PARTIAL);
110138
#else
111-
lv_display_set_buffers(display, &buf0, NULL, BUFFER_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);
112-
#endif /* CONFIG_LV_Z_DOUBLE_VDB */
139+
lv_display_set_buffers(display, buf0_p[disp_idx], NULL, disp_buf_size[disp_idx],
140+
LV_DISPLAY_RENDER_MODE_PARTIAL);
141+
#endif /* CONFIG_LV_Z_DOUBLE_VDB */
113142

114143
#if ALLOC_MONOCHROME_CONV_BUFFER
115-
lvgl_set_mono_conversion_buffer(mono_vtile_buf, BUFFER_SIZE);
116-
#endif /* ALLOC_MONOCHROME_CONV_BUFFER */
117-
118-
return err;
144+
lvgl_set_mono_conversion_buffer(mono_vtile_buf_p[disp_idx], disp_buf_size[disp_idx]);
145+
#endif
119146
}
120147

121148
#else
@@ -224,15 +251,22 @@ lv_result_t lv_mem_test_core(void)
224251
return LV_RESULT_OK;
225252
}
226253

254+
#define ENUMERATE_DISPLAY_DEVS(n) display_dev[n] = DEVICE_DT_GET(DISPLAY_NODE(n));
255+
227256
int lvgl_init(void)
228257
{
229-
const struct device *display_dev = DEVICE_DT_GET(DISPLAY_NODE);
230-
231-
int err = 0;
232-
233-
if (!device_is_ready(display_dev)) {
234-
LOG_ERR("Display device not ready.");
235-
return -ENODEV;
258+
const struct device *display_dev[DT_ZEPHYR_DISPLAYS_COUNT];
259+
struct lvgl_disp_data *p_disp_data;
260+
int err;
261+
262+
/* clang-format off */
263+
FOR_EACH(ENUMERATE_DISPLAY_DEVS, (), LV_DISPLAYS_IDX_LIST);
264+
/* clang-format on */
265+
for (int i = 0; i < DT_ZEPHYR_DISPLAYS_COUNT; i++) {
266+
if (!device_is_ready(display_dev[i])) {
267+
LOG_ERR("Display device %d is not ready", i);
268+
return -ENODEV;
269+
}
236270
}
237271

238272
#if CONFIG_LV_Z_LOG_LEVEL != 0
@@ -246,28 +280,43 @@ int lvgl_init(void)
246280
lvgl_fs_init();
247281
#endif
248282

249-
disp_data.display_dev = display_dev;
250-
display_get_capabilities(display_dev, &disp_data.cap);
283+
#ifdef CONFIG_LV_Z_BUFFER_ALLOC_STATIC
284+
/* clang-format off */
285+
FOR_EACH(LV_BUFFERS_REFERENCES, (), LV_DISPLAYS_IDX_LIST);
286+
/* clang-format on */
287+
#endif
251288

252-
display = lv_display_create(disp_data.cap.x_resolution, disp_data.cap.y_resolution);
253-
if (!display) {
254-
return -ENOMEM;
255-
}
256-
lv_display_set_user_data(display, &disp_data);
289+
for (int i = 0; i < DT_ZEPHYR_DISPLAYS_COUNT; i++) {
290+
p_disp_data = &disp_data[i];
291+
p_disp_data->display_dev = display_dev[i];
292+
display_get_capabilities(display_dev[i], &p_disp_data->cap);
257293

258-
if (set_lvgl_rendering_cb(display) != 0) {
259-
LOG_ERR("Display not supported.");
260-
return -ENOTSUP;
261-
}
294+
lv_displays[i] = lv_display_create(p_disp_data->cap.x_resolution,
295+
p_disp_data->cap.y_resolution);
296+
if (!lv_displays[i]) {
297+
LOG_ERR("Failed to create display %d LV object.", i);
298+
return -ENOMEM;
299+
}
262300

263-
err = lvgl_allocate_rendering_buffers(display);
264-
if (err != 0) {
265-
return err;
266-
}
301+
lv_display_set_user_data(lv_displays[i], p_disp_data);
302+
if (set_lvgl_rendering_cb(lv_displays[i]) != 0) {
303+
LOG_ERR("Display %d not supported.", i);
304+
return -ENOTSUP;
305+
}
306+
307+
#ifdef CONFIG_LV_Z_BUFFER_ALLOC_STATIC
308+
lvgl_allocate_rendering_buffers_static(lv_displays[i], i);
309+
#else
310+
err = lvgl_allocate_rendering_buffers(lv_displays[i]);
311+
if (err < 0) {
312+
return err;
313+
}
314+
#endif
267315

268316
#ifdef CONFIG_LV_Z_FULL_REFRESH
269-
lv_display_set_render_mode(display, LV_DISPLAY_RENDER_MODE_FULL);
317+
lv_display_set_render_mode(lv_displays[i], LV_DISPLAY_RENDER_MODE_FULL);
270318
#endif
319+
}
271320

272321
err = lvgl_init_input_devices();
273322
if (err < 0) {

0 commit comments

Comments
 (0)