ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32F746移植TouchGFX实战:从LTDC配置到RGB屏界面流畅运行

STM32F746移植TouchGFX实战:从LTDC配置到RGB屏界面流畅运行

做嵌入式的人大概都有一段时间特别“怕做界面”。裸机点灯、串口打印这些还好说,一接到“给设备加个显示”的需求,经常连续熬夜调参数:画一条直线要想抗锯齿,显示中文得先找字库,换几页菜单就得重新设计刷新逻辑。我是在给微雪Open746开发板移植TouchGFX之后,才真正觉得STM32上做GUI这件事可以变得很轻松。这块板子用的主控是STM32F746IGT6,CM7内核跑到216MHz,再加上板载SDRAM和RGB接口电容屏,可以说是为TouchGFX量身准备的硬件。这篇文章就把我从CubeMX配置、LTDC时序、SDRAM帧缓冲、触摸芯片接入,到TouchGFX Designer出页面、最后在板上跑通的完整过程写出来,目的很明确:让手里有Open746或者类似F746板子的朋友,少走几个我踩过的坑。

1. 先看清这块板子:Open746凭什么适合跑TouchGFX

1.1 Open746的硬件底子

微雪Open746的核心是STM32F746IGT6,这颗料天生就是干显示这活的。Cortex-M7内核,主频最高216MHz,片上带1MB Flash和320KB的SRAM。320KB的RAM听起来不算小,但如果在800x480的屏上开一个ARGB8888的帧缓冲,一次就要吃掉大概1.5MB,单靠片上RAM肯定不够。所以Open746板载了一颗外部SDRAM,常见型号是华邦的W9825G6KH,容量为32MB,正好解决帧缓冲的内存问题。板上还引出了RGB接口的LCD排座,配套使用的是4.3寸、800x480的电容触摸屏,触摸控制器走I2C接口。

F746这颗芯片最值得关注的是内部集成了LTDC控制器和DMA2D图形加速器。LTDC的作用是不断从指定的显存地址读取像素数据,按照面板的时序信号把数据推到LCD排线上,整个过程由硬件DMA完成,CPU只需要保证显存里面的内容正确。对比老式的8080并口屏,MCU省下的时间相当可观。所以只要在SDRAM里放好几张帧缓冲,再告诉LTDC这些缓冲的基地址,屏幕就能自己刷新起来,CPU就可以腾出手去跑业务逻辑。

1.2 为什么选TouchGFX而不是继续用老办法

很多朋友会问,STM32上做界面的方案不是挺多吗,为什么偏偏要折腾TouchGFX?我之前也用过一些轻量级GUI库,说实话,简单的菜单页面它们完全能搞定,但要做到漂亮的动效、平滑的滑动切换、复杂的自定义控件,写起来就很吃力。TouchGFX的优势在于它跟F746的硬件配合得很紧,底层直接利用了LTDC和DMA2D,做透明叠加、混合效果都不需要CPU去算像素。而且它的PC工具TouchGFX Designer是可视化拖拽界面,写完界面代码由工具生成,你只需要关心页面逻辑,不用维护一堆坐标计算。

另外要提一个很多人在意的事:ST整合TouchGFX之后,这个框架已经对STM32用户免费开放,不需要担心版权费用问题。对我这种习惯先跑通再深入的人来说,把TouchGFX跑在Open746上还有一个大好处——它生成的项目结构很清晰,UI代码和应用代码分层,后面做工程维护比全手写绘图逻辑要舒服得多。我的结论很直接:如果你手里有一块带SDRAM和RGB屏的F7系列板子,第一选择就应该是TouchGFX。

2. 移植前的准备工作:工具链和基础工程

2.1 三个软件怎么配才不打架

工欲善其事,必先利其器。做TouchGFX移植,我前后会用到三个工具:STM32CubeMX负责生成底层工程和外设初始化,TouchGFX Designer负责设计界面并生成UI代码,编译环境我习惯用STM32CubeIDE。如果偏好其他IDE也没关系,IAR和Keil同样能编译TouchGFX工程,只是工程文件结构略有不同。

