lv_port_disp.c 详情介绍
lv_port_disp.c是 LVGL 显示移植核心,负责把 LVGL 渲染结果从内存缓冲刷到物理屏幕。
可以把它理解为 3 个阶段:初始化显示对象、接收刷新区域、完成硬件提交。
1. 文件职责与执行时序
- LVGL 初始化时调用
lv_port_disp_init()完成显示驱动注册。 - LVGL 产生重绘后触发
flush_cb(即disp_flush(...))。 disp_flush将color_p/px_map指向的像素数据送入 LCD 控制器或 SPI 驱动。- 提交完成后通知 LVGL 刷新结束(LVGL8 常见为
lv_disp_flush_ready)。
如果第 4 步缺失,现象通常是界面只刷一帧后卡死。
2. ra6m3-hmi-board 实现要点
对应文件:bsp/renesas/ra6m3-hmi-board/board/lvgl/lv_port_disp.c
显示接口
- 使用 GLCDC;
- 通过
R_GLCDC_BufferChange(...)在刷新时切换到新帧缓冲。
Vsync 同步
- 通过
DisplayVsyncCallback(...)+_SemaphoreVsync做同步; - 目的:在正确时机切缓冲,减少 tearing(撕裂)。
- 通过
LVGL8/LVGL9 双分支
- LVGL8 分支使用
lv_disp_drv_t/lv_disp_draw_buf_t; - LVGL9 分支使用
lv_display_create、lv_display_set_flush_cb、lv_display_set_buffers。
- LVGL8 分支使用
缓冲模式
- 使用
fb_background双缓冲(direct render 路径); - 分辨率变化后要同步检查缓冲大小与底层显存定义。
- 使用
3. ra6m3-ek 实现要点
对应文件:bsp/renesas/ra6m3-ek/board/lvgl/lv_port_disp.c
两条输出路径
PKG_USING_ILI9341:SPI 屏路径,调用lcd_fill_array_spi(...);- 非 SPI 分支:直接写 framebuffer(RGB/LCD 设备)。
软件拷贝路径
- 非 GPU 分支下,逐像素把 LVGL 颜色写入 framebuffer;
- 包含区域裁剪逻辑(防止越界写显存)。
显存缓冲
buf_1通过链接段放在固定地址;COLOR_BUFFER = LV_HOR_RES_MAX * LV_VER_RES_MAX / 4,表示四分之一屏缓冲。
刷新完成通知
disp_flush末尾调用lv_disp_flush_ready(disp_drv);- 该调用必须在所有输出分支都可达。
4. 常改参数与接口
分辨率联动
disp_drv.hor_res/ver_res(LVGL8)或lv_display_create参数(LVGL9);- 必须与
lv_conf.h的LV_HOR_RES_MAX/LV_VER_RES_MAX一致。
flush 提交函数
- RGB 屏:替换为平台 LCD 控制器切缓冲/拷贝接口;
- SPI 屏:替换为 SPI 批量刷图函数。
缓冲策略
- 单缓冲:省内存,可能更卡;
- 双缓冲:更流畅,RAM 占用更高;
- 部分缓冲(如 1/4 屏):折中方案,常用于 RAM 紧张场景。
同步机制
- 使用 Vsync/中断/信号量时,要避免死等和时序反转;
- 若
flush偶发卡住,优先排查信号量初值与回调触发条件。
5. 常见故障与定位
- 黑屏:确认
lv_port_disp_init()是否被调用、LCD 设备是否打开成功、分辨率是否一致。 - 只刷一次:确认每次
flush都有flush_ready通知。 - 花屏/偏色:核对
LV_COLOR_DEPTH、字节序、像素格式转换逻辑。 - 撕裂明显:检查是否在 Vsync 同步点提交缓冲。
- 拖动卡顿:检查
COLOR_BUFFER大小、是否走了低效逐像素路径、SPI 时钟是否过低。
6. 推荐调试步骤(显示链路)
- 先用纯色填充测试确认“能亮屏”;
- 再跑简单控件(label/button)确认
flush连续触发; - 再引入动画或列表滚动观察帧率与撕裂;
- 最后再开 GPU/2D 加速做性能优化。
7.lv_port_disp_init()详解
lv_port_disp_init()是显示移植的入口函数,它决定了 LVGL 后续能否正常刷新。
核心任务可以概括为 5 步:准备硬件资源 -> 准备绘图缓冲 -> 创建显示驱动对象 -> 绑定刷新回调 -> 注册到 LVGL。
7.1 通用流程(抽象视角)
准备底层显示资源
- 打开 LCD/SPI 设备,或准备 GLCDC 控制器;
- 可选:创建同步资源(如 Vsync 信号量)。
初始化 draw buffer
- 告诉 LVGL 使用哪块 RAM 作为绘图缓冲;
- 决定单缓冲、双缓冲或部分缓冲策略。
创建并配置 display driver/display 对象
- LVGL8:
lv_disp_drv_init(...); - LVGL9:
lv_display_create(...)。
- LVGL8:
绑定刷新回调
- 把
disp_flush(...)绑定给 LVGL; - 可选绑定等待回调(如 Vsync wait)。
- 把
完成注册
- LVGL8:
lv_disp_drv_register(...); - LVGL9:对象创建并设置完成后即生效。
- LVGL8:
7.2ra6m3-hmi-board中的初始化细节
对应:bsp/renesas/ra6m3-hmi-board/board/lvgl/lv_port_disp.c
- 先创建
_SemaphoreVsync,用于刷新同步; - LVGL8 路径:
lv_disp_draw_buf_init(&disp_buf, &fb_background[0][0], &fb_background[1][0], sizeof(fb_background[0]))- 双缓冲直接对接
fb_background; - 设置
disp_drv.hor_res/ver_res与flush_cb; lv_disp_drv_register(&disp_drv)完成注册。
- LVGL9 路径:
lv_display_create(LV_HOR_RES_MAX, LV_VER_RES_MAX);- 设置
lv_display_set_flush_cb(...)与lv_display_set_flush_wait_cb(...); lv_display_set_buffers(..., LV_DISPLAY_RENDER_MODE_DIRECT)配置 direct 模式双缓冲。
- 最后置
lvgl_init_flag = 1,使 Vsync 回调开始真正参与同步。
7.3ra6m3-ek中的初始化细节
对应:bsp/renesas/ra6m3-ek/board/lvgl/lv_port_disp.c
- 先走硬件分支初始化:
- SPI 屏分支:
spi_lcd_init(20); - RGB/LCD 分支:查找并打开
lcd设备,读取rt_device_graphic_info。
- SPI 屏分支:
- 初始化绘图缓冲:
lv_disp_draw_buf_init(&disp_buf, buf_1, NULL, COLOR_BUFFER);- 当前是单缓冲(第二缓冲传
NULL)。
- 初始化显示驱动:
lv_disp_drv_init(&disp_drv);- 设置
hor_res/ver_res、draw_buf、flush_cb。
- 可选 GPU 路径:
- 若使能
DLG_LVGL_USE_GPU_RA6M3,会额外lv_port_gpu_init()。
- 若使能
lv_disp_drv_register(&disp_drv)完成注册。
7.4 这个函数最容易出错的点
- 分辨率不一致:
lv_conf.h与lv_port_disp_init()设置不一致,会导致越界、显示异常或黑屏。 - 缓冲大小不够:改分辨率后没同步改
COLOR_BUFFER或底层 framebuffer。 - 回调未绑定:
flush_cb未设置或被条件编译排除,界面不刷新。 - 设备未打开:
rt_device_find/open失败,后续写 framebuffer 会异常。 - 同步对象时序问题:Vsync 信号量初值、等待时机不当,可能导致
flush卡死。
7.5 建议的最小验证法(只测 init)
- 断点或日志确认
lv_port_disp_init()执行到末尾; - 输出当前分辨率、缓冲地址和缓冲长度;
- 统计
disp_flush调用次数(确认刷新链路已建立); - 屏幕显示单个静态 label(先不加动画)验证基本通路。