RISC-V移植LVGL图形库实践记录
背景
最近在一个RISC-V项目上需要用到图形界面,选用了LVGL。移植过程遇到了一些问题,记录下来供参考。本文以CH32V307为例,LVGL版本为v7.11.0。不同版本之间接口可能有差异,请注意对应。
一、准备工作
硬件
- 主控:RISC-V MCU(CH32V307)
- 显示屏:TFT LCD(SPI或并口均可)
- 调试工具:USB转串口、下载器
软件
- IDE:MounRiver Studio
- 编译器:RISC-V GCC
- 源码:LVGL核心库 + 官方Demo
前置条件
建议先确保以下几点:
- 能正常编译烧录一个简单的裸机工程(如点灯)
- LCD底层驱动已经调通,能正常画点、填充颜色
- 对C语言和嵌入式开发有一定基础
二、获取源码
从GitHub克隆在国内可能较慢,可以通过官方镜像下载:
wget https://download.lvgl.io/lvgl/v7/lvgl-v7.11.0.tar.gz
wget https://download.lvgl.io/demos/v7/lv_demos-v7.11.0.tar.gz
注意核心库与Demo版本需要保持一致,否则可能出现运行时错误。解压后得到lvgl和lv_demos两个文件夹。
三、工程目录结构
建议按以下方式组织文件:
Project/
├── Drivers/ # LCD、触摸等硬件驱动
│ ├── lcd.c
│ ├── lcd.h
│ └── touch.c
├── GUI/ # LVGL相关
│ ├── lvgl/ # 核心库
│ ├── lvgl_port/ # 移植层(显示、输入驱动)
│ └── lv_demos/ # 示例代码
├── User/
│ └── main.c
└── lv_conf.h # LVGL配置文件
这样区分便于维护,后续升级时也容易替换。
四、配置LVGL
4.1 复制配置文件
将lvgl/lv_conf_template.h复制到工程根目录,重命名为lv_conf.h。
将lv_demos/lv_examples/lv_ex_conf_template.h复制到工程根目录,重命名为lv_ex_conf.h。
4.2 修改 lv_conf.h
打开lv_conf.h,将开头的#if 0改为#if 1启用配置。根据实际硬件修改以下参数(以240×480分辨率、16位色深为例):
/* 屏幕分辨率 */
#define LV_HOR_RES_MAX 240
#define LV_VER_RES_MAX 480
/* 颜色深度:16位RGB565 */
#define LV_COLOR_DEPTH 16
/* DPI设置,适合小尺寸屏幕 */
#define LV_DPI 130
/* 内存配置:根据实际RAM大小调整 /
#define LV_MEM_SIZE (24U * 1024U) / 24KB足够运行Widgets Demo */
#define LV_MEM_CUSTOM 0
/* 显示刷新周期:30ms ≈ 33FPS */
#define LV_DISP_DEF_REFR_PERIOD 30
如果出现画面撕裂,可以尝试增加刷新周期或减小缓冲区大小,最后再考虑增大LV_MEM_SIZE。
4.3 修改 lv_ex_conf.h
打开lv_ex_conf.h,同样将#if 0改为#if 1,根据需要开启对应的Demo宏。
五、移植显示驱动
这是移植的核心部分,需要实现一个disp_flush函数,将LVGL的绘制数据输出到LCD。
5.1 准备移植文件
在lvgl/examples/porting/目录下:
lv_port_disp_template.c→lv_port_disp.clv_port_disp_template.h→lv_port_disp.hlv_port_indev_template.c→lv_port_indev.c(如需触摸)lv_port_indev_template.h→lv_port_indev.h(如需触摸)
5.2 实现显示驱动
打开lv_port_disp.c:
- 将文件开头的
#if 0改为#if 1。 - 包含LCD驱动头文件:
#include “lv_port_disp.h”
#include “lcd.h”
- 定义显示缓冲区。LVGL提供了三种方案:
/* 方案1:单缓冲,最简单,适合资源极紧张的场合 */
static lv_color_t draw_buf_1[LV_HOR_RES_MAX * 10];
lv_disp_buf_init(&draw_buf_dsc_1, draw_buf_1, NULL, LV_HOR_RES_MAX * 10);
/* 方案2:双部分缓冲,推荐,兼顾性能与内存 */
static lv_color_t buf_2_1[LV_HOR_RES_MAX * 20];
static lv_color_t buf_2_2[LV_HOR_RES_MAX * 20];
lv_disp_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, LV_HOR_RES_MAX * 20);
/* 方案3:双全屏缓冲,性能最好,内存消耗最大 */
static lv_color_t buf_3_1[LV_HOR_RES_MAX * LV_VER_RES_MAX];
static lv_color_t buf_3_2[LV_HOR_RES_MAX * LV_VER_RES_MAX];
lv_disp_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, LV_HOR_RES_MAX * LV_VER_RES_MAX);
对于RAM有限的MCU,建议使用方案2。
- 实现
disp_flush函数:
void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
int32_t x, y;
for(y = area->y1; y <= area->y2; y++) {
for(x = area->x1; x <= area->x2; x++) {
lcd_draw_point(x, y, color_p->full);
color_p++;
}
}
lv_disp_flush_ready(disp_drv);
}
注意:每次写入一个像素后,color_p指针需要自增,否则会一直写同一个颜色。如果LCD驱动支持区域填充,建议改为批量写入,能明显提高刷新速度。
六、移植触摸驱动(可选)
如果需要触摸功能,在lv_port_indev.c中实现坐标读取:
bool touch_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
uint16_t x, y;
bool pressed = touch_get_coord(&x, &y);
if(pressed) {
>七、将LVGL加入工程
7.1 添加源文件
在IDE中添加以下目录中的所有.c文件:
lvgl/src/lvgl_port/lv_demos/(如果需要Demo)
7.2 添加头文件路径
编译器搜索路径需包含:
lvgl/lvgl/src/lvgl_port/lv_demos/- 工程根目录(
lv_conf.h所在位置)
7.3 编译器优化选项
在使用MounRiver Studio时,默认的-O2优化可能导致动画异常。建议改为-Og,并勾选-fno-strict-aliasing。位置在项目属性的C/C++ Build → Settings → Optimization中。
八、主程序编写
8.1 初始化顺序
#include “lvgl.h”
#include “lv_port_disp.h”
#include “lv_port_indev.h”
int main(void)
{
SystemClock_Config();
lcd_init();
touch_init(); // 可选
lv_init();
lv_port_disp_init();
lv_port_indev_init(); // 可选
lv_demo_widgets(); // 或者自定义UI
while(1) {
lv_task_handler();
delay_ms(5);
}
}
8.2 提供心跳
LVGL需要周期性的时钟中断来驱动动画和定时器。可以在定时器中断中调用:
void TIMx_IRQHandler(void)
{
lv_tick_inc(1); // 1ms一次
}
九、编译与调试
常见问题
- 找不到 lv_conf.h:检查头文件路径是否包含工程根目录,或添加宏
LV_CONF_INCLUDE_SIMPLE。 - 内存不足:减小
LV_MEM_SIZE或缓冲区大小。 - 链接错误:检查是否所有
.c文件都已添加。
烧录后如果屏幕正常显示Demo界面,说明移植成功。如果黑屏,建议按以下顺序检查:
disp_flush中color_p指针是否自增- LCD初始化是否正确
lv_conf.h中的分辨率是否与屏幕匹配- LVGL初始化是否执行完毕
十、一些优化思路
如果界面不够流畅,可以尝试:
- 使用双部分缓冲代替单缓冲
- 在
disp_flush中使用区域填充代替逐点绘制 - 适当降低刷新周期
- 如有DMA,可用于传输显示数据
- 使用
-Og优化等级,避免-O2引起的动画问题
总结
移植LVGL到RISC-V平台,核心就是实现显示驱动的disp_flush回调,让LVGL能够把绘制数据送到屏幕上。配置好分辨率、颜色深度和内存大小,按正确的顺序初始化,基本就能跑起来。整个过程不算复杂,但细节较多。希望这份记录对你有帮助。