LVGL图形库快速入门:从零开始构建嵌入式GUI¶
学习目标¶
完成本教程后,你将能够:
- 理解LVGL图形库的架构和工作原理
- 在嵌入式项目中正确安装和配置LVGL
- 实现LCD显示驱动并集成到LVGL
- 配置触摸屏输入设备
- 使用LVGL基本控件创建简单界面
- 掌握LVGL的事件处理机制
- 实现一个完整的GUI应用示例
前置要求¶
在开始本教程之前,你需要:
知识要求: - 熟悉C语言编程 - 了解嵌入式系统基础 - 掌握基本的显示器工作原理 - 了解SPI/I2C通信协议
技能要求: - 能够使用嵌入式开发IDE - 会配置GPIO和外设 - 具备基本的调试能力
准备工作¶
硬件准备¶
| 名称 | 数量 | 说明 | 参考型号 |
|---|---|---|---|
| 开发板 | 1 | STM32F4系列或ESP32 | STM32F407VGT6 |
| LCD显示屏 | 1 | 2.4-3.5寸TFT屏 | ILI9341/ST7789 |
| 触摸屏 | 1 | 电阻式或电容式 | XPT2046/FT6236 |
| 杜邦线 | 若干 | 用于连接 | - |
软件准备¶
- 开发环境:STM32CubeIDE / ESP-IDF / Arduino IDE
- LVGL版本:v8.3+ (本教程使用v8.3)
- 辅助工具:
- Git (用于下载LVGL源码)
- 串口调试工具
- LVGL模拟器 (可选,用于PC端测试)
环境配置¶
- 下载LVGL源码
- 项目目录结构
project/
├── lvgl/ # LVGL核心库
├── lv_drivers/ # 显示和输入驱动
├── lv_conf.h # LVGL配置文件
├── main.c # 主程序
└── drivers/ # 硬件驱动
├── lcd_driver.c # LCD驱动
└── touch_driver.c # 触摸驱动
步骤1:LVGL库集成¶
1.1 添加LVGL源码到项目¶
- 将下载的
lvgl文件夹复制到项目目录 - 在IDE中添加LVGL源文件路径
- 复制
lvgl/lv_conf_template.h为lv_conf.h到项目根目录
1.2 配置lv_conf.h¶
打开 lv_conf.h 文件,进行基本配置:
/* 启用LVGL配置 */
#if 1 // 改为1启用配置
/* 颜色深度设置 */
#define LV_COLOR_DEPTH 16 // 16位色深(RGB565)
/* 显示分辨率 */
#define LV_HOR_RES_MAX 240 // 水平分辨率
#define LV_VER_RES_MAX 320 // 垂直分辨率
/* 内存配置 */
#define LV_MEM_CUSTOM 0 // 使用LVGL内置内存管理
#define LV_MEM_SIZE (32U * 1024U) // 分配32KB内存
/* 刷新周期 */
#define LV_DISP_DEF_REFR_PERIOD 30 // 30ms刷新一次
/* 启用常用控件 */
#define LV_USE_BTN 1 // 按钮
#define LV_USE_LABEL 1 // 标签
#define LV_USE_SLIDER 1 // 滑块
#define LV_USE_SWITCH 1 // 开关
#define LV_USE_TEXTAREA 1 // 文本框
/* 字体配置 */
#define LV_FONT_MONTSERRAT_14 1 // 14号字体
#define LV_FONT_MONTSERRAT_16 1 // 16号字体
#endif
配置说明:
- LV_COLOR_DEPTH: 根据LCD支持的色深设置(8/16/32)
- LV_HOR_RES_MAX/LV_VER_RES_MAX: 设置为LCD的实际分辨率
- LV_MEM_SIZE: 根据可用RAM调整,建议至少16KB
- 控件宏定义: 只启用需要的控件可减小代码体积
1.3 初始化LVGL¶
在 main.c 中添加LVGL初始化代码:
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
/* 定义显示缓冲区 */
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf1[LV_HOR_RES_MAX * 10]; // 缓冲区1
static lv_color_t buf2[LV_HOR_RES_MAX * 10]; // 缓冲区2(可选)
int main(void)
{
/* 系统初始化 */
HAL_Init();
SystemClock_Config();
/* 初始化LVGL */
lv_init();
/* 初始化显示缓冲区 */
lv_disp_draw_buf_init(&draw_buf, buf1, buf2, LV_HOR_RES_MAX * 10);
/* 注册显示驱动 */
lv_port_disp_init();
/* 注册输入设备驱动 */
lv_port_indev_init();
/* 创建UI */
ui_init();
/* 主循环 */
while(1)
{
lv_timer_handler(); // 处理LVGL任务
HAL_Delay(5); // 延时5ms
}
}
代码说明:
- lv_init(): 初始化LVGL库
- lv_disp_draw_buf_init(): 初始化显示缓冲区,使用双缓冲可提高性能
- lv_timer_handler(): LVGL任务处理函数,需要周期性调用(建议5-10ms)
- 缓冲区大小: 屏幕宽度 × 行数,行数越多性能越好但占用内存越大
步骤2:实现显示驱动¶
2.1 创建显示驱动文件¶
创建 lv_port_disp.c 和 lv_port_disp.h 文件。
lv_port_disp.h:
#ifndef LV_PORT_DISP_H
#define LV_PORT_DISP_H
#include "lvgl.h"
void lv_port_disp_init(void);
#endif
2.2 实现显示驱动¶
lv_port_disp.c:
#include "lv_port_disp.h"
#include "lcd_driver.h" // 你的LCD驱动头文件
/* 显示刷新回调函数 */
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
/* 获取刷新区域 */
int32_t x1 = area->x1;
int32_t y1 = area->y1;
int32_t x2 = area->x2;
int32_t y2 = area->y2;
/* 设置LCD显示窗口 */
LCD_SetWindow(x1, y1, x2, y2);
/* 计算像素数量 */
uint32_t size = (x2 - x1 + 1) * (y2 - y1 + 1);
/* 发送像素数据到LCD */
LCD_WriteData((uint16_t*)color_p, size);
/* 通知LVGL刷新完成 */
lv_disp_flush_ready(disp_drv);
}
void lv_port_disp_init(void)
{
/* 初始化LCD硬件 */
LCD_Init();
/* 创建显示驱动 */
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
/* 设置分辨率 */
disp_drv.hor_res = LV_HOR_RES_MAX;
disp_drv.ver_res = LV_VER_RES_MAX;
/* 注册刷新回调函数 */
disp_drv.flush_cb = disp_flush;
/* 设置显示缓冲区 */
extern lv_disp_draw_buf_t draw_buf;
disp_drv.draw_buf = &draw_buf;
/* 注册显示驱动 */
lv_disp_drv_register(&disp_drv);
}
2.3 LCD底层驱动示例¶
以ILI9341为例,实现基本的LCD驱动函数:
/* lcd_driver.c */
#include "lcd_driver.h"
/* LCD控制引脚定义 */
#define LCD_CS_PIN GPIO_PIN_4
#define LCD_DC_PIN GPIO_PIN_3
#define LCD_RST_PIN GPIO_PIN_2
/* 写命令 */
void LCD_WriteCmd(uint8_t cmd)
{
HAL_GPIO_WritePin(GPIOA, LCD_DC_PIN, GPIO_PIN_RESET); // DC=0
HAL_GPIO_WritePin(GPIOA, LCD_CS_PIN, GPIO_PIN_RESET); // CS=0
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, LCD_CS_PIN, GPIO_PIN_SET); // CS=1
}
/* 写数据 */
void LCD_WriteData(uint16_t *data, uint32_t len)
{
HAL_GPIO_WritePin(GPIOA, LCD_DC_PIN, GPIO_PIN_SET); // DC=1
HAL_GPIO_WritePin(GPIOA, LCD_CS_PIN, GPIO_PIN_RESET); // CS=0
HAL_SPI_Transmit(&hspi1, (uint8_t*)data, len * 2, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, LCD_CS_PIN, GPIO_PIN_SET); // CS=1
}
/* 设置显示窗口 */
void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
/* 列地址设置 */
LCD_WriteCmd(0x2A);
LCD_WriteData16(x1);
LCD_WriteData16(x2);
/* 行地址设置 */
LCD_WriteCmd(0x2B);
LCD_WriteData16(y1);
LCD_WriteData16(y2);
/* 写入显存 */
LCD_WriteCmd(0x2C);
}
/* LCD初始化 */
void LCD_Init(void)
{
/* 硬件复位 */
HAL_GPIO_WritePin(GPIOA, LCD_RST_PIN, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOA, LCD_RST_PIN, GPIO_PIN_SET);
HAL_Delay(100);
/* ILI9341初始化序列 */
LCD_WriteCmd(0x11); // 退出睡眠模式
HAL_Delay(120);
LCD_WriteCmd(0x36); // 内存访问控制
LCD_WriteData8(0x48);
LCD_WriteCmd(0x3A); // 像素格式设置
LCD_WriteData8(0x55); // 16位色
LCD_WriteCmd(0x29); // 开启显示
HAL_Delay(100);
}
驱动说明:
- LCD_WriteCmd(): 发送命令到LCD控制器
- LCD_WriteData(): 发送像素数据
- LCD_SetWindow(): 设置要刷新的矩形区域
- 使用SPI接口通信,也可以使用并口(8080/6800)
步骤3:配置输入设备¶
3.1 创建输入设备驱动¶
创建 lv_port_indev.c 和 lv_port_indev.h 文件。
lv_port_indev.h:
#ifndef LV_PORT_INDEV_H
#define LV_PORT_INDEV_H
#include "lvgl.h"
void lv_port_indev_init(void);
#endif
3.2 实现触摸屏驱动¶
lv_port_indev.c:
#include "lv_port_indev.h"
#include "touch_driver.h" // 触摸屏驱动
/* 触摸读取回调函数 */
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static int16_t last_x = 0;
static int16_t last_y = 0;
/* 读取触摸状态 */
if(Touch_Scan())
{
/* 触摸按下 */
data->state = LV_INDEV_STATE_PRESSED;
/* 读取触摸坐标 */
data->point.x = Touch_GetX();
data->point.y = Touch_GetY();
/* 保存坐标 */
last_x = data->point.x;
last_y = data->point.y;
}
else
{
/* 触摸释放 */
data->state = LV_INDEV_STATE_RELEASED;
/* 使用上次坐标 */
data->point.x = last_x;
data->point.y = last_y;
}
}
void lv_port_indev_init(void)
{
/* 初始化触摸屏硬件 */
Touch_Init();
/* 创建输入设备驱动 */
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
/* 设置输入设备类型 */
indev_drv.type = LV_INDEV_TYPE_POINTER; // 指针设备(触摸屏)
/* 注册读取回调函数 */
indev_drv.read_cb = touchpad_read;
/* 注册输入设备 */
lv_indev_drv_register(&indev_drv);
}
3.3 触摸屏底层驱动示例¶
以XPT2046电阻式触摸屏为例:
/* touch_driver.c */
#include "touch_driver.h"
#define TOUCH_CS_PIN GPIO_PIN_5
/* 触摸坐标 */
static uint16_t touch_x = 0;
static uint16_t touch_y = 0;
/* SPI读写 */
static uint16_t Touch_ReadAD(uint8_t cmd)
{
uint16_t data = 0;
HAL_GPIO_WritePin(GPIOA, TOUCH_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi2, &cmd, 1, HAL_MAX_DELAY);
HAL_SPI_Receive(&hspi2, (uint8_t*)&data, 2, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, TOUCH_CS_PIN, GPIO_PIN_SET);
return data >> 3; // 12位数据
}
/* 扫描触摸 */
bool Touch_Scan(void)
{
/* 读取触摸状态引脚 */
if(HAL_GPIO_ReadPin(GPIOA, TOUCH_IRQ_PIN) == GPIO_PIN_RESET)
{
/* 读取X坐标 */
uint16_t x1 = Touch_ReadAD(0x90);
uint16_t x2 = Touch_ReadAD(0x90);
/* 读取Y坐标 */
uint16_t y1 = Touch_ReadAD(0xD0);
uint16_t y2 = Touch_ReadAD(0xD0);
/* 滤波处理 */
if(abs(x1 - x2) < 50 && abs(y1 - y2) < 50)
{
touch_x = (x1 + x2) / 2;
touch_y = (y1 + y2) / 2;
/* 坐标转换和校准 */
touch_x = (touch_x - X_MIN) * LV_HOR_RES_MAX / (X_MAX - X_MIN);
touch_y = (touch_y - Y_MIN) * LV_VER_RES_MAX / (Y_MAX - Y_MIN);
return true;
}
}
return false;
}
/* 获取坐标 */
uint16_t Touch_GetX(void) { return touch_x; }
uint16_t Touch_GetY(void) { return touch_y; }
/* 触摸初始化 */
void Touch_Init(void)
{
/* 配置SPI和GPIO */
// ...
}
触摸驱动说明: - 电阻式触摸屏通过SPI读取AD值 - 需要进行坐标校准,将AD值映射到屏幕坐标 - 使用滤波算法减少抖动 - 电容式触摸屏(如FT6236)使用I2C接口,驱动方式类似
步骤4:创建基本界面¶
4.1 创建第一个标签¶
void ui_init(void)
{
/* 创建标签对象 */
lv_obj_t * label = lv_label_create(lv_scr_act());
/* 设置标签文本 */
lv_label_set_text(label, "Hello LVGL!");
/* 设置标签位置(居中) */
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
/* 设置字体 */
lv_obj_set_style_text_font(label, &lv_font_montserrat_16, 0);
}
运行结果: 屏幕中央显示 "Hello LVGL!" 文字。
4.2 创建按钮¶
/* 按钮点击事件回调 */
static void btn_event_cb(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED)
{
/* 按钮被点击 */
printf("Button clicked!\n");
/* 获取按钮对象 */
lv_obj_t * btn = lv_event_get_target(e);
/* 获取按钮上的标签 */
lv_obj_t * label = lv_obj_get_child(btn, 0);
/* 修改标签文本 */
lv_label_set_text(label, "Clicked!");
}
}
void create_button(void)
{
/* 创建按钮 */
lv_obj_t * btn = lv_btn_create(lv_scr_act());
/* 设置按钮大小 */
lv_obj_set_size(btn, 120, 50);
/* 设置按钮位置 */
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
/* 添加事件回调 */
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
/* 在按钮上创建标签 */
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "Click Me");
lv_obj_center(label);
}
代码说明:
- lv_btn_create(): 创建按钮对象
- lv_obj_set_size(): 设置对象大小
- lv_obj_align(): 设置对象对齐方式
- lv_obj_add_event_cb(): 添加事件回调函数
- 事件类型包括: CLICKED, PRESSED, RELEASED, VALUE_CHANGED等
4.3 创建滑块控件¶
/* 滑块值改变事件 */
static void slider_event_cb(lv_event_t * e)
{
lv_obj_t * slider = lv_event_get_target(e);
/* 获取滑块当前值 */
int32_t value = lv_slider_get_value(slider);
/* 更新标签显示 */
lv_obj_t * label = lv_event_get_user_data(e);
lv_label_set_text_fmt(label, "Value: %d", value);
}
void create_slider(void)
{
/* 创建滑块 */
lv_obj_t * slider = lv_slider_create(lv_scr_act());
/* 设置滑块大小和位置 */
lv_obj_set_width(slider, 200);
lv_obj_align(slider, LV_ALIGN_CENTER, 0, -30);
/* 设置滑块范围 */
lv_slider_set_range(slider, 0, 100);
/* 设置初始值 */
lv_slider_set_value(slider, 50, LV_ANIM_OFF);
/* 创建显示值的标签 */
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Value: 50");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 20);
/* 添加值改变事件 */
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, label);
}
4.4 创建开关控件¶
/* 开关状态改变事件 */
static void switch_event_cb(lv_event_t * e)
{
lv_obj_t * sw = lv_event_get_target(e);
if(lv_obj_has_state(sw, LV_STATE_CHECKED))
{
printf("Switch ON\n");
/* 执行开启操作 */
}
else
{
printf("Switch OFF\n");
/* 执行关闭操作 */
}
}
void create_switch(void)
{
/* 创建开关 */
lv_obj_t * sw = lv_switch_create(lv_scr_act());
/* 设置位置 */
lv_obj_align(sw, LV_ALIGN_CENTER, 0, 60);
/* 添加事件 */
lv_obj_add_event_cb(sw, switch_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
/* 创建标签 */
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "LED Control");
lv_obj_align_to(label, sw, LV_ALIGN_OUT_LEFT_MID, -10, 0);
}
步骤5:完整示例 - 温度监控界面¶
5.1 界面设计¶
创建一个简单的温度监控界面,包含: - 温度显示标签 - 温度曲线图 - 风扇控制开关 - 温度阈值滑块
5.2 完整代码实现¶
/* 全局对象 */
static lv_obj_t * temp_label;
static lv_obj_t * chart;
static lv_chart_series_t * ser;
static lv_obj_t * fan_switch;
static lv_obj_t * threshold_slider;
/* 模拟温度读取 */
float read_temperature(void)
{
/* 这里应该读取实际的温度传感器 */
static float temp = 25.0;
temp += (rand() % 20 - 10) / 10.0; // 模拟温度变化
if(temp < 20.0) temp = 20.0;
if(temp > 40.0) temp = 40.0;
return temp;
}
/* 风扇控制事件 */
static void fan_event_cb(lv_event_t * e)
{
lv_obj_t * sw = lv_event_get_target(e);
if(lv_obj_has_state(sw, LV_STATE_CHECKED))
{
/* 开启风扇 */
printf("Fan ON\n");
// HAL_GPIO_WritePin(FAN_GPIO, FAN_PIN, GPIO_PIN_SET);
}
else
{
/* 关闭风扇 */
printf("Fan OFF\n");
// HAL_GPIO_WritePin(FAN_GPIO, FAN_PIN, GPIO_PIN_RESET);
}
}
/* 阈值滑块事件 */
static void threshold_event_cb(lv_event_t * e)
{
lv_obj_t * slider = lv_event_get_target(e);
int32_t value = lv_slider_get_value(slider);
lv_obj_t * label = lv_event_get_user_data(e);
lv_label_set_text_fmt(label, "Threshold: %d°C", value);
}
/* 创建温度监控界面 */
void create_temp_monitor_ui(void)
{
/* 创建标题 */
lv_obj_t * title = lv_label_create(lv_scr_act());
lv_label_set_text(title, "Temperature Monitor");
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0);
/* 创建温度显示 */
temp_label = lv_label_create(lv_scr_act());
lv_label_set_text(temp_label, "25.0°C");
lv_obj_align(temp_label, LV_ALIGN_TOP_MID, 0, 40);
lv_obj_set_style_text_font(temp_label, &lv_font_montserrat_24, 0);
/* 创建温度曲线图 */
chart = lv_chart_create(lv_scr_act());
lv_obj_set_size(chart, 200, 100);
lv_obj_align(chart, LV_ALIGN_CENTER, 0, -20);
lv_chart_set_type(chart, LV_CHART_TYPE_LINE);
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 0, 50);
lv_chart_set_point_count(chart, 20);
/* 添加数据系列 */
ser = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED),
LV_CHART_AXIS_PRIMARY_Y);
/* 创建风扇控制 */
lv_obj_t * fan_label = lv_label_create(lv_scr_act());
lv_label_set_text(fan_label, "Fan:");
lv_obj_align(fan_label, LV_ALIGN_BOTTOM_LEFT, 20, -60);
fan_switch = lv_switch_create(lv_scr_act());
lv_obj_align(fan_switch, LV_ALIGN_BOTTOM_LEFT, 70, -65);
lv_obj_add_event_cb(fan_switch, fan_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
/* 创建阈值设置 */
lv_obj_t * threshold_label = lv_label_create(lv_scr_act());
lv_label_set_text(threshold_label, "Threshold: 30°C");
lv_obj_align(threshold_label, LV_ALIGN_BOTTOM_LEFT, 20, -30);
threshold_slider = lv_slider_create(lv_scr_act());
lv_obj_set_width(threshold_slider, 180);
lv_obj_align(threshold_slider, LV_ALIGN_BOTTOM_MID, 0, -10);
lv_slider_set_range(threshold_slider, 20, 40);
lv_slider_set_value(threshold_slider, 30, LV_ANIM_OFF);
lv_obj_add_event_cb(threshold_slider, threshold_event_cb,
LV_EVENT_VALUE_CHANGED, threshold_label);
}
5.3 定时更新温度数据¶
/* 定时器回调函数 */
static void temp_update_timer_cb(lv_timer_t * timer)
{
/* 读取温度 */
float temp = read_temperature();
/* 更新温度显示 */
lv_label_set_text_fmt(temp_label, "%.1f°C", temp);
/* 更新曲线图 */
lv_chart_set_next_value(chart, ser, (int32_t)temp);
/* 检查温度阈值 */
int32_t threshold = lv_slider_get_value(threshold_slider);
if(temp > threshold)
{
/* 温度超过阈值,自动开启风扇 */
if(!lv_obj_has_state(fan_switch, LV_STATE_CHECKED))
{
lv_obj_add_state(fan_switch, LV_STATE_CHECKED);
printf("Auto fan ON: temp=%.1f, threshold=%d\n", temp, threshold);
}
}
}
/* 在ui_init中创建定时器 */
void ui_init(void)
{
/* 创建界面 */
create_temp_monitor_ui();
/* 创建定时器,每1秒更新一次 */
lv_timer_create(temp_update_timer_cb, 1000, NULL);
}
定时器说明:
- lv_timer_create(): 创建LVGL定时器
- 第一个参数: 回调函数
- 第二个参数: 周期(毫秒)
- 第三个参数: 用户数据
- LVGL定时器在 lv_timer_handler() 中自动调用
步骤6:样式和主题¶
6.1 自定义样式¶
void apply_custom_style(void)
{
/* 创建样式对象 */
static lv_style_t style_btn;
lv_style_init(&style_btn);
/* 设置背景颜色 */
lv_style_set_bg_color(&style_btn, lv_palette_main(LV_PALETTE_BLUE));
/* 设置圆角 */
lv_style_set_radius(&style_btn, 10);
/* 设置阴影 */
lv_style_set_shadow_width(&style_btn, 10);
lv_style_set_shadow_color(&style_btn, lv_palette_main(LV_PALETTE_BLUE));
/* 创建按钮并应用样式 */
lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_add_style(btn, &style_btn, 0);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
/* 添加标签 */
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "Styled Button");
lv_obj_center(label);
}
6.2 使用内置主题¶
void set_theme(void)
{
/* 设置深色主题 */
lv_theme_t * theme = lv_theme_default_init(
lv_disp_get_default(), // 显示设备
lv_palette_main(LV_PALETTE_BLUE), // 主色调
lv_palette_main(LV_PALETTE_RED), // 次色调
true, // 深色模式
LV_FONT_DEFAULT // 字体
);
lv_disp_set_theme(lv_disp_get_default(), theme);
}
6.3 动画效果¶
/* 按钮点击动画 */
static void btn_anim_event_cb(lv_event_t * e)
{
lv_obj_t * btn = lv_event_get_target(e);
/* 创建动画 */
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, btn);
lv_anim_set_time(&a, 300);
lv_anim_set_values(&a, 100, 120); // 从100%放大到120%
lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out);
/* 设置宽度动画 */
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_width);
lv_anim_start(&a);
/* 设置高度动画 */
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_height);
lv_anim_start(&a);
}
步骤7:编译和测试¶
7.1 编译项目¶
- 确保所有文件已添加到项目
- 检查头文件路径配置
- 点击编译按钮
可能的编译错误:
错误1: undefined reference to 'lv_xxx'
解决: 检查LVGL源文件是否全部添加到项目
错误2: lv_conf.h: No such file or directory
解决: 确保lv_conf.h在项目根目录,并添加到包含路径
错误3: #error "LV_CONF_INCLUDE_SIMPLE is not defined"
解决: 在lv_conf.h开头添加 #if 1
7.2 下载和运行¶
- 连接开发板
- 下载程序
- 观察LCD显示
预期结果: - LCD正常显示界面 - 触摸操作响应正常 - 控件交互正常
7.3 性能测试¶
/* 测试帧率 */
void test_fps(void)
{
static uint32_t last_time = 0;
static uint32_t frame_count = 0;
frame_count++;
uint32_t current_time = HAL_GetTick();
if(current_time - last_time >= 1000)
{
printf("FPS: %d\n", frame_count);
frame_count = 0;
last_time = current_time;
}
}
/* 在主循环中调用 */
while(1)
{
lv_timer_handler();
test_fps();
HAL_Delay(5);
}
性能优化建议: - 增大显示缓冲区可提高刷新速度 - 使用DMA传输像素数据 - 减少不必要的重绘 - 优化显示驱动的SPI速度
故障排除¶
问题1:LCD无显示¶
可能原因: - 硬件连接错误 - LCD初始化失败 - 显示驱动配置错误 - 背光未开启
解决方法:
1. 检查LCD电源和信号线连接
2. 用万用表测试SPI信号
3. 检查LCD初始化序列是否正确
4. 确认背光引脚已配置并拉高
5. 在 disp_flush() 中添加调试信息
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
printf("Flush: x1=%d, y1=%d, x2=%d, y2=%d\n",
area->x1, area->y1, area->x2, area->y2);
// ...
}
问题2:触摸不响应¶
可能原因: - 触摸屏未正确连接 - 触摸驱动配置错误 - 坐标校准不准确 - 触摸中断未配置
解决方法:
1. 检查触摸屏连接
2. 测试触摸芯片通信
3. 重新校准触摸坐标
4. 在 touchpad_read() 中添加调试信息
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
if(Touch_Scan())
{
printf("Touch: x=%d, y=%d\n", Touch_GetX(), Touch_GetY());
// ...
}
}
问题3:显示闪烁或撕裂¶
可能原因: - 缓冲区太小 - 刷新频率不合适 - SPI速度太慢 - 未使用双缓冲
解决方法:
1. 增大显示缓冲区大小
2. 调整 LV_DISP_DEF_REFR_PERIOD
3. 提高SPI时钟频率
4. 启用双缓冲
/* 增大缓冲区 */
static lv_color_t buf1[LV_HOR_RES_MAX * 20]; // 20行
static lv_color_t buf2[LV_HOR_RES_MAX * 20]; // 双缓冲
问题4:内存不足¶
可能原因:
- LV_MEM_SIZE 设置过大
- 创建了太多对象
- 缓冲区占用过多内存
解决方法:
1. 减小 LV_MEM_SIZE
2. 及时删除不用的对象
3. 减小显示缓冲区
4. 使用外部RAM
/* 监控内存使用 */
lv_mem_monitor_t mon;
lv_mem_monitor(&mon);
printf("Memory: used=%d, free=%d, frag=%d%%\n",
mon.used_cnt, mon.free_cnt, mon.frag_pct);
问题5:程序运行缓慢¶
可能原因:
- lv_timer_handler() 调用频率不够
- 显示刷新太慢
- CPU主频太低
- 启用了太多动画效果
解决方法:
1. 确保 lv_timer_handler() 每5-10ms调用一次
2. 优化显示驱动
3. 提高CPU主频
4. 减少动画和特效
总结¶
通过本教程,你学习了:
- ✅ LVGL图形库的基本架构和工作原理
- ✅ 如何在嵌入式项目中集成LVGL
- ✅ 显示驱动的实现方法和关键函数
- ✅ 触摸屏输入设备的配置和校准
- ✅ LVGL基本控件的使用(标签、按钮、滑块、开关)
- ✅ 事件处理机制和回调函数
- ✅ 定时器的使用和界面更新
- ✅ 样式定制和主题设置
- ✅ 完整GUI应用的开发流程
关键要点:
- 显示驱动: 核心是实现
disp_flush()回调函数,将LVGL的像素数据传输到LCD - 输入设备: 实现
touchpad_read()回调函数,读取触摸坐标并返回给LVGL - 主循环: 必须周期性调用
lv_timer_handler(),建议5-10ms调用一次 - 内存管理: 合理配置
LV_MEM_SIZE和显示缓冲区大小 - 性能优化: 使用双缓冲、DMA传输、提高SPI速度
进阶挑战¶
尝试以下挑战来巩固学习:
- 挑战1:多页面切换
- 创建多个界面页面
- 实现页面切换动画
-
使用
lv_scr_load_anim()函数 -
挑战2:自定义控件
- 创建一个自定义的仪表盘控件
- 实现指针旋转动画
-
添加刻度和数值显示
-
挑战3:图片显示
- 使用LVGL图片转换工具
- 显示PNG/JPG图片
-
实现图片缩放和旋转
-
挑战4:中文字体支持
- 使用LVGL字体转换工具
- 生成中文字体文件
-
在界面中显示中文
-
挑战5:实时数据可视化
- 创建实时曲线图
- 实现数据滚动显示
- 添加数据统计功能
完整代码¶
完整的项目代码可以在这里下载:
- GitHub仓库: lvgl-quickstart-example
- 代码结构:
下一步¶
建议继续学习:
- 触摸屏驱动开发 - 深入学习触摸屏驱动原理
- LCD/OLED显示驱动开发 - 学习不同显示器的驱动方法
- GUI设计模式与架构 - 学习GUI架构设计
- 图形性能优化技术 - 提升GUI性能
推荐资源:
- 官方文档
- LVGL官方文档
- LVGL示例代码
-
工具
- LVGL模拟器 - PC端测试工具
- 图片转换工具 - 在线图片转换
-
字体转换工具 - 在线字体转换
-
视频教程
- LVGL官方YouTube频道
-
B站LVGL教程合集
-
参考书籍
- 《嵌入式GUI开发实战》
- 《LVGL图形库应用开发》
参考资料¶
- LVGL官方文档
- LVGL Documentation
- Porting Guide
- Display Interface
-
硬件参考
- ILI9341 LCD Controller Datasheet
- ST7789 LCD Controller Datasheet
- XPT2046 Touch Controller Datasheet
-
FT6236 Touch Controller Datasheet
-
开发板文档
- STM32F407 Reference Manual
- ESP32 Technical Reference Manual
-
HAL Library User Manual
-
社区资源
- LVGL GitHub
- LVGL Forum
-
相关教程
- SPI通信协议详解
- I2C通信协议详解
- DMA数据传输
反馈与支持:
如果你在学习过程中遇到问题,欢迎通过以下方式获取帮助:
- 💬 在评论区留言
- 📧 发送邮件至 support@embedded-platform.com
- 🐛 在GitHub提交Issue
- 💡 在论坛发起讨论
贡献:
欢迎为本教程贡献代码示例、改进建议或错误修正!
版权声明:本教程采用 CC BY-SA 4.0 许可协议。
最后更新: 2024-01-15
教程版本: 1.0
LVGL版本: 8.3+