From 102f22f7e99d87989cd95e10370863c3f96ba7e2 Mon Sep 17 00:00:00 2001 From: jpe230 Date: Mon, 12 Dec 2022 14:51:14 -0600 Subject: [Core] Quantum Painter - LVGL Integration (#18499) Co-authored-by: Nick Brassel --- quantum/painter/lvgl/qp_lvgl.c | 144 ++++++++++++++++++++++++++++++++++++++++ quantum/painter/lvgl/qp_lvgl.h | 25 +++++++ quantum/painter/lvgl/rules.mk | 24 +++++++ quantum/painter/qp.h | 7 ++ quantum/painter/qp_draw_image.c | 12 ++++ quantum/painter/rules.mk | 6 ++ 6 files changed, 218 insertions(+) create mode 100644 quantum/painter/lvgl/qp_lvgl.c create mode 100644 quantum/painter/lvgl/qp_lvgl.h create mode 100644 quantum/painter/lvgl/rules.mk (limited to 'quantum/painter') diff --git a/quantum/painter/lvgl/qp_lvgl.c b/quantum/painter/lvgl/qp_lvgl.c new file mode 100644 index 0000000000..41ca3f98c2 --- /dev/null +++ b/quantum/painter/lvgl/qp_lvgl.c @@ -0,0 +1,144 @@ +// Copyright 2022 Jose Pablo Ramirez (@jpe230) +// Copyright 2022 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "qp_lvgl.h" +#include "timer.h" +#include "deferred_exec.h" +#include "lvgl.h" + +typedef struct lvgl_state_t { + uint8_t fnc_id; // Ideally this should be the pointer of the function to run + uint16_t delay_ms; + deferred_token defer_token; +} lvgl_state_t; + +static deferred_executor_t lvgl_executors[2] = {0}; // For lv_tick_inc and lv_task_handler +static lvgl_state_t lvgl_states[2] = {0}; // For lv_tick_inc and lv_task_handler + +painter_device_t selected_display = NULL; +void * color_buffer = NULL; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter LVGL Integration Internal: qp_lvgl_flush + +void qp_lvgl_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { + if (selected_display) { + uint32_t number_pixels = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); + qp_viewport(selected_display, area->x1, area->y1, area->x2, area->y2); + qp_pixdata(selected_display, (void *)color_p, number_pixels); + qp_flush(selected_display); + lv_disp_flush_ready(disp); + } +} + +static uint32_t tick_task_callback(uint32_t trigger_time, void *cb_arg) { + lvgl_state_t * state = (lvgl_state_t *)cb_arg; + static uint32_t last_tick = 0; + switch (state->fnc_id) { + case 0: { + uint32_t now = timer_read32(); + lv_tick_inc(TIMER_DIFF_32(now, last_tick)); + last_tick = now; + } break; + case 1: + lv_task_handler(); + break; + + default: + break; + } + + // The tasks should run indefinitely + return state->delay_ms; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter LVGL Integration API: qp_lvgl_attach + +bool qp_lvgl_attach(painter_device_t device) { + qp_dprintf("qp_lvgl_start: entry\n"); + qp_lvgl_detach(); + + struct painter_driver_t *driver = (struct painter_driver_t *)device; + if (!driver->validate_ok) { + qp_dprintf("qp_lvgl_attach: fail (validation_ok == false)\n"); + qp_lvgl_detach(); + return false; + } + + // Setting up the tasks + lvgl_state_t *lv_tick_inc_state = &lvgl_states[0]; + lv_tick_inc_state->fnc_id = 0; + lv_tick_inc_state->delay_ms = 1; + lv_tick_inc_state->defer_token = defer_exec_advanced(lvgl_executors, 2, 1, tick_task_callback, lv_tick_inc_state); + + if (lv_tick_inc_state->defer_token == INVALID_DEFERRED_TOKEN) { + qp_dprintf("qp_lvgl_attach: fail (could not set up qp_lvgl executor)\n"); + qp_lvgl_detach(); + return false; + } + + lvgl_state_t *lv_task_handler_state = &lvgl_states[1]; + lv_task_handler_state->fnc_id = 1; + lv_task_handler_state->delay_ms = 5; + lv_task_handler_state->defer_token = defer_exec_advanced(lvgl_executors, 2, 5, tick_task_callback, lv_task_handler_state); + + if (lv_task_handler_state->defer_token == INVALID_DEFERRED_TOKEN) { + qp_dprintf("qp_lvgl_attach: fail (could not set up qp_lvgl executor)\n"); + qp_lvgl_detach(); + return false; + } + + // Init LVGL + lv_init(); + + // Set up lvgl display buffer + static lv_disp_draw_buf_t draw_buf; + // Allocate a buffer for 1/10 screen size + const size_t count_required = driver->panel_width * driver->panel_height / 10; + color_buffer = color_buffer ? realloc(color_buffer, sizeof(lv_color_t) * count_required) : malloc(sizeof(lv_color_t) * count_required); + if (!color_buffer) { + qp_dprintf("qp_lvgl_attach: fail (could not set up memory buffer)\n"); + qp_lvgl_detach(); + return false; + } + memset(color_buffer, 0, sizeof(lv_color_t) * count_required); + // Initialize the display buffer. + lv_disp_draw_buf_init(&draw_buf, color_buffer, NULL, count_required); + + selected_display = device; + + // Setting up display driver + static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/ + lv_disp_drv_init(&disp_drv); /*Basic initialization*/ + disp_drv.flush_cb = qp_lvgl_flush; /*Set your driver function*/ + disp_drv.draw_buf = &draw_buf; /*Assign the buffer to the display*/ + disp_drv.hor_res = driver->panel_width; /*Set the horizontal resolution of the display*/ + disp_drv.ver_res = driver->panel_height; /*Set the vertical resolution of the display*/ + lv_disp_drv_register(&disp_drv); /*Finally register the driver*/ + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter LVGL Integration API: qp_lvgl_detach + +void qp_lvgl_detach(void) { + for (int i = 0; i < 2; ++i) { + cancel_deferred_exec_advanced(lvgl_executors, 2, lvgl_states[i].defer_token); + } + if (color_buffer) { + free(color_buffer); + color_buffer = NULL; + } + selected_display = NULL; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter LVGL Integration Internal: qp_lvgl_internal_tick + +void qp_lvgl_internal_tick(void) { + static uint32_t last_lvgl_exec = 0; + deferred_exec_advanced_task(lvgl_executors, 2, &last_lvgl_exec); +} diff --git a/quantum/painter/lvgl/qp_lvgl.h b/quantum/painter/lvgl/qp_lvgl.h new file mode 100644 index 0000000000..d9ad5e8df1 --- /dev/null +++ b/quantum/painter/lvgl/qp_lvgl.h @@ -0,0 +1,25 @@ +// Copyright 2022 Jose Pablo Ramirez (@jpe230) +// Copyright 2022 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "qp.h" +#include "lvgl.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter - LVGL External API + +/** + * Sets up LVGL with the supplied display. + * + * @param device[in] the handle of the device to control + * @return true if init. of LVGL succeeded + * @return false if init. of LVGL failed + */ +bool qp_lvgl_attach(painter_device_t device); + +/** + * Disconnects LVGL from any attached display + */ +void qp_lvgl_detach(void); diff --git a/quantum/painter/lvgl/rules.mk b/quantum/painter/lvgl/rules.mk new file mode 100644 index 0000000000..50226941b3 --- /dev/null +++ b/quantum/painter/lvgl/rules.mk @@ -0,0 +1,24 @@ +# LVGL Integration + +OPT_DEFS += -DQUANTUM_PAINTER_LVGL_INTEGRATION_ENABLE -DLV_CONF_INCLUDE_SIMPLE +DEFERRED_EXEC_ENABLE := yes + +LVGL_DIR_NAME = lvgl +LVGL_DIR = $(LIB_DIR) +LVGL_PATH = $(LVGL_DIR)/$(LVGL_DIR_NAME) + +COMMON_VPATH += $(PLATFORM_PATH) \ + $(QUANTUM_DIR)/painter/$(LVGL_DIR_NAME) \ + $(LVGL_PATH) + +include $(LVGL_PATH)/src/extra/extra.mk +include $(LVGL_PATH)/src/core/lv_core.mk +include $(LVGL_PATH)/src/draw/lv_draw.mk +include $(LVGL_PATH)/src/draw/sw/lv_draw_sw.mk +include $(LVGL_PATH)/src/font/lv_font.mk +include $(LVGL_PATH)/src/hal/lv_hal.mk +include $(LVGL_PATH)/src/misc/lv_misc.mk +include $(LVGL_PATH)/src/widgets/lv_widgets.mk + +SRC += qp_lvgl.c \ + $(CSRCS) diff --git a/quantum/painter/qp.h b/quantum/painter/qp.h index 69bc435961..e5f595d71d 100644 --- a/quantum/painter/qp.h +++ b/quantum/painter/qp.h @@ -463,3 +463,10 @@ int16_t qp_drawtext_recolor(painter_device_t device, uint16_t x, uint16_t y, pai #ifdef QUANTUM_PAINTER_SSD1351_ENABLE # include "qp_ssd1351.h" #endif // QUANTUM_PAINTER_SSD1351_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter Extras + +#ifdef QUANTUM_PAINTER_LVGL_INTEGRATION_ENABLE +# include "qp_lvgl.h" +#endif // QUANTUM_PAINTER_LVGL_INTEGRATION_ENABLE diff --git a/quantum/painter/qp_draw_image.c b/quantum/painter/qp_draw_image.c index e9b975f23a..943cbfef5b 100644 --- a/quantum/painter/qp_draw_image.c +++ b/quantum/painter/qp_draw_image.c @@ -399,3 +399,15 @@ void qp_internal_animation_tick(void) { static uint32_t last_anim_exec = 0; deferred_exec_advanced_task(animation_executors, QUANTUM_PAINTER_CONCURRENT_ANIMATIONS, &last_anim_exec); } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter Core API: qp_internal_task + +void qp_internal_task(void) { + qp_internal_animation_tick(); +#ifdef QUANTUM_PAINTER_LVGL_INTEGRATION_ENABLE + // Run LVGL ticks + void qp_lvgl_internal_tick(void); + qp_lvgl_internal_tick(); +#endif +} diff --git a/quantum/painter/rules.mk b/quantum/painter/rules.mk index 5ac374a96e..199e406dd6 100644 --- a/quantum/painter/rules.mk +++ b/quantum/painter/rules.mk @@ -2,6 +2,8 @@ QUANTUM_PAINTER_DRIVERS ?= QUANTUM_PAINTER_ANIMATIONS_ENABLE ?= yes +QUANTUM_PAINTER_LVGL_INTEGRATION ?= no + # The list of permissible drivers that can be listed in QUANTUM_PAINTER_DRIVERS VALID_QUANTUM_PAINTER_DRIVERS := \ rgb565_surface \ @@ -152,3 +154,7 @@ ifeq ($(strip $(QUANTUM_PAINTER_NEEDS_COMMS_SPI)), yes) endif endif +# Check if LVGL needs to be enabled +ifeq ($(strip $(QUANTUM_PAINTER_LVGL_INTEGRATION)), yes) + include $(QUANTUM_DIR)/painter/lvgl/rules.mk +endif -- cgit v1.2.3