#143_屏幕画撕裂的问题及解决方法

启动界面到 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
  **********************/
相关推荐
小年糕是糕手1 小时前
【AI】用 Python 做一个 AI 截图检索助手:把收藏夹里的截图找回来
linux·人工智能·python·chatgpt·agent·codex·deepseek
李永奉1 小时前
杰理可视化SDK开发-音频录制功能使用教程
笔记·单片机·嵌入式硬件·mcu·物联网·音视频
hhhjhl1 小时前
Zod 跨端三方库仓颉适配全流程
linux·运维·ubuntu
_YouziTech_1 小时前
【STM32+MAX98357】MP3转PCM存入Flash并控制音频输出
stm32·单片机·音视频·内存·pcm·i2s·wav
沐欣工作室_lvyiyi1 小时前
基于STM32的老人跌倒与防走失报警系统设计与实现(论文+源码)
stm32·单片机·嵌入式硬件
叼烟扛炮1 小时前
Linux 学习笔记(三十二):线程控制——创建、等待与分离
linux·运维·服务器·笔记·学习·1024程序员节
我叫洋洋2 小时前
Allegro 画布“垃圾显示“清理指南:把板子调成干净工作台(实战总结)
c语言·c++·单片机·嵌入式硬件·eda·嘉立创
xiangw@GZ2 小时前
SPI Flash 接口模式与 XIP 技术详解
单片机·嵌入式硬件
夜雪一千2 小时前
Linux 权限、用户、用户组内容介绍
linux·运维·服务器