工具版本需要注意一个点:TouchGFX Designer和CubeMX的集成度是慢慢变高的,新版CubeMX可以直接在中间件列表里找到TouchGFX Generator。我建议装新一点的版本,比如TouchGFX Designer活动维护版本配合对应的CubeMX版本,能省去很多手动搬文件的麻烦。装好之后,CubeMX界的New Project向导里会多出TouchGFX相关选项,这就是两者打通了。

我见过有人卡在“CubeMX生成不了TouchGFX代码”上,多半原因是两个软件的版本对不上。最省事的做法是打开CubeMX时先别急着建工程,检查一下它识别到的TouchGFX路径是否正确,也就是TouchGFX Designer的安装目录。路径里尽量不要带中文或空格,老话常谈,但真的有人因为这个编译不过。

2.2 CubeMX把外设和时钟搭好

新建工程时需要选择正确的芯片型号,也就是STM32F746IGT6,软件会自动匹配引脚定义。一个比较关键的地方是时钟树,Open746板载晶振的值需要跟硬件对应,F746最高主频216MHz,CubeMX里配置成最高频率即可,TouchGFX对性能的要求不低,建议把主频拉满。

接下来要开启的外设主要是这几个:LTDC用于控制显示接口,FMC用于访问外部SDRAM,I2C用于读触摸屏,另外还要配置一个串口,后面调试打印信息会用到。从CubeMX的角度讲,外设的初始化代码生成非常自动化,你只需要在图形界面上把参数填对,点击生成代码,它就帮你把这些寄存器的初始化全部写好。这一步骤做到底,就为正式移植打好了底子,后面出错的可能性会小很多。

这里有一个细节建议:在CubeMX的引脚分配界面里,先把LCD和触摸相关的引脚都翻开看一下,确认它们没有被复用成其他功能。比如RGB接口的很多引脚的默认复用功能可能就是GPIO,你需要手动改成LTDC的复用设置。微雪Open746的引脚布局在板子手册里会给一个清晰的对应关系,照着改就行。不要嫌这一步啰嗦,引脚冲突是后面白屏和触摸无响应最常见的根源之一。

3. 关键配置逐个过:LTDC、SDRAM、触摸和系统任务

3.1 LTDC时序:屏幕白屏还是花屏全看它

LTDC的配置是我觉得整个移植过程中最需要耐心的部分。你要理解它本质上是一套“时钟产生器”,它负责产生LCD面板需要的HSYNC、VSYNC、DE和像素时钟信号。每个屏都有自己的时序参数,而且这些参数通常写在屏幕的数据手册里,大多数4.3寸800x480的RGB屏参数是比较接近的,但千万不能拍脑袋乱填。

纯手工移植的老玩家肯定看过很多教程都贴过这么一组经典参数:水平同步脉冲宽度1、水平后肩46、水平前肩210、垂直同步脉冲宽度1、垂直后肩23、垂直前肩22,像素时钟大约33.3MHz。这个参数在很多4.3寸屏幕上是通用的,可以直接作为起点。如果把前肩和后肩填错,屏幕上通常会出现明显的偏移或者显示花屏;如果像素时钟差得太远,屏幕亮度会异常或者干脆白屏。

在CubeMX的LTDC配置界面里,把这几个值填进去之后,还需要启用Layer1图层。注意图层要设置正确的窗口大小,也就是800x480,并且要把像素格式跟后面TouchGFX里选择的格式对应起来。F746的LTDC支持很多种像素格式,比如RGB565、RGB888、ARGB8888,格式选错会在屏幕上出现颜色严重失真、每像素错位的现象,这类问题定位起来比花屏还要恼火。

3.2 SDRAM分配:帧缓冲放哪、放几份

TouchGFX的帧缓冲如果放在内部SRAM里,800x480的分辨率根本放不下。正常做法是把帧缓冲放到外部的SDRAM中,SDRAM的基地址通常从0xC0000000开始。在CubeMX里把FMC配置成连接SDRAM的方式,然后确认访问时序能让存储器稳定工作,这一步一般要做个简单的写读测试,确保SDRAM能正确读写才往下继续。

