
做车载显示项目这几年我有个习惯一听到需求方提“Video Stop”先追问一句“停到什么程度”。大多数时候对方说的“停止播放视频”心里真正想的其实只是“屏幕别再动画了”但在 STM32U5G9ZJT6Q 这类集成了 LTDC、DMA2D、甚至 MIPI DSI 外设的高性能 MCU 上真正干净地停住一路视频画面牵扯到的是一条完整的硬件链路而不是某个 GPIO 拉一下就能完事。我在这个项目里就吃过亏视频停住以后屏幕偶尔闪彩色横条背光明明关了却仍能隐约看到残留画面系统从低功耗模式唤醒后还会花屏。排查到最后所有现象的矛头都指向同一个根源——停止顺序错了。这篇文章把我在 STM32U5G9ZJT6Q 上做 Video Stop 的完整过程、关键代码和踩坑记录整理出来适合正在用 STM32U5 系列做显示门禁、HMI 仪表、工业手持设备尤其是需要视频播放/暂停/关闭功能的开发者参考。1. 先说清楚STM32U5G9ZJT6Q 上的 Video Stop到底要停什么1.1 一次“黑屏但没断电”的故障让我重新理解了停止这个词项目初期我接到一个仪表显示需求开机播放一段演示视频用户按下按键后视频停止画面切回主菜单同时整机进入低功耗待机。听起来很常规对吧我当时的第一版实现非常简单收到停止指令后先关背光再调 HAL_LTDC_Stop()最后让 MCU 进入 STOP2 模式。结果实测时发现屏幕虽然变黑了但用示波器量 LVDS 输出端的差分信号仍然有持续的波形输出用手电筒侧着照屏幕甚至能看到上一帧画面的残影。也就是说MCU 已经以为自己把视频“停干净了”实际上显示链路还在偷偷工作。后来我翻了 STM32U5 系列的参考手册才意识到 LTDC 停止函数只是把 LCD_TFT 控制器的主使能位清掉了但它不会替你去管 DMA2D 是否还在搬运、帧缓冲里是否残留旧数据、DSI 主机的时钟域是否还在跑、以及用于生成像素时钟的 PLL 是否还挂在系统时钟树上。你如果只是调一个函数就进入低功耗等于把一堆还在运转的外设扔在原地不管功耗自然降不下去复位的瞬间也可能出各种随机问题。1.2 视频链路的三个层面数据源、搬运链路、显示输出想做好 Video Stop第一步是把视频数据的流动路径拆开看。在 STM32U5G9ZJT6Q 上一路典型视频显示通常由三个层面组成。第一层是数据源。视频帧可能来自片内 Flash 里存的一段 MJPEG/JPEG 流也可能来自外部 OctoSPI NOR Flash、SD 卡或者通过 UART/USB 接收。这一层负责“生产”原始帧数据如果不停掉它会持续产生新的画面帧后面的链路永远不知道什么时候才算完。第二层是搬运链路。解码后的 RGB 原始数据不一定直接进 LTDC中间往往要经过 DMA2D 做格式转换比如 YUV 转 RGB565、ARGB8888 转 RGB888、图像缩放、alpha 混合或者经过 GFXMMU 做地址映射。DMA2D 是异步的启动一次传输后由硬件独立搬运CPU 只会在传输完成时收到中断如果你不等它停稳就执行下一步操作很容易出现“传输到一半数据断流”或者“下一次启动时 DMA2D 还在忙”的状态。第三层是显示输出。由 LTDC 控制器负责从帧缓冲里按行列读取像素生成 HSync/VSync/DE/CLK 等时序信号送给面板接口。如果面板走的是 MIPI DSI中间还要经过 DSI 主机控制器做协议转换。这一层一旦开始刷屏就会按照设定的像素时钟持续不停地读内存直到你把 LTDC 的层使能位和主使能位按正确顺序关掉。这三个层面必须按“数据源 → 搬运链路 → 显示输出”的方向依次停止。反过来操作相当于你在电影放映途中先把投影仪的灯泡拔了但胶片还在继续走等你下一次开机时放映机里的胶片已经卡成一团了。1.3 不同业务场景下的 Video Stop 定义差别很大调这个功能之前我建议先明确你属于哪一种需求因为不同需求对应的停止策略完全不一样。第一种是“视频暂停”画面冻结在某一帧但屏幕保持显示。这种场景虽然也调 LTDC 停止流程但停止的位置是数据源和 DMA2D帧缓冲里的最后一帧要保留LTDC 可以继续工作也可以把层输入切换到另一块静态缓冲区。这样做的关键是保证“冻结帧”和“界面帧”之间平滑切换别让用户看到撕裂或者闪屏。第二种是“视频关闭但界面保留”视频区域不再显示但屏幕上还有其他 UI 元素。这种相当于把视频层从 LTDC 里禁用保留 UI 层UI 层继续刷新。实现上要小心 LTDC 图层之间的混合顺序以及旧视频帧是否还残留在图层缓冲区里。第三种是“整机进入低功耗待机”这需要把整条显示链路全部停干净包括时钟、PLL、DSI PHY、外部存储器最后才能让 MCU 进入 STOP2/STANDBY。我这次项目踩坑就是第三种需求里“停不干净”造成的。下面全部围绕“完全关闭视频链路的 Video Stop”来讲但前两种场景的很多思路是通用的。2. 停止之前先把 STM32U5G9ZJT6Q 显示外设之间的依赖关系理清2.1 LTDC、DMA2D、GFXMMU 和帧缓冲在一次画面刷新中的接力在没有接触过 STM32 图形外设的人看来显示就是一帧数据送到屏幕而已。实际上在 STM32U5G9JT6Q 这类芯片上一帧 800x480 或者 1280x720 的画面从内存到屏幕要经过好几级接力。最简单的一种路径是CPU 或硬件解码器把视频帧解码到帧缓冲 ADMA2D 把帧缓冲 A 的格式转换成 LTDC 需要的格式写入帧缓冲 BLTDC 再按照 HSync/VSync 时序逐行从帧缓冲 B 读取通过 RGB 并口或者 DSI 送出去。如果启用了 GFXMMUDMA2D 和 LTDC 看到的还不是实际内存地址而是一个虚拟连续地址由 GFXMMU 把分散在多个内存块里的数据映射成连续缓冲区。这相当于一条流水线前一个环节产出的是后一个环节的原料。任何时候只要有一个环节还在跑下一个环节甚至下下个环节就会被“带着走”。比如 DMA2D 已经把一帧新数据写到帧缓冲 B 了但你还没关 LTDCLTDC 就会继续把新数据刷到屏幕上而你的“停止视频”逻辑其实是把数据源停了屏幕上却显示出了最后一帧新数据——这在很多场景下是无所谓的但如果你的本意是“屏幕保持菜单不变”就会看到菜单被新视频帧盖掉一瞬。2.2 真正的停止难点异步外设之间的同步等待视频链路里最坑人的地方在于三个层面各自是异步的它们的停止并不是一个“按下开关全线停转”的过程而是需要互相等待。LTDC 正在刷新一行画面时你直接关掉 DMA2D很有可能会导致帧缓冲只写了一半DMA2D 正在搬运数据时你直接关掉 LTDC屏幕上可能出现撕裂。反过来DMA2D 正在搬运时你把它的时钟关了那连“搬运完成中断”都收不到状态机可能永远卡在错误状态。所以停止流程里必须显式地做“同步等待”。等待什么等待 DMA2D 的当前传输完成等待 LTDC 的 VSYNC/行同步信号让所有外设都停在一个安全的边界上。这个道理跟多线程编程里的 join 一样——你要停一个线程就得先等它执行完当前的任务而不是直接把它杀了。我这里说的“安全边界”具体到显示场景通常是以下三种DMA2D 的传输完成中断表示一次搬运已经完整写入目标帧缓冲。LTDC 的垂直消隐区间VSYNC 之后表示屏幕已经完成一帧刷新可以安全切换帧缓冲或关闭图层。DSI 命令链路的发送完成表示主机发给面板的 shutdown 命令已经下发而不是还堵在 FIFO 里。在真正的代码里你不需要每个环节都等但至少要在关键切换点等。我第一版代码就是所有等待都省了结果出问题的时间点非常随机——有时候是开机后第 3 次操作出问题有时候第 20 次才出极难复现这类“异步边界没等”的 bug 比显性的错误更让人抓狂。2.3 外部存储器在停止流程里扮演的角色如果你的视频帧数据存在外部 PSRAM、SDRAM 或者 OctoSPI NOR Flash 里那么停止视频链路时还得把外部存储器的状态考虑进来因为外部存储器的时钟一旦关闭正在执行的读操作就会立即失败。我当时用的是外部 Quad-SPI PSRAM帧缓冲放在里面。DMA2D 从 PSRAM 读数据转换成 RGB 格式。一开始我先把 DMA2D 的时钟关了导致 DMA2D 还在读的途中直接被掐断PSRAM 控制器那边就挂起了一个未完成的事务后续再访问 PSRAM 时一直报超时。正确的做法是先让 DMA2D 停稳确定没有针对 PSRAM 的在途访问了再关闭外部存储器的时钟或者将 SDRAM / PSRAM 切换到自刷新模式把它 “冻结” 在当前状态。这个顺序一旦搞反后面想恢复运行时遇到的问题会变得非常复杂因为你需要先重置外部存储控制器才能恢复访问可能导致整段显示数据都不可信。3. Video Stop 的标准操作流程五个关键步骤与代码3.1 第一步掐断视频数据源让“生产端”先停下来停止流程的第一个动作永远是从最上游的数据源开始。我这里的视频数据源是一段 MJPEG 流由一个定时器驱动的软件解码任务逐帧解码。停止时我先设置一个全局标志位通知解码任务处理完当前帧后立即退出循环而不是马上打断它。这一步有两个好处。第一解码任务可以安全释放它占用的内存缓冲和 DMA 描述符避免资源泄漏第二避免出现“帧解码到一半被强停下一帧进来时数据不完整”的脏状态。代码大致是这样volatile uint8_t video_source_stop_flag 0; void video_source_abort(void) { video_source_stop_flag 1; /* 请求解码任务退出 */ while (!video_source_stopped_flag) /* 等待解码任务确认退出 */ { /* 这里可以加超时保护比如等待 100ms */ if (timeout_elapsed()) break; } }使用标志位而不是直接挂起任务可以让停止流程变得更加可控不会在解码任务正在写帧缓冲时强行打断它。最终的效果是解码器不会再向 DMA2D 或者下一级缓冲区提交新的数据帧链路的最上游被掐断后续步骤才能在不慌不忙的状态下进行。3.2 第二步安全停止 DMA2D别一上来就 AbortDMA2D 的停止要分两种情况处理。第一种DMA2D 正在做长时间的大块传输你希望尽快停住第二种DMA2D 刚好处于空闲或传输即将完成的状态你只需要等待它结束。如果直接调用 HAL_DMA2D_Abort() 把当前传输强制终止可能造成源地址或者目的地址所在的帧缓冲只有一部分被更新留下半个新帧和半个旧帧拼接的缓存状态。所以我采用的是“先查询状态再决定是等待完成还是中止”。void dma2d_safe_stop(void) { if (HAL_DMA2D_GetState(hdma2d) HAL_DMA2D_STATE_BUSY) { /* 如果当前是内存到内存的普通搬运让它跑完比打断更安全。 这里等待完成但要加超时。 */ HAL_DMA2D_PollForTransfer(hdma2d, 1000); } else if (HAL_DMA2D_GetState(hdma2d) HAL_DMA2D_STATE_ERROR) { HAL_DMA2D_Abort(hdma2d); __HAL_DMA2D_CLEAR_FLAG(hdma2d, DMA2D_FLAG_TC | DMA2D_FLAG_TE); } /* 等待 DMA2D 状态机回到 READY */ while (HAL_DMA2D_GetState(hdma2d) ! HAL_DMA2D_STATE_READY) { /* 超时保护 */ } }为什么等待比中止更好因为 DMA2D 在传输过程中操作的是目标帧缓冲的连续地址如果传输被中断下一帧的数据会从断点继续写入目标缓冲区的后半段残留的是旧数据。这种数据不一致一旦发生即使你后面把 LTDC 停了下一次重新启动视频时也会从脏缓冲里读到错误内容。而等待传输完成最多多花几十毫秒换来的是链路上每一级缓冲都是干净的。3.3 第三步在 VSYNC 边界关闭 LTDC 图层和控制器LTDC 的停止是整个 Video Stop 流程里最讲究时序的一步因为 LTDC 是严格按照 HSync/VSync 时序刷新屏幕的。如果你在一个行中断的中间位置改它的层使能寄存器硬件可能会按一个不一致的配置完成当前行的读取严重时会在屏幕上出现一条固定位置的横向亮线这就是非常典型的“闪横线”故障。所以我建议的步骤是先等一个垂直消隐信号VSYNC到达然后再在中断服务程序里关闭图层最后关闭 LTDC 控制器。一个可行的做法是配置 LTDC 的行中断事件在指定行号产生一个中断。把中断触发行设为帧的最后一行的前一两条线这样中断触发时基本处于垂直消隐区域执行关闭操作不会干扰到正在进行的行扫描。void LTDC_IRQHandler(void) { if (__HAL_LTDC_GET_FLAG(hltdc, LTDC_FLAG_LI)) { __HAL_LTDC_CLEAR_FLAG(hltdc, LTDC_FLAG_LI); ltdc_vsync_marker 1; /* 设置一个帧边界标记 */ } } void ltdc_safe_stop(void) { /* 等待一个帧边界靠近 */ ltdc_vsync_marker 0; while (!ltdc_vsync_marker) { /* 等待可加超时 */ } /* 关闭所有正在使用的图层 */ HAL_LTDC_DisableLayer(hltdc, LTDC_LAYER_1); HAL_LTDC_DisableLayer(hltdc, LTDC_LAYER_2); /* 关闭 LTDC 控制器本身 */ HAL_LTDC_Stop(hltdc); /* 再强制清一次寄存器位确保上一帧的状态不会残留 */ LTDC-GCR ~LTDC_GCR_LTDCEN; }这里有一个容易被忽略的细节HAL_LTDC_DisableLayer() 只是把 LTDC_LxCR 的 LEN 位清零但 LTDC 控制器本身如果还在使能状态它仍然会以透明背景继续刷新屏幕。所以图层全部关闭之后要立刻调用 HAL_LTDC_Stop()把 GCR 里的 LTDCEN 位也清掉否则你会看到屏幕变黑但行场时钟还在向外输出。3.4 第四步关闭 DSI/并口输出与背光如果你的面板是 MIPI DSI 接口LTDC 停止之后DSI 主机控制器可能还维持着高速时钟向面板发送 LP/HS 信号。这时你不能直接关 DSI 的时钟要先通过 DSI 主机发送一条“进入低功耗模式”或者“关闭显示”的命令给面板等命令发送完成再将 DSI 的 PHY 置于 shutdown 状态。这一步很关键因为有些 DSI 面板在收到关屏命令后内部的行场扫描逻辑才会停止你不发命令直接断电面板内部状态可能停留在某个异常位置下次上电时会出现显示偏移或者花屏。我的面板支持 DCS 标准的 0x28Display Off和 0x10Sleep In命令时序上先发 0x28延时 50ms再发 0x10延时 100ms最后才把 DSI 时钟关掉。如果你用的是 RGB 并口屏那就跳过 DSI 主机直接把背光控制引脚拉低再关闭 LTDC 的像素时钟。背光的关闭顺序也有讲究。我一般是在 LTDC 图层关闭前先把背光亮度降到最低再在 LTDC 控制器关闭后完全关闭背光电源。这样做的好处是即使关闭过程中有一两帧异常画面也会因为背光已经降暗而不易被用户察觉。直接瞬间关闭背光虽然简单粗暴但容易在关闭边缘捕捉到一个撕裂的残影影响观感。3.5 第五步关闭外设时钟准备进入低功耗前面四步做完视频链路上的外设已经全部停止但它们的时钟可能还挂在系统时钟树上。如果此时直接进入低功耗模式有些外设的时钟还在翻转功耗自然降不下来。所以我最后一步是逐个关闭 DMA2D、LTDC、DSI如果启用的时钟同时检查它们对应的中断是否已屏蔽。void video_peripheral_clk_off(void) { /* 关闭 DMA2D 时钟 */ __HAL_RCC_DMA2D_CLK_DISABLE(); /* 关闭 LTDC 时钟 */ __HAL_RCC_LTDC_CLK_DISABLE(); /* 关闭 DSI 时钟如果启用 */ __HAL_RCC_DSI_CLK_DISABLE(); /* 如果 LTDC 的像素时钟来自独立 PLL关闭对应 PLL */ HAL_RCCEx_DisablePll(...); /* 按实际工程调整 */ }这里强烈建议在低功耗模式下关掉像素时钟相关的 PLL。LTDC 的像素时钟通常由一个独立 PLL 生成进入低功耗前不关掉它系统功耗会额外多出几毫安这在用电池供电的便携设备里是没法接受的。完整的五步流程跑完之后我建议再用一个状态函数做一次收尾检查确保所有寄存器状态都符合预期void video_stop_assert(void) { assert_param((LTDC-GCR LTDC_GCR_LTDCEN) 0); assert_param(HAL_DMA2D_GetState(hdma2d) HAL_DMA2D_STATE_READY); assert_param((DSI-CR DSI_CR_EN) 0); }这一步相当于把“停止”和“已经完全停止”区分开宁可多花几十微秒也绝不让任何外设带着未知状态进入低功耗。4. 实测踩过的坑闪屏、撕裂、残留和重启后花屏4.1 闪屏停止顺序里最容易被忽略的“先关哪头”我在第一版实现里是严格按照“关解码 → 关 DMA2D → 关 LTDC → 关 DSI → 关时钟”的顺序写的但还是偶发闪屏。后来抓了很长时间才发现问题出在背光关闭的时机上。我当时先关背光再走后面的链路从背光熄灭到 LTDC 真正停稳之间有一小段窗口期屏幕虽然背光灭了但 LTDC 还在继续输出信号面板的扫描电路仍然在工作当 DMA2D 最后一帧数据写一半时由于背光已经灭了用户看不到这个变化但如果此时触发了一瞬间的行场抖动会让面板内部开始执行异常复位从而在画面完全消失前闪出一丝亮线。后来我把顺序调成先通知背光进入降亮度模式再执行完整停止流程最后完全关闭背光电源。这样即使停止过程中有异常背光处于低亮度状态人眼几乎察觉不到。解决闪屏问题的关键不是让某个寄存器变快而是调整好感知层面的时机。4.2 撕裂没等 VSYNC 就切换帧缓冲的下场另一个高频问题撕裂。我测试视频暂停再恢复时偶尔会出现画面在中间位置产生明显的上下错位好像一条锯齿形的横线把画面分成两半。这就是经典的 tearing 现象原因是在 LTDC 扫描到屏幕中间某一行时我切换了帧缓冲地址LTDC 的上半行读的是旧帧缓冲地址下半行读的是新帧缓冲地址两个地址的内容不一样于是显示错位。解决方式就是我 3.3 节里说的必须在 VSYNC 垂直消隐期间切换帧缓冲或关闭图层。ST 的 LTDC 外设本身并没有硬件上的帧缓冲自动切换保护需要软件配合 VSYNC 中断来做安全的 buffer 切换。我用 LTDC 的行中断做了一个简单的 “帧同步门闩”void switch_frame_buffer(uint32_t new_lcd_address) { ltdc_vsync_marker 0; while (!ltdc_vsync_marker) { } /* 等到垂直消隐到来 */ LTDC_LAYER_1-CFBAR new_lcd_address; __HAL_LTDC_RELOAD_CONFIG(hltdc); }实测下来加上这个门闩之后撕裂问题就再也没有出现过。代价是切换可能会等待一整帧的时间比如 60Hz 屏最多等 16.7ms但显示效果永远优先于速度。4.3 残留画面帧缓冲不清空会污染下一次启动如果你需要彻底关闭视频显示而不是暂停那么帧缓冲里的内容必须被显式清空否则下一次开机时可能会看到上次残留的画面。我遇到的现象是设备从低功耗唤醒后屏幕先短暂显示了一帧上一次的视频画面然后才切换到主界面菜单。用户反馈就像“屏幕被冻住了”。排查后发现原因是低功耗前我关闭了 LTDC但帧缓冲里还保留着视频最后一帧的数据。唤醒后 LTDC 重新使能时首先扫描的就是这个残留缓冲如果此时背光也已经打开用户会看到这帧残留画面一闪而过。解决方法是在最后一步关闭 LTDC 之前先把当前显示用的帧缓冲全部填充为背景色比如黑色或纯色。void clear_frame_buffer(uint32_t *buf, uint32_t size, uint32_t color) { for (uint32_t i 0; i size; i) { buf[i] color; } SCB_CleanDCache_by_Addr((uint32_t *)buf, size * sizeof(uint32_t)); }注意这里有个缓存一致性的大坑。STM32U5 带 D-Cache如果你用 CPU 直接写帧缓冲写完必须做 Cache Clean 操作否则数据可能还停留在 Cache 里LTDC 读的真实内存地址还是旧数据。我在第一次实现清理函数时忘了加 CleanDCache结果清了又好像没清在项目里折腾了半天。4.4 复位后花屏DMA2D 还忙就把时钟关了这个坑是让我真正重视“停止顺序”的导火索。现象是从 STOP2 模式唤醒后屏幕 90% 概率花屏而且花的图案每次都不同没有任何规律。用调试器抓现场后我发现 DMA2D 的状态机在唤醒后直接进入 ERROR 状态而且 LTDC 的配置寄存器里还有未清零的层使能位。再往前追问题出在进 STOP2 之前我的代码把 DMA2D 时钟关了但 DMA2D 当时其实还有一个 pending 的传输请求这是我在某个中断服务程序里启动的异步操作主流程并不知道它还在运行。时钟一断DMA2D 的状态机停在一个非法状态复位时硬件不会自动清掉它唤醒后自然就误动作。这让我后来在代码里加了一个强制检查关闭任何外设时钟之前先查询外设状态必要时等待超时后再关。宁可让停止流程多花几十毫秒也不能让外设带着未完成任务被时钟“砍掉”。4.5 定位这类问题的调试手段遇到这类显示相关 bug我常用的手段有三种按优先级排序。第一种串口日志加时间戳把每个停止步骤的进入和退出时间都打出来。这样能快速看出来哪个步骤异常比如某个等待超时了或者顺序有问题。我在项目里给每个停止函数都加了宏封装打印耗时很快就发现 DMA2D 等待完成经常需要额外 300ms因为我在等它之前还有个外设一直没停干净。第二种逻辑分析仪 / 示波器挂在 HSync 和 VSync 引脚上观察时序。如果停止后 VSync 还在持续翻转说明 LTDC 主使能或时钟配置没关干净。通过在 VSync 引脚上触发停止命令能精确定位到哪一行扫描时执行了关闭操作从而判断是否有撕裂风险。第三种也是最容易被忽略的查看 DSI 面板端的错误报告寄存器。很多 MIPI DSI 面板内部有错误状态位比如收到非法指令、CRC 错误、ECC 错误等。通过 DSI 读回面板寄存器能快速确认面板是否已经正确进入了关闭状态而不是停留在某个半开半关的异常状态。5. 停止后功耗优化从十几毫安降到几微安的实际配置5.1 STM32U5 低功耗模式下显示外设的处理原则STM32U5 系列主打低功耗但如果你把整个显示链路的外设时钟都开着它是很难进入真正的超低功耗状态的。我实测过LTDC、DMA2D、DSI 三个外设的时钟如果不关进入 STOP2 模式前的系统电流一直在 8~12mA 左右这对电池供电设备来说是灾难性的。进入低功耗前需要确保的不只是外设时钟关闭还包括中断状态的清理。外设时钟关掉后如果对应的中断标志位没有手动清除唤醒时容易触发误中断导致系统从 STOP2 唤醒后立刻又跑进某个显示相关的中断服务程序重新把外设时钟打开功耗功亏一篑。所以在进入低功耗前我会做一次外设中断的全面掩码操作void video_peripheral_irq_mask(void) { NVIC_DisableIRQ(DMA2D_IRQn); NVIC_DisableIRQ(LTDC_IRQn); NVIC_DisableIRQ(LTDC_ER_IRQn); NVIC_DisableIRQ(DSI_IRQn); /* 清除挂起的中断标志 */ NVIC_ClearPendingIRQ(DMA2D_IRQn); NVIC_ClearPendingIRQ(LTDC_IRQn); NVIC_ClearPendingIRQ(DSI_IRQn); }这一步不做干净的话即使 RCC 里的时钟已经关闭中断控制器里挂起的 pending 状态也会在唤醒瞬间把你重新带进显示流程非常坑。5.2 带外部 PSRAM/SDRAM 时不能直接进 STOP2如果帧缓冲放在外部 PSRAM 或 SDRAM 里进低功耗前还需要确认外部存储器的状态。我用的 PSRAM 是通过 OctoSPI 接口连接的进入 STOP2 前我做了两件关键操作先把 PSRAM 切换到自刷新模式再关闭 OctoSPI 控制器时钟。PSRAM 进入自刷新模式后即使外部时钟停了它内部也会自己维持数据不会丢内容。这相当于让外部存储器和 CPU 解耦CPU 进低功耗PSRAM 自己保持数据。唤醒后先重新使能 OctoSPI 时钟再执行一次“退出自刷新”命令就能正常继续访问。这个顺序在数据手册里有明确操作步骤但我第一次做时只做了“退出自刷新”没有在进入低功耗前先执行“进入自刷新”结果唤醒后读回来数据全是 0xFF因为我那时候的 PSRAM 由于失去外部时钟内部状态其实已经乱了。5.3 实测三组功耗数据对比我把同一套配置、同一块板子测试了三组不同停止策略下的功耗数据结果非常直观测试场景停止策略待机电流典型值场景 A只调用 HAL_LTDC_Stop()不关外设时钟不关 DMA2D11.8 mA场景 B按顺序停止数据源/DMA2D/LTDC/DSI并关闭外设时钟1.4 mA场景 C场景 B 外部 PSRAM 自刷新 关闭 PLL 屏蔽中断4.2 µA场景 A 和场景 C 之间差了接近 3000 倍这就是为什么“把 Video Stop 做干净”在功耗敏感型产品里极其重要。如果只是把功能跑通不追求低功耗场景 A 也能用但如果你想做便携式仪表、电池门显、手持终端必须做到场景 C。另外进入 STOP2 模式后还建议关闭内部 LDO 稳压器的旁路模式如果硬件设计上启用了 SMPS 开关电源供电要让 STM32U5 切换到 SMPS 模式下确保内核电压域的效率达到最高。这些细节在参考手册的电源管理章节里都有实测能再降零点几毫安。6. 长期稳定做 Video Stop 调试我养成的几个习惯做视频停止这个功能前前后后折腾了差不多三周最后稳定运行后我总结出几个值得长期保留的习惯分享给后来者。第一个习惯给每个停止步骤加状态机。不要用一堆无状态的顺序函数拼接停止流程而是用一个 stop_state 变量维护当前阶段每个阶段完成后再推进到下一个阶段。这样如果某个阶段卡住或者超时你可以迅速定位到是哪个外设的问题而且方便做“重复停止”操作——用户可能在视频已经停止的状态下再次按下停止键无状态函数没法区分“已经在停止过程中”和“全新的停止请求”。第二个习惯统一的超时机制。所有等待循环包括等待 DMA2D 完成、等待 VSYNC、等待 DSI 命令发送都必须加超时保护。我最初没加结果遇到一个极端情况下 DMA2D 永远等不到完成中断整个系统死循环在停止流程里连看门狗喂狗的任务都被饿死了。后来我把所有等待都统一封装成wait_with_timeout()超时后强制进入错误恢复流程虽然不能完全避免异常但至少不会让系统卡死。第三个习惯做停止前后的状态快照对比。我把停止前和停止后关键寄存器的值都保存下来比如 DMA2D_ISR、LTDC_GCR、LTDC_L1CR、DSI_CR通过对比能快速发现哪些标志位没有清零。这个方法在好几次疑难问题排查里起了大作用比肉眼盯调试变量列表高效得多。第四个习惯把 Video Stop 做成可重复调用的“恢复友好”函数。停止后重新启动视频不是简单地把停止流程反过来走一遍因为有些外设的状态不是对称的。比如 DMA2D 停止后它内部的状态机可能还保留着上一个配置重新启动前必须重新 Init 或者 ResetDSI 面板从 Sleep In 恢复过来需要至少 100ms 延时。这些不对称的恢复时序我在代码里单独用一个 video_start_after_stop() 函数维护避免和首次启动的路径混在一起。回到最初那个“黑屏但没断电”的故障现在回看本质原因只有一个我只让软件停止没有让硬件链路上的每个环节都停止。嵌入式开发里这种问题非常多见表面上是“一个功能没做好”实际上是对硬件的完整数据路径缺乏掌控。希望这篇关于 STM32U5G9ZJT6Q Video Stop 的复盘能帮你在做类似功能时少走几个弯路。