最近把TouchGFX在STM32F746 Discovery板上跑通了,整个过程比想象中顺,前提是找对路径。这次做的是“动手实验_1.1”,标题叫“使用STM32CubeMX快速移植TouchGFX——简化版本”。本着能自动化就不手写、能图形化就不翻寄存器的原则,我把整套流程整理成这篇文章。适合刚开始接触STM32图形界面开发、被各种分散教程绕晕的读者参考,也适合想快速在F746 Discovery板上看到实际界面效果的朋友照着手动一遍。
先说结论:STM32CubeMX负责把时钟树、SDRAM、LTDC这些又碎又容易错的底层配置用图形界面搞定,TouchGFX Designer负责把UI拖出来,最后IDE负责把两边代码捏在一起编译烧录。三步走,缺一不可,但也仅此三步。这块板子自带4.3寸480x272电容触摸屏,配合STM32F746的高主频和片上2D图形加速器DMA2D,跑TouchGFX可以说是教科书级别的标准平台。
1. 方案是怎么定的:为什么不是裸手撸驱动
1.1 这套组合到底解决了什么痛点
STM32F746 Discovery是一块非常适合跑GUI的板子:Cortex-M7内核、216MHz主频、320KB内部SRAM、板载8MB SDRAM、4.3寸RGB接口电容屏。硬件条件摆在那,但真正动手时会发现麻烦全在软件:RGB屏需要LTDC控制器初始化、图像帧缓冲要放在SDRAM里、触摸芯片要通过I2C读取坐标,这些外设交织在一起,手动写初始化代码很容易出错,而且排查起来非常痛苦。
STM32CubeMX的价值在于,它把这些外设的初始化代码全部生成出来,用户只要在图形界面里把参数填对,生成后代码直接可用。TouchGFX的价值在于,它把UI层抽象出来了,不用关心像素怎么填,只需要在Designer里拖控件、写交互,生成的代码会自动处理绘制、刷新、触摸事件分发。两者结合,硬件初始化和UI开发彻底解耦,这是简化版的核心理由。
我见过不少人在F746上跑图形界面,卡在LTDC时序参数上好几天的。说实话,如果是从零手写,显示这一关就劝退很多人。而用CubeMX生成后,LTDC的寄存器配置都是自动算好的,只要照着屏幕数据手册把宽高和消隐参数填进去,生成出来就能亮。
1.2 简化版和完整版的差别在哪
完整移植TouchGFX通常涉及修改HAL层、自写显示驱动、接触摸控制器、调帧缓冲策略,这些工作量大且需要比较深的嵌入式功底。简化版则把重点放在“跑通”上,具体做法是三不原则:不手写BSP、不改造TouchGFX底层、不做复杂的帧缓冲优化。
简化版依赖两条自动生成路径:一是STM32CubeMX生成的外设初始化代码,二是TouchGFX Designer生成的UI和HAL模板代码。我们需要做的只是把两边拼到一起,然后编译烧录。这样做的代价是代码体积不是最优、初始化流程可能有一些冗余,但换来的是极高的成功率和极低的入门门槛。对于实验性质的学习验证,这已经足够了。
2. 环境准备:软件版本和插件安装是第一个坑
2.1 三件套怎么下载和安装
做这个实验需要三个软件:STM32CubeMX、TouchGFX Designer、任意一款能编译STM32工程的IDE,我用的是STM32CubeIDE,也可以用Keil MDK。
STM32CubeMX直接去ST官网下载,安装过程没有特别要求,不过建议安装在默认路径,后面装插件时省得找目录。TouchGFX Designer同样在ST官网下载,它会要求一个Java运行环境,安装时如果提示找不到Java,先去装一个 OpenJDK 17 再装TouchGFX Designer,顺序不要反。
IDE方面,如果用STM32CubeIDE,它会自带编译工具链和烧录工具,和CubeMX配合最方便;如果用Keil,需要额外安装F7系列器件支持包。我这次用CubeIDE,因为CubeMX生成的工程可以直接用CubeIDE打开,少一步转换。
2.2 TouchGFX Generator插件:最容易卡住的地方
光装TouchGFX Designer还不够,STM32CubeMX要能生成TouchGFX相关代码,需要在CubeMX里安装TouchGFX Generator插件。我见过很多人卡在这一步,CubeMX的Additional Software里找不到TouchGFX选项,或者勾选了但生成工程时报错。
正确操作是:打开STM32CubeMX,进入Help -> Manage Embedded Software Packages,在STMicroelectronics分类下找到X-CUBE-TOUCHGFX,选中后安装。安装完成后,新建或打开工程,在左侧Categories的Additional Software里就会出现TouchGFX相关的软件组件,勾选它,生成代码时CubeMX就会把TouchGFX的工程骨架一并生成。
需要注意版本匹配:TouchGFX Designer、TouchGFX Generator和CubeMX三者之间有时会有兼容性问题,建议全部更新到最新版。老版本的CubeMX打开新TouchGFX工程时会提示版本过低,直接换个新版本就好,不用纠结。
3. STM32CubeMX配置详解:三步配置三块关键外设
3.1 时钟树:从25MHz外部晶振到216MHz系统主频
F746 Discovery板上有一颗25MHz的外部晶振,CubeMX新建工程后,在Clock Configuration页面里把HSE选为Crystal/Ceramic Resonator,然后配置PLL。我用的参数是:PLLM=25,PLLN=432,PLLP=2,这样VCO输入频率为1MHz,VCO输出432MHz,分频后得到216MHz系统时钟。AHB总线设216MHz,APB1设54MHz,APB2设108MHz。
这里有个容易忽略的点:LTDC的像素时钟不是直接来自系统主频,而是来自PLLSAI。在CubeMX的时钟配置界面里,把PLLSAI使能,配置PLLSAIN和PLLSAIR,让输出的像素时钟落在屏幕需要的范围内。我这边设的是约24MHz,实际根据屏幕数据手册的时序参数范围来定。像素时钟设太高会闪屏,设太低画面刷新慢,这个后面还会提到。
3.2 三个外设配置:SDRAM、LTDC、I2C触摸
先看SDRAM。Discovery板上SDRAM是16位数据宽度,挂在FMC的Bank1上。在CubeMX里使能FMC -> SDRAM,配置数据宽度16bit,行地址12位,列地址9位,CAS延迟3,刷新周期按64ms/4096行来算,约等于15.6us。这些参数在CubeMX里都有默认值,大部分情况下不用改,只要确认行列地址和芯片手册一致。
再看LTDC。使能LTDC后激活Layer1,分辨率设为480x272,像素格式选择RGB888,背景色设成黑色。时序参数我这里填的是:HSYNC=2,HBP=2,HFP=2,VSYNC=2,VBP=2,VFP=2,这些是常见的TFT屏参考值。如果你的屏幕参数不同,以屏幕数据手册为准。Layer1的起始坐标设(0,0),宽度高度跟屏幕一致,帧缓冲地址填SDRAM起始地址0xC0000000。
最后是触摸。F746 Discovery板上的触摸芯片是FT5336,挂在I2C3上,引脚对应PH7(SCL)和PH8(SDA),工作在Fast Mode 400kHz。FT5336的I2C地址,7位地址是0x70,换算成8位读写地址就是0x38,后面写触摸驱动时会用到。触摸中断引脚是PI13,配置成外部中断模式,用于触摸事件通知。
3.3 工程属性设置:记得勾选TouchGFX组件
外设配置完毕后,进入Project Manager。工程名随意,工具链选STM32CubeIDE或者Keil。关键一步在Additional Software里确认TouchGFX组件已经勾选,并且选择了合适的版本。生成代码前,还要在Project Manager的Code Generator里勾选“Generate peripheral initialization as a pair of .c/.h files per peripheral”,这样每个外设单独一套文件,后期定位问题方便。
生成代码后,打开工程目录会发现多了一个TouchGFX文件夹,里面是初级的TouchGFX工程骨架。如果这一步没有生成,大概率是TouchGFX Generator插件没装好,回到2.2节排查。
4. TouchGFX Designer:把UI从零拖出来
4.1 建立一个能与CubeMX工程对接的TouchGFX工程
打开TouchGFX Designer,选择新建Application,模板选空模板,名称、路径自己定,关键是分辨率要设置成480x272,颜色格式选RGB888。建立后会生成一个完整的TouchGFX工程,里面代码结构和CubeMX生成的那个TouchGFX骨架是对应的。
如果CubeMX生成了TouchGFX组件,更推荐的对接方式是用TouchGFX Designer直接打开CubeMX生成的.ioc文件,Designer会识别其中的TouchGFX配置并建立关联。这样UI代码和硬件代码是同一个工程体系里维护的,后续CubeMX改配置再生成,不会把UI代码冲掉。简化版实验里,我用的是这种方式,省去后续手动合并代码的麻烦。
4.2 一个最简单的可交互界面
UI设计我建议从这两个控件开始:一个文本控件和一个按钮控件。文本控件用来显示运行状态,按钮控件用来验证触摸交互。
具体操作是在Designer左侧控件库里拖一个Box到画布上,设置背景颜色,再拖一个TextArea放进来,输入初始文本,字体暂时用默认字体就行。然后拖一个Button,设置好点击状态和释放状态的图片资源。Designer会自动维护控件之间的层级关系,不需要写任何代码。
在Designer的Interactions面板里,给按钮添加一个交互:点击时切换到另一帧屏幕,或者触发文本内容变化。这个交互逻辑Designer会生成对应的C++代码,我们要做的只是设定触发条件和响应动作。
4.3 用模拟器快速验证UI逻辑
TouchGFX Designer自带PC模拟器,可以在生成板级工程之前,先在电脑上运行UI,验证布局、字体、交互逻辑有没有问题。模拟器的运行速度比实际硬件快很多,效果也比较接近,因为TouchGFX的渲染引擎在PC上是用SDL模拟的。
模拟器验证通过后再生成代码。这一步很强地帮助排除了UI层的低级问题,比如控件叠放顺序错了、文本显示不全、按钮点击热区不对。等代码烧到板子上之后,只要显示正常,基本可以断定问题出在硬件初始化而不是UI代码。
5. 代码集成:让硬件初始化和UI逻辑握手
5.1 理解TouchGFX工程的目录结构
集成之前先理解一下TouchGFX工程的目录,不然会迷失在文件堆里。TouchGFX根目录下有几个关键子目录:gui目录存放界面UI相关的C++代码,包括每个Screen的View和Presenter;generated目录存放Designer生成的代码,包括字体、图片、文本资源的定义和初始化;target目录存放平台相关代码,包括HAL初始化、显示驱动、触摸驱动。
CubeMX生成的TouchGFX骨架一般会自动包含target部分的基础文件,但触摸驱动这类的文件往往需要从示例工程里补充。如果发现target目录下没有触摸相关代码,去ST官方TouchGFX示例工程里找到F746 Discovery对应的模板文件,拷贝进来即可。这个目录结构理解透了,后面遇到编译报错或者找不到函数定义的问题,定位起来会快很多。
5.2 main.c要改动的两个点
CubeMX生成工程后,main.c里已经调用了MX_TouchGFX_Init(),这是TouchGFX的初始化入口,函数名可能因版本略有不同,但通常都在main函数外设初始化部分被自动调用了。如果没有被调用,需要手动加上,并且包含TouchGFX/touchgfx.h头文件。
第二个点是主循环。在main函数的while(1)里,确保MX_TouchGFX_Process()被持续调用。这个函数负责TouchGFX的任务调度、触摸事件处理和界面刷新,必须放在主循环里高频调用。有人说TouchGFX需要跑在RTOS上,其实简化版跑裸机完全没问题,只要主循环里没有长时间阻塞的代码就行。我个人习惯是裸机验证完功能后再考虑上RTOS。
这里有一个很关键的注意点:TouchGFX的代码是C++写的,而CubeMX生成的main.c是C文件。如果手动调用TouchGFX函数时遇到链接错误,通常是语言混编的问题。CubeMX生成的TouchGFX接口通常会做extern "C"导出处理,但如果自己写的C++文件需要被C文件调用,记得在头文件里加extern "C"声明。
5.3 编译烧录:一次成功的小技巧
用CubeIDE打开工程后,直接编译。第一次编译耗时比较长,因为TouchGFX的库和生成代码需要全量编译。如果编译报错,优先检查错误提示里的文件路径是否指向TouchGFX目录,很多问题都出在代码生成不完整,回到CubeMX重新生成一次就好。
烧录用的是Discovery板载ST-LINK,USB线接上后CubeIDE里直接Run即可。烧录完成后如果看到屏幕上出现UI界面,触摸有反应,这次移植就算成功了。如果屏幕没显示,先别急着改代码,按下面第6节的排查思路走一遍,通常能快速定位。
6. 现场排查:显示、触摸、编译三类问题的实战记录
6.1 黑屏和花屏,先从时序和帧缓冲查起
黑屏最常见的原因有三个:LTDC像素时钟没配好、SDRAM初始化失败、帧缓冲地址不对。
判断方法很简单,把帧缓冲地址打印出来,对比代码中传给LTDC的地址是否一致。SDRAM初始化失败的话,屏幕通常会保持全黑或者花点,通过调试器查看SDRAM的数据能不能正确读写就可以确认。
花屏的话,重点是像素时钟和RGB格式。像素时钟太高会导致画面闪烁或撕裂,太低则显示刷新慢、有拖影。调整时钟配置里PLLSAIR分频系数,多试几个值,直到画面稳定。另一个隐蔽问题是颜色格式不匹配,CubeMX的LTDC里选了RGB888,TouchGFX工程的颜色格式也必须选RGB888,两者不一致会让画面颜色完全错乱。
6.2 触摸没反应,八成是地址或驱动问题
触摸问题一般分两种:完全没响应和响应不准确。完全没响应优先查I2C通信是否正常,用调试器读FT5336的设备ID寄存器,读不到就查I2C引脚配置、地址是否正确。这里容易混的是7位地址和8位地址,驱动里如果直接调用HAL的I2C接口,要注意把7位地址0x70转换成8位地址0x38。
响应不准确,比如点了左上角但触摸点跑到右下角,通常是坐标系没做翻转或者触摸坐标和屏幕坐标的映射关系不对。TouchGFX的HAL层里有触摸坐标转换的接口,检查触摸驱动上报的坐标是否经过了正确的旋转和缩放。
触摸中断的配置也很关键。如果触摸中断引脚没配置成外部中断,或者中断服务函数没有正确调用TouchGFX的输入处理接口,就会出现触摸事件丢失的情况,表现就是时不时没响应。
6.3 编译链接报错,集中在TouchGFX集成上
编译报错常见的有三种:找不到TouchGFX头文件、找不到函数定义、C和C++混编问题。找不到头文件是因为include路径没包含TouchGFX工程的目录,在IDE的编译设置里把TouchGFX根目录加进include path即可。找不到函数定义,多半是TouchGFX生成代码不完整,或者链接时没有把TouchGFX源文件编译进去,检查工程文件里是否包含了gui、generated、target这些目录下的cpp文件。
C和C++混编问题前面提到了,解决方式就是在C文件调用的C++函数声明处加extern "C"。如果你完全按照CubeMX的自动生成流程走,通常不会遇到这个问题;如果你自己手改了main.c或者新增了文件,就很可能踩到。
7. 一些个人经验和扩展方向
这次简化版实验跑通之后,我对TouchGFX的底层的运行机制有了更直观的理解。有几件事如果重做一次,我会直接注意:第一,LTDC时序参数一定要从屏幕数据手册里抄,不要凭感觉填;第二,SDRAM和LTDC的初始化顺序不能乱,SDRAM必须先行,因为LTDC要用的帧缓冲就在SDRAM里;第三,TouchGFX Designer的模拟器是个宝,先模拟后上板能省大量调试时间。
再分享一个我后来的优化小技巧:给LTDC配置多缓冲。TouchGFX支持双缓冲甚至三缓冲,配合DMA2D可以在渲染和显示之间重叠,帧率有明显提升,而且CubeMX生成的工程里已经预留了相关配置接口,改起来不难。简化版第一步用单缓冲跑通,认真做产品时再开多缓冲。
这个实验做完之后,后面可以继续往几个方向扩展:一是把自定义字体加进去,尤其是中文字体需要做子集化处理;二是接入FreeRTOS,把TouchGFX放在独立任务里,主任务做业务逻辑;三是针对Discovery板上的其他资源做界面,比如显示传感器数据、动画效果等等。这些都是建立在这次跑通的基础上的。
最后说句实在话,用STM32CubeMX加TouchGFX这套组合,最大的价值在于把嵌入式图形界面开发的门槛拉低了很多。我最早学GUI开发时,光一个显示驱动就能折腾两周。现在工具链成熟了,F746 Discovery这种板子又有现成模板,老老实实按照本文的流程走,基本一个晚上就能看到自己设计的界面跑起来。