帧缓冲数量的选择很有讲究。最保守的方案是用单帧缓冲,占内存最少,但画面刷新过程中如果LTDC正在读上半屏,CPU或DMA2D已经开始写下半屏,就可能看到撕裂效果。我的建议是至少开双缓冲,也就是准备两份同样大小的帧缓冲,TouchGFX在其中一块上绘制,另一块正在被LTDC扫描显示,当前者绘制完毕后再进行交换,这样画面会稳定很多。

算一下内存需求其实很简单:800x480的屏幕,用ARGB8888格式时每个像素占4字节,单帧缓冲约1.54MB,双缓冲约3MB;用RGB565时每像素占2字节,双缓冲也就约1.5MB。Open746上的32MB SDRAM完全扛得住。我在工程里是这样安排的:SDRAM前几个MB留给帧缓冲,后面部分留给其他用途,比如缓存图片资源或者做堆。这样布局清晰,后面排查内存覆盖问题会方便很多。

3.3 触摸芯片接入:把触摸数据送进TouchGFX

Open746配套的电容触摸屏,常见的触摸控制器有汇顶的GT9xx系列,也有FT5x06系列,具体可以看板子上丝印。以GT9xx为例,它通过I2C跟主控通信,地址通常由复位引脚的电平决定,常见的有0x5D和0x29两个地址。读触摸数据的大致过程是:先读控制寄存器,判断有没有触摸事件,然后读出触摸点的坐标值。GT9xx内部有一块映射寄存器区,坐标数据就存在特定的偏移位置,顺着规格书查就行。

TouchGFX对触摸层的接口定义得很简单,在工程里会有一个STM32TouchController.cpp文件,里面有个sampleTouch函数,TouchGFX的框架会在需要时反复调用它。你只需要在这个函数里把触摸坐标读出来,写入x和y两个引用参数,返回true表示有触点。也就是说,从硬件的I2C数据到TouchGFX的触摸事件,中间就差这么一小段驱动代码。

很多第一次接触这个接口的人会疑惑坐标方向的问题。触摸芯片输出的坐标通常是原始坐标,而触屏在板子上的安装方向可能跟屏的扫描方向不一致。遇到这种情况,最简单的办法就是在sampleTouch里对坐标做一次坐标变换,比如交换X和Y,或者做x = 800 - x这样的反转。先用一段串口打印代码把原始坐标打印出来,判断方向后改变换公式,是最快的调试思路。

3.4 把TouchGFX挂进FreeRTOS任务

如果CubeMX工程里用了FreeRTOS,TouchGFX推荐跑在一个独立的任务里,而不是放在主循环裸跑。原因很简单:TouchGFX内部有一套事件循环机制,它负责处理触摸事件、定时器、动画刷新等,如果跟业务逻辑挤在同一个主循环里,很容易互相卡顿。把它放进独立的FreeRTOS任务,UI线程和其它业务线程就能各跑各的。

CubeMX的TouchGFX Generator可以帮你自动生成这个任务,你只需要在中间件配置里打开TouchGFX,再设置好显示分辨率和像素格式,生成代码后它会自动创建一个名叫TouchGFX_Task的线程。任务函数内部调用核心的touchgfxRun()函数,这个函数正常情况下不会返回,而是不断地处理触摸事件和渲染请求。

单独把UI放一个任务还不够,还要注意优先级和栈大小。TouchGFX的绘制过程涉及大量函数调用和浮点运算,任务栈要给足,给太少会直接进HardFault。我在调试的时候遇到过几次非常隐蔽的进硬件错误异常问题,检查完内存访问、时钟配置都没问题,最后发现是任务栈爆了,这个教训很深刻。

4. 正式移植流程:从Designer到板上跑通

4.1 用TouchGFX Designer搭第一个页面

