LVGL初始化操作流程
一、前言
在嵌入式项目中,使用 ESP32 驱动一块 SPI 接口的 ST7789 屏幕,并跑起 LVGL 图形库,是非常常见的需求。ESP-IDF 提供了 esp_lcd 组件,将屏幕初始化、命令发送等底层操作封装得很好,而 LVGL 则提供了丰富的 UI 控件。
本文将按照一条清晰的链路,从 SPI 总线初始化 → 屏幕驱动适配 → LVGL 显示注册 依次梳理,以我初学者的视角完成初始化LVGL所需的流程
二、硬件与软件环境
-
主控:ESP32-S3 / ESP32(其他芯片类似)
-
屏幕:ST7789 240×240 SPI 接口 LCD
-
开发框架:ESP-IDF v5.x
-
LVGL:v8.4 / v9.x(本文以 v9 为例,v8 接口略有差异但思路一致)
三、屏幕硬件初始化——打通物理链路
在进行LVGL的使用以前,需要进行硬件的初始化,这个过程和就是配置基本硬件资源如SPI模块的引脚配置以及数据传输参数和片选引脚的参数配置等等。
3.1 SPI 总线初始化
spi_bus_config_t bus_cfg = {
.sclk_io_num = PIN_NUM_SCLK,
.mosi_io_num = PIN_NUM_MOSI,
.miso_io_num = PIN_NUM_MISO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = LCD_H_RES * 120 * sizeof(uint16_t),
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &bus_cfg, SPI_DMA_CH_AUTO));
初始化一条 SPI 物理总线,告诉 ESP32 用哪些引脚作为时钟、数据线,并分配 DMA 通道。
3.2 创建带 DC/CS 控制的 IO 句柄
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = PIN_NUM_DC,
.cs_gpio_num = PIN_NUM_CS,
.pclk_hz = LCD_PIXEL_CLOCK_HZ,//时钟频率,不能超过屏幕支持的最大值(一般 40~80 MHz)。
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.spi_mode = 3, //ST7789 通常要求 SPI 模式 3(CPOL=1, CPHA=1)
.trans_queue_depth = 2, //允许排队等待的传输数量,双缓冲时可设为 2。
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST,
&io_config,
&s_lcd_io_handle));
3.3 绑定 ST7789 驱动,生成面板句柄
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = PIN_NUM_RST,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
esp_lcd_panel_handle_t panel_handle = NULL;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(s_lcd_io_handle,
&panel_config,
&panel_handle));
“绑定”的含义:
把上一步建好的“传输通道”和一个具体的 ST7789 控制器逻辑捆绑在一起。panel_config 中定义了复位引脚、色彩格式(RGB)和色深(16位 RGB565)。
调用这个函数后,ESP-IDF 内部就固化了 ST7789 的初始化命令序列(尚未发送),后续只要调用 esp_lcd_panel_init(),就能自动发送 Sleep Out、像素格式、方向、伽马等命令,而无需我们查着手册一条条写。
3.4 执行屏幕初始化序列及微调
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); // 硬件复位
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); // 发送 ST7789 初始化命令
// 以下为可选设置,根据实际屏幕安装方向调整
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, 0, 0)); // 偏移量
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, false)); // 是否交换 XY
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, false, false));// 水平、垂直翻转
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); // 颜色反转(ST7789 通常需要)
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); // 开显示
四、LVGL 显示驱动移植
1. 初始化 LVGL 核心库
lv_init(); // 必须在其他 LVGL 操作之前调用
2. 配置并注册显示驱动
void my_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
{
// 计算实际需要刷新的区域(注意右边界坐标要 +1)
uint16_t x1 = area->x1;
uint16_t y1 = area->y1;
uint16_t x2 = area->x2 + 1;
uint16_t y2 = area->y2 + 1;
// 将 LVGL 绘制好的像素块通过 panel_handle 发送到屏幕
esp_lcd_panel_draw_bitmap(panel_handle, x1, y1, x2, y2, px_map);
// 通知 LVGL 刷新完成
lv_disp_flush_ready(disp);
}
/*调用LVGL官方的注册函数,创建显示并注册回调*/
disp = lv_display_create(DISP_HOR_RES, DISP_VER_RES);//创建屏幕并传入分辨率
lv_display_set_flush_cb(disp, my_flush_cb); //屏幕刷新回调注册
3. 配置输入设备(如果有触摸屏)
void my_touch_read_cb(lv_indev_t * indev, lv_indev_data_t * data) {
// 读取触摸坐标和状态
data->point.x = touch_x;
data->point.y = touch_y;
data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
}
// 在初始化中:
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, my_touch_read_cb);
4. 提供心跳(Tick)定时器
方式1:常见时基注册方式--初始化之后时绑定回调即可
// 自定义回调,返回自启动以来经过的毫秒数
static uint32_t my_tick_get_cb(void)
{
return (uint32_t)(esp_timer_get_time() / 1000);
}
void lvgl_tick_init(void)
{
lv_tick_set_cb(my_tick_get_cb); // 注册回调
}
方式2:手动递增时基方式(可以用操作系统获取时基,这里以FreeRTOS为例)
此方法需要在循环任务中、或设置定时器,循环调用该函数--即为手动递增
/*在 LVGL 任务循环中每次调用 lv_tick_inc(),时基完全由 FreeRTOS tick 驱动*/
lv_tick_inc((uint32_t)diff_tick * portTICK_PERIOD_MS);
五、重要配置与常见问题FR
5.1缓冲区的定义和创建
#define LCD_H_RES 240
#define LCD_V_RES 240
#define BUF_LINES 10 // 缓冲区覆盖的行数
#define BUF_SIZE (LCD_H_RES * BUF_LINES) // 总像素数
static lv_color_t draw_buf[BUF_SIZE]; // 绘制缓冲区
lv_display_t *disp = lv_display_create(LCD_H_RES, LCD_V_RES);
lv_display_set_flush_cb(disp, my_flush_cb);
/* 单缓冲:第二个参数传 NULL */
lv_display_set_buffers(disp, draw_buf, NULL, sizeof(draw_buf),
LV_DISPLAY_RENDER_MODE_PARTIAL);
5.2三种常见缓冲模式
| 模式 | 宏值(v9) | 说明 |
|---|---|---|
| 单缓冲 + 局部刷新 | LV_DISPLAY_RENDER_MODE_PARTIAL |
一块缓冲,LVGL 只绘制变化的部分,分多次 flush_cb 完成一帧。 |
| 双缓冲 + 局部刷新 | LV_DISPLAY_RENDER_MODE_PARTIAL + 两块缓冲 |
LVGL 可以在上一块数据发送时,同时在另一块绘制下一部分。 |
| 全缓冲(单/双) | LV_DISPLAY_RENDER_MODE_FULL |
缓冲区大小等于整帧大小,LVGL 一次性画完整帧再刷新,效果最好但内存占用巨大。 |
结语
写下次文作为自己的学习笔记,嵌入式小白,如果有错误,欢迎大佬指正
更多推荐

所有评论(0)