diff --git a/Kconfig b/Kconfig index bd532bdc066e..20fd5a53ebf5 100644 --- a/Kconfig +++ b/Kconfig @@ -1871,6 +1871,15 @@ menu "LVGL configuration" bool "Use Renesas GLCDC driver" default n + config LV_USE_ST_LTDC + bool "Driver for ST LTDC" + default n + + config LV_ST_LTDC_USE_DMA2D_FLUSH + bool "Only used for created partial mode LTDC displays" + default n + depends on LV_USE_ST_LTDC && !LV_USE_DRAW_DMA2D + config LV_USE_WINDOWS bool "Use LVGL Windows backend" depends on LV_OS_WINDOWS diff --git a/docs/integration/driver/display/index.rst b/docs/integration/driver/display/index.rst index 07e87259b730..4920eac775ca 100644 --- a/docs/integration/driver/display/index.rst +++ b/docs/integration/driver/display/index.rst @@ -13,3 +13,4 @@ Display st7789 st7796 renesas_glcdc + st_ltdc diff --git a/docs/integration/driver/display/st_ltdc.rst b/docs/integration/driver/display/st_ltdc.rst new file mode 100644 index 000000000000..c47c9ed94fbd --- /dev/null +++ b/docs/integration/driver/display/st_ltdc.rst @@ -0,0 +1,102 @@ +================= +STM32 LTDC Driver +================= + +Some STM32s have a specialized peripheral for driving +displays called LTDC (LCD-TFT display controller). + +Usage Modes With LVGL +********************* + +The driver within LVGL is designed to work with an +already-configured LTDC peripheral. It relies on the +HAL to detect information about the configuration. +The color format of the created LVGL display will +match the LTDC layer's color format. Use STM32CubeIDE +or STM32CubeMX to generate LTDC initialization code. + +There are some different use cases for LVGL's driver. +All permutations of the below options are well supported. + +- single or double buffered +- direct or partial render mode +- OS and no OS +- paralellized flushing with DMA2D (only for partial render mode) + +If OS is enabled, a synchronization primitive will be used to +give the thread a chance to yield to other threads while blocked, +improving CPU utilization. See :c:macro:`LV_USE_OS` in your lv_conf.h + +LTDC Layers +*********** + +This driver creates an LVGL display +which is only concerned with a specific layer of the LTDC peripheral, meaning +two LVGL LTDC displays can be created and operate independently on the separate +layers. + +Direct Render Mode +****************** + +For direct render mode, invoke :cpp:func:`lv_st_ltdc_create_direct` like this: + +.. code-block:: c + + void * my_ltdc_framebuffer_address = (void *)0x20000000u; + uint32_t my_ltdc_layer_index = 0; /* typically 0 or 1 */ + lv_display_t * disp = lv_st_ltdc_create_direct(my_ltdc_framebuffer_address, + optional_other_full_size_buffer, + my_ltdc_layer_index); + +``my_ltdc_framebuffer_address`` is the framebuffer configured for use by +LTDC. ``optional_other_full_size_buffer`` can be another buffer which is the same +size as the default framebuffer for double-buffered +mode, or ``NULL`` otherwise. ``my_ltdc_layer_index`` is the layer index of the +LTDC layer to create the display for. + +For the best visial results, ``optional_other_full_size_buffer`` should be used +if enough memory is available. Single-buffered mode is what you should use +if memory is very scarce. If there is almost enough memory for double-buffered +direct mode, but not quite, then use partial render mode. + +Partial Render Mode +******************* + +For partial render mode, invoke :cpp:func:`lv_st_ltdc_create_partial` like this: + +.. code-block:: c + + static uint8_t partial_buf1[65536]; + static uint8_t optional_partial_buf2[65536]; + uint32_t my_ltdc_layer_index = 0; /* typically 0 or 1 */ + lv_display_t * disp = lv_st_ltdc_create_partial(partial_buf1, + optional_partial_buf2, + 65536, + my_ltdc_layer_index); + +The driver will use the information in the LTDC layer configuration to find the +layer's framebuffer and flush to it. + +Providing a second partial buffer can improve CPU utilization and increase +performance compared to +a single buffer if :c:macro:`LV_ST_LTDC_USE_DMA2D_FLUSH` is enabled. + +DMA2D +***** + +:c:macro:`LV_ST_LTDC_USE_DMA2D_FLUSH` can be enabled to use DMA2D to flush +partial buffers in parallel with other LVGL tasks, whether or not OS is +enabled. If the display is not partial, then there is no need to enable this +option. + +It must not be enabled at the same time as :c:macro:`LV_USE_DRAW_DMA2D`. +See the :ref:`DMA2D support `. + +Further Reading +*************** + +You may be interested in enabling the :ref:`Nema GFX renderer ` +if your STM32 has a GPU which is supported by Nema GFX. + +`lv_port_riverdi_stm32u5 `__ +is a way to quick way to get started with LTDC on LVGL. diff --git a/lv_conf_template.h b/lv_conf_template.h index 6dabbad1257d..ae3c533d5e35 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -1157,6 +1157,13 @@ /** Driver for Renesas GLCD */ #define LV_USE_RENESAS_GLCDC 0 +/** Driver for ST LTDC */ +#define LV_USE_ST_LTDC 0 +#if LV_USE_ST_LTDC + /* Only used for partial. */ + #define LV_ST_LTDC_USE_DMA2D_FLUSH 0 +#endif + /** LVGL Windows backend */ #define LV_USE_WINDOWS 0 diff --git a/src/drivers/display/st_ltdc/lv_st_ltdc.c b/src/drivers/display/st_ltdc/lv_st_ltdc.c new file mode 100644 index 000000000000..a84e26886f77 --- /dev/null +++ b/src/drivers/display/st_ltdc/lv_st_ltdc.c @@ -0,0 +1,279 @@ +/** + * @file lv_st_ltdc.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "../../../lv_conf_internal.h" +#if LV_USE_ST_LTDC + +#include "lv_st_ltdc.h" +#include "../../../display/lv_display_private.h" +#include "ltdc.h" + +#if LV_ST_LTDC_USE_DMA2D_FLUSH + #if LV_USE_DRAW_DMA2D + #error cannot use LV_ST_LTDC_USE_DMA2D_FLUSH with LV_USE_DRAW_DMA2D + #endif /*LV_USE_DRAW_DMA2D*/ + + #include "dma2d.h" +#endif /*LV_ST_LTDC_USE_DMA2D_FLUSH*/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +#if LV_USE_OS != LV_OS_NONE + typedef lv_thread_sync_t sync_t; +#else + typedef volatile bool sync_t; +#endif + +/********************** + * STATIC PROTOTYPES + **********************/ + +static lv_display_t * create(void * buf1, void * buf2, uint32_t buf_size, uint32_t layer_idx, + lv_display_render_mode_t mode); +static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map); +static void flush_wait_cb(lv_display_t * disp); +static lv_color_format_t get_lv_cf_from_layer_cf(uint32_t cf); +static void reload_event_callback(LTDC_HandleTypeDef * hltdc); + +#if LV_ST_LTDC_USE_DMA2D_FLUSH + static void transfer_complete_callback(DMA2D_HandleTypeDef * hdma2d); + static uint32_t get_dma2d_output_cf_from_layer_cf(uint32_t cf); + static uint32_t get_dma2d_input_cf_from_lv_cf(uint32_t cf); +#endif + +/********************** + * STATIC VARIABLES + **********************/ + +static struct { + bool disp_flushed_in_flush_cb[MAX_LAYER]; + sync_t sync[MAX_LAYER]; + volatile bool layer_interrupt_is_owned[MAX_LAYER]; +#if LV_ST_LTDC_USE_DMA2D_FLUSH + volatile uint32_t dma2d_interrupt_owner; /*layer_idx + 1, or 0 for none*/ +#endif +} g_data; + +/********************** + * MACROS + **********************/ + +#if LV_USE_OS != LV_OS_NONE + #define SYNC_INIT(layer_idx) lv_thread_sync_init(&g_data.sync[layer_idx]) + #define SYNC_WAIT(layer_idx) lv_thread_sync_wait(&g_data.sync[layer_idx]) + #define SYNC_SIGNAL_ISR(layer_idx) lv_thread_sync_signal_isr(&g_data.sync[layer_idx]) +#else + #define SYNC_INIT(layer_idx) do { g_data.sync[layer_idx] = false; } while(0) + #define SYNC_WAIT(layer_idx) do { while(!g_data.sync[layer_idx]); g_data.sync[layer_idx] = false; } while(0) + #define SYNC_SIGNAL_ISR(layer_idx) do { g_data.sync[layer_idx] = true; } while(0) +#endif + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_display_t * lv_st_ltdc_create_direct(void * fb_adr_1, void * fb_adr_2, uint32_t layer_idx) +{ + return create(fb_adr_1, fb_adr_2, 0, layer_idx, LV_DISPLAY_RENDER_MODE_DIRECT); +} + +lv_display_t * lv_st_ltdc_create_partial(void * render_buf_1, void * render_buf_2, uint32_t buf_size, + uint32_t layer_idx) +{ + return create(render_buf_1, render_buf_2, buf_size, layer_idx, LV_DISPLAY_RENDER_MODE_PARTIAL); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static lv_display_t * create(void * buf1, void * buf2, uint32_t buf_size, uint32_t layer_idx, + lv_display_render_mode_t mode) +{ + LTDC_LayerCfgTypeDef * layer_cfg = &hltdc.LayerCfg[layer_idx]; + uint32_t layer_width = layer_cfg->ImageWidth; + uint32_t layer_height = layer_cfg->ImageHeight; + uint32_t layer_cf = layer_cfg->PixelFormat; + lv_color_format_t cf = get_lv_cf_from_layer_cf(layer_cf); + + lv_display_t * disp = lv_display_create(layer_width, layer_height); + lv_display_set_color_format(disp, cf); + lv_display_set_flush_cb(disp, flush_cb); + lv_display_set_flush_wait_cb(disp, flush_wait_cb); + lv_display_set_driver_data(disp, (void *)(uintptr_t)layer_idx); + + if(mode == LV_DISPLAY_RENDER_MODE_DIRECT) { + uint32_t cf_size = lv_color_format_get_size(cf); + lv_display_set_buffers(disp, buf1, buf2, layer_width * layer_height * cf_size, LV_DISPLAY_RENDER_MODE_DIRECT); + + if(buf1 != NULL && buf2 != NULL) { + HAL_LTDC_RegisterCallback(&hltdc, HAL_LTDC_RELOAD_EVENT_CB_ID, reload_event_callback); + SYNC_INIT(layer_idx); + } + } + else { + lv_display_set_buffers(disp, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL); + +#if LV_ST_LTDC_USE_DMA2D_FLUSH + hdma2d.XferCpltCallback = transfer_complete_callback; + SYNC_INIT(layer_idx); +#endif + } + + return disp; +} + +static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map) +{ + uint32_t layer_idx = (uint32_t)(uintptr_t)lv_display_get_driver_data(disp); + g_data.disp_flushed_in_flush_cb[layer_idx] = false; + + if(disp->render_mode == LV_DISPLAY_RENDER_MODE_DIRECT) { + if(lv_display_is_double_buffered(disp) && lv_display_flush_is_last(disp)) { + HAL_LTDC_SetAddress_NoReload(&hltdc, (uint32_t)px_map, layer_idx); + g_data.layer_interrupt_is_owned[layer_idx] = true; + HAL_LTDC_Reload(&hltdc, LTDC_RELOAD_VERTICAL_BLANKING); + } + else { + g_data.disp_flushed_in_flush_cb[layer_idx] = true; + } + } + else { + LTDC_LayerCfgTypeDef * layer_cfg = &hltdc.LayerCfg[layer_idx]; + + lv_color_format_t cf = lv_display_get_color_format(disp); + int32_t disp_width = lv_display_get_horizontal_resolution(disp); + + uint8_t * fb = (uint8_t *) layer_cfg->FBStartAdress; + uint32_t px_size = lv_color_format_get_size(cf); + uint32_t fb_stride = px_size * disp_width; + uint8_t * first_pixel = fb + fb_stride * area->y1 + px_size * area->x1; + + int32_t area_width = lv_area_get_width(area); + int32_t area_height = lv_area_get_height(area); + +#if LV_ST_LTDC_USE_DMA2D_FLUSH + uint32_t dma2d_input_cf = get_dma2d_input_cf_from_lv_cf(cf); + uint32_t dma2d_output_cf = get_dma2d_output_cf_from_layer_cf(layer_cfg->PixelFormat); + + while(DMA2D->CR & DMA2D_CR_START); + DMA2D->FGPFCCR = dma2d_input_cf; + DMA2D->FGMAR = (uint32_t)px_map; + DMA2D->FGOR = 0; + DMA2D->OPFCCR = dma2d_output_cf; + DMA2D->OMAR = (uint32_t)first_pixel; + DMA2D->OOR = disp_width - area_width; + DMA2D->NLR = (area_width << DMA2D_NLR_PL_Pos) | (area_height << DMA2D_NLR_NL_Pos); + g_data.dma2d_interrupt_owner = layer_idx + 1; + DMA2D->CR = DMA2D_CR_START | DMA2D_CR_TCIE | (0x1U << DMA2D_CR_MODE_Pos); /* memory-to-memory with PFC */ +#else + uint32_t area_stride = px_size * area_width; + uint8_t * fb_p = first_pixel; + uint8_t * px_map_p = px_map; + for(int i = 0; i < area_height; i++) { + lv_memcpy(fb_p, px_map_p, area_stride); + fb_p += fb_stride; + px_map_p += area_stride; + } + g_data.disp_flushed_in_flush_cb[layer_idx] = true; +#endif + } +} + +static void flush_wait_cb(lv_display_t * disp) +{ + uint32_t layer_idx = (uint32_t)(uintptr_t)lv_display_get_driver_data(disp); + if(!g_data.disp_flushed_in_flush_cb[layer_idx]) { + SYNC_WAIT(layer_idx); + } +} + +static lv_color_format_t get_lv_cf_from_layer_cf(uint32_t cf) +{ + switch(cf) { + case LTDC_PIXEL_FORMAT_ARGB8888: + return LV_COLOR_FORMAT_ARGB8888; + case LTDC_PIXEL_FORMAT_RGB888: + return LV_COLOR_FORMAT_RGB888; + case LTDC_PIXEL_FORMAT_RGB565: + return LV_COLOR_FORMAT_RGB565; + case LTDC_PIXEL_FORMAT_L8: + return LV_COLOR_FORMAT_L8; + case LTDC_PIXEL_FORMAT_AL88: + return LV_COLOR_FORMAT_AL88; + default: + LV_ASSERT_MSG(0, "the LTDC color format is not supported"); + } +} + +static void reload_event_callback(LTDC_HandleTypeDef * hltdc) +{ + uint32_t i; + for(i = 0; i < MAX_LAYER; i++) { + if(g_data.layer_interrupt_is_owned[i]) { + g_data.layer_interrupt_is_owned[i] = false; + SYNC_SIGNAL_ISR(i); + } + } +} + +#if LV_ST_LTDC_USE_DMA2D_FLUSH +static void transfer_complete_callback(DMA2D_HandleTypeDef * hdma2d) +{ + DMA2D->IFCR = 0x3FU; + uint32_t owner = g_data.dma2d_interrupt_owner; + if(owner) { + g_data.dma2d_interrupt_owner = 0; + owner -= 1; + SYNC_SIGNAL_ISR(owner); + } +} + +static uint32_t get_dma2d_output_cf_from_layer_cf(uint32_t cf) +{ + switch(cf) { + case LTDC_PIXEL_FORMAT_ARGB8888: + return DMA2D_OUTPUT_ARGB8888; + case LTDC_PIXEL_FORMAT_RGB888: + return DMA2D_OUTPUT_RGB888; + case LTDC_PIXEL_FORMAT_RGB565: + return DMA2D_OUTPUT_RGB565; + default: + LV_ASSERT_MSG(0, "DMA2D cannot output to the LTDC color format"); + } +} + +static uint32_t get_dma2d_input_cf_from_lv_cf(uint32_t cf) +{ + switch(cf) { + case LV_COLOR_FORMAT_ARGB8888: + return DMA2D_INPUT_ARGB8888; + case LV_COLOR_FORMAT_RGB888: + return DMA2D_INPUT_RGB888; + case LV_COLOR_FORMAT_RGB565: + return DMA2D_INPUT_RGB565; + case LV_COLOR_FORMAT_L8: + return DMA2D_INPUT_L8; + case LV_COLOR_FORMAT_AL88: + return DMA2D_INPUT_AL88; + case LV_COLOR_FORMAT_A8: + return DMA2D_INPUT_A8; + default: + LV_ASSERT_MSG(0, "the LVGL color format is not a DMA2D input color format"); + } +} +#endif /*LV_ST_LTDC_USE_DMA2D_FLUSH*/ + +#endif /*LV_USE_ST_LTDC*/ diff --git a/src/drivers/display/st_ltdc/lv_st_ltdc.h b/src/drivers/display/st_ltdc/lv_st_ltdc.h new file mode 100644 index 000000000000..69a0e7b7196d --- /dev/null +++ b/src/drivers/display/st_ltdc/lv_st_ltdc.h @@ -0,0 +1,65 @@ +/** + * @file lv_st_ltdc.h + * + */ + +#ifndef LV_ST_LTDC_H +#define LV_ST_LTDC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../../lv_conf_internal.h" +#if LV_USE_ST_LTDC + +#include "../../../display/lv_display.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a direct render mode display bound to a LTDC layer. + * @param fb_adr_1 The LTDC layer's framebuffer memory address. + * @param fb_adr_2 An additional framebuffer-sized buffer to use for double buffering, or `NULL`. + * @param layer_idx The LTDC layer number to bind the display to. Typically 0 or 1. + * @return The display. + */ +lv_display_t * lv_st_ltdc_create_direct(void * fb_adr_1, void * fb_adr_2, uint32_t layer_idx); + +/** + * Create a partial render mode display bound to a LTDC layer. The layer's framebuffer is flushed to internally. + * Enable `LV_ST_LTDC_USE_DMA2D_FLUSH` for parallel flushing. + * @param render_buf_1 A render buffer. + * @param render_buf_2 An additional render buffer for double-buffering, or `NULL`. + * @param buf_size The size of the buffer(s) in bytes. + * @param layer_idx The LTDC layer number to bind the display to. Typically 0 or 1. + * @return The display. + */ +lv_display_t * lv_st_ltdc_create_partial(void * render_buf_1, void * render_buf_2, uint32_t buf_size, + uint32_t layer_idx); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_ST_LTDC*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_ST_LTDC_H*/ diff --git a/src/drivers/lv_drivers.h b/src/drivers/lv_drivers.h index c2481a9c2fd7..621f15654153 100644 --- a/src/drivers/lv_drivers.h +++ b/src/drivers/lv_drivers.h @@ -32,6 +32,7 @@ extern "C" { #include "display/st7796/lv_st7796.h" #include "display/renesas_glcdc/lv_renesas_glcdc.h" +#include "display/st_ltdc/lv_st_ltdc.h" #include "nuttx/lv_nuttx_entry.h" #include "nuttx/lv_nuttx_fbdev.h" diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index 237accb32af5..1044b3a75c14 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -3746,6 +3746,25 @@ #endif #endif +/** Driver for ST LTDC */ +#ifndef LV_USE_ST_LTDC + #ifdef CONFIG_LV_USE_ST_LTDC + #define LV_USE_ST_LTDC CONFIG_LV_USE_ST_LTDC + #else + #define LV_USE_ST_LTDC 0 + #endif +#endif +#if LV_USE_ST_LTDC + /* Only used for partial. */ + #ifndef LV_ST_LTDC_USE_DMA2D_FLUSH + #ifdef CONFIG_LV_ST_LTDC_USE_DMA2D_FLUSH + #define LV_ST_LTDC_USE_DMA2D_FLUSH CONFIG_LV_ST_LTDC_USE_DMA2D_FLUSH + #else + #define LV_ST_LTDC_USE_DMA2D_FLUSH 0 + #endif + #endif +#endif + /** LVGL Windows backend */ #ifndef LV_USE_WINDOWS #ifdef CONFIG_LV_USE_WINDOWS