
简介面向STM32F7系列嵌入式开发者的LTDC RGB屏驱动工程包围绕STM32F767的高性能Cortex-M7平台解决从GPIO、时序到多层显示与帧缓冲管理的完整驱动落地问题。内容适合已具备一定HAL库基础、希望掌握RGB液晶屏底层显示控制的开发者也可作为嵌入式图形界面项目的参考工程。压缩包共182个文件以92个h头文件和81个c源文件为主体另含Keil工程文件、hex烧录文件、批处理脚本及少量配置与说明文件整体约1.12MB便于直接导入编译和按模块查阅。已有1693人学习下载说明其在LTDC驱动学习场景中具备一定参考价值。工程覆盖LTDC架构配置、LCD时序参数设置、层属性与颜色格式管理、帧缓冲区组织以及DMA与中断配合等关键环节读者可据此梳理初始化流程、对照硬件接口设计并排查显示异常积累可复用的RGB屏驱动经验。1. 800×480 RGB 屏点不亮时先别换屏LTDC 的时序账本比排线更容易错一块 800×480 的 RGB 屏接到 STM32F767 上背光亮了却满屏白或者图像整体右移几十像素很多人第一反应是屏坏了、排线松了。血泪经验是LTDC 本身不复杂难的是把屏幕手册里的 8 个时序参数、PLLSAI 像素时钟和 SDRAM 显存带宽对上账。这个标题讲的是用 F767 的 LTDC 外设直接驱动并行 RGB 屏不经过 SPI/I2C 转接适合想做中尺寸彩屏、GUI 或多图层显示的开发者。先跑通彩条再谈 UI。2. STM32F767 的 LTDC 数据通路PLLSAI 像素时钟、FMC 显存和图层取数LTDC 不是独立时钟源它吃的是 PLLSAI 的 R 分频输出显存也不是随便找个数组就能放。F767 内部 SRAM 对 800×480 的 RGB565 单缓冲还能勉强塞下一旦上双缓冲或者 1024×600就必须外挂 SDRAM。这一章先把数据通路拆开像素时钟从哪来、一帧显存放哪、图层怎么从显存取数最后用彩条把最小通路打通。2.1 从 PLLSAI 到像素时钟先算总宽高再选 R 分频屏幕手册给的往往不是像素时钟而是水平同步宽度、水平后肩、水平前肩、垂直同步宽度、垂直后肩、垂直前肩和有效分辨率。像素时钟的算法是总宽等于有效宽加三个水平消隐参数总高等于有效高加三个垂直消隐参数再乘刷新率。常见 800×480 屏在 60 Hz 下算出来约 3033 MHz这个值要落进 PLLSAI 的 R 分频不能随手拿系统主频去分。def ltdc_pixel_clock(h_active, h_sync, h_bp, h_fp, v_active, v_sync, v_bp, v_fp, fps): # 总宽高包含消隐区像素时钟按整帧扫描面积计算 h_total h_active h_sync h_bp h_fp v_total v_active v_sync v_bp v_fp return h_total * v_total * fps # 示例800×480 屏同步宽度 48/3后肩 88/32前肩 40/13 f ltdc_pixel_clock(800, 48, 88, 40, 480, 3, 32, 13, 60) print(f) # 约 30.9 MHz这段脚本只做一件事把屏参换算成像素时钟。参数说明里h_sync 是水平同步脉冲宽度h_bp 是水平后肩h_fp 是水平前肩垂直方向同理。算出来的值不要顶到 LTDC 上限我一般会留 10% 左右余量再用 PLLSAI 的 N、R 去凑凑不到就微调前肩别动同步宽度。2.2 显存为什么必须外挂 SDRAM一帧 RGB565 的字节预算800×480 的 RGB565 一帧是 800×480×2 字节等于 768000 字节约 750 KiB。单缓冲还能在内部 SRAM 里挤一挤双缓冲就是 1.5 MiB再叠加 GUI 的临时缓冲和 DMA2D 的目标区内部 SRAM 很快见底。常见做法是把帧缓冲放到 FMC 外扩的 16 位 SDRAM 上地址从 FMC Bank1 的 0xC0000000 起映射LTDC 的 FBStartAdress 直接写这个地址。这里有个容易忽略的点SDRAM 不是“能读写就行”LTDC 是实时取数DMA2D 和 CPU 也会抢同一块总线。显存地址要按 32 字节对齐双缓冲两个地址之间不要跨到别的 Bank否则切换时容易出现半帧旧数据。我一般会把第一缓冲放在 0xC0000000第二缓冲放在 0xC00C0000 之后中间留一点空隙方便用逻辑分析仪抓地址。2.3 用彩条测试打通 LTDC 最小通路不要一上来就贴图片。先让 LTDC 输出纯色再输出彩条最后才接 GUI。纯色能验证背光、像素时钟和图层使能彩条能验证 RGB 引脚顺序和像素格式。下面这段 C 代码用 CPU 直接写显存不依赖 DMA2D适合第一次点屏时排除外设干扰。// 假设 fb 指向 SDRAM 中的帧缓冲格式 RGB565 void lcd_draw_color_bars(uint16_t *fb, uint32_t w, uint32_t h) { // 8 条彩条白、黄、青、绿、品红、红、蓝、黑 const uint16_t bars[8] { 0xFFFF, 0xFFE0, 0x07FF, 0x07E0, 0xF81F, 0xF800, 0x001F, 0x0000 }; for (uint32_t y 0; y h; y) { for (uint32_t x 0; x w; x) { fb[y * w x] bars[(x * 8) / w]; } } }逻辑说明按横坐标把屏幕切成 8 段每段填一个 RGB565 颜色值。参数说明fb 必须是 LTDC 当前图层配置的起始地址w 和 h 要和 ImageWidth、ImageHeight 一致如果屏幕颜色顺序是 BGR红蓝会互换这时先别改代码回头查 RGB 引脚映射。彩条正常后再换图片能省掉很多“到底哪一层错了”的排查时间。3. 把屏幕手册的 8 个时序参数写进 LTDC 寄存器HSYNC/HBP/HFP 与 VSYNC/VBP/VFP屏幕手册的时序表通常画成一行波形真正要写进 LTDC 的是寄存器字段而且很多字段是“值减 1”。这一章把手册参数翻译成 LTDC 初始化结构体再讲 CubeMX 生成骨架后必须手改的地方最后落到图层窗口和帧缓冲地址。先把单缓冲跑稳再考虑双缓冲不然撕裂和花屏会混在一起排查起来像黑匣子。3.1 从手册抓参数同步宽度、前后肩和有效区拿到屏参先别急着填代码先把 8 个数抄到一张表里HSYNC 宽度、HBP、HFP、VSYNC 宽度、VBP、VFP、有效宽、有效高。LTDC 的 HorizontalSync 填 HSYNC 宽度减 1VerticalSync 填 VSYNC 宽度减 1AccumulatedHBP 填 HSYNC 加 HBP 再减 1AccumulatedVBP 填 VSYNC 加 VBP 再减 1AccumulatedActiveW 填 HSYNC 加 HBP 加有效宽再减 1TotalWidth 填 HSYNC 加 HBP 加有效宽加 HFP 再减 1。垂直方向同理。参数屏幕手册符号LTDC 字段800×480 示例水平同步宽度HSYNCHorizontalSync48 - 1水平后肩HBPAccumulatedHBP48 88 - 1水平前肩HFPTotalWidth 反推40垂直同步宽度VSYNCVerticalSync3 - 1垂直后肩VBPAccumulatedVBP3 32 - 1垂直前肩VFPTotalHeigh 反推13有效分辨率Width/HeightAccumulatedActiveW/H800/480像素时钟ClockPLLSAI R约 30.9 MHz这张表建议打印出来改一版参数就用铅笔标一版。屏幕手册的极性也要看HSPolarity、VSPolarity、DEPolarity 常见是低有效但不同屏不一样。极性填错不一定全黑有时表现为图像整体偏移或边缘抖动很容易误判成时序算错。3.2 CubeMX 生成 LTDC 初始化骨架后要改哪几处CubeMX 能生成 LTDC 的初始化骨架但屏幕参数、显存地址和图层格式还是要手改。下面这段代码是常见的初始化结构体重点看累加值是不是按“减 1”填的FBStartAdress 是不是指向 SDRAM。LTDC_HandleTypeDef hltdc; LTDC_LayerCfgTypeDef layer; hltdc.Instance LTDC; hltdc.Init.HSPolarity LTDC_HSPOLARITY_AL; hltdc.Init.VSPolarity LTDC_VSPOLARITY_AL; hltdc.Init.DEPolarity LTDC_DEPOLARITY_AL; hltdc.Init.PCPolarity LTDC_PCPOLARITY_IPC; hltdc.Init.HorizontalSync 48 - 1; hltdc.Init.VerticalSync 3 - 1; hltdc.Init.AccumulatedHBP 48 88 - 1; hltdc.Init.AccumulatedVBP 3 32 - 1; hltdc.Init.AccumulatedActiveW 48 88 800 - 1; hltdc.Init.AccumulatedActiveH 3 32 480 - 1; hltdc.Init.TotalWidth 48 88 800 40 - 1; hltdc.Init.TotalHeigh 3 32 480 13 - 1; HAL_LTDC_Init(hltdc); layer.WindowX0 0; layer.WindowX1 800; layer.WindowY0 0; layer.WindowY1 480; layer.PixelFormat LTDC_PIXEL_FORMAT_RGB565; layer.Alpha 255; layer.Alpha0 0; layer.BlendingFactor1 LTDC_BLENDING_FACTOR1_PAxCA; layer.BlendingFactor2 LTDC_BLENDING_FACTOR2_PAxCA; layer.FBStartAdress 0xC0000000; layer.ImageWidth 800; layer.ImageHeight 480; HAL_LTDC_ConfigLayer(hltdc, layer, 0);逻辑说明先初始化 LTDC 全局时序再配置第 0 层窗口和帧缓冲。参数说明HorizontalSync 等字段填的是寄存器值不是手册里的脉冲宽度FBStartAdress 是 SDRAM 地址必须和 FMC 初始化后的映射一致PixelFormat 要和显存里的数据格式一致RGB565 每像素 2 字节别把 RGB888 的 3 字节数据塞进 RGB565 图层。注意LTDC 的累加字段大多是“值减 1”把手册脉冲宽度直接写进去轻则图像偏移重则整屏不亮。3.3 图层窗口与帧缓冲地址单缓冲先跑通再上双缓冲图层窗口的 WindowX1 和 WindowY1 是右下角坐标不是宽度和高度。800×480 全屏就是 X00、X1800、Y00、Y1480。ImageWidth 和 ImageHeight 是帧缓冲里的图像尺寸通常和窗口一致如果窗口只显示图像的一部分这两个值可以大于窗口靠 WindowX0/Y0 做偏移。单缓冲时 FBStartAdress 固定改完显存后等一次垂直消隐再让 LTDC 重载画面才不会撕。双缓冲的做法是准备两个地址在垂直消隐期间调用 HAL_LTDC_SetAddress 切换或者直接改 LTDC_LxCFBAR 再触发重载。我一般会先让单缓冲跑一天确认没有偶发花屏再上双缓冲。单缓冲都花屏双缓冲只会把问题藏得更深。切换地址时不要在屏幕扫描到一半时改否则上半屏旧图、下半屏新图看起来就是撕裂。4. SDRAM 显存与 LTDC 抢带宽800×480 也会花屏的根因和参数很多人以为 800×480 分辨率不高SDRAM 随便配就能跑。实际 LTDC 是实时取数像素时钟 30 MHz 左右时每秒钟要从显存读走几十兆字节DMA2D 和 CPU 再一挤SDRAM 的刷新和行列切换就会变成瓶颈。这一章先算带宽再看 FMC SDRAM 的关键参数最后用 DMA2D 把 CPU 从显存搬运里解放出来。4.1 算清 LTDC 实时带宽像素时钟 × 每像素字节数带宽的粗算方法是像素时钟乘每像素字节数。800×480 RGB565 在 60 Hz 下有效像素带宽约 800×480×2×60等于 46.08 MB/s但 LTDC 扫描时还要读消隐区实际取数按总宽高算接近 56 MB/s。双缓冲不会让 LTDC 读两倍但 CPU 和 DMA2D 写另一块缓冲会占总线所以显存带宽要按“读一帧加写一帧”留余量。分辨率像素格式每帧字节60 Hz 有效带宽双缓冲占用800×480RGB565768000约 46.1 MB/s约 1.5 MiB800×480RGB8881152000约 69.1 MB/s约 2.3 MiB1024×600RGB5651228800约 73.7 MB/s约 2.4 MiB1024×600RGB8881843200约 110.6 MB/s约 3.5 MiB这张表不是让你死记而是选型时快速判断16 位 SDRAM、100 MHz 左右的时钟跑 800×480 RGB565 单缓冲比较稳上 1024×600 或 RGB888就要看 SDRAM 的时序余量和 FMC 的突发配置。别等花屏了才回头算先算再画板。4.2 FMC SDRAM 初始化刷新计数、CAS 延迟和突发长度怎么定FMC 初始化 SDRAM 时刷新计数按“SDRAM 时钟频率 × 刷新周期 ÷ 行数 - 20”估算具体用器件手册的 tRFC 和行数代进去。CAS 延迟常见填 2 或 3要跟 SDRAM 颗粒支持的最快值一致突发长度常见 1、2、4、8LTDC 连续取数时突发长度太短会增加命令开销太长又可能被高优先级访问打断。我一般先用保守参数点亮再用示波器看 FMC 的读写波形逐步收紧。还有一个常被忽略的参数是 FMC 的读等待和写等待。SDRAM 不是 SRAM行列地址复用切换 Bank 有额外周期。LTDC 取数模式比较规律DMA2D 写显存则是大块连续写两者交错时如果写等待太短读数据就会偶发错位。表现不是一直花屏而是刷新几次后某一行颜色不对像显存里被写进了脏数据。4.3 用 DMA2D 填充和拷贝把 CPU 从显存搬运里解放出来CPU 循环写显存不仅慢还会让 D-Cache 和 SDRAM 之间产生大量写回。DMA2D 做纯色填充、矩形拷贝和 Alpha 混合更合适。下面这段代码用 DMA2D 的寄存器到存储器模式填一块 RGB565 区域适合清屏和画背景。DMA2D_HandleTypeDef hdma2d; void lcd_fill_rgb565(uint32_t fb_addr, uint16_t color, uint32_t w, uint32_t h) { hdma2d.Instance DMA2D; hdma2d.Init.Mode DMA2D_R2M; hdma2d.Init.ColorMode DMA2D_OUTPUT_RGB565; hdma2d.Init.OutputOffset 0; HAL_DMA2D_Init(hdma2d); // color 是 RGB565 打包值DMA2D 直接写入 SDRAM HAL_DMA2D_Start(hdma2d, (uint32_t)color, fb_addr, w, h); HAL_DMA2D_PollForTransfer(hdma2d, 100); }逻辑说明DMA2D 工作在 R2M 模式把前景色填到目标地址不用 CPU 逐像素写。参数说明fb_addr 是显存起始地址w 和 h 是要填充的区域大小color 要按 RGB565 打包比如红色是 0xF800。轮询超时给 100 ms 是保守值实际填充一帧远小于这个时间如果超时先查 DMA2D 时钟使能和目标地址是否在可写内存区。注意CPU 写过显存后如果马上让 LTDC 显示D-Cache 可能还没写回 SDRAM。要么把显存区配成 non-cacheable要么在切换前做一次 Cache 清理。5. RGB 屏调试避坑从全白到偶发花屏的 5 条排查路径RGB 屏的故障现象很会骗人全白可能是背光也可能是 LTDC 没使能颜色偏红可能是引脚顺序也可能是像素格式偶发花屏可能是 SDRAM 带宽也可能是 Cache 一致性。这一章按现象、原因、解决三步走把最容易翻车的 5 个点拆开。每条都先别改代码先量信号或看寄存器避免越改越乱。5.1 全白或全黑先量背光使能再查 LTDC 是否真的在刷现象上电后背光亮屏幕全白或者背光不亮屏幕全黑。原因全白常见于 LTDC 没输出、图层没使能、帧缓冲全 0xFF全黑常见于背光使能脚没拉高、背光升压没工作、LTDC 像素时钟没出来。解决先用万用表量背光使能脚和升压输出再用示波器量 LTDC 的像素时钟和 HSYNC/VSYNC。如果时钟没有回头查 PLLSAI 的 R 分频和 LTDC 时钟使能如果时钟有但屏全白查图层使能和 FBStartAdress 是否指向有效显存。我遇到过一种情况背光使能脚接在 GPIO 上初始化顺序把 GPIO 复用成了别的功能背光一闪就灭看起来像 LTDC 输出黑屏。后来把背光使能放在 LTDC 初始化之后、开显示之前问题消失。电源和使能顺序不要嫌麻烦先固定下来后面排查会少很多变量。5.2 图像偏移或抖动时序参数和像素时钟余量现象图像整体右移或下移几十像素边缘有一条黑边或彩边有时图像会左右抖动。原因HSYNC/HBP/HFP 或 VSYNC/VBP/VFP 填错或者像素时钟和屏幕手册不匹配。解决拿手册的时序表逐项对寄存器重点看累加值是不是“同步宽度加后肩减 1”像素时钟不要顶上限先降 10% 看抖动是否消失。如果降频后正常说明 SDRAM 带宽或 PLLSAI 分频余量不够不要硬撑。还有一种情况是极性填反。HSPolarity 和 VSPolarity 填错时图像不一定全黑可能整体偏移到消隐区看起来像“屏幕分辨率不对”。我一般会把四种极性组合列出来每次只改一个改完复位看现象别一次改多个参数。5.3 颜色偏红偏蓝RGB 引脚顺序和像素格式现象彩条测试时红色和蓝色互换或者颜色整体发紫、发绿。原因RGB 数据线顺序接反或者 LTDC 图层像素格式和显存数据格式不一致。解决先看硬件原理图的 RGB 引脚映射R0R7、G0G7、B0B7 有没有交叉再看 layer.PixelFormat 是 RGB565 还是 RGB888显存里写的字节序是不是和 LTDC 一致。RGB565 下红色是 0xF800蓝色是 0x001F用纯色填充能最快确认。如果只有某几种颜色不对比如白色正常、红色偏暗可能是低位数据线没接好。RGB565 的 R 和 B 各 5 位G 有 6 位低位悬空时颜色会阶梯状失真。这类问题用彩条不一定看得出来要用渐变图或纯色逐通道测试。5.4 刷新撕裂双缓冲切换时机和显存对齐现象快速刷新画面时屏幕上半部分是新图下半部分是旧图像被刀切过。原因在 LTDC 扫描过程中改了帧缓冲地址或者双缓冲切换没有等垂直消隐。解决用 HAL_LTDC_SetAddress 切换后等 LTDC 的寄存器重载完成再释放旧缓冲或者用 LTDC 的垂直消隐中断做切换。显存地址要按 32 字节对齐两个缓冲不要重叠。单缓冲下也可能撕裂因为 CPU 写显存时 LTDC 正在读同一块区域。这种场景要么用双缓冲要么在垂直消隐期间集中写。不要指望提高 CPU 主频解决LTDC 的读取是实时的CPU 再快也会和扫描撞车。5.5 偶发花屏SDRAM 带宽、Cache 一致性和中断优先级现象画面偶尔闪一下或者某一行颜色错乱复位后又正常。原因SDRAM 带宽不够、FMC 时序太紧、D-Cache 没清理、DMA2D 和 LTDC 抢总线。解决先把 LTDC 和 DMA2D 的突发访问错开DMA2D 填充大块显存时不要让 CPU 同时大量写 SDRAM再把显存区配成 non-cacheable或者在 CPU 写完后调用 SCB_CleanDCache_by_Addr最后看中断优先级LTDC 的行中断不要被长任务堵住。这类问题最难查因为每次现象不一样。我一般会先降像素时钟如果花屏消失基本就是带宽或时序余量问题如果降频还花再查 Cache 和电源。别一上来就换屏先让系统跑最保守的参数再逐项放开。6. 把 LTDC 用顺手的三个进阶动作双缓冲校验、DMA2D 混合和帧率实测第一个动作是双缓冲校验。准备两块显存画不同颜色在垂直消隐中断里切换地址肉眼看有没有撕裂再用逻辑分析仪抓 LTDC 的 VSYNC 和显存写使能确认切换落在消隐区。第二个动作是 DMA2D 混合把前景图层做成 ARGB8888背景用 RGB565用 DMA2D 做 Alpha 混合后再交给 LTDC 显示适合做半透明菜单。第三个动作是帧率实测不要靠感觉用 GPIO 翻转加示波器最直接。// 在 LTDC 行中断里翻转测试 GPIO示波器上相邻上升沿就是一帧 void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef *hltdc) { // 只使能第 0 行中断避免每行都翻转 HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); }这段回调的关键是只使能一行中断否则每行都翻转示波器上看到的是行频而不是帧频。参数说明GPIOA_PIN5 只是示例测试脚换成板上空闲引脚即可中断里不要做耗时操作翻转完就退出。测到帧率后再和屏幕手册的刷新率对比差太多就回头查像素时钟和消隐参数。我一般会把屏幕手册的时序页打印出来贴在工位改一版参数就用铅笔标一版先量背光和像素时钟再动代码。LTDC 调通一次之后后面换屏就是换一张时序表的事别怕麻烦。希望帮到你。本文还有配套的精品资源点击获取