公司动态
野火STM32F407开发板TouchGFX移植实战:从底层驱动到界面流畅运行
简介面向嵌入式图形界面开发者的STM32F429适配TouchGFX完整工程包以野火开发板为载体演示如何将TouchGFX图形库移植到Cortex-M4平台并成功运行动态3D dome示例解决复杂UI在资源受限MCU上的落地问题。压缩包12.83MB共759个文件以C/C源头文件为主h/hpp/c/cpp约600余个另含TouchGFX核心库、lib/a静态库、PNG资源及工程配置文件类型齐全便于直接导入Keil等IDE编译调试。已有4307人学习下载人气较旺。资源价值在于提供了一套从STM32CubeMX配置、LCD/触摸屏驱动适配到UI工程整合、编译烧录的完整可运行工程附带的TouchGFX Designer生成代码和预编译库可帮助开发者快速验证图形渲染效果并迁移到自己的STM32F4项目缩短开发周期。1. 项目背景与适配思路1.1 为什么要在野火F4上跑TouchGFX嵌入式GUI开发这些年变化太快了。早几年大家在STM32F4上做界面主流方案还是裸机ucGUI或者直接怼LTDC寄存器刷屏做出来的东西要么颗粒感重要么开发效率低得让人想砸板子。后来TouchGFX被ST收购并免费开放之后局面一下子就不一样了——它能在资源有限的MCU上跑出接近手机App的动画效果界面设计用拖拽方式完成代码自动生成开发效率直线上升。不过TouchGFX官方提供的大多是自家评估板的工程直接烧到野火STM32F4开发板上是跑不起来的。原因很简单TouchGFX的硬件抽象层跟板子的LCD型号、触摸IC、引脚分配、时钟树强相关不同板子的LTD C引脚映射和显存配置都不一样。我这里用野火F407开发板指南者系列做适配核心工作就是把TouchGFX的上层框架接到野火板自己的底层驱动上让两边能正常协作。这个适配方案适合谁手上刚好有野火STM32F4开发板、想学TouchGFX但不想再买一块官方评估板的人或者已经在用野火板做产品原型、需要快速加一套好看界面的开发者。这篇文章我会把移植过程中的关键步骤、踩过的坑、排错的思路全部整理出来照着操作基本能跑通。1.2 适配方案选型的思考做TouchGFX适配摆在面前的有两条路。第一条是直接用TouchGFX Designer生成一个空工程然后把野火官方的LCD驱动、触摸驱动移植进去改掉底层接口。这条路优点是干净所有代码结构都是TouchGFX原生的后期升级组件版本不容易出兼容问题缺点是移植工作量大你得把野火那边HAL库或者标准库的驱动代码理解透然后重新封装成TouchGFX需要的接口格式。第二条路是在野火官方提供的LCD驱动例程基础上加入TouchGFX的库文件和框架代码把一个简单的演示界面嵌进去。这条路的好处是硬件层完全不用操心毕竟野火例程已经把RGB屏幕的初始化、背光控制、触摸读取都调通了你只需要在中间层做适配缺点是工程结构会有点“嫁接”感两个BSP体系混在一起如果对代码组织不熟容易混乱。我自己最后选了第二条路。原因很直接我手头这块野火板子用了挺久底层BSP很稳定重新造轮子不划算。适配的本质是让TouchGFX能正确驱动这块屏它的核心就是两个接口——HAL::flushFrameBuffer()负责把显存刷到LCD上触摸部分通过Application::setTouchSampleRate()和touch controller回调把坐标送进框架。只要这两条链路通了界面跑起来是没问题的。2. 环境准备与前置配置2.1 工具链组装先把需要用到的工具列出来版本很关键用错版本容易卡在奇怪的地方Keil MDK 5.27以上我用的5.31ARMCC v5编译器TouchGFX对AC5支持最稳STM32CubeMX 6.x用来确认引脚配置和时钟树不是强制但建议装TouchGFX Designer 4.16以上我用的4.18ST官网免费下载需要注册账号野火F407指南者开发板一块板载RGB接口4.3寸电容屏分辨率800x480或480x272我这里是480x272ST-Link V2或者野火DAP下载器一个容易被忽略的点是显示器的驱动IC。野火的4.3寸屏用的是SSD1963或者RA8876这类控制器而TouchGFX的底层刷新逻辑默认针对内置LTDC控制器的STM32系列设计的。也就是说TouchGFX并不直接关心你的屏幕是什么型号它只管往指定的显存地址写像素数据真正把数据送给屏幕的工作由你提供的函数完成。这一点是能否顺利适配的关键认知。2.2 获取官方组件与工程模板TouchGFX Designer安装完之后在它的安装目录下能找到Middleware文件夹里面是STMicroelectronics提供的ST芯片适配包。我们需要用到的核心文件:TouchGFX核心库的头文件和源码版本V4.18.0ST平台相关的HAL模板代码野火官方资料包里的LCD驱动例程我建议直接用“5-LCD显示”例程做底子因为它已经包含了完整的RGB屏初始化、背光控制和画点函数工程结构也清晰不需要从零调屏。在开始动代码之前还有一件重要的事确认板子上触摸的接口类型。野火F4指南者板的触摸屏通常是通过I2C接口连接FT5x06或者GT9xx系列触摸控制器的TouchGFX自带的STM32TouchController模板默认使用的是FMC总线扩展IO的触摸方案所以这块需要重点改。提示软件版本别一味求新。TouchGFX 4.18配Keil AC5是我验证过的稳定组合用AC6编译器会有内存对齐的警告虽然能通过但界面刷新偶尔有残影排查起来麻烦。3. 核心移植过程解析3.1 移植文件结构与关键函数我把整个适配分成三个层次这样脑子里有个清晰的地图再动手不会迷失方向第一层是BSP层这层直接用野火的驱动代码管LCD初始化、点灯画点、触摸读坐标。这一层与TouchGFX完全无关它是基石。第二层是HAL桥接层这层是移植的核心工作区。需要实现的接口包括void touchgfx::HAL::flushFrameBuffer()实现把TouchGFX的帧缓冲framebuffer内容整体搬运到LCD显示。void touchgfx::HAL::setTFTAddress(uint32_t addr)告诉HAL当前帧缓冲的地址这个地址会被LTDC或外部DMA从SDRAM/内部SRAM读取并显示。void TouchGFXHAL::initialize()初始化HAL相关的内部状态。第三层是TouchGFX核心层包括图形库、字体引擎、控件渲染这些由Designer生成不需要动。野火指南者的F407板子板载了IS42S16400J1M x 16bit x 4banks 8MBSDRAM但TouchGFX默认的模板工程对F4系列通常把帧缓冲放在内部SRAM中因为F407没有FMC接口直接映射的SDRAM在HAL里并不好直接跟DMA2D配合。我这里更推荐一个稳妥的做法——把帧缓冲放到内部SRAM。480x272分辨率24位色深RGB888每个像素3字节一帧大小是480×272×3 391,680字节 ≈ 383KB。F407的内部SRAM总大小是192KB单帧都放不下所以必须用RGB565色深每个像素2字节一帧是480×272×2 261,120字节 ≈ 255KB还是超了192KB。这时候就有两种解法把分辨率降级到480x272的RGB565然后启用部分帧缓冲机制Partial Frame BufferTouchGFX支持把帧缓冲拆成若干个小的strip逐块渲染。这是最优雅的方案但配置复杂。直接把帧缓冲放到外部SDRAM用F407的FMC外设映射到0xC0000000地址。这种做法在野火板上已经有很多验证稳定可靠但需要把SDRAM初始化代码在TouchGFX的HAL初始化之前完成而且要注意刷新性能。我自己选的是方案二——放到SDRAM。原因有两个一是实现简单代码改动最小二是F407加外部SDRAM带宽足够支撑480x27260fps的刷新实测下来CPU占用也不算高跟MCU主频和代码优化有关。TouchGFX官方对F4系列也支持外部RAM做帧缓冲所以这条路是通的。3.2 底层驱动对接详细步骤在这里我把实际操作步骤完整地走一遍。第一步准备基础工程打开野火“5-LCD显示”例程先确认能正常显示野火的初始化界面或者测试色块。如果这一步都不通后面全是空中楼阁。在工程里需要加上SDRAM初始化相关的代码野火例程中SDRAM通常已经在先前的测试程序里初始化过但LCD的独立例程不一定包含所以要检查并添加。第二步添加TouchGFX核心库在Keil工程中新建一个分组TouchGFX然后把TouchGFX目录下的src/touchgfx源码或者预编译lib添加进去。我这里用的是源码编译方式这样方便后续调试。注意TouchGFX库依赖C标准库Keil AC5需要确保--cpp选项被正确设置且在工程选项把GNU extensions关掉。同时把TouchGFX的头文件路径添加进Include Paths:{TouchGFX}/include{TouchGFX}/include/platform以及generated/gui_generated/include等Designer生成的头文件路径第三步编写HAL实现文件这是最核心的步骤直接上关键代码// TouchGFXHAL.cpp #include touchgfx/hal/HAL.hpp #include touchgfx/hal/BoardConfiguration.hpp extern C { #include bsp_lcd.h #include bsp_sdram.h } using namespace touchgfx; void touchgfx::HAL::initialize() { // 确保SDRAM已初始化 BSP_SDRAM_Init(); // 确保LCD已初始化 BSP_LCD_Init(); // 设置帧缓冲地址为SDRAM基地址注意需要4字节对齐 setFrameBufferStartAddresses((uint16_t*)0xC0000000); // 初始化TouchGFX HAL内部状态 touchgfx::HAL::initialize(); }void touchgfx::HAL::flushFrameBuffer() { // 对于RGB LCD直接驱动理论上不需要手动刷新因为LTDC会 // 不停从帧缓冲地址读取并刷新屏幕。但是为了兼容TouchGFX的 // 软件渲染流程这里留一个空的刷新函数即可。 }这里有个关键点因为野火F4板子没有内置LTDC外设ST官方F4系列里只有F429以上才带LTDCF407不带所以屏幕的刷新完全靠外部RA8876/SSD1963之类的LCD控制器完成。在这种情况下TouchGFX的flushFrameBuffer其实需要把SDRAM里的一整帧数据通过FSMC总线或者SPI/DMA方式发送给LCD控制器。这也意味着不能再简单地把显示地址映射给LTDC需要手动驱动屏幕控制器的写GRAM指令。注意F407没有LTDC是关键差异点。如果你用的是F429或更高的F469/F767LTDC直推屏会简单很多帧缓冲地址直接告诉寄存器就行不需要手动刷。野火F4指弹者板对应的是F407所以这里走的是“MCU写屏控制器”路线。我的实际实现是直接用FSMC的写16位数据命令把帧缓冲内容逐行写给RA8876void LCD_DrawRGBImage(uint16_t *pColors, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { // 设置窗口显示区域 RA8876_SetWindow(x, y, x width - 1, y height - 1); // 写入GRAM RA8876_WriteDataWordBurst((uint16_t*)pColors, width * height); }然后在flushFrameBuffer里调用这个函数把当前活动帧缓冲的内容整体刷一次。第四步接入触摸驱动TouchGFX的触摸框架是通过STM32TouchController类实现的。需要在项目中实现这个类class STM32TouchController : public touchgfx::TouchController { public: void init(); bool sampleTouch(int32_t x, int32_t y); }; void STM32TouchController::init() { BSP_TS_Init(480, 272); } bool STM32TouchController::sampleTouch(int32_t x, int32_t y) { // 读取野火触摸芯片通常通过I2C if (BSP_TS_Get_Point(touchX, touchY) 0) { x touchX; y touchY; return true; } return false; }注意坐标映射关系。野火的触摸驱动返回的坐标通常是原点在左上角的像素坐标TouchGFX内部也是这个坐标系所以直接透传即可。但如果触摸方向和LCD扫描方向不一致比如屏幕横屏但触摸还是竖屏坐标就需要在sampleTouch里做坐标变换否则会出现点击位置和控件位置偏移的怪异问题。3.3 TouchGFX Designer工程配置细节用TouchGFX Designer新建工程时在Target选择部分裸机工程可以直接用“F407”模板分辨率调成480x272色彩格式选RGB565。如果生成的工程代码结构和你的Keil工程不匹配最简单的方式是只用Designer生成界面相关的代码generated和gui目录然后手动把这两个目录拷贝到野火工程里把main.cpp里的入口部分替换成TouchGFX需要的初始化调用顺序。main函数里关键的初始化顺序初始化系统时钟初始化SDRAM帧缓冲区域依赖它初始化LCD控制器初始化触摸控制器初始化TouchGFX HAL进入TouchGFX的消息循环一个完整的初始化序列长这样int main(void) { HAL_Init(); SystemClock_Config(); BSP_SDRAM_Init(); BSP_LCD_Init(); touchgfx::HAL::getInstance()-initialize(); touchgfx::Application::getInstance()-run(); }Designer生成的Application::run()内部是一个while(1)循环不断采样触摸、处理事件、刷新界面不返回。所以不需要额外加主循环。注意在初始化touchgfx::HAL::getInstance()-initialize()之前必须保证SDRAM和LCD都已就绪。我踩过的坑是先初始化HAL再初始化SDRAM结果HAL在构造帧缓冲时读到的是一块未初始化的内存显示完全花屏。4. 常见问题与调试技巧4.1 花屏与残影问题排查花屏是移植过程中最打击人的问题没有之一。我在适配过程中遇到的花屏原因是多样化的。第一个坑是SDRAM初始化时序不对。野火F407的SDRAM初始化代码是配合官方FMC配置的其中有几个关键的时序参数SDCLK分频、TRP、TRC、TMRD这些在CubeMX里的配置值与板子上的SDRAM芯片不一致会导致读写数据出错表现出来就是花屏或者屏幕有一部分颜色不对。排查方法很简单先用野火的SDRAM测试例程往SDRAM里写入固定模式的数据比如0xAAAA、0x5555再读出来比对确认SDRAM读写没问题再做后续工作。第二个坑是帧缓冲地址没有按4字节对齐。TouchGFX内部对帧缓冲的访问使用了DMA2D或者SIMD优化如果起始地址末两位不是0轻则性能下降重则硬件进HardFault。检查方法打印(uint32_t)frameBuffer 0x3如果不为0就把帧缓冲基地址强制往上对齐8字节。第三个坑是残留拖影或重影。这通常是因为LCD控制器刷新太快而MCU的FSMC写数据速度跟不上导致屏幕控制器读到了半帧数据。解决办法要么降低FSMC的写时序速度要么把LCD控制器的TFT时钟稍微调低。我在RA8876上遇到过把PLL参数调低一点拖影就消失了。4.2 触摸失准与漂移触摸坐标和显示坐标不一致是我遇到的高频问题尤其是从野火官方测试程序比如画板程序切换到TouchGFX工程时。野火板子上的触摸驱动有一个“校准”流程通常通过ADC采集触摸面板上的电压然后转换成坐标。但是触摸屏的坐标原点不一定和LCD显示的原点重合而且不同批次的屏幕偏转方向不同。在TouchGFX里做校准有两种办法硬件层校准在sampleTouch里硬编码偏移量和镜像变换比如如果触摸的方向反了就做x width - 1 - x。软件层校准TouchGFX提供了一套校准接口在TouchController里调用touchgfx::TouchCalibration相关的函数。我建议在实际产品开发中用硬编码校准因为TouchGFX的软件校准矩阵需要存储在校准配置里调试周期长。硬编码虽然“土”但简单可靠。至少先把方向弄对再考虑精度。4.3 编译链接错误集锦移植过程中会遇到一些典型的编译错误这里整理成速查表错误现象常见原因解决办法Undefined symbol __cpp没有启用C编译选项Keil中把main.c改成main.cpp或确保工程默认编译CNo space in execution regionsFlash或RAM资源不够把帧缓冲移到SDRAM减少Font和Image资源开启编译优化-O1以上error: #20: identifier std is undefined缺少C标准库头文件包含cstdint或cstdio等头文件HardFault when calling HAL::initialize()帧缓冲地址非法或SDRAM未初始化先初始化SDRAM再调用initializeLinker error: region FLASH overflowedTouchGFX图形库太大Flash放不下降低优化级别里Debug Info的生成关掉--keep选项只链接用到的图形资源其中我特别想提的是Flash空间问题。F407的Flash有1MB看起来挺大但TouchGFX的完整库加上中文字体动辄400-500KB再塞一个复杂界面就容易爆。解决方法字体仅保留用到的文字和字号不要全字库链接。图片尽量用L8压缩格式或者减少色深。TouchGFX的核心库可以链接预编译lib体积比源码编译小不少。4.4 调整时钟与刷新率的实操心得F407的主频我配置到168MHzFSMC外设时钟等分系数相应调整才能保证LCD控制器的读写时序在合理范围。这里我给出一个我验证过的时钟配置以野火F407指南者为例系统时钟HCLK 168MHzAPB1 42MHzAPB2 84MHzFSMC时钟 HCLK / 3 56MHzFSMC写时序我设置成ADDSET 0x0FDATAST 0x3C这样写速度大约在10MHz上下RA8876完全能接受。如果你用的是SSD1963其最快写周期是12nsFSMC 10MHz的写周期100ns完全在规格内。实测刷新性能480x272 RGB565全屏填充用这种方式刷新一次完整frame大约需要12ms左右算下来能达到80fps的理论刷新率上限实际动画中用TouchGFX的UI渲染帧率在30-40fps是非常流畅的肉眼基本看不出卡顿。对于绝大多数界面应用这个性能足够了。5. 实测验证与运行效果5.1 基础例程验证移植完成后我先跑TouchGFX自带的“Hello World”模板Designer生成的一个简单界面上面有文本和渐变背景验证文本渲染、背景刷新的基本功能。首次运行如果只是白屏先别急着查代码用示波器或者直接看屏幕控制器的寄存器状态确认有没有刷新数据进来。我遇到过一次白屏原因是SDRAM初始化里的延时函数还没跑完TouchGFX就开始往帧缓冲写数据整个初始化顺序错乱。后来我在HAL::initialize()里加上一个DWT_Delay(200ms)的延时问题解决。接着跑一个“图形加速”例程里面包含大量2D图形绘制圆形、线条、填充多边形用来检验CPU负载和帧率。实测下来在168MHz主频的F407上这些图形操作基本保持60fps不掉帧这主要得益于TouchGFX对RGB565优化的软件渲染器。不过要注意如果用24位色背景图会明显变卡因为RGB565转RGB888带宽翻倍建议界面风格以16位色为主。5.2 字体与图标资源的使用建议中文显示是很多人的痛点。TouchGFX支持Unicode但中文字体文件体积很大。我实验了方案从TouchGFX Designer的字体设置里选择字体例如“Microsoft YaHei”并只生成需要的文字范围比如只生成项目中用到的几十个汉字这样字库体积可以从几MB降到几十KB。使用TypedText机制把文字预写到Flash中不要运行时生成速度快还不占RAM。图标的话TouchGFX支持字体图标icon font你可以把svg/png转成ttf字体然后作为Text样式使用这是一个很实用的技巧。我之前做过一套用Font Awesome图标字体替代贴图的项目体积省了80%显示清晰度更好。这也回应了网上很多人问“TouchGFX可以使用字体图标吗”——可以的而且非常推荐。5.3 调试过程中的工具辅助调试这个项目时我的主要工具组合Keil的RTX/Event Recorder来查看CPU负载和帧率用SDRAM调试窗口直接查看帧缓冲地址里的像素值确认渲染是否正确一个逻辑分析仪挂在FSMC的写使能脚上抓取写时序确认LCD控制器波形是否正确在排查触摸问题时我在sampleTouch里临时加了一个串口打印把每次触摸的坐标通过UART发送到PC端的小工具中画出来能快速看出触摸方向、坐标范围是否正常。这种土办法虽然慢但非常有效。6. 最后再分享一些心得整个适配过程不算短但走通之后你会对TouchGFX的架构以及MCU图形显示的整体链路应用层渲染 → 帧缓冲 → LCD控制器 → 屏幕物理显示有一个非常深入的理解。这种理解不只是在TouchGFX上有效做LVGL、AWTK或者裸机GUI时同样适用。我在实际测试中还发现了一个小技巧TouchGFX Designer生成的Screen1View.cpp里有handleTickEvent()函数它能获得周期性调用默认16ms很适合放一些UI逻辑轮询代码比如按键状态检测或传感器数据刷新。很多新手不知道这个入口什么逻辑都往handleClickEvent里塞导致交互卡顿其实用Tick事件做周期任务才是标准做法。最后提醒一句市面上关于TouchGXF的资料越来越多但是适配野火板这种“非官方硬件”的案例还是太少。如果你也用的野火板跟着这篇文章走完应该能顺利跑起来。如果遇到跟我上面描述不太一样的情况大概率是屏幕型号或者触摸驱动芯片批次不同先排查驱动IC的型号再去对应改寄存器配置千万不要盲目照搬时序参数。适配这种事手里有原理图和数据手册心里就不慌。本文还有配套的精品资源点击获取