
启动界面到 LVGL 撕裂修复结果
一、根因
不是屏幕方向、RGB 时序或硬件故障。
对照可正常显示的 M646-04 示例工程后确认:
- 分辨率均为 1024×600。
- LCD 扫描方向均为 0°。
- RGB 接口均为 RGB565。
- 像素时钟、同步极性一致。
- UI 均使用三个物理 Frame Buffer。
- 当前工程的 HX35XX GDMA 硬件加速已经启用。
真正的问题是:当前工程在 LVGL 第一帧尚未完成时就打开了 UI 图层,LCD 可能扫描到空帧或正在搬运的半成品帧,从而在启动图切换到 LVGL 时出现撕裂。
二、已经完成的修改
现在切换流程为:
显示启动图片
↓
隐藏初始化 UI 图层
↓
初始化 LVGL
↓
创建 LVGL 主界面
↓
强制绘制完整首帧
↓
检查 Frame Buffer 参数
↓
GDMA 搬运完整画面
↓
等待 LCD 帧边界接收
↓
打开 UI 图层
主要修改位置: - app_init.c (line 306)
- app_lcdshow.c (line 424)
- lv_port_disp.c (line 571)
同时增加了: - UI 隐藏初始化接口。
- LVGL 首帧强制刷新与提交确认。
- Frame Buffer 宽度、高度、stride、容量检查。
- UI/Video 方向与分辨率检查。
- GDMA 成功次数和软件回退次数统计。
- 首帧失败时保留启动图,不显示空白 UI 帧。
我的修复方式是"首帧同步后再切换图层",而不是改屏幕时序或盲目增加缓冲区。
启动图和 LVGL 实际走两条显示层:
text
启动图 JPEG → Video 图层
LVGL UI → UI 图层
原来的流程是:
text
启动图显示
→ UI 图层立即打开
→ 初始化 LVGL
→ 创建页面
→ LVGL 绘制首帧
→ GDMA 搬运
→ 提交 Frame Buffer
问题在于 UI 图层打开得太早。此时它对应的物理帧可能还是空内容,或 GDMA 正在搬运,LCD 扫描到这类帧便会在启动图切换的一瞬间出现撕裂、白块或半帧。
现在改为:
text
启动图持续显示
→ 初始化 UI 图层但保持隐藏
→ 初始化 LVGL、创建主界面
→ 强制绘制完整首帧
→ 校验 Frame Buffer 的宽高、stride、容量
→ 使用 GDMA 搬运完整画面
→ 提交并等待 LCD 帧边界接收该帧
→ 打开 UI 图层
所以,屏幕只会从"完整启动图"切换到"完整 LVGL 首帧",中间不会显示未准备好的 UI 缓冲。
同时我补了三类保护:
- 帧参数检查:确认 UI 帧确实是
1024×600、stride 足够、缓存容量足够,避免方向或像素格式配置错误。 - 方向检查:启动时打印 UI 与 Video 的分辨率、扫描方向;当前预期都是
1024×600、scan=0。 - GDMA 检查:统计
gdma-ok与copy-sw。gdma-ok > 0且copy-sw:0表明硬件搬运正常,没有退回 CPU 软件复制。
没有修改原理图相关引脚、LCD 时序、屏幕方向或启动图片;因为它们和能正常显示的 M646-04 示例工程一致。修复的是 LVGL 首帧与硬件图层切换的同步顺序。
c
diff --git a/Project_Cold/app/app_common/inc/app_lcdshow.h b/Project_Cold/app/app_common/inc/app_lcdshow.h
index 5e9d780..d0cb56c 100644
--- a/Project_Cold/app/app_common/inc/app_lcdshow.h
+++ b/Project_Cold/app/app_common/inc/app_lcdshow.h
@@ -94,6 +94,15 @@ void* app_lcdVideoIdleFrameGet(void);
*******************************************************************************/
int app_lcdUiShowInit(void);
/*******************************************************************************
+* Function Name : app_lcdUiShowInitHidden
+* Description : init lcd ui layer but keep it hidden until a complete frame
+* is ready (used for boot logo -> LVGL hand-off)
+* Input : none
+* Output : none
+* Return : 0 on success, negative value on failure
+*******************************************************************************/
+int app_lcdUiShowInitHidden(void);
+/*******************************************************************************
* Function Name : app_lcdCsiVideoShowStop
* Description : lcd video csi show stop
* Input : none
diff --git a/Project_Cold/app/app_common/src/app_init.c b/Project_Cold/app/app_common/src/app_init.c
index 84bf4c6..f1ff199 100644
--- a/Project_Cold/app/app_common/src/app_init.c
+++ b/Project_Cold/app/app_common/src/app_init.c
@@ -302,9 +302,13 @@ void app_draw_init(void)
#else
- app_lcdUiShowInit(); // enable ui display layer
-
#if GUI_LVGL
+ int ret = app_lcdUiShowInitHidden();
+ if(ret < 0)
+ {
+ deg_Printf("[LVGL DISP] hidden UI layer init failed:%d\n", ret);
+ return;
+ }
#if LV_USE_LOG && !LV_LOG_PRINTF
lv_log_register_print_cb((lv_log_print_g_cb_t)hal_uartPrintString);
@@ -321,7 +325,20 @@ void app_draw_init(void)
XWorkCreate(10 * X_TICK, lvgl_tick); // lvgl tick
lvgl_ui_init();
+
+ /* Do not expose a partially initialised UI frame. Keep the boot
+ * image visible until LVGL has submitted one complete frame and
+ * the LCD HAL has consumed it at a frame boundary. */
+ ret = lv_port_disp_prepare_first_frame();
+ if(ret < 0)
+ {
+ deg_Printf("[LVGL DISP] first frame prepare failed:%d; keep boot image\n", ret);
+ return;
+ }
+ hal_lcdUiEnable(UI_LAYER0, 1);
+ deg_Printf("[LVGL DISP] first frame ready; UI layer enabled\n");
#else
+ app_lcdUiShowInit(); // enable legacy ui display layer
uiWinDrawInit(app_lcdUiDrawIdleFrameGet());
uiWinInit();
diff --git a/Project_Cold/app/app_common/src/app_lcdshow.c b/Project_Cold/app/app_common/src/app_lcdshow.c
index 3cb7c6e..a5138a2 100644
--- a/Project_Cold/app/app_common/src/app_lcdshow.c
+++ b/Project_Cold/app/app_common/src/app_lcdshow.c
@@ -421,7 +421,7 @@ void* app_lcdVideoIdleFrameGet(void)
* Output : none
* Return : 0
*******************************************************************************/
-int app_lcdUiShowInit(void)
+static int app_lcdUiShowInitInternal(u8 enable)
{
int i;
@@ -431,8 +431,10 @@ int app_lcdUiShowInit(void)
INT32 scanmode = hal_lcdUiScanModeGet();
if(app_lcdshow_ctrl.ui_layer_en != 0)
-
- return 0;
+ {
+ hal_lcdUiEnable(UI_LAYER0, enable ? 1 : 0);
+ return 0;
+ }
if(hal_lcdGetUiResolution(&ui_w,&ui_h) < 0)
return -2;
@@ -494,7 +496,8 @@ int app_lcdUiShowInit(void)
return -7;
}
- hal_lcdUiEnable(UI_LAYER0,1);
+ /* Keep LVGL hidden until its first complete frame is ready. */
+ hal_lcdUiEnable(UI_LAYER0, enable ? 1 : 0);
if (palette)
{
@@ -505,6 +508,16 @@ int app_lcdUiShowInit(void)
return 0;
}
+
+int app_lcdUiShowInit(void)
+{
+ return app_lcdUiShowInitInternal(1);
+}
+
+int app_lcdUiShowInitHidden(void)
+{
+ return app_lcdUiShowInitInternal(0);
+}
/*******************************************************************************
* Function Name : app_lcdCsiVideoShowStop
* Description : lcd video csi show stop
diff --git a/Project_Cold/sys_manage/lvgl_manage/lv_port/lv_port_disp.c b/Project_Cold/sys_manage/lvgl_manage/lv_port/lv_port_disp.c
index 0a3bdc9..dc17f05 100644
--- a/Project_Cold/sys_manage/lvgl_manage/lv_port/lv_port_disp.c
+++ b/Project_Cold/sys_manage/lvgl_manage/lv_port/lv_port_disp.c
@@ -31,6 +31,7 @@ typedef struct hx_lvgl_ctx
lv_color_t *canvas; /* 鎸佷箙鐢诲竷锛歀VGL direct 妯″紡鍞竴娓叉煋鐩爣锛岃法甯т繚鐣欏唴瀹?*/
u32 canvas_stride; /* 鐢诲竷琛屽瓧鑺傛暟 = hor_res * sizeof(lv_color_t)锛圠VGL 绱у噾琛屽锛?*/
lcdshow_frame_t *disp_frame; /* 褰撳墠鍒锋柊鐙崰鐨勬惉杩愮洰鏍囷紱鎻愪氦鍚庝氦杩?LCD HAL锛屼笉璺ㄥ埛鏂伴鍗?*/
+ u8 frame_submitted;
} hx_lvgl_ctx_t;
#else
typedef struct hx_lvgl_ctx
@@ -39,6 +40,7 @@ typedef struct hx_lvgl_ctx
lv_disp_draw_buf_t disp_buf;
lcdshow_frame_t *draw_frame; /* 褰撳墠娓叉煋鐗╃悊甯э紙LVGL 鐩存帴娓叉煋杩涘抚姹狅紝鏃犵敾甯冨紑閿€锛?*/
+ u8 frame_submitted;
} hx_lvgl_ctx_t;
#endif
@@ -82,11 +84,12 @@ static void disp_stats_report(void)
if ((u32)(now - stats->last_report_ms) < 1000)
return;
- deg_Printf("[LVGL DISP] 1s refresh:%d submit:%d idle:%d no-free-dirty:%d no-free-idle:%d copy-avg/max:%d/%d ms refresh-max:%d ms copy-sw:%d\n",
+ deg_Printf("[LVGL DISP] 1s refresh:%d submit:%d idle:%d no-free-dirty:%d no-free-idle:%d copy-avg/max:%d/%d ms refresh-max:%d ms gdma-ok:%d copy-sw:%d\n",
stats->render_count, stats->submit_count, stats->idle_count,
stats->no_free_dirty_count, stats->no_free_idle_count,
stats->submit_count ? stats->copy_time_sum_ms / stats->submit_count : 0,
stats->copy_time_max_ms, stats->refresh_time_max_ms,
+ stats->submit_count - stats->copy_sw_count,
stats->copy_sw_count);
stats->last_report_ms = now;
stats->render_count = 0;
@@ -114,6 +117,27 @@ static inline lcdshow_frame_t * disp_drawBufMalloc(void)
return hal_uiDrawBufMalloc(UI_LAYER0, GUI_FORMAT);
}
+static int disp_frame_validate(const lcdshow_frame_t *frame, const lv_disp_drv_t *disp_drv)
+{
+ u32 min_stride = (u32)disp_drv->hor_res * sizeof(lv_color_t);
+ u32 min_size;
+
+ if(frame == NULL || frame->y_addr == NULL)
+ return -1;
+
+ min_size = (u32)frame->stride * disp_drv->ver_res;
+ if(frame->w != disp_drv->hor_res || frame->h != disp_drv->ver_res ||
+ frame->stride < min_stride || frame->buf_size < min_size)
+ {
+ deg_Printf("[LVGL DISP] invalid frame w:%d/%d h:%d/%d stride:%d/>=%d size:%d/>=%d\n",
+ frame->w, disp_drv->hor_res, frame->h, disp_drv->ver_res,
+ frame->stride, min_stride, frame->buf_size, min_size);
+ return -2;
+ }
+
+ return 0;
+}
+
#if LV_CANVAS_DIRECT_MODE
static void disp_clear(struct _lv_disp_drv_t * disp_drv, uint8_t * buf, uint32_t size)
{
@@ -273,6 +297,14 @@ static void disp_init(void)
return;
}
+ if (disp_frame_validate(cur_frame, p_disp_drv) < 0)
+ {
+ hal_uiBuffFree(cur_frame);
+ ctx->disp_frame = NULL;
+ lv_disp_flush_ready(p_disp_drv);
+ return;
+ }
+
/* ---- 鏈€鍚庝竴绗?flush锛氭湰甯ф墍鏈夎剰鍖哄凡娓叉煋杩涙寔涔呯敾甯冿紝鏁村抚鎼繍鎻愪氦 ----
* 蹇呴』鏁村抚鎼繍锛堜笉鑳藉彧鎼湰甯ц剰鍖猴級锛氬抚姹犲甯ц疆鎹㈡樉绀猴紝姣忎釜姹犲抚鍙湪杞埌 kick 鏃? * 鏀跺埌褰撳抚琛ヤ竵锛屽悇甯у唴瀹逛細褰兼涓嶄竴鑷达紙鍚勭己涓嶅悓甯х殑鏇存柊锛夛紝LCD 杞崲鏄剧ず鏃剁敾闈㈤棯鐑侊紱
@@ -302,6 +334,7 @@ static void disp_init(void)
hal_lcdUiSetBufferWaitDone(UI_LAYER0);
hal_lcdUiKick(cur_frame);
g_lvgl_disp_stats.submit_count++;
+ ctx->frame_submitted = 1;
/* Do not pre-allocate the next physical frame here. The LCD HAL
* owns the submitted frame; claim a new one only for the next refresh. */
@@ -333,6 +366,14 @@ static void disp_flush(lv_disp_drv_t * p_disp_drv, const lv_area_t * area, lv_co
return;
}
+ if (disp_frame_validate(cur_frame, p_disp_drv) < 0)
+ {
+ hal_uiBuffFree(cur_frame);
+ ctx->draw_frame = NULL;
+ lv_disp_flush_ready(p_disp_drv);
+ return;
+ }
+
/* Flush only the dirty region instead of the entire framebuffer */
{
u32 flush_addr = (u32)cur_frame->y_addr + (area->y1 * p_disp_drv->hor_res * sizeof(lv_color_t));
@@ -345,6 +386,7 @@ static void disp_flush(lv_disp_drv_t * p_disp_drv, const lv_area_t * area, lv_co
* LCD 鎾炰笂娓叉煋涓殑鍗婃垚鍝佸抚 鈫?鐧藉潡 */
hal_lcdUiKick(cur_frame);
hal_lcdUiSetBufferWaitDone(UI_LAYER0);
+ ctx->frame_submitted = 1;
ctx->draw_frame = NULL;
// 鑾峰彇绌洪棽 fb锛屽鏋滄湁灏辫皟鐢?lv_disp_flush_ready锛屽鏋滄病鏈夛紝绛夐噴鏀?@@ -412,6 +454,10 @@ void lv_port_disp_init(int ui_width, int ui_height)
{
struct hx_lvgl_ctx *ctx = &g_lvgl_ctx;
lv_disp_drv_t *disp_drv = &ctx->disp_drv;
+ u16 screen_w = 0;
+ u16 screen_h = 0;
+ s32 ui_scan = hal_lcdUiScanModeGet();
+ s32 video_scan = hal_lcdVideoScanModeGet();
memset(ctx, 0, sizeof(struct hx_lvgl_ctx));
@@ -421,6 +467,19 @@ void lv_port_disp_init(int ui_width, int ui_height)
disp_init();
deg_msg("[%s] w:%d, h:%d\n", __FUNCTION__, ui_width, ui_height);
+ if(hal_lcdGetSreenResolution(&screen_w, &screen_h) < 0)
+ {
+ screen_w = 0;
+ screen_h = 0;
+ }
+ deg_Printf("[LVGL DISP] config screen:%dx%d ui:%dx%d ui-scan:%d video-scan:%d format:%d depth:%d bpp:%d direct:%d gdma:1\n",
+ screen_w, screen_h, ui_width, ui_height, ui_scan, video_scan,
+ GUI_FORMAT, LV_COLOR_DEPTH, (u32)sizeof(lv_color_t),
+ LV_CANVAS_DIRECT_MODE);
+ if(screen_w != ui_width || screen_h != ui_height || ui_scan != video_scan)
+ {
+ deg_Printf("[LVGL DISP] warning: UI/video geometry or scan direction mismatch\n");
+ }
/*-----------------------------
* Create a buffer for drawing
@@ -508,3 +567,29 @@ void lv_port_disp_init(int ui_width, int ui_height)
ctx->disp_frame = NULL;
#endif
}
+
+int lv_port_disp_prepare_first_frame(void)
+{
+ struct hx_lvgl_ctx *ctx = &g_lvgl_ctx;
+ lv_disp_t *disp = lv_disp_get_default();
+ lv_obj_t *screen;
+
+ if(disp == NULL)
+ return -1;
+
+ screen = lv_disp_get_scr_act(disp);
+ if(screen == NULL)
+ return -2;
+
+ ctx->frame_submitted = 0;
+ lv_obj_invalidate(screen);
+ lv_refr_now(disp);
+
+ if(!ctx->frame_submitted)
+ return -3;
+
+ /* The hardware UI layer is still hidden here. Wait until the submitted
+ * buffer is consumed at a frame boundary before app_init exposes it. */
+ hal_lcdUiSetBufferWaitDone(UI_LAYER0);
+ return 0;
+}
diff --git a/Project_Cold/sys_manage/lvgl_manage/lv_port/lv_port_disp.h b/Project_Cold/sys_manage/lvgl_manage/lv_port/lv_port_disp.h
index dd96448..5be916e 100644
--- a/Project_Cold/sys_manage/lvgl_manage/lv_port/lv_port_disp.h
+++ b/Project_Cold/sys_manage/lvgl_manage/lv_port/lv_port_disp.h
@@ -33,6 +33,10 @@ extern "C" {
/* Initialize low level display driver */
void lv_port_disp_init(int ui_width, int ui_height);
+/* Render and synchronise the first complete frame while the hardware UI layer
+ * is hidden. Returns 0 only after a frame has actually been submitted. */
+int lv_port_disp_prepare_first_frame(void);
+
/**********************
* MACROS
**********************/