TouchGFX自研板卡移植实战:从白屏到流畅UI的完整指南 TouchGFX 做的界面确实是目前 STM32 平台上最能打的丝滑的动画放在几年前几乎不敢想象。不过大部分新手第一次被劝退往往不是 UI 设计本身而是把它从官方评估板挪到自己的 custom board 上。你会在某个深夜盯着白屏陷入沉思明明屏幕是好的代码也是官方生成的为什么就是不出画这篇就聊聊我在实际项目里把 TouchGFX 移植到自研板卡上踩过的坑和总结出来的流程。适合打算或者正在把 TouchGFX 往自己硬件上搬的嵌入式工程师至少得用过 STM32CubeMX能看懂 I2C、SPI、LTDC 这些外设。准备好之后咱们从最让人困惑的部分开始说。1. 从官方板到自研板TouchGFX 到底卡在哪一环1.1 你以为你用的是 TouchGFX其实你用的是评估板的 BSP很多人第一次接触 TouchGFX 是在官方评估板上比如 STM32F746G-DISCO 或者 STM32F429I-DISCO。打开 CubeMX勾选 TouchGFX生成工程点几下编译屏幕就出动画了。整个过程顺到让你产生一种“移动 UI 不过如此”的错觉。等你换上自己的板子同样的步骤走一遍编译通过烧录进去屏幕却一片白或者干脆黑屏这时候才反应过来官方板不是“TouchGFX 跑起来了”而是有人已经把底层那些脏活累活替你干完了。评估板的适配代码藏在 X-CUBE-TOUCHGFX 的包里针对具体的 LCD 型号、触摸芯片、SDRAM 布局、时钟树全都预设好了。你自己的板子没有这些预设所以需要重新适配。这个适配工作的核心是 TouchGFX 的 HAL 层它负责把 UI 渲染指令变成对具体硬件的操作包括往帧缓冲里写像素、调用 DMA2D 做图块拷贝、在 VSYNC 中断里切换缓冲区等等。官方板的 HAL 实现是现成的你只需要改几个参数。自研板则需要你自己看着硬件原理图把这一层补完整。1.2 TouchGFX 的启动链路Designer、CubeMX、HAL 各管什么先把这个框架理清楚后面排查问题会轻松很多。TouchGFX 大致分三层TouchGFX Designer桌面端工具设计 UI 布局、动画、字体、图片资源导出代码。STM32CubeMX 里的 TouchGFX Generator在生成外设初始化代码的时候把 TouchGFX 引擎、HAL 适配代码框架也一起生成。HAL 层夹在引擎和硬件之间负责任何跟板子有关的细节比如帧缓冲地址、LTDC 层配置、触摸驱动、定时器基准。这里有个关键认知CubeMX 生成的 HAL 代码不是“完整可运行”的它是一个骨架里面有大量需要你填的 TODO。真正要命的是这些 TODO 往往不会编译报错你漏掉它程序照样跑就是不出画面。所以移植的第一步不是写业务代码而是把 HAL 层的每一处 TODO 全部落实。你可以去生成目录下翻一下Target文件夹里面一堆文件就是留给你写的。第一次移植的人往往会忽略这个目录以为只要改了链接脚本就够了。2. 先谈硬件选型有些板子天生不适合跑 TouchGFX2.1 主控、屏幕接口、显存方案要一次想清楚如果你还没定板子先停一下别急着画板。TouchGFX 虽然是个软件框架但对硬件的要求非常硬。第一个大前提是主控要带 LTDC 控制器和 DMA2D 加速器。LTDC 是 STM32 的 RGB 并行屏幕控制器负责不停地把帧缓冲里的数据刷到屏幕上不需要 CPU 参与。DMA2D 则是一个 2D 图形加速器专门做像素搬运、填充、混合这些操作。没有 DMA2DTouchGFX 也能跑但所有图像操作都会退化成 CPU 逐像素处理帧率会非常难看。典型的选择是 STM32F429、F746、H750 这类内置 LTDC 的型号。老一点的 F103 是没法直接接 RGB 并口屏的只能接 SPI 屏而 TouchGFX 跑在 SPI 屏上体验会打很大折扣。分辨率方面480x272 或者 800x480 是比较平衡的选择再往上 1024x600 甚至 1080p对帧缓冲带宽和内存容量的要求会指数级上升。屏幕接口也要提前确认。市面上常见的 RGB 屏分成 DE 模式和 SYNC 模式两种。DE 模式下LTDC 只需要提供时钟、数据、行同步、场同步和数据使能信号时序参数相对简单。SYNC 模式没有 DE 信号需要依靠 HSYNC/VSYNC 的组合来定位配置起来更容易出错。绝大多数 RGB 屏都是 DE 模式建议优先选这种。另外要注意屏的 RGB 位数有 16 位、18 位、24 位之分TouchGFX 里配置的颜色格式必须跟它匹配。2.2 帧缓冲到底放内部 SRAM 还是外部 SDRAM帧缓冲是 TouchGFX 渲染图像的画布CPU 或 DMA2D 往里面写像素LTDC 从里面读像素。这个缓冲区放哪里直接影响性能和内存规划。这里有一个简单的带宽估算公式屏幕刷新率乘以分辨率乘以每像素字节数。以 480x272、16 位色深、60Hz 为例480x272 约 13 万像素每个像素 2 字节算出来大约每秒需要读取 15.6MB 数据。这是纯 LTDC 读取的带宽如果 CPU 或 DMA2D 同时写帧缓冲总带宽还得再叠加。480x272 的单帧缓冲只有约 260KB如果你的 MCU 有足够的内部 SRAM可以全部放内部。但很多应用需要双缓冲甚至三缓冲来消除撕裂和提升流畅度那就要考虑 520KB 甚至 780KB 的占用。大多数 STM32 内部 SRAM 只有 256KB 到 512KB不够用于是就得外挂 SDRAM。SDRAM 的带宽又受限于 FMC 控制器频率和数据位宽。16 位 SDRAM 跑在 100MHz 左右理论带宽 200MB/s实际扣除刷新开销和总线竞争剩余可用带宽大约 100 到 150MB/s。对于 800x480 RGB565 的屏单帧 768KB60Hz 读取带宽约 46MB/s加渲染写操作宽带SDRAM 仍然能撑住但余量不大了。所以方案上我一般建议 480x272 及以下分辨率尽量放内部 SRAM简单省心800x480 及以上老老实实用 SDRAM。如果你选了不带 SDRAM 控制器的小封装芯片那分辨率就只能被帧缓冲内存卡死。3. 移植实操CubeMX 配置、HAL 修改、触摸适配3.1 LTDC 时序参数配置这一张表搞定配置 LTDC 是移植里最直观也最容易出错的一步。CubeMX 里照着屏厂数据手册填参数即可很多人栽在单位和极性的问题上。以一块 480x272 的 5 寸 RGB 屏为例屏厂手册通常会给出这样一组参数参数典型值说明像素时钟9 MHz 左右可由 PLLSAI 分频得到HSYNC41 像素行同步脉冲宽度HBP2 像素行同步后肩HFP2 像素行同步前肩VSYNC10 行场同步脉冲宽度VBP2 行场同步后肩VFP2 行场同步前肩DE 极性高有效通常为正极性CLK 极性上升沿采样视屏而定这里特别提醒有些屏厂数据手册里给的时间单位是纳秒不是像素数。你需要把时间除以像素时钟周期换算成像素数再填进 CubeMX。比如某个参数写 2us像素时钟 9MHz 时一个周期约 111ns2us 大约就是 18 个时钟周期。如果直接拿纳秒数往像素数里填画面会偏移或者压根不亮这类问题排查起来很费时间。ST 的 LTDC 时钟树里像素时钟是由 PLLSAI 分频出来的。CubeMX 会把时钟树精确到小数你最好让实际像素时钟跟屏的规格尽量接近误差在 5% 以内通常都能接受。如果你设置的像素时钟偏离太大画面会出现滚动或者条纹看起来像花屏。3.2 生成工程后HAL 适配到底要改哪些文件CubeMX 配置完外设在中间件里选中 TouchGFX Generator设置分辨率、颜色格式、帧缓冲个数和工作队列后生成工程。生成后的目录里会有TouchGFX文件夹下面通常会有Target、App、generated、gui这几个目录。核心原则是generated里的代码不要手改因为重新生成时会被覆盖Target里的代码就是留给你的。第一个要改的是BoardConfiguration.cpp主要处理 LCD 和触摸的 GPIO/外设初始化。LTDC 的时钟和引脚已经在 CubeMX 里配置好了但背光、触摸复位、触摸中断这些额外引脚需要你自己添加。背光不能漏漏了屏幕不亮很多人还以为是 LTDC 没配置好。第二个是TouchGFXHAL.cpp这里要确认帧缓冲地址。如果你用了外部 SDRAM需要把 LTDC 层的数据地址指向 SDRAM 区域。有些工程的链接脚本没有把 SDRAM 映射进可寻址空间你就需要在分散加载文件里加一个 Region。这里最容易犯的错误是帧缓冲地址没有 32 字节对齐DMA2D 的高效传输要求地址对齐不对齐会出现各种奇怪的渲染闪烁。第三个是触摸驱动。TouchGFX 引擎本身不关心你用哪颗触摸芯片它只认一个回调接口你有触摸事件了调touchgfx::click_event通知它。所以你要写一个触摸芯片的驱动比如 GT911 或者 FT5x06把坐标读取出来做方向映射后交给 TouchGFX。还有一个容易被忽略的地方是 RTOS 配置。如果你在 CubeMX 里选了 FreeRTOSTouchGFX 会有一个独立的任务处理渲染。任务栈给多大很重要给太小会在页面切换时触发 HardFault。经验值是堆栈给到 2KB 以上具体看你界面的复杂度复杂动画可以给 4KB。3.3 触摸坐标方向不对不是硬件问题是映射问题触摸移植最典型的坑是坐标不对。屏幕的物理安装方向和触摸屏的坐标系有时候是反的比如屏是竖着装的但触摸芯片默认横着扫描。你看到的症状就是点左上角界面反应在右下角或者 X/Y 轴互换了。做法是在触摸驱动里读出原始坐标后根据实际安装方向做一次变换。变换逻辑不复杂无非就是 x 翻转、y 翻转、x/y 互换。我习惯在驱动里写一个 debug 模式先用串口打印原始坐标和屏上显示光标的位置确认真实对应关系后再固化映射逻辑。别一上来就想校准很多电容屏根本不需要复杂的校准流程。4. 从白屏到完整显示实测中最常见的故障与定位方法4.1 白屏排查顺序不是先看代码而是先看波形白屏是移植路上出现率最高的故障。我自己的排查顺序是先硬件后软件先电源后信号。第一步看背光有没有亮如果背光亮但屏幕全白说明 LCD 面板是好的驱动电路在输出白色电平问题多半在信号或者配置上。如果背光都不亮先查背光使能引脚和背光电源。第二步用示波器测 LTDC 的时钟和数据信号。测 CLK 引脚看是否有连续脉冲输出测 DE 引脚看是否有行场有效信号。如果 CLK 和 DE 都有波形但数据线全为高屏幕自然显示纯白。这说明 LTDC 已经在输出但内容不对主要检查帧缓冲地址是否有效、图层是否被正确使能。如果 CLK 没有输出问题在时钟树配置先查 PLLSAI 有没有工作。第三步单独测试帧缓冲。把 LTDC 的帧缓冲地址指向一段固定的内存手动把整块内存填成 0xF800也就是 RGB565 的纯红色看看屏幕是否整屏变红。能变红说明整个 LTDC 链路已经从内存到面板通了剩下的事情都好说不变红则需要回到前面的步骤继续排查。4.2 花屏、错位、撕裂先看看时序算对没有花屏比白屏更让人崩溃因为画面是有的但完全不可读。最常见的场景是画面左右偏移或者顶部出现一条杂色带。这种问题基本可以锁定时序参数不对具体来说就是 HFP、HBP 或者 VSYNC、VBP 这些值填错了。LTDC 要求这些参数至少为 1很多屏手册还会给最小值不要强行往小了填。我见过有人把 HBP 填 0 导致画面整体左移看起来像是数据错位其实是同步信号时间不够。画面随机闪烁或者撕裂大概率是帧缓冲读取和写入的竞争问题。如果你用的双缓冲需要在 VSYNC 中断里完成缓冲切换让 LTDC 读到的是完整的新画面否则就可能出现上半屏是新画面、下半屏还是旧画面的撕裂现象。触摸屏上出现这种问题往往是切换时机不对或者中断优先级配得不够高。还有一类花屏跟 SDRAM 有关。SDRAM 时序参数配置不当比如 CAS Latency 或者刷新周期设置偏短会导致随机读取错误。这种故障的典型表现是画面大部分正常但偶发出现一块块颜色噪声。排查手段是先降低 SDRAM 时钟频率如果噪声消失说明时序裕量不足。别急着调寄存器先看看电路板走线SDRAM 数据线等长没等长、电源去耦够不够很多时候是硬件问题。4.3 开了 D-Cache 的芯片记得先把 MPU 配好如果你用的是 STM32F7 或者 H7 这类 Cortex-M7 内核的芯片还有一个绕不开的坑Cache 一致性问题。LTDC 通过 DMA 读取帧缓冲而 CPU 往帧缓冲里写数据时先经过 D-Cache这时 Cache 里的数据还没写回内存LTDC 读到的就可能是旧的脏数据表现出来就是画面不对、字体边缘有杂点甚至整个画面随机花屏。解决方案是配置 MPU把帧缓冲对应的内存区域设置为非缓存或者 write-through。如果使用 SDRAM 做帧缓冲需要开一个 MPU Region使该区域对 DMA 可见。很多人在 F4 上调得好好的一换 F7 就出各种诡异花屏十有八九就是这个原因。5. 性能优化别让 TouchGFX 变成“能跑但没法用”5.1 帧缓冲策略单缓冲、双缓冲还是局部缓冲TouchGFX 的流畅感很大程度上取决于帧缓冲策略。单缓冲内存占用最低但 LTDC 在输出画面的同时渲染引擎在往同一个缓冲里写内容必然出现撕裂。双缓冲是一边渲染一边显示利用 VSYNC 中断切换视觉上就感觉不到撕裂了代价是内存翻倍。如果你的内存紧张可以考虑 TouchGFX 的局部缓冲特性它只分配一部分屏幕大小的帧缓冲渲染时只更新需要变化的区域。但局部缓冲对场景有要求全屏动画效果下性能会比双缓冲差。实际操作中480x272 的屏我一般优先选双缓冲。如果内存实在吃紧才考虑局部缓冲。800x480 以上的分辨率双缓冲确实需要很多内存这时先用单缓冲跑起来再结合局部刷新去优化。不要一开始就贪多能跑通比跑得炫更重要。5.2 把资源压下来帧率自然就上去了UI 卡顿不一定是 CPU 频率不够很多时候是资源格式选得不好。图片资源在 Designer 里导出时选择 RGB565 会比 ARGB8888 少一半内存和带宽。除非你真的需要透明效果否则没必要用 ARGB8888。动画里的图片尽量用合适尺寸不要内存里存了一张大图只在界面上显示一个小缩略图。字体的优化更明显。TouchGFX 默认会对字体做抗锯齿每个字符需要存灰度信息中文字库动辄几十 MB不能全量生成。我的做法是在 Designer 里只把用到的字符集加进去或者用工具对字体做子集化处理只保留界面需要的文字。这一步能让字体资源从几 MB 降到几百 KB对内存和性能都有很大帮助。5.3 让 DMA2D 分担 CPU 的渲染压力TouchGFX 引擎本身会自动判断哪些绘制操作适合交给 DMA2D。如果你的工程打开 DMA2D 后反而变慢检查是不是频繁地小块操作占满了 DMA2D 的启动开销。正确的做法是让渲染尽可能合并成大块操作比如大面积背景色填充、整张图片绘制这些场景 DMA2D 优势非常明显。频繁的小图标逐帧动画DMA2D 未必比 CPU 快。调试时可以用 TouchGFX 自带的性能监控或者简单地用 GPIO 翻转方法测量渲染耗时看瓶颈到底在哪一块。6. 调试技巧与速查表遇到问题不抓瞎6.1 三个容易忽略但其实很关键的细节移植过程中有三个细节我经常提因为它们让我半夜加过班。第一个是链接脚本。如果你用了 SDRAM 做帧缓冲但链接脚本没把 SDRAM 声明为可执行区域运行时会跑飞。很多生成工具不会自动帮你把外部 RAM 加上需要手动改分散加载文件。第二个是中断优先级。TouchGFX 的 VSYNC 中断优先级如果配得太低很容易被其他外设中断打断导致帧率不稳。第三个是 I2C 触摸芯片的地址问题。GT911 这类触摸芯片上电瞬间的电平状态决定 I2C 地址经常出现两个板子一样代码但一颗能读到一颗读不到就是硬件设计时复位时序和地址引脚处理不当。另外强烈建议在移植早期就把串口日志打出来。在 TouchGFX 启动路径的每个关键函数里加printf包括touchgfx_init、HAL 初始化、消息循环开始等。这样至少能明确启动卡在哪一步。很多问题其实是前面某段初始化失败但表面现象是屏幕黑色。6.2 移植问题速查表现象可能原因排查方向背光亮但全白屏LTDC 未输出有效数据或帧缓冲内容为默认值检查图层使能、帧缓冲地址手动填纯色测试背光不亮背光 GPIO/PWM 未配置检查背光电源和使能信号画面偏移HBP/HFP、VBP/VFP 或极性不对重新核对屏手册时序参数颜色怪异色深格式不匹配检查 RGB565/RGB888 设置与屏接口位宽随机花点SDRAM 时序裕量不足降时钟、检查 Cache/MPU 配置撕裂双缓冲切换时序错误把切换放到 VSYNC 中断里做触摸无反应I2C 地址错误或中断引脚未配置用示波器量触摸中断测 I2C 通信触摸坐标反向坐标映射不对原始坐标打印后做翻转/交换处理6.3 移植阶段最稳的开发节奏最后一个经验是关于开发顺序的。我踩过不少次“一次武装到牙齿然后失败”的坑。现在我的习惯是分四步走。第一步用裸机程序把 LTDC 点亮手动填一个纯色确认屏的基本通路完整。第二步加入 SDRAM 和帧缓冲把纯色和简单的几何图形跑起来。第三步接上 TouchGFX加载一个最简单的空白界面确认 HAL 链路通畅。第四步再加入触摸、多界面、动画和实际业务逻辑。每一步都有明确的验证点出问题之后定位范围很小不至于在全金属堆里找一根断了的连线。就我个人实际体会而言TouchGFX 移植这件事看起来复杂本质上是把官方板替你做过的那些适配工作自己在自研板上重做一遍。只要把时序、帧缓冲、触摸映射、Cache 一致性和中断优先级这几个点稳住80% 的问题都不会出现。最后再分享一个小技巧把工程收进 Git每次改完一个参数就提交一次配合串口日志发现哪次改动引起画面异常git diff一眼就能看出来比翻 Hex 文件和原始备份要高效得多。