硬件底层配置基本就绪后,重心转移到TouchGFX Designer这边。启动后可以新建一个空项目,选择对应的芯片型号和显示参数,设计器会根据配置生成一个包含基础工程结构的.touchgfx文件。设计器的界面是非常友好的,左边是控件库,中间是画布,右边是属性和交互面板。我会建议先从最简单的文本和按钮开始,不要一上来就堆复杂的图表和动画,先把跑通流程走完。

在设计器里拖一个背景图片和按钮,再给按钮添加一个跳转页面的交互动作,这些操作完全是图形化的。完成后点击生成代码,设计器会把GUI相关的源码输出到工程指定目录下。如果你是把设计器工程跟CubeMX工程放在同一个文件夹管理,生成的代码会被整合到CubeMX工程里。注意TouchGFX Designer生成的是C++代码,底层驱动继续使用C代码,这种混合架构对嵌入式工程来说是很常见的。

4.2 工程合并和链接器配置

使用TouchGFX Generator时,CubeMX工程会自动引用TouchGFX目录下的代码,而手动移植方式则需要自己处理文件拷贝。我建议能自动集成的话尽量用自动方式,能把工作量减少一大部分。但无论自动还是手动,有一个地方必须亲自动手,那就是链接脚本。TouchGFX生成的帧缓冲通常定义在SDRAM地址段,你要在链接脚本里声明一个SDRAM区域,并且给帧缓冲分配一个合适的起始地址。

链接器的写法取决于你用的IDE,我用的STM32CubeIDE是GCC链接脚本,里面需要增加类似这样一段:

/* 声明外部SDRAM区域 */ SDRAM (xrw) : ORIGIN = 0xC0000000, LENGTH = 32M

然后确保工程链接的符号把帧缓冲数组放到这个区域里。TouchGFX通常在TouchGFXHAL里配置帧缓冲地址,你可以直接在配置头文件里指定一个具体的SDRAM地址,比如0xC0000000,也可以让链接器把一个大数组分配到.sdram段再传入地址。两种方式都行,第二种对内存布局管理更清晰,推荐用它。

如果链接脚本没有正确include SDRAM区域,编译出来的程序运行时即使能启动,也会在初始化SDRAM帧缓冲期间访问非法地址,表现就是死机。另外,链接脚本中SDRAM区域的起始地址一定不能跟其它用途的内存段冲突,这点在你手动规划内存时要有全局意识。

4.3 编译烧录后第一次点亮

一切配置完成后就可以尝试第一次编译了。第一次编译大概率会有报错,常见的有函数未定义、头文件找不到、C++链接不兼容提示等。不要慌,先把报错信息逐条看一遍。绝大多数“未定义引用”都指向触摸驱动或者显示驱动的某个函数没实现,你可以先写一个返回空的实现来占位,确认编译链接通过后再填充功能。

烧录之后第一次上电,如果一切正常,屏幕会先显示TouchGFX的默认启动界面。如果屏幕没有显示任何东西,先把LTDC相关初始化代码打印日志确认它有没有执行,再检查LCD排线是否接好,排除硬件问题。在这个过程中我强烈建议串口打印要提前做好,因为屏幕本身不亮的时候,你唯一的排查窗口就是串口。

当屏幕上出现默认页面后,第一件事是确认触摸是否可用。用手指点在屏幕上,如果UI响应了按钮按下效果,说明触摸链路已经打通。如果触摸没反应,回到触摸驱动上排查,先确认I2C通信正常,再确认中断引脚和复位引脚的配置。第一次触摸能跑通,整个移植工程的骨架就算是立住了。

5. 翻车现场与排查速查表

5.1 白屏、花屏和条纹

我在调试过程中被LTDC坑的次数最多,白屏问题基本可以按这个优先级排查。第一,确认LTDC时钟是否使能,像素时钟是否产生。用示波器量LCD接口上的像素时钟是最直接的办法,没有示波器也可以在代码里打印相关寄存器的值来确认。第二,确认时序参数,尤其要核对同步脉冲、前肩和后肩。第三个常见原因是图层没有使能或者图层默认透明色被设成了全透明,导致LTDC反复扫描但整层都没有有效像素输出。

