目录
LVGL(轻量级和通用图形库)是一个免费和开源的图形库,它提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素,美丽的视觉效果和低内存占用。
控制器采用STM32F767IGT6,显示屏采用中景园1.3 英寸240RGB*240分辨率类似的拆机小显示屏,此显示屏采用ST7789Z作为驱动芯片,SPI接口。CubeIDE使用1.16.1版本,内嵌CubeMX。STM32固件库采用1.17.2版本。注意:本次移植只为使用最少的操作让LVGL的demo程序运行起来,不含剪裁和优化。
一、下载LVGL源码
LVGL最新源代码下载地址:https://github.com/lvgl/lvgl/archive/refs/tags/v9.2.2.zip
下载解压后目录:
二、创建STM32工程
使用CubeIDE自带的CubeMAX创建STM32工程,硬件使用自己设计的STM32开发板,通过杜邦线链接验证。
本次使用硬件SPI4驱动显示屏。单片机管脚配置如下:
显示屏使用到的管脚为:
LCD_CS:PA0 片选信号,低电平有效
LCD_ENA:PA1背光使能信号,高电平有效
LCD_DCS:PA2指令数据选择信号
LCD_RST:PA3复位信号
LCD_SCK:PE2时钟信号
LCD_MOSI:PE6数据信号
SPI接口,采用DMA发送,因此需开启DMA,具体配置如下:
工程创建基本上外设使用系统默认参数即可。
三、添加LVGL库代码
工程创建好后添加LVGL代码目录,在此目录下创建lvgl文件夹,并将LVGL源码目录中src、demos、examples三个文件夹内容以及根目录下的lv_version.h、lvgl.h全部拷贝到lvgl文件夹。将根目录下的lv_conf_template.h拷贝到 LVGL目录下,并修改lv_conf_template.h文件名称为lv_conf.h。
配置工程属性,增加LVGL相关的头文件包含目录,修改后的配置如下:
工程整体结构如下所示:
四、增加基础调用代码
1、增加头文件
修改main.c,增加lvgl库相关头文件和demo相关都文件包含(lvgl.h和demos/lv_demos.h)
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "lvgl.h"
#include "examples/porting/lv_port_disp.h"
#include "demos/lv_demos.h"
/* USER CODE END Includes */
2、增加LVGL库和demo
调用初始化函数初始化lvgl库和显示驱动(lv_init()和lv_port_disp_init()),调用demo创建函数(lv_demos_create()),由于此函数需传入启动参数自动识别demo名称调用对应的demo,在STM32系统并不适合,因此传入空参数即可运行默认的widgets demo,再在主循环添加lvgl的任务管理器(lv_task_handler()),要处理lvgl的任务就需要定期调用 lv_task_handler,main函数修改后如下:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MPU Configuration--------------------------------------------------------*/
MPU_Config();
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_SPI4_Init();
MX_UART8_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
lv_init();
lv_port_disp_init();
lv_demos_create(NULL,0);
while (1)
{
lv_task_handler();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
注意,此时的lv_port_disp.h头文件不存在,lv_port_disp_init()函数还未实现,后面的显示接口移植会实现此函数。
3、增加LGVL的时基
LVGL 需要一个系统滴答来了解动画和其他任务所用的时间。需要定期调用 lv_tick_inc(tick_period)
函数,并提供以毫秒为单位的调用周期。使用CubeMax创建的STM32工程默认使用滴答定时器作为HAL库的延时定时器,且默认周期为1ms,因此可以直接使用。
在stm32f7xx_it.c文件中的void SysTick_Handler(void)中断函数中添加函数调用:
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
lv_tick_inc(1);
/* USER CODE END SysTick_IRQn 1 */
}
五、配置LVGL
1、开启配置文件
打开lv_conf.h修改15行代码,生效配置文件:
#if 0 /*Set it to "1" to enable content*/
修改为
#if 1 /*Set it to "1" to enable content*/
2、修改内存大小
由于控件内存都是动态创建的,在运行复杂控件或控件较多时需要修改第56行,增加lv_malloc()的内存大小,此处为了运行自带的默认demo程序( widgets demo)默认的64K内存不够,需要根据实际需求扩大:
#define LV_MEM_SIZE (64 * 1024U) /*[bytes]*/
改为
#define LV_MEM_SIZE (128 * 1024U) /*[bytes]*/
3、开启ST7789驱动
LVGL自带ST7789驱动,需要开启ST7789驱动,修改1048行:
#define LV_USE_ST7789 0
改为
#define LV_USE_ST7789 1
4、开启demo程序
开启widgets demo程序,修改1084行:
#define LV_USE_DEMO_WIDGETS 0
改为
#define LV_USE_DEMO_WIDGETS 1
六、移植显示接口
显示驱动适配在examples目录的porting目录内:
1、创建显示接口文件
修改lv_port_lcd_stm32_template.c为lv_port_disp.c,修改lv_port_lcd_stm32_template.h为lv_port_disp.h
修改后如下:
增加头文件:
#include "main.h"
#include "spi.h"
#include "gpio.h"
#include "lv_port_disp.h"
#include "./src/drivers/display/st7789/lv_st7789.h"
开启代码,修改lv_port_disp.c第8行和修改lv_port_disp.h第7行:
#if 0
改为
#if 1
2、适配屏幕分辨率
修改屏幕分辨率为240x240:
#ifndef MY_DISP_HOR_RES
#warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now.
#define MY_DISP_HOR_RES 240
#endif
#ifndef MY_DISP_VER_RES
#warning Please define or replace the macro MY_DISP_VER_RES with the actual screen height, default value 240 is used for now.
#define MY_DISP_VER_RES 240
#endif
3、适配显示初始化函数
修改函数名称void lv_port_display_init(void)为void lv_port_disp_init(void),这里应该是个小BUG,跟
lv_port_disp.h文件申明的函数名称不同。
void lv_port_display_init(void)
改为
void lv_port_disp_init(void)
删除老版本STM固件库使用的SPI传输回调函数static void lcd_color_transfer_ready_cb(SPI_HandleTypeDef * hspi);
增加新版固件库使用的回调函数:
/* Callback is called when background transfer finished */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef * hspi)
{
/* CS high */
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);
lcd_bus_busy = 0;
lv_display_flush_ready(lcd_disp);
}
4、硬件接口初始化设置
修改lcd_io_init函数,适配对应的管脚,删除老版本STM固件库使用的SPI回调注册函数HAL_SPI_RegisterCallback(&hspi1, HAL_SPI_TX_COMPLETE_CB_ID, lcd_color_transfer_ready_cb),增加背光开启,修改后如下:
/* Initialize LCD I/O bus, reset LCD */
static int32_t lcd_io_init(void)
{
/* reset LCD */
HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LCD_DCX_GPIO_Port, LCD_DCX_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LCD_ENA_GPIO_Port, LCD_ENA_Pin, GPIO_PIN_SET);
return HAL_OK;
}
5、适配发送命令接口
修改lcd_send_cmd函数,原SPI1适配为SPI4:
static void lcd_send_cmd(lv_display_t * disp, const uint8_t * cmd, size_t cmd_size, const uint8_t * param,
size_t param_size)
{
LV_UNUSED(disp);
while(lcd_bus_busy); /* wait until previous transfer is finished */
/* Set the SPI in 8-bit mode */
hspi4.Init.DataSize = SPI_DATASIZE_8BIT;
HAL_SPI_Init(&hspi4);
/* DCX low (command) */
HAL_GPIO_WritePin(LCD_DCX_GPIO_Port, LCD_DCX_Pin, GPIO_PIN_RESET);
/* CS low */
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET);
/* send command */
if(HAL_SPI_Transmit(&hspi4, cmd, cmd_size, BUS_SPI1_POLL_TIMEOUT) == HAL_OK) {
/* DCX high (data) */
HAL_GPIO_WritePin(LCD_DCX_GPIO_Port, LCD_DCX_Pin, GPIO_PIN_SET);
/* for short data blocks we use polling transfer */
HAL_SPI_Transmit(&hspi4, (uint8_t *)param, (uint16_t)param_size, BUS_SPI1_POLL_TIMEOUT);
/* CS high */
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);
}
}
6、适配像素显示接口
修改lcd_send_color函数,原SPI1适配为SPI4,由于默认颜色为16位色,需且将原传输大小(param_size / 2)修改为param_size :
static void lcd_send_color(lv_display_t * disp, const uint8_t * cmd, size_t cmd_size, uint8_t * param,
size_t param_size)
{
LV_UNUSED(disp);
while(lcd_bus_busy); /* wait until previous transfer is finished */
/* Set the SPI in 8-bit mode */
hspi4.Init.DataSize = SPI_DATASIZE_8BIT;
HAL_SPI_Init(&hspi4);
/* DCX low (command) */
HAL_GPIO_WritePin(LCD_DCX_GPIO_Port, LCD_DCX_Pin, GPIO_PIN_RESET);
/* CS low */
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET);
/* send command */
if(HAL_SPI_Transmit(&hspi4, cmd, cmd_size, BUS_SPI1_POLL_TIMEOUT) == HAL_OK) {
/* DCX high (data) */
HAL_GPIO_WritePin(LCD_DCX_GPIO_Port, LCD_DCX_Pin, GPIO_PIN_SET);
/* for color data use DMA transfer */
/* Set the SPI in 16-bit mode to match endianness */
hspi4.Init.DataSize = SPI_DATASIZE_16BIT;
HAL_SPI_Init(&hspi4);
lcd_bus_busy = 1;
HAL_SPI_Transmit_DMA(&hspi4, param, (uint16_t)param_size);
/* NOTE: CS will be reset in the transfer ready callback */
}
}
七、移植ST7789驱动
LVGL自带ST7789驱动,用的MIPI接口框架,适配屏幕只需要修改参数列表即可,驱动位于:
打开lv_st7789.c,修改init_cmd_list数组,这里主要是设置显示屏的显示参数,加入适配自己屏幕的参数即可,我的屏幕使用如下参数:
static const uint8_t init_cmd_list[] = {
CMD_PORCTRL, 5, 0x0C,0x0C,0x00,0x33,0x33,
CMD_GCTRL, 1, 0x35, /* GCTRL -- panel dependent */
CMD_VCOMS, 1, 0x19, /* VCOMS -- panel dependent */
CMD_LCMCTRL, 1, 0x2c,
CMD_VDVVRHEN, 1, 0x01,
CMD_VRHS, 1, 0x12, /* VRHS - panel dependent */
CMD_VDVSET, 1, 0x20,
CMD_FRCTR2, 1, 0x0F,
CMD_PWCTRL1, 2, 0xa4, 0xa1,
CMD_PVGAMCTRL, 14, 0xd0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23,
CMD_NVGAMCTRL, 14, 0xd0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23,
LV_LCD_CMD_DELAY_MS, LV_LCD_CMD_EOF
};
除此之外,我的屏幕还得开启反向显示,在初始化代码增加lv_st7789_set_invert(disp,true)调用:
lv_display_t * lv_st7789_create(uint32_t hor_res, uint32_t ver_res, lv_lcd_flag_t flags,
lv_st7789_send_cmd_cb_t send_cmd_cb, lv_st7789_send_color_cb_t send_color_cb)
{
lv_display_t * disp = lv_lcd_generic_mipi_create(hor_res, ver_res, flags, send_cmd_cb, send_color_cb);
lv_st7789_set_invert(disp,true);
lv_lcd_generic_mipi_send_cmd_list(disp, init_cmd_list);
return disp;
}
至此编译工程,运行即可正常显示。
八,运行效果
实际显示效果如图所示: