
最近在把一个仪表盘项目从老平台翻到 STM32屏幕是 7 英寸 1024x600 的 RGB 电容触摸屏主控选了 STM32H750。一开始我确实没打算用 TouchGFX因为印象里图形界面库都挺重觉得老老实实调个裸机 UI 够了。真做起来才发现STM32CubeMX 加上 TouchGFX 这套组合真正省下的是从芯片启动到 GUI 显示之间那一大堆“接缝”代码时钟树、LTDC 初始化、SDRAM 帧缓冲、触摸控制器、DMA 搬运全都要有人管。CubeMX 管外设TouchGFX 管界面两者通过生成代码衔接。这篇内容适合要接大屏、想做图形界面的嵌入式工程师读完至少能把自己项目的工程串起来也知道哪些坑容易翻。我尽量把这几个月的实测经历写出来包括遇到的白屏、触摸丢点、帧缓冲内存不够以及最后怎么定位解决的。1. CubeMX 和 TouchGFX 到底怎么分工为什么必须串起来用很多人第一次看 TouchGFX 会觉得它就是一套 UI 库下载个 Designer 画两个界面就能跑。事实远不是这样。TouchGFX 的定位是“GUI 应用框架”它必须有底层硬件驱动作支撑否则连点亮屏幕都做不到而 STM32CubeMX 恰好就是那个负责生成底层驱动的工具。两者分开用都别扭合在一起才顺。1.1 两者各自解决的痛点CubeMX 解决的是芯片级配置引脚分配、时钟树、LTDC 接口、FMC 控制外部 SDRAM、DMA2D、FreeRTOS、I2C 触摸驱动。这些配置如果手工写不同型号寄存器差异很大每换一颗芯片就要重写一遍。CubeMX 生成代码后底层初始化基本就踏实了但你不能指望它帮你画按钮和动画。TouchGFX 解决的是图形应用层控件布局、文字渲染、图片解码、状态跳转、MVP 模式下的逻辑分层。它能直接在 PC 上模拟界面然后把工程导出成 C 源码。可它不会主动去设置 PLL3Q 给 LTDC 提供像素时钟也不知道你板子外挂了哪颗 SDRAM更不知道触摸屏中断脚接到哪个 GPIO。所以 TouchGFX 需要一份“由 CubeMX 生成的配置信息”才能和硬件对上。这就是为什么 ST 官方把 TouchGFX Generator 做成了 CubeMX 的一个软件组件。在 CubeMX 界面里生成工程的同时会生成一个.touchgfx文件TouchGFX Designer 读这个文件后才能把生成的代码嵌回同一个工程目录。两边都生成代码最终由同一个 IDE 编译成固件。1.2 生成后的工程目录怎么看以我用的 STM32H750 CubeIDE 为例生成完后主目录大致是Core/Inc、Core/SrcCubeMX 生成的 HAL 初始化代码包括main.c、stm32h7xx_it.c、ltdc.c、fmc.c、i2c.c。TouchGFX/AppTouchGFX 应用层代码最常改的就是model.cpp、view目录里的各个界面文件。TouchGFX/target板级适配代码比如TouchGFXHAL.cpp、TouchGFXGPIO.cpp、TouchGFXTask.cpp触摸控制器基本在这里接。TouchGFX/generatedDesigner 自动生成的字体、图片、文本资源不要手改一改下次生成就没了。TouchGFX/ConfigTouchGFX 配置头文件帧缓冲地址、使用多少缓冲都在这里反映。搞清楚这个结构出问题才知道去哪里翻。新手最容易犯的错误就是直接改generated下的文件结果 Designer 一刷新所有改动被覆盖白白浪费时间。1.3 关于“STM32CubeMX2”这个写法我的项目记录里也写过STM32 CubeMX2 with TouchGFX其实这是不准确的叫法。官方并没有“STM32CubeMX2”这个产品大家口中的“CubeMX2”多半是指某个版本号比如 6.2 或 6.9.2说得多了就被人误解成第二代。实际使用时版本匹配比叫法重要得多。CubeMX 版本太老可能找不到 TouchGFX GeneratorTouchGFX Designer 版本太新又可能要求更高的 CubeMX 版本。建议用 CubeMX 6.9 以后、TouchGFX Designer 4.20 左右的组合相对稳定。2. CubeMX 端的关键配置每个参数都别想当然在 CubeMX 里外设多不要紧关键是先把时钟树、外部 SDRAM、LTDC、触摸 I2C 这几块设置对。任何一个参数不对后面 TouchGFX 跑起来都是白屏或者花屏。2.1 LTDC 像素时钟的计算LTDC 是 STM32 接 RGB 接口屏幕的专用外设它负责把你的帧缓冲数据按设定的时序发到屏幕。像素时钟指的就是 LTDC 向屏幕输出的像素点时钟。对于 1024x60060Hz 屏幕不能光算有效像素还要把前后肩和同步脉冲算进去。举例常见 7 寸屏参数为 HFP160、HSYNC30、HBP40VFP12、VSYNC3、VBP10那完整一行是 102430401601254 个像素完整一帧是 60031012625 行乘以刷新率 60Hz像素时钟约为 1254×625×6047MHz。实际留点余量我配了 50MHz。在 CubeMX 的 Clock Configuration 里把 PLL3Q 调整到 50MHz然后在 LTDC 的配置界面选择该时钟源。像素时钟太低会闪屏太高屏幕可能不亮或者颜色噪点明显。2.2 外部 SDRAM 的 FMC 参数设置1024x600 RGB565 的单帧缓冲已经要 1.2MB双缓冲 2.4MBSTM32H750 片内 RAM 一共才 512KB不挂外部 SDRAM 根本跑不动。我这里用的是 W9825G6KH8MB挂在 FMC Bank1起始地址 0xC0000000。CubeMX 里配置 FMC 的 SDRAM 时有几个参数很关键参数我用的值说明CAS Latency3读命令到数据输出延迟Write Recovery Time2写恢复时间Row Cycle Delay64ns行周期Row Precharge Delay64ns行预充电时间Row Active Delay18ns行激活到读写命令的延迟Write Recovery Time24ns写命令到预充电的最小间隔这些数值不是随手填的要按照 SDRAM 芯片手册和你的 FMC 时钟频率来换算。如果参数太小SDRAM 不稳定表现就是屏幕偶尔花一格太大则性能下降。建议先把 CubeMX 生成的fmc.c跑通再用一个简单的裸机程序往 0xC0000000 地址写、读校验一致后再接 TouchGFX。2.3 触摸 I2C 和中断引脚的设置触摸屏我用的是 FT5x06 方案I2C 地址 0x38。CubeMX 里把 I2C1 速率改为 400kHz引脚按实际硬件映射。这里有个容易被忽略的点触摸芯片的 INT 中断脚必须配置为外部中断输入并且在 NVIC 里启用 EXTI 中断。我一开始只把 I2C 配置好没有处理 INT 中断TouchGFX 里触摸始终没反应折腾了半天才发现触摸控制器根本没有请求 MCU 读数据。在 CubeMX 中把 INT 脚对应的 GPIO 设置为EXTI模式选择下降沿触发。然后到 NVIC 设置里勾选对应的 EXTI 中断建议优先级设置成 5 或 6。优先级太低了丢触摸事件太高了又可能打断关键时序我最后用 5 才稳定。3. 把 TouchGFX Generator 接到 CubeMX 工程里并跑通第一版 UI配置好外设后下一步就是让 CubeMX 生成带 TouchGFX 的工程。这个环节的坑主要在版本匹配和首次生成后的代码回填习惯上。3.1 CubeMX 里如何安装和启用 TouchGFX Generator打开 CubeMX 的.ioc文件后左侧导航栏有 Software Packs进入后能找到 TouchGFX Generator。如果你没安装可以从 Embedded Software Packages Manager 里搜索TouchGFX Generator安装。注意选择版本最好和已安装的 TouchGFX Designer 版本一致或只差一个小版本。启用后在 CubeMX 的 Project Manager 里Toolchain/IDE 选STM32CubeIDE或MDK-ARM然后点 GENERATE CODE。如果一切正常CubeMX 会生成 TouchGFX 工程文件并在完成后自动打开 TouchGFX Designer。如果没自动打开手动双击工程目录下的.touchgfx文件就行。我第一次生成时没有任何 GUI 相关代码后来发现是在 CubeMX 里没有勾选 TouchGFX Generator 的组件属性。具体位置是 Software Packs 里选中 TouchGFX Generator右侧会显示 Mode要选Graphic或Framework Designer这类选项然后重新生成。这一点很多教程不会细讲但它决定了是否生成 TouchGFX 目录。3.2 Designer 里搭建第一个界面TouchGFX Designer 打开后新建 Application选择屏幕分辨率和颜色深度。我选了 1024x600 和 RGB565。RGB565 比较省内存ARGB8888 支持透明效果但帧缓冲和图片资源占用大如果只是仪表盘界面RGB565 完全够用。界面我放了三个元素背景图、一个显示数值的 TextArea、一个切换页面按钮。所有文字用 Texts 资源统一管理不要直接在控件里写死字符串。这样后期改语言、改字体都不需要动代码。控件添加完成后会增加一个 Interaction比如按钮点击时触发虚拟按键事件交给后台代码响应。Designer 本身有模拟器可以先在 PC 上跑一遍界面效果确认布局没问题再烧到板子。不过模拟器不能替代真实硬件触摸、DMA 这些还是要上板验证。3.3 回到 IDE业务逻辑写在哪里才不会被覆盖TouchGFX 的代码分成 base 和具体实现两层。Designer 每次生成会覆盖view/base和generated下的文件。我的习惯是永远不改 base 文件需要写业务逻辑时改TouchGFX/App下的对应文件。例如温度传感器界面我打开TemperatureView.cpp在handleTickEvent()里获取传感器数据然后更新控件而不会去动TemperatureViewBase.cpp。数据从 MCU 侧传到 UI 侧官方 MVP 模型有点绕但用熟了很清晰。Model 层负责调用底层接口比如void Model::tick() { if (tickCount % 50 0) { uint16_t val readTemperature(); setTemperature(val); } }然后在TemperaturePresenter.cpp或TemperatureView.cpp里通过 listener 接收这个值。如果只是想先跑通也可以直接用全局变量加周期轮询但项目复杂后还是按 MVP 来否则状态管理会乱。4. 帧缓冲、双缓冲和内存占用这些优化直接决定流畅度一个 GUI 项目做完功能不难难的是让界面不掉帧、不闪烁、触摸操作跟手。我的经验是先计算内存再决定用什么缓冲策略最后才谈优化代码。4.1 FrameBuffer 放在哪内存占用怎么算帧缓冲是屏幕一帧图像在内存里的缓存区LTDC 会不断读取这块内存发送到 LCD。计算方式非常简单RGB565宽度 × 高度 × 2 字节ARGB8888宽度 × 高度 × 4 字节所以 1024x600 RGB565 单帧是 1,228,800 字节约 1.17MB双缓冲就是 2.34MB。如果 ARGB8888 双缓冲则要 4.59MB。H750 片内 RAM 不够因此必须把帧缓冲放在外部 SDRAM。TouchGFX 默认的帧缓冲地址在TouchGFX/Config配置里也可以通过 linker 或代码宏指定。我的做法是直接在配置头文件里把 FrameBuffer0 地址设为 0xC0000000FrameBuffer1 设为 0xC0130000两个地址错开 1.2MB。一旦改错地址屏幕会显示完全乱掉或白屏。4.2 DMA2D 和双缓冲到底怎么配TouchGFX 会自动调用 DMA2D 做颜色填充和图像搬运前提是 CubeMX 里开启了 DMA2D 外设。TouchGFX 的 HAL 层会优先使用 DMA2D所以你不用自己写 DMA 搬运函数。双缓冲的意义是一帧在后台渲染另一帧从 LTDC 发送到屏幕两个缓冲交替使用。这样可以避免屏幕刷新一半时被改动造成撕裂。在 TouchGFX Designer 的 Project Settings 里勾选Double buffering生成的TouchGFXHAL.cpp就会自动在 VSYNC 后切换缓冲。STM32H7 上开双缓冲后最容易踩的是 Cache 问题。CPU 写了 FrameBuffer数据可能还留在 D-Cache 里而 LTDC 直接读 SDRAM 时看不到 Cache结果就是屏幕出现“上一帧残留”或花块。解决办法是把帧缓冲所在 SDRAM 区域配置为 Non-cacheable或者在每次刷新帧缓冲后执行SCB_CleanDCache()。TouchGFX 的部分版本会在 HAL 里做 cache clean但我实测下来如果 MPU 配置不对还是会有偶发花屏。稳妥做法是在MPU_Config里给 0xC0000000 区域配置为 Device 或 Non-cacheable 属性。4.3 图片格式、字体资源和 Flash 空间H750 的片内 Flash 只有 128KB放一套完整中文字体和几张背景图就会爆。因此图片资源我都会在 Designer 里提前转换格式普通背景图从 PNG/JPEG 转成 RGB565不用 ARGB8888。简单图标尽量转成 L8 或 L4 索引色或者使用 alpha 通道的 RGB565A8。字体能不用通配符就不用只生成用到的中文字符可以大幅减少字体文件体积。如果项目资源量大建议把 TouchGFX 的资源放在外部 QSPI Flash通过 Board Configuration 里的 ResourceCache 配置。这样 UI 运行时有资源缓存机制不会一读出图就卡顿。我的仪表盘界面背景就放在外部 Flash启动时能看到加载过程但进入主界面后操作很流畅这是可以接受的。5. 实测中遇到的触摸失灵、白屏和卡死完整排查链路功能跑通后最大精力其实花在调稳定性和排错。我把自己遇到的几个典型问题整理成笔记后来发现这些问题在网上被反复问干脆写清楚。5.1 FT5x06 触摸中断和 FreeRTOS 任务怎么配合我在 CubeMX 里启用了 FreeRTOSGUI 刷新在 TouchGFX 任务里跑。触摸事件则靠 FT5x06 的 INT 引脚触发外部中断。最开始我把 EXTI 中断优先级设成了默认的 15结果触摸经常丢点。原因是 GUI 任务和系统其他中断把 CPU 占住等中断响应时触摸控制器里的数据已经被覆盖了。处理办法分两步在stm32h7xx_it.c的 EXTI 回调里只置一个标志位void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin TOUCH_INT_PIN) { touchIrqPending 1; } }在 TouchGFX 的 main loop 或任务循环里检测标志位然后通过 I2C 读取全部触摸点数据再喂给TouchGFXHAL::touchController()。不推荐在中断里直接读 I2CI2C 时序长、容易和屏幕刷新冲突。把中断优先级调到 5 以后丢点问题基本消失。5.2 白屏排查从电源到时钟到帧缓冲白屏是 TouchGFX 项目最常见的故障但原因往往不在一处。我总结了排查顺序每次遇到都按这个链路走测量 LCD 背光电压和电源确认背光亮。如果背光不亮先查背光驱动。用示波器看 LTDC 的 VSYNC、HSYNC、DOTCLK 是否有波形。没有波形多半是 LTDC 时钟没起来或配置没生效。回 CubeMX 重新检查 PLL3Q 和 LTDC 时钟源。有波形但屏幕全白那就是帧缓冲数据不对。用调试器停在HAL::DisplayDriver::displayInit()之后查看 FrameBuffer 地址的内容如果是 0xFF 或 0x00说明 SDRAM 没初始化或帧缓冲地址写错。确认 SDRAM 初始化在 TouchGFX 之前。FMC 初始化被 CubeMX 放在main前半段但如果你手动调整了初始化顺序很容易在touchgfx_init()时 SDRAM 还没工作。我自己的做法是不改动main()里生成顺序只把业务逻辑放到 TouchGFX 任务里。5.3 最顺手的几个调试工具SEGGER RTT可以实时打印触摸点坐标、帧率、内存占用比用串口快也不影响实时性。TouchGFX Simulator调试 UI 布局时可以脱离硬件但真机问题无法复现只能作为辅助。GPIO 翻转测时间在TouchGFXHAL::flushFrameBuffer()前后翻转一个 GPIO用示波器量出真正刷新花费的时间来判断是性能瓶颈还是参数配置问题。我当时就是靠 RTT 打印每一次tick()的耗时定位到字体渲染占了大头后来把大号中文字体改成预编译的小字库性能立刻上来。做这个项目到现在我最大的体会是STM32CubeMX 和 TouchGFX 不是简单的“一个配置、一个画 UI”它们的边界和配合方式决定了你后期改需求时是半小时完成还是加班三天。版本号别乱跳生成代码别乱改资源类型提前规划这几条做到后续所有问题都能顺着代码目录一路追下去。如果你正打算在 STM32 上做带触摸的图形界面建议先用这套流程跑一个最小工程把帧缓冲和触摸链路打通再开始画正式界面。