lvgl是十分受欢迎的开源嵌入式图形解决方案。以下对STM32H735G-DK适配lvgl进行介绍。
1、获取lvgl源码
从Github上拉取lvgl的源码。
https://github.com/lvgl/lvgl
2、配置开发板外设
参考官方提供的TouchGFX工程的外设设置,使用CubeMX创建基于开发板芯片型号的工程。

设置MUC的MPU(Memory Protection Unit)的地址空间访问设置。



RCC选项卡中选择外部晶振做为MCU的高速时钟源。

选择HAL库的时钟基准

触摸外设通过I2C4外设进行控制,设置I2C4的配置参数如下。

HyperRAM和HyperFlash外设通过板载的OSPI1和OSPI2控制,外设的配置参数如下。


使能DMA2D外设,用于图形加速。

设置LTDC外设的像素点类型和屏幕分辨率相关的刷新参数。

设置LTDC Layer的参数,其中的FrameBuffer StartAddress参数勾选No Check,从而可以设置任意的字符串,用于在代码中通过调用函数的方式分配其地址。

在Clock Configuration界面根据外设的需要,调整时钟树的配置。

配置完成后,和TouchGFX工程中的CubeMX外设参数进行对比,确认参数设置合理。生成相应的工程代码。
3、添加外设驱动代码和引用lvgl代码
MCU的外设参数配置完成后,需要添加外设芯片的驱动代码,官方提供的TouchGFX的工程中包含STM32H735G-DK板载外设的驱动代码,将TouchGFX工程的Drivers\BSP文件夹复制到工程中的Drivers\BSP文件夹中,同时复制Core\Inc中与外设配置相关的头文件。

lvgl代码通过文件夹链接的方式添加到工程中。

完成上述代码添加后,在工程的配置属性中,添加额外的头文件路径。

lvgl仓库中无关的代码可以通过源码路径过滤项将其排除在工程编译过程外。