花屏通常跟像素格式不匹配有关。你在一帧像素数据里按照RGB565写入,但LTDC却按照ARGB8888去解释,那么每个点的颜色必然错乱。还有一个原因是屏幕数据线和F746的引脚顺序接得不对,这种纯硬件问题在整板底板上一般不会出现,但如果你自己做的底板,就要拿起放大镜检查PCB走线对照。

屏幕上出现横向条纹,很多情况下是SDRAM带宽不够或者是刷新过程中帧缓冲被同时访问。把像素时钟稍微降低一点测试,如果条纹减轻,就基本确定是带宽问题。另外开启双缓冲也能改善这种撕裂感。实在不行就上RGB565,内存带宽压力小很多,肉眼对这种格式下的颜色损失是不敏感的。

5.2 触摸漂移、方向不对

触摸能用但点击位置跟手指的位置偏了,这种问题通常不是硬件坏了,而是坐标映射没做好。触摸芯片直接读到的坐标是它在触摸面板物理坐标系下的值,屏幕显示有它自己的坐标方向,二者如果不一致,就会出现点击A点响应在B点的现象。我调试的时候会专门做一个测试页面,在四个角和中心各放一个按钮,点击时观察实际响应按钮在哪个位置,这样判断方向偏差非常直观。

方向修正完成后,如果还存在整体偏移,比如总是偏左上,通常是坐标范围没有做线性映射。把读出的X和Y按照触摸面板的最大值和屏幕分辨率做一次线性变换,基本上就能解决。这个过程中最好把原始坐标打印出来,总结规律后再写映射代码。触摸的灵敏度如果偏弱,可以检查触摸控制器初始化时是否配置了合适的增益和阈值,这些寄存器参数一般在触摸芯片的初始化序列里可以调整。

还有一种更隐蔽的情况:触摸能上报,但是点击后界面卡死。原因往往是触摸中断引脚没有正确接入,或者驱动在等待触摸状态时陷入死循环。建议把触摸流程做成非阻塞的读取方式,始终给底层的触摸检测留有超时退出,避免硬件异常时把整个UI任务挂死。

5.3 帧率拖沓怎么办

如果点击按钮后动画的过渡不够流畅,第一件事是确认芯片的I-Cache和D-Cache是否开启了。Cortex-M7有很强的Cache能力,我见过很多工程因为没开Cache,TouchGFX运行性能至少损失三成。打开Cache的代码很简单,CubeMX甚至可以在系统初始化阶段自动生成,务必确认它已经执行。

第二个方向是看CPU主频,Open746是能到216MHz的,如果你把系统时钟配置成180MHz甚至更低,都会影响渲染速度。在主频和Cache都正常的前提下,帧率依然不理想,就要考虑像素格式了。ARGB8888确实漂亮,但渲染和搬运的开销大,如果不是特别需要透明混合效果,换成RGB565能让流畅度上一个台阶。

还有一个容易忽略的点就是DMA2D的使用。TouchGFX在内部会尽量把图片混合、拷贝这类操作交给DMA2D去处理,但前提是你要在TouchGFX的配置里使能DMA通道。配置正确后,你可以从日志里看到大量绘制任务被承接给了DMA2D,CPU负载明显降低,刷新效果也会更平滑。

写在最后的经验

整套移植流程走下来,我最想跟大家分享的是:TouchGFX移植这件事,真正难的不是框架本身,而是对目标板硬件细节的把握。LTDC时序、SDRAM初始化、触摸坐标映射,每一个都是独立的“坑点”,但它们组合在一起时,就变成了整个GUI能否跑起来的关键链路。我个人的习惯是每完成一个环节就在串口打一条明确日志,从时钟配置、SDRAM自检到触摸事件上报,层层递进地确认,而不是等屏幕上出现结果了再去猜。这样就算出问题,也能快速定位到具体是哪一环断了。如果你也在Open746上移植TouchGFX,大可不必被那些参数吓住,按顺序一步步搭下来,最后看到自己拖出来的界面在屏幕上一帧一帧流畅运行,那种成就感是值得的。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进