在自制板卡上跑 TouchGFX 这件事,我前后折腾了将近三周。中间一度怀疑是硬件画错了、屏幕买错了、甚至是芯片本身有问题,最后才发现大部分时间都耗在了几个非常隐蔽的软件配置和内存细节上。这篇东西就是把这段经历里最有价值的排查思路、参数计算和避坑点整理出来,给正在和 TouchGFX 死磕自定义板卡的朋友一个参考。
先说结论:TouchGFX 本身并不难,难的是“把它放到一块不是官方评估板的硬件上”。官方板卡的工程一生成就能跑,那是因为 ST 已经把引脚映射、SDRAM 时序、触摸芯片型号、屏幕参数全部匹配好了。换到自定义板卡,这些隐含的“正确答案”全部失效,每一个环节都需要你自己重新确认。这篇内容适合手里已经有了一块能跑裸机程序的板子、准备或正在把 TouchGFX 集成进来的人阅读,我会按照实际踩坑的顺序,把从显示链路到触摸适配再到性能调优的完整路径讲清楚。
1. 自定义板卡和官方评估板之间的“隐形差距”
很多人会有一个误解:TouchGFX 就是一套软件库,放到哪块 MCU 上都应该一样跑。但实际上,TouchGFX 的设计前提是“GPU 辅助渲染”和“帧缓冲直接输出到显示控制器”,这两点让它对底层硬件接口的依赖远高于一般 GUI。这也是为什么在自定义板卡上,它表现得格外“娇气”。
1.1 先分清 TouchGFX 和普通 GUI 的定位差异
普通的嵌入式 GUI,比如 LittlevGL 或者 emWin,本质上是软件把像素画进一块内存区域,然后再通过某种方式搬运到屏幕上。这个过程里,MCU 的算力决定了绘图速度,显示接口只负责“搬运”,两者相对独立。
TouchGFX 则不同。它针对 STM32 的 LTDC(LCD-TFT Display Controller)和 DMA2D(内存到内存的图形加速器)做了深度优化,渲染管线的理念是“尽可能在写入显存的同时就让数据流到屏幕上”,并通过双缓冲、局部刷新、帧缓冲在 SDRAM 和内部 SRAM 之间的分配来换取性能。这意味着,如果没有配置好 LTDC、没有分配好帧缓冲、没有让 DMA2D 按预期工作,TouchGFX 的表现会变得非常奇怪——有时候是画面撕裂,有时候是颜色错乱,有时候干脆黑屏。
另一个容易忽略的点是:TouchGFX Designer 生成的代码默认是为 CubeMX 生成的工程服务的,它会假设你已经通过 CubeMX 配置好了时钟树、FMC(Flexible Memory Controller)和 LTDC。如果这些底层配置和你的板子不一致,生成的代码几乎不可能直接跑通。
1.2 硬件差异带来的第一个坑:显示接口类型
官方评估板上常见的屏幕接口有两种:
- 并行 RGB 接口,通过 LTDC 外设直接驱动,数据线数量从 16 位到 24 位不等。
- MIPI DSI 接口,通过 DSI 主机外设驱动,数据走差分串行链路。
你的自定义板卡是哪一种,直接决定了 TouchGFX 工程里需要启用的外设和引脚分配。很多人在这一步就卡住了:用了一块 RGB 接口的屏幕,却在 CubeMX 里参考了 MIPI DSI 的评估板工程,或者反过来。
我在排查时最先做的就是“接口三确认”:
- 屏幕数据手册上标明的接口类型是什么?并行 RGB 还是 MIPI DSI 或 SPI?
- MCU 上对应外设是否支持这种接口?例如 STM32F429 系列只有 LTDC,而 STM32F769 系列同时有 LTDC 和 DSI。
- CubeMX 的引脚分配是否与硬件原理图完全一致,特别是像素时钟、HSYNC、VSYNC、DE、数据引脚的位置。
这三项只要有一项不对,后续不管怎么调软件都是白费功夫。
2. LTDC 显示链路排查:接线、时序与花屏的真相
LTDC 是 TouchGFX 在大部分 STM32 平台上最关键的显示外设。它的作用是把内存中的帧缓冲数据,按照你设置的时序参数,源源不断地输出到屏幕。如果把整条链路比作一条水管,LTDC 就是那个水龙头,帧缓冲是水箱,屏幕是出水口。任何一个环节堵塞,最终看到的结果就是花屏或者黑屏。
2.1 引脚映射核对:按键级别的最佳实践
我在这次项目中踩的第一个具体问题,是屏幕颜色看起来整体偏绿,而且画面严重错位。排查到最后,发现原因简直让人哭笑不得——RGB 引脚映射时,把红色和绿色的两组数据线接反了。
LTDC 的数据引脚是这样的:RGB888 模式下,LTDC_R2到LTDC_R7对应红色的 6 位数据,LTDC_G2到LTDC_G7对应绿色 6 位数据,LTDC_B2到LTDC_B7对应蓝色 6 位数据。如果你在自定义板卡的原理图上,将 MCU 的某个引脚分配给了 LTDC 的某个信号,但 PCB 布线上这个信号实际连到了屏幕的另一组数据线,那么屏幕显示的图像就会是通道互换后的结果。
排查这个问题最快的方式,不是用 TouchGFX 去调,而是先写一个裸机测试程序,直接往 LTDC 的帧缓冲地址里填几个纯色像素(红色 0x00FF0000、绿色 0x0000FF00、蓝色 0x000000FF),然后用示波器或者逻辑分析仪检查对应数据线上的电平。如果硬件连线本身有问题,那么无论怎么在软件里折腾,都不可能得到正确的颜色。
另外一个经常被忽略的是 LTDC 的 DE(Data Enable)信号极性。很多屏幕要求 DE 为高电平有效,但某些屏的驱动 IC 对极性的要求比较特殊。在 CubeMX 的 LTDC 配置里,Polarity有 Active High 和 Active Low 两个选项,大部分情况下选 Active High,但也有例外。遇到整屏有条纹、或者画面左右颠倒、上下颠倒时,除了检查 HSYNC/VSYNC 极性,也要顺手确认 DE 的极性。
2.2 LTDC 时序参数的确定过程
LTDC 的时序参数包括:
| 参数 | 含义 | 典型来源 |
|---|---|---|
| Hsync 宽度 | 水平同步信号脉冲宽度(像素时钟数) | 屏幕数据手册 |
| Hsync 前肩 | 有效数据开始前的同步信号后延时 | 屏幕数据手册 |
| Hsync 后肩 | 有效数据结束到下一同步信号的间隔 | 屏幕数据手册 |
| Vsync 宽度 | 垂直同步信号脉冲宽度(行数) | 屏幕数据手册 |
| Vsync 前肩 | 有效帧开始前的同步行数 | 屏幕数据手册 |
| Vsync 后肩 | 有效帧结束后的同步行数 | 屏幕数据手册 |
很多人直接用 CubeMX 里的默认值,然后发现屏幕显示位置偏移或者出现扫描线。正确做法是严格对照屏幕数据手册中的“Timing Characteristics”表格填写。以常见的 4.3 寸 480x272 RGB 屏为例,数据手册里通常会给出类似这样的参数:
HsyncWidth: 41 HsyncFrontPorch: 2 HsyncBackPorch: 2 VsyncWidth: 10 VsyncFrontPorch: 2 VsyncBackPorch: 2把这些数值填入 CubeMX 后,还差一个像素时钟的计算。LTDC 的像素时钟PixelClock决定了数据的输出速率,计算公式是:
PixelClock = (屏幕宽度 + HsyncWidth + HsyncFrontPorch + HsyncBackPorch) × (屏幕高度 + VsyncWidth + VsyncFrontPorch + VsyncBackPorch) × 刷新率以 480x272、60Hz 刷新率为例,代入上面给的时序参数:
水平总像素 = 480 + 41 + 2 + 2 = 525 垂直总行数 = 272 + 10 + 2 + 2 = 286 PixelClock = 525 × 286 × 60 ≈ 9.01 MHz所以,PixelClock大约设为 9 MHz 即可。如果设得太高,屏幕可能超出带宽导致闪屏;设得太低,刷新率下降,肉眼能感觉到卡顿。
这里有一个很重要的实操建议:在你把 TouchGFX 工程烧进去之前,先用裸机程序把 LTDC 调好,确认屏幕能正常显示纯色和颜色渐变。这一步如果花半天时间,换来的是之后整整少掉几天排查黑屏的时间,非常划算。
3. 帧缓冲落进 SDRAM:带宽、对齐和 Cache 三个拦路虎
显示链路通了之后,下一个大坑就是帧缓冲。TouchGFX 在自定义板卡上“跑不动”的原因,十有八九出在 SDRAM 的配置上。
3.1 为什么必须用到 SDRAM
先算一笔账。假设屏幕分辨率是 480x272,采用 RGB888 颜色格式,每个像素 4 字节(32 位),单帧缓冲大小:
480 × 272 × 4 = 522,240 字节 ≈ 510 KB如果按照 TouchGFX 默认的双缓冲策略,就需要约 1 MB 的内存。再算上图形资源、字库、图片缓存,至少需要 1.5~2 MB 的连续内存。STM32 内部 SRAM 通常只有 192 KB 到 512 KB,完全不够用。所以,SDRAM 基本是必须的。
有些人试图用单缓冲 + 小分辨率来绕过 SDRAM,但这会牺牲 TouchGFX 的核心优势——平滑的动画效果和局部刷新能力,相当于买了个跑车只用来在小区里买菜,意义不大。
3.2 FMC 配置的坑:时序、位宽和 Bank 地址
SDRAM 挂在 STM32 的 FMC(Flexible Memory Controller)上,CubeMX 中有一个完整的 SDRAM 配置界面。常见的 SDRAM 是 16 位数据宽度,挂在 Bank 1 或 Bank 2 上,地址范围根据 Bank 和行/列地址数而定。
配置 SDRAM 时有几个关键参数:
- 列地址位数(Column Bits):常见 8、9、11 位,取决于 SDRAM 芯片型号。
- 行地址位数(Row Bits):常见 11、12、13 位。
- CAS 延迟(CAS Latency):常见 2 或 3,需要和 SDRAM 芯片规格匹配。
- 刷新周期(Refresh Period):通常 64 ms,但具体在 FMC 里填的是刷新计数,需要根据 SDRAM 数据手册计算。
一个非常容易犯的错误是行地址和列地址数填错,导致访问 SDRAM 时地址错乱。表现是:TouchGFX 跑起来时,画面某些区域出现规律的乱码或花屏,且每次运行位置固定。这是因为帧缓冲的地址映射与 SDRAM 实际物理地址不对应,数据写入了错误的行列。
正确做法:先查你的 SDRAM 芯片数据手册,确认行列地址位数和刷新周期。例如 W9825G6KH 是 256Mb SDRAM,行地址 13 位,列地址 9 位,Bank 数 4,数据宽度 16 位。那么在 CubeMX 中就应该把 Row Bits 设为 13,Column Bits 设为 9。不要想当然地照搬其他工程。
3.3 内存对齐和 D-Cache 导致的花屏
TouchGFX 的帧缓冲地址有对齐要求,通常是 32 字节对齐。如果帧缓冲变量定义时没有指定对齐属性,编译器可能会把它放在一个未对齐的地址上,导致图形渲染时出现奇怪的偏移或者是边缘出现噪点。
在 TouchGFX 生成的代码中,帧缓冲通常通过 CubeMX 分配在 SDRAM 的起始位置。你可以通过链接脚本(Linker Script)来保证这一点。例如,在 STM32CubeIDE 的.ld文件中,可以为 SDRAM 单独定义一个内存区域,然后在代码中显式声明:
__attribute__((section(".sdram"))) uint32_t framebuffer[480*272];这样帧缓冲就会被放置到你指定的 SDRAM 地址段,对齐问题也能通过区域起始地址对齐来解决。更稳妥的做法是检查生成的链接脚本中.sdram段的ALIGN(32)设置。
D-Cache 是另一个隐性杀手。当你开启了 STM32 的 D-Cache 后,CPU 会先把数据缓存在 Cache 里,而不是立刻写入 SDRAM。如果 TouchGFX 的 DMA2D 直接读取 SDRAM 中的帧缓冲进行图形操作,而 CPU 已经写入的数据还在 Cache 中,就会产生数据不一致。
表现为:画面要么出现“残留”“拖影”,要么某个区域一直保持旧内容。解决办法是,在 TouchGFX 的帧缓冲切换或刷新之后,做一次 Cache 的 Clean 和 Invalidate:
SCB_CleanDCache_by_Addr((uint32_t*)framebuffer, sizeof(framebuffer)); SCB_InvalidateDCache_by_Addr((uint32_t*)framebuffer, sizeof(framebuffer));这两个函数的调用时机需要仔细斟酌。一般做法是,在写入帧缓冲之后调用 Clean(确保脏数据写回内存),在 DMA2D 读操作之前调用 Invalidate(确保 Cache 中的数据是最新的)。如果时机不对,效果甚至会更差。
3.4 带宽计算的必要性
SDRAM 带宽是很多人忽视的瓶颈。计算方式为:
屏幕总像素 × 每像素字节数 × 刷新率 = 最低带宽需求继续用 480x272 的屏幕和 32 位色彩来算:
480 × 272 × 4 × 60 = 31,334,400 字节/秒 ≈ 31.3 MB/s这只是显示所需的数据量,还没算 TouchGFX 在动画、局部刷新时需要额外从 SDRAM 读取图像资源、字库的开销。如果 SDRAM 的配置时序过于保守,实际可用带宽不够,就会出现掉帧、卡顿。从实测来看,把 SDRAM 时钟和刷新参数调到符合芯片规格的安全上限范围内,是提升整体流畅度最直接的硬件手段。
4. 触摸驱动适配:从 I2C 地址到坐标映射的完整打通
显示链路通了之后,屏幕上能看到 TouchGFX 的界面了,但点击完全没反应。这个问题卡了我两天。TouchGFX 的“触摸”并不是自己知悉触摸芯片的存在,而是通过一个TouchController抽象层来获取坐标信息的。这个抽象层必须由你根据实际的触摸芯片驱动来实现。
4.1 触摸芯片的 I2C 探测与地址确认
市面上常见的触摸芯片包括 GT911、FT5x06、GT811、CST816 等。每种芯片的 I2C 地址可能不同,甚至同一型号可能有多个地址位可选,取决于芯片引脚电平。
首先,确认你的触摸芯片型号,然后从数据手册中找到其 I2C 地址。以 FT5x06 为例,其 7 位 I2C 地址常见为 0x38,但某些版本的地址可能通过引脚配置为 0x28。如果你直接用 0x38 去读,读不到任何数据,触摸自然就不工作。
一个非常实用的探测方法:写一个简单的 I2C 扫描程序,遍历地址 0x00 到 0x7F,看哪个地址有 ACK 响应。我之前就是这样找出了板子上触摸芯片的真实地址,节省了大量时间。
4.2 TouchGFX 中的 TouchController 接口
在 TouchGFX 生成的工程中,有一个名为TouchController的抽象类,需要实现它的init()和sampleTouch()方法。
sampleTouch(int32_t& x, int32_t& y)的返回值代表是否有触摸事件,x和y则是触摸坐标。这里有一个非常容易踩的坑:TouchGFX 期望x和y的范围与屏幕分辨率一致,且坐标原点在屏幕左上角。而很多触摸芯片报告的坐标是“原始坐标”,范围可能是 0~4095 之间,甚至坐标系也可能相反。
举个例子,GT911 在 480x272 屏幕上,常常会报告 0~1023 或 0~2047 范围内的值,且 X 轴和 Y 轴正向可能与屏幕相反。这时就需要在sampleTouch()里做缩放和映射:
x = (raw_x * 480) / 1024; y = (raw_y * 272) / 1024;如果发现触摸位置和点击位置不对应,是镜像或者旋转的关系,还需要做坐标翻转。例如:
x = 480 - x; y = 272 - y;确定坐标映射是否正确的办法,是在屏幕上画一个十字准星,然后依次点击左上、右上、左下、右下四个角,用串口打印出 TouchGFX 收到的坐标值。通过这四个点的实际值和期望值,就可以反推映射关系。
4.3 中断模式和轮询模式的取舍
触摸芯片一般会有中断引脚(INT),在有触摸事件时拉低或拉高,通知 MCU 读取数据。你可以选择用 MCU 的外部中断来触发读操作,也可以选择让 TouchController 在sampleTouch()里面直接轮询 I2C 读取。
在实际项目里,推荐使用 GPIO 外部中断 + 标志位的方式。理由是:轮询模式下,TouchController 每次被 TouchGFX 调用时都会去读一次 I2C,即使没有任何触摸事件。这会白白占用 CPU 和 I2C 总线时间,拖慢 GUI 本身的渲染效率。而中断模式只在有触摸事件时才触发读取,效率高得多。
但中断模式也有个坑:触摸芯片的 INT 引脚通常是开漏输出,需要外部上拉或者 MCU 配置内部上拉。如果你的原理图上没有这个上拉电阻,中断可能会一直拉低或者一直拉高,导致检测不到正确的边沿。排查时需要先用万用表测一下 INT 引脚的静态电平。关于这点,我是吃了亏的:原理图上漏画了一个上拉电阻,导致中断信号一直无效,白白查了两天。
4.4 TouchGFX 与触摸 IC 驱动之间的数据流
TouchGFX 的主循环会定期调用sampleTouch(),把返回值交给 GUI 的事件处理器。如果你发现点击之后有反应,但反应有几百毫秒的延迟,可能的原因之一是 I2C 时钟频率太低,读一次完整的数据需要太长时间。很多触摸芯片支持 400 kHz 的快速模式,而 CubeMX 默认可能在 100 kHz。把 I2C 时钟频率调到芯片允许的上限,可以明显降低触摸延迟。
5. 构建链路与调试方法:从编译报错到画面渲染
当你把显示和触摸都调通之后,下一个阶段的问题集中在“工程集成”和“构建配置”上。TouchGFX 生成的代码不像普通 HAL 工程那么简单,它依赖多个外部库和特定构建选项。以下是我实际遇到并解决过的几类典型问题。
5.1 CubeMX 工程与 TouchGFX 工程的联动步骤
TouchGFX 的工作方式是这样的:在 TouchGFX Designer 中创建 UI 工程,指定 STM32 型号和分辨率,它会生成一份 UI 代码和一个.ioc文件引用。然后你在 CubeMX 中打开这个.ioc,进行外设配置,生成初始化代码。最后,把两部分代码合并到一个 IDE 工程中编译。
一个很常见的错误是:在 CubeMX 中打开了 TouchGFX 生成的.ioc之后,重新生成了代码,结果 TouchGFX 的库文件或中间层代码没有被正确包含进来。解决办法是,确认工程里的 Middleware 选项中已经把 TouchGFX 勾选上,并且设置里指定了正确的 TouchGFX 安装路径。
我用的版本是 TouchGFX Designer 4.24 和 STM32CubeMX 6.10,两者配合时有一个细节:CubeMX 生成代码后,会在工程目录下创建一个TouchGFX文件夹,里面有target、generated、gui、App等子目录。这些目录里的文件不要随意改动,尤其是generated目录,里面是由 Designer 生成的代码,和 UI 文件一一对应。如果手动改了它,下次 Designer 重新生成时可能会覆盖掉你的修改。
5.2 链接脚本中的 SDRAM 段和帧缓冲分配
我之前提到过,帧缓冲最好放在 SDRAM 中。这就需要在链接脚本中为 SDRAM 增加一个段。以 STM32F429 为例,默认的链接脚本只定义了内部 FLASH 和 SRAM,并没有 SDRAM 段。
一个常见的做法是在.ld文件中添加:
MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K SDRAM (xrw) : ORIGIN = 0xC0000000, LENGTH = 8192K }然后在代码段定义中,增加一个.sdram段:
. = ALIGN(4); .sdram : { . = ALIGN(32); *(.sdram) . = ALIGN(32); } > SDRAM之所以要对齐 32 字节,是因为 DMA2D 和 LTDC 对帧缓冲地址有对齐要求,未对齐会导致性能下降甚至硬件错误。这个细节,我在最初做实验时没有留意,结果折腾了一天才发现问题是地址没有 32 字节对齐。
5.3 HardFault 的常见诱因和定位手段
自定义板卡上跑 TouchGFX,HardFault 可以说是“必修课”。最常见的诱因有三个:
- 帧缓冲未初始化:TouchGFX 启动时,会直接向帧缓冲地址写入数据。如果这个地址对应的内存还没有被启用(比如 SDRAM 的 FMC 控制器还没初始化就尝试访问),立即 HardFault。
- 堆栈溢出:TouchGFX 的 GUI 线程和渲染线程需要较大的栈空间。默认的启动文件可能只分配了 8 KB,实际需要更大。如果动画复杂,很容易溢出。
- 外设时钟未使能:LTDC、DMA2D、FMC 这些外设的时钟在 CubeMX 里默认会生成,但如果你手动合并代码时漏掉了某一段初始化代码,访问外设时也会 HardFault。
定位 HardFault 最有效的方式,是用调试器停在异常处理函数里,查看栈回溯(Call Stack),找到触发异常的函数名。如果栈回溯信息不完整,可以在HardFault_Handler里加上一段代码,把程序计数器(PC)和链接寄存器(LR)的值打印出来,然后对照汇编代码找到具体的函数。这部分比较烧脑,但一旦找到位置,修复通常很快。
5.4 关于调试输出的建议
TouchGFX 本身运行效率很高,但它的渲染是没有“printf”的。也就是说,你不能直接在屏幕上看到每个像素是怎么画出来的。想要确认系统在跑,最好的方式是串口输出日志。
我自己的习惯是:
- 在外设初始化完成、TouchGFX 启动之前,用串口打印 “Start”;
- 在 TouchGFX 的主循环里,使用一个强撸计数器,每秒打印一次帧率或者 CPU 占用;
- 在触摸事件发生时,打印触摸坐标。
这套日志体系帮我快速定位了很多问题。比如,如果“Start”能打印但画面始终黑屏,说明问题出在 LTDC 配置;如果画面能显示但 TouchGFX 的帧率极低,说明问题出在帧缓冲或 SDRAM 带宽;如果触摸事件完全没有,说明问题出在 I2C 初始化或中断配置。
6. 性能调优:从掉帧到流畅的实测路径
画面能显示、触摸能点击,但这只是“能跑”。如果你想让界面像产品而不是演示板,性能调优逃不掉。这一节讲的是我从掉帧明显到基本流畅的实测路径。
6.1 帧时间分析:瓶颈在哪里
我先说一个概念:TouchGFX 的渲染过程中,CPU 负责绘制图形,LTDC 负责把帧缓冲推送到屏幕,DMA2D 负责图形加速。三者并行工作时,帧率取决于最慢的那一个。
从实际操作上看,掉帧最常发生在动画切换和复杂图片填充的场景。这时候你需要判断瓶颈是 CPU 还是总线带宽。
一个比较简单的判断方法:在动画过程中用调试器观察 CPU 占用率。如果 CPU 占用率接近 100%,且 DMA2D 的工作量并不大,那瓶颈大概率在 CPU 的图形绘制逻辑,可以通过将部分绘制任务交给 DMA2D 来缓解。如果 CPU 占用率并不高但帧率还是上不去,瓶颈就更可能在内存带宽或 LTDC 的像素时钟。
6.2 降低颜色深度是性价比最高的优化
TouchGFX 支持 RGB888(24 位/像素)和 RGB565(16 位/像素)。从视觉效果上说,RGB565 在某些渐变色场景会有轻微色带,但换取的是帧缓冲带宽直接减半。在之前 480x272 的例子里,RGB888 的带宽需求是 31.3 MB/s,降到 RGB565 就是约 15.7 MB/s,SDRAM 压力小了很多。
如果你想追求流畅度,且对颜色精度要求不是极高,直接改成 RGB565 是立竿见影的做法。具体的修改位置在 TouchGFX Designer 的“Screen”属性或 CubeMX 的 LTDC 配置中,需要把像素格式改为LTDC_PIXEL_FORMAT_RGB565,同时在 TouchGFX 生成代码时的颜色深度也要对齐。如果这两处不一致,会出现颜色完全错乱的情况。
6.3 局部刷新与 DMA2D 加速的取舍
TouchGFX 的局部刷新(Partial Rendering)是它相对传统 GUI 的一大优势。它只在画面变化的部分重新绘制,而不是整帧刷新。这个特性在常用界面下可以大幅降低 SDRAM 带宽需求。
但局部刷新也有一个代价:它需要额外的一块“脏矩形”管理区域,并且在某些操作中比全帧刷新更复杂。如果你的动画是全屏切换,局部刷新的优势反而不明显。
实际使用中,我是这样取舍的:
- 静态界面、按钮点击、数字更新:开启局部刷新,效果明显。
- 全屏动画、视频播放、地图平移:关闭局部刷新,直接用全帧双缓冲,反而更稳。
DMA2D 加速则建议全程开启。它可以在后台搬运数据,减轻 CPU 负担。不过,如果你发现 DMA2D 的缓冲区和 SDRAM 时序有冲突,出现花屏,可以先关闭 DMA2D,验证是否为配置问题。
6.4 关于内存分配的最终建议
最后想提一个关于内存分配的思路,这也是我在多个项目中验证过有效的做法:
在 SDRAM 中预留三个主要区域:
- 帧缓冲区(双缓冲,大小 = 屏幕分辨率 × 每像素字节数 × 2)
- 图形资源区(图片、字库、缓存,大小根据资源大小而定)
- 动态分配区(用于 TouchGFX 运行时创建的动态对象)
把这三个区域在链接脚本或启动代码中明确划分,然后用宏定义引用地址,而不是让编译器随意放置。这样做的好处是,你可以通过查看内存占用报告,快速判断是否需要裁掉某些图片资源,或者在帧缓冲区和动态区之间做出权衡。
我见过不少人把所有的内存都放在一起,用默认的堆分配来管理,结果帧缓冲和数据资源相互干扰,出现各种莫名其妙的 bug。划分区域虽然看似死板,但能让系统行为更可预测,排除问题时也更有针对性。
6.5 实测数据和最终调优效果
总结一下我在这块板子上的实测数据,方便你做参考:
| 项目 | 调优前 | 调优后 |
|---|---|---|
| 颜色深度 | RGB888 | RGB565 |
| 刷新模式 | 全帧刷新 | 局部刷新 |
| 帧率(静态界面) | 约 55 fps | 约 60 fps |
| 帧率(日历翻页动画) | 约 28 fps | 约 58 fps |
| SDRAM 带宽占用 | 约 31.3 MB/s | 约 15.7 MB/s |
| 触摸响应延迟 | 约 60 ms | 约 15 ms |
帧率提升最明显的是动画场景,从 28 帧提升到接近满帧,观感完全是两回事。触摸延迟的改善则主要归功于 I2C 时钟从 100 kHz 提到 400 kHz,以及中断模式的正确配置。
回到开头那句话:TouchGFX 本身不复杂,复杂的是自定义板卡带来的各种“隐含变量”。但只要能把显示、内存、触摸、构建这四件事逐个确定下来,剩下的工作就和官方评估板没太大区别了。希望这篇记录能帮正在同样挣扎的人,少走我走过的弯路。