4、添加自定义代码
lvgl组件的配置,通过lvgl_conf.h文件来确定。复制lvgl仓库顶层的lv_conf_template.h,并重命名为lvgl_conf.h,在此基础上修改配置。
需要修改的宏定义如下
/** Color depth: 1 (I1), 8 (L8), 16 (RGB565), 24 (RGB888), 32 (XRGB8888) */
#define LV_COLOR_DEPTH 32
/** Default display refresh, input device read and animation step period. */
#define LV_DEF_REFR_PERIOD 16
/** The target buffer size for simple layer chunks. */
#define LV_DRAW_LAYER_SIMPLE_BUF_SIZE (48 * 1024)
/*=====================
* COMPILER SETTINGS
*====================*/
/** A macro to generate the passed argument in double quotes */
#define STR(X) STR_I(X)
/** A macro to generate the passed argument in double quotes */
#define STR_I(X) #X
#define LOCATION_PRAGMA(name)
#define LOCATION_PRAGMA_32(name)
#define LOCATION_PRAGMA_NOLOAD(name)
#define LOCATION_ATTRIBUTE(name) __attribute__((section(STR(name)))) __attribute__((aligned(4)))
#define LOCATION_ATTRIBUTE_32(name) __attribute__((section(STR(name)))) __attribute__((aligned(32)))
#define LOCATION_ATTRIBUTE_NOLOAD(name) __attribute__((section(STR(name)))) __attribute__((aligned(4)))
#define FORCE_INLINE_FUNCTION __attribute__((always_inline)) inline
#define TOUCHGFX_DEPRECATED(message, decl) [[deprecated(message)]] decl
/** A generic macro for placing an element in memory. */
#define LOCATION_EXTFLASH_PRAGMA LOCATION_PRAGMA("ExtFlashSection")
/** A generic macro for placing an element attribute in memory. */
#define LOCATION_EXTFLASH_ATTRIBUTE LOCATION_ATTRIBUTE("ExtFlashSection")
/** For big endian systems set to 1 */
#define LV_BIG_ENDIAN_SYSTEM 0
/** Define a custom attribute for `lv_tick_inc` function */
#define LV_ATTRIBUTE_TICK_INC
/** Define a custom attribute for `lv_timer_handler` function */
#define LV_ATTRIBUTE_TIMER_HANDLER
/** Define a custom attribute for `lv_display_flush_ready` function */
#define LV_ATTRIBUTE_FLUSH_READY
/** Align VG_LITE buffers on this number of bytes.
* @note vglite_src_buf_aligned() uses this value to validate alignment of passed buffer pointers. */
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1
/** Will be added where memory needs to be aligned (with -Os data might not be aligned to boundary by default).
* E.g. __attribute__((aligned(4)))*/
#define LV_ATTRIBUTE_MEM_ALIGN __attribute__((aligned(4)))
/** Attribute to mark large constant arrays, for example for font bitmaps */
#define LV_ATTRIBUTE_LARGE_CONST LOCATION_EXTFLASH_ATTRIBUTE
/** Compiler prefix for a large array declaration in RAM */
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY LOCATION_ATTRIBUTE_NOLOAD("TouchGFX_Framebuffer")
#define LV_USE_SYSMON 1
#define LV_USE_PERF_MONITOR 1
#define LV_USE_MEM_MONITOR 1
#if LV_BUILD_DEMOS
/** Show some widgets. This might be required to increase `LV_MEM_SIZE`. */
#define LV_USE_DEMO_WIDGETS 1
/** Demonstrate usage of encoder and keyboard. */
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
/** Benchmark your system */
#define LV_USE_DEMO_BENCHMARK 1
#if LV_USE_DEMO_BENCHMARK
/** Use fonts where bitmaps are aligned 16 byte and has Nx16 byte stride */
#define LV_DEMO_BENCHMARK_ALIGNED_FONTS 1
修改上述的宏定义,用于运行lv_demo_benchmark
添加lvgl_port.c和lvgl_port.h用于实现lvgl适配代码,实现触摸驱动、回调函数注册、lvgl时钟设置。
lvgl_port.h
void lvgl_port_init(void);
lvgl_port.c
/*********************
* INCLUDES
*********************/
#include "lvgl_port.h"
#include "lvgl/lvgl.h"
#include "stm32h7xx_hal.h"
#include "main.h"
extern I2C_HandleTypeDef hi2c4;
extern LTDC_HandleTypeDef hltdc;
/*********************
* DEFINES
*********************/
// GT911 specific defines
#define GT911_ID_REG ((uint16_t)0x8140)
#define GT911_ID ((uint8_t)0x39)
#define GT911_TOUCH_POS_REG ((uint16_t)0x8150)
#define GT911_STATUS_REG ((uint16_t)0x814E)
#define GT911_I2C_ADDR ((uint8_t)0xBA)
#define GT911_REG_BYTES ((uint8_t)2)
#define GT911_TOUCH_MSB_MASK ((uint8_t)0xFF)
#define GT911_STATUS_MASK ((uint8_t)0x0F)
#define FT5336_TOUCH_POS_REG ((uint16_t)0x0003)
#define FT5336_STATUS_REG ((uint16_t)0x0002)
#define FT5336_I2C_ADDR ((uint8_t)0x70)
#define FT5336_REG_BYTES ((uint8_t)1)
#define FT5336_TOUCH_MSB_MASK ((uint8_t)0x07)
#define FT5336_STATUS_MASK ((uint8_t)0x07)
/**********************
* TYPEDEFS
**********************/
extern DMA2D_HandleTypeDef hdma2d;
/**********************
* STATIC PROTOTYPES
**********************/
void touch_read(lv_indev_t * indev, lv_indev_data_t * data);
/**********************
* STATIC VARIABLES
**********************/
static volatile bool do_sample_touch = false;
static lv_indev_state_t last_state = LV_INDEV_STATE_RELEASED;
static uint8_t ts_address = 0;
static uint8_t ts_reg_bytes = 0;
static uint8_t ts_status_mask = 0;
static uint8_t ts_touch_msb_mask = 0;
static uint16_t ts_pos_reg = 0;
static uint16_t ts_status_reg = 0;
static uint8_t ts_x_low_pos = 0;
static uint8_t ts_x_high_pos = 0;
static uint8_t ts_y_low_pos = 0;
static uint8_t ts_y_high_pos = 0;
/*Screen Resolution*/
#define MY_DISP_HOR_RES 480
#define MY_DISP_VER_RES 272
/*Declare two drawing buffers*/
static lv_display_t * disp;
static uint8_t buf_1[MY_DISP_HOR_RES*MY_DISP_VER_RES*4] LOCATION_ATTRIBUTE_NOLOAD("TouchGFX_Framebuffer");
static uint8_t buf_2[MY_DISP_HOR_RES*MY_DISP_VER_RES*4] LOCATION_ATTRIBUTE_NOLOAD("TouchGFX_Framebuffer");
/*DMA2D transfer complete interrupt handler*/
static void disp_flush_complete (DMA2D_HandleTypeDef*);
/*Display flush function prototype*/
static void disp_flush (lv_display_t *, const lv_area_t *, uint8_t *);
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lvgl_port_init(void)
{
lv_init();
lv_tick_set_cb(HAL_GetTick);
/*Create the display*/
disp = lv_display_create(MY_DISP_HOR_RES, MY_DISP_VER_RES);
/*Set the buffers*/
lv_display_set_buffers(disp, (void*) buf_1, (void*) buf_2, sizeof(buf_1), LV_DISPLAY_RENDER_MODE_PARTIAL);
/* interrupt callback for DMA2D transfer */
hdma2d.XferCpltCallback = disp_flush_complete;
lv_display_set_flush_cb(disp, disp_flush);
uint8_t id = 0;
HAL_I2C_Mem_Read(&hi2c4, GT911_I2C_ADDR, GT911_ID_REG, 2, &id, 1, HAL_MAX_DELAY);
if (id == GT911_ID)
{
ts_reg_bytes = GT911_REG_BYTES;
ts_address = GT911_I2C_ADDR;
ts_pos_reg = GT911_TOUCH_POS_REG;
ts_status_reg = GT911_STATUS_REG;
ts_status_mask = GT911_STATUS_MASK;
ts_touch_msb_mask = GT911_TOUCH_MSB_MASK;
ts_x_low_pos = 0;
ts_x_high_pos = 1;
ts_y_low_pos = 2;
ts_y_high_pos = 3;
}
else
{
ts_reg_bytes = FT5336_REG_BYTES;
ts_address = FT5336_I2C_ADDR;
ts_pos_reg = FT5336_TOUCH_POS_REG;
ts_status_reg = FT5336_STATUS_REG;
ts_status_mask = FT5336_STATUS_MASK;
ts_touch_msb_mask = FT5336_TOUCH_MSB_MASK;
ts_x_low_pos = 3;
ts_x_high_pos = 2;
ts_y_low_pos = 1;
ts_y_high_pos = 0;
}
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, touch_read);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == TP_IRQ_Pin)
{
do_sample_touch = true;
}
}
/**********************
* STATIC FUNCTIONS
**********************/
void touch_read(lv_indev_t * indev, lv_indev_data_t * data)
{
uint8_t touches = 0;
uint8_t buf[6];
uint8_t ZERO = 0;
NVIC_DisableIRQ(TP_IRQ_EXTI_IRQn);
if (do_sample_touch)
{
HAL_I2C_Mem_Read(&hi2c4, ts_address, ts_status_reg, ts_reg_bytes, buf, 1, HAL_MAX_DELAY);
touches = (ts_status_mask & buf[0]);
// Clear the status register, only for GT911
if (ts_address == GT911_I2C_ADDR)
{
HAL_I2C_Mem_Write(&hi2c4, ts_address, ts_status_reg, ts_reg_bytes, &ZERO, 1, HAL_MAX_DELAY);
}
do_sample_touch = false;
if (touches > 0)
{
last_state = LV_INDEV_STATE_PRESSED;
HAL_I2C_Mem_Read(&hi2c4, ts_address, ts_pos_reg, ts_reg_bytes, buf, 4, HAL_MAX_DELAY);
data->point.x = buf[ts_x_low_pos] + ((buf[ts_x_high_pos] & ts_touch_msb_mask) << 8);
data->point.y = buf[ts_y_low_pos] + ((buf[ts_y_high_pos] & ts_touch_msb_mask) << 8);
}
else
{
last_state = LV_INDEV_STATE_RELEASED;
}
}
NVIC_EnableIRQ(TP_IRQ_EXTI_IRQn);
data->state = last_state;
}
static void disp_flush (lv_display_t * display,
const lv_area_t * area,
uint8_t * px_map)
{
lv_coord_t width = lv_area_get_width(area);
lv_coord_t height = lv_area_get_height(area);
SCB_CleanDCache_by_Addr((uint32_t *)px_map, width * height * 4);
DMA2D->FGPFCCR = DMA2D_INPUT_ARGB8888;
DMA2D->CR = 0x0U << DMA2D_CR_MODE_Pos; // no conversion
DMA2D->FGMAR = (uint32_t)px_map;
DMA2D->FGOR = 0;
DMA2D->OPFCCR = DMA2D_OUTPUT_ARGB8888;
DMA2D->OMAR = hltdc.LayerCfg[0].FBStartAdress + 4 * \
(area->y1 * MY_DISP_HOR_RES + area->x1);
DMA2D->OOR = MY_DISP_HOR_RES - width;
DMA2D->NLR = (width << DMA2D_NLR_PL_Pos) | (height << DMA2D_NLR_NL_Pos);
DMA2D->IFCR = 0x3FU;
DMA2D->CR |= DMA2D_CR_TCIE;
DMA2D->CR |= DMA2D_CR_START;
}
static void disp_flush_complete (DMA2D_HandleTypeDef *hdma2d)
{
lv_display_flush_ready(disp);
}
main.h中添加用于实现字段控制的宏。
/** A macro to generate the passed argument in double quotes */
#define STR(X) STR_I(X)
/** A macro to generate the passed argument in double quotes */
#define STR_I(X) #X
// xgcc
#define LOCATION_PRAGMA(name)
#define LOCATION_PRAGMA_32(name)
#define LOCATION_PRAGMA_NOLOAD(name)
#define LOCATION_ATTRIBUTE(name) __attribute__((section(STR(name)))) __attribute__((aligned(4)))
#define LOCATION_ATTRIBUTE_32(name) __attribute__((section(STR(name)))) __attribute__((aligned(32)))
#define LOCATION_ATTRIBUTE_NOLOAD(name) __attribute__((section(STR(name)))) __attribute__((aligned(4)))
#define FORCE_INLINE_FUNCTION __attribute__((always_inline)) inline
#define TOUCHGFX_DEPRECATED(message, decl) [[deprecated(message)]] decl
在main.c函数中添加BSP和lvgl相关的头文件引用。
#include "stm32h735g_discovery_ospi.h"
#include "stm32h7xx_hal_ospi.h"
#include "lvgl/lvgl.h"
#include "lvgl/demos/lv_demos.h"
#include "lvgl_port.h"
定义用于获取ltdc FrameBuffer起始地址的函数,用于和CubeMX中的设置进行匹配。
LOCATION_PRAGMA_NOLOAD("TouchGFX_Framebuffer")
uint8_t ltdc_buffer[480*272*4] LOCATION_ATTRIBUTE_NOLOAD("TouchGFX_Framebuffer") = {0};
uint32_t ltdc_buffer_addr(void)
{
return (uint32_t)<dc_buffer[0];
}
MX_OCTOSPI1_Init函数中添加BSP驱动的函数调用,用于初始化外部HyperFlash
/* USER CODE BEGIN OCTOSPI1_Init 0 */
BSP_OSPI_NOR_Init_t ospi_nor_int;
/* USER CODE END OCTOSPI1_Init 0 */
/* USER CODE BEGIN OCTOSPI1_Init 2 */
HAL_OSPI_DeInit(&hospi1);
ospi_nor_int.InterfaceMode = BSP_OSPI_NOR_OPI_MODE;
ospi_nor_int.TransferRate = BSP_OSPI_NOR_DTR_TRANSFER;
BSP_OSPI_NOR_DeInit(0);
if(BSP_OSPI_NOR_Init(0, &ospi_nor_int) != BSP_ERROR_NONE)
{
Error_Handler();
}
if(BSP_OSPI_NOR_EnableMemoryMappedMode(0) != BSP_ERROR_NONE)
{
Error_Handler();
}
/* USER CODE END OCTOSPI1_Init 2 */
MX_OCTOSPI2_Init函数中添加BSP驱动的函数调用,用于初始化外部HyperRAM
/* USER CODE BEGIN OCTOSPI2_Init 1 */
OSPI_HyperbusCmdTypeDef sCommand = {0};
OSPI_MemoryMappedTypeDef sMemMappedCfg = {0};
/* USER CODE END OCTOSPI2_Init 1 */
/* USER CODE BEGIN OCTOSPI2_Init 2 */
sCommand.AddressSpace = HAL_OSPI_MEMORY_ADDRESS_SPACE;
sCommand.AddressSize = HAL_OSPI_ADDRESS_32_BITS;
sCommand.DQSMode = HAL_OSPI_DQS_ENABLE;
sCommand.Address = 0;
sCommand.NbData = 1;
if (HAL_OSPI_HyperbusCmd(&hospi2, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
sMemMappedCfg.TimeOutActivation = HAL_OSPI_TIMEOUT_COUNTER_DISABLE;
if (HAL_OSPI_MemoryMapped(&hospi2, &sMemMappedCfg) != HAL_OK)
{
Error_Handler();
}
/* USER CODE END OCTOSPI2_Init 2 */
main.c中添加lvgl的初始化、demo初始化函数以及周期处理函数
/* USER CODE BEGIN 2 */
lvgl_port_init();
lv_demo_benchmark();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
lv_timer_handler();
HAL_Delay(1);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
5、修改链接文件和添加STLDR
默认创建的工程的链接文件中没有定义外部的Flash和HyperRAM地址空间,在工程的ld文件中添加相应存储器件的地址空间定义和相应的链接文件的Section。以下添加OSPI和HYPERRAM用于管理外部存储器件的地址空间。
/* Specify the memory areas */
MEMORY
{
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 320K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 32K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 16K
OSPI (xrw) : ORIGIN = 0x90000000, LENGTH = 64M
HYPERRAM (xrw) : ORIGIN = 0x70000000, LENGTH = 16M
}
/* Define specific placement of memory areas */
SECTIONS
{
TextFlashSection :
{
*(TextFlashSection TextFlashSection.*)
. = ALIGN(0x4);
} >OSPI
FontFlashSection :
{
*(FontFlashSection FontFlashSection.*)
. = ALIGN(0x4);
} >OSPI
ExtFlashSection :
{
*(ExtFlashSection ExtFlashSection.*)
. = ALIGN(0x4);
} >OSPI
BufferSection (NOLOAD) :
{
*(TouchGFX_Framebuffer TouchGFX_Framebuffer.*)
. = ALIGN(0x4);
*(Video_RGB_Buffer Video_RGB_Buffer.*)
. = ALIGN(0x4);
} >HYPERRAM
}
在Debug Configuration界面中添加

调试并烧录到开发板运行lvgl示例程序,程序的运行效果如下。
