feat(drivers): add STM32 LTDC support (#7059)

This commit is contained in:
Liam 2024-10-15 12:05:19 +02:00 committed by GitHub
parent 4a75a62fe2
commit 09d9c575b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 483 additions and 0 deletions

View File

@ -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

View File

@ -13,3 +13,4 @@ Display
st7789
st7796
renesas_glcdc
st_ltdc

View File

@ -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 <dma2d>`.
Further Reading
***************
You may be interested in enabling the :ref:`Nema GFX renderer <stm32_nema_gfx>`
if your STM32 has a GPU which is supported by Nema GFX.
`lv_port_riverdi_stm32u5 <https://github.com/lvgl/lv_port_riverdi_stm32u5>`__
is a way to quick way to get started with LTDC on LVGL.

View File

@ -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

View File

@ -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*/

View File

@ -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*/

View File

@ -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"

View File

@ -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