简介这是一套基于STM32H743IIT6单片机的TouchGFX官方综合Demo工程面向嵌入式开发者及需要快速搭建人机交互界面的工程师以480x272分辨率屏幕为基准演示图形绘制、动画效果与触摸输入处理的完整流程。压缩包共1133个文件大小约60.23MB主要包含cpp/hpp/h源码、png图标资源、TouchGFX图形库静态库lib/a、Keil/STM32CubeMX等工程配置uvprojx、ioc、makefile以及可直接烧录的hex文件目录结构清晰适合直接导入环境进行二次开发。例程内容覆盖STM32H743基础架构与内存布局、TouchGFX环境初始化与配置、界面设计、动画实现、触摸屏驱动及事件响应等关键知识点并附带官方库文件和工程脚本可帮助读者快速理解从底层驱动到上层UI的完整开发链路。目前已有46人学习下载适合具备一定单片机基础、希望掌握TouchGFX开发的中高级开发者可作为实际项目中的参考模板与排错依据。1. 拿到STM32H743IIT6的TouchGFX综合Demo先确认它解决什么问题第一次把 STM32H743IIT6 的 TouchGFX 综合 Demo 烧进板子我盯着那块 480x272 的屏愣了几秒画面出来了但按钮一个都点不了。后来查了半天发现是触摸方向没对齐——这类官方例程真正的问题从来不是画不出界面而是你不知道它背后占了多少显存、怎么接线、重编译后哪些东西会被覆盖。这份综合 Demo 把 LTDC、DMA2D、触摸、显存分配都串好了适合刚接触 TouchGFX、想快速在这块屏上验证交互逻辑的人。它解决的是从拿到工程到真正能改自己页面的那段空窗期也顺带把 H743 大内存平台上的 UI 资源管理摊开给你看。2. 拆解综合Demo的工程结构generated、assets 与 480x272 显存开销2.1 目录拆解哪些文件是生成的、哪些是手改的解压这份综合 Demo 后直接进工程根目录能看到.touchgfx和.ioc两种工程文件目录上则是一排App、generated、gui、Target、assets再加一个MDK-ARM或EWARM的 IDE 工程目录。很多第一次接触的人会不习惯因为这不是普通单片机工程“main.c 一把梭”的结构它是有意拆成“设计器管理”和“手写代码”两块。assets/放原始素材UI 用到的 PNG 图片、字体文件、文本资源。你在 TouchGFX Designer 里能看到的东西基本都能在这里找到原文件。generated/是自动生成区Designer 每次保存都会把 assets 里的素材转成 C 数组同时生成字体缓存、位图索引、文本字典。这一整个目录不建议直接改因为重新生成一次就会被覆盖改了也留不住。gui/是手写代码的主战场按 Screen 分目录每个 Screen 通常包含 View、ViewBase 和对应 Presenter。Target/是硬件桥接层把 TouchGFX 的抽象接口接到 STM32 的 LTDC、DMA2D、触摸控制器上拿到例程先不用动除非你换了屏或换了触摸方案。App/则是工程入口和生成的私有头文件目录。一句话assets 是原料generated 是流水线产品gui 才是你干活的地方。2.2 从 .ioc 到 .touchgfx一套工程两套生成器这套工程最大的迷惑点是同时存在两个生成器。CubeMX 管外设TouchGFX Designer 管界面两套生成逻辑又互相依赖。CubeMX 从.ioc生成 HAL 初始化代码时钟树、GPIO、LTDC、DMA2D、FMC SDRAM、触摸用的 I2C/SPI以及中断优先级。TouchGFX Designer 从.touchgfx生成 UI 代码页面、控件、图片引用、字体缓存。生成链的顺序有讲究。改外设去.ioc改界面去.touchgfx不要在生成文件里直接改 HAL 配置。在 CubeMX 里启用 TouchGFX 中间件后它会在生成的main()里自动插入TouchGFX_Init()和TouchGFX_Task()调用链所以你重新生成 CubeMX 代码后UI 引擎入口还在但外设初始化参数会被刷新成.ioc里的最新值。常见做法是先在 CubeMX 里确认外设正确生成一次再双击.touchgfx用 TouchGFX Designer 继续改 UI。以后每次改完.ioc重新生成generated/会同步更新手写在gui/里的代码通常不受影响。但有两个前提一是 TouchGFX Designer 的版本要和 CubeMX 集成的 TouchGFX 版本匹配否则工程打不开二是改.ioc前最好整体备份一下工程因为 CubeMX 重新生成时有小概率把你自己加的中间层文件从 build 里挤掉。2.3 480x272 在 H743 上的显存账算清楚再决定用哪种像素格式很多人在这一步翻车直接照着综合 Demo 的默认配置改成 ARGB8888 双缓冲编译过了烧进去不是花屏就是黑屏。本质是显存不够或访问冲突。480x272 的分辨率看着不大但不同像素格式的帧缓冲占用差距很明显我习惯先用一段脚本把账算明白# 帧缓冲内存估算480x272 在不同像素格式下的占用 width, height 480, 272 formats { RGB565 (2Bpp): 2, ARGB8888 (4Bpp): 4, L8CLUT (1Bpp): 1, } for name, bpp in formats.items(): per_buf width * height * bpp print(f{name}: 单缓冲 {per_buf / 1024:.1f} KB, 双缓冲 {2 * per_buf / 1024:.1f} KB)逻辑很直白把像素宽高乘起来再乘每像素字节数就是一块帧缓冲的字节数除以 1024 转成 KB。双缓冲自然就是两倍。我在本机跑一遍的结果是RGB565 单缓冲约 261.1 KB双缓冲约 522.2 KBARGB8888 单缓冲约 522.2 KB双缓冲直接超过 1 MBL8CLUT 单缓冲约 130.6 KB代价是每个像素只有 256 色。H743 的 AXI SRAM 是 320KB不算其它消耗ARGB8888 双缓冲硬放内部 SRAM 铁定爆。所以综合 Demo 常见做法是内部 SRAM 放 RGB565 单缓冲或者把帧缓冲放到外部 SDRAM。480x272 这个分辨率配 RGB565多数 UI 场景画质都够L8 适合大量图片素材但颜色受限官方 Demo 里有时用于背景图压缩需要配合 CLUT 使用。后面遇到花屏先算这笔账。3. 从CubeMX到板子亮屏配置、编译与烧录的完整动作3.1 打开 .ioc 先看什么时钟树、LTDC 和 SDRAM 时序拿到例程不要急着编译烧录先打开.ioc做三件事。第一看 Clock Configuration 里 HCLK 是不是 480MHz。TouchGFX 的帧率、DMA2D 吞吐、SDRAM 刷新定时都跟这条主线有关H743 一路分频下来如果主频不对LTDC 像素时钟也会跟着偏轻则闪屏重则直接白屏。第二看 LTDC 的 Pixel Clock 配置。480x272 的屏加上 HSYNC、HBP、HFP、VSYNC、VBP、VFP 的空闲周期我一般先估算一下:总行宽约 480411332566总帧高约 2721022286按 60Hz 刷新率乘出来像素时钟大约 9.7MHz。综合 Demo 里通常已经算好你在 CubeMX 里只确认分频系数别被其它外设改动带跑。第三看 FMC 里 SDRAM 的 Timing。如果帧缓冲在外部 SDRAM时序参数错了会表现为画面一半正常一半雪花或者触摸响应卡顿。我一般会在 main 函数初始化后写一个循环对 SDRAM 全地址做 0xAA55 回读测试跑通了再初始化 TouchGFX这样能快速区分是显存问题还是 UI 问题。3.2 Keil MDK 编译前必须确认的三个设置用 Keil 打开 MDK-ARM 工程后第一件事确认 Device 选的是 STM32H743IIT6 或同系列型号选错会导致寄存器地址映射错位。第二件事打开 Options for Target - C/C看 Define 里是否有STM32H743xx和USE_HAL_DRIVER这两个宏直接决定 HAL 库和寄存器定义走哪套分支缺一个就会冒出一堆 undefine 错误。第三件事检查 Include Paths。TouchGFX 工程对头文件路径非常敏感App、Target、generated、gui这几个目录缺一个编译报错就是找不到头文件。我见过有人图省事把整个 TouchGFX 库路径全加进去结果同名头文件互相冲突编译报了几百个错不如老老实实按工程文件里的列表配。优化等级我建议开-O2-O0下 TouchGFX 也能跑但刷帧效率差一截。3.3 烧录内部 Flash、外部 Flash 资源和校验综合 Demo 一般分两部分烧录一部分是编译出来的固件本身放内部 Flash另一部分是 UI 用到的图片、字体等资源如果工程启用了外部 Flash这些资源会单独生成一个 bin 或带地址的 hex。只烧固件不烧资源画面会出现“有的页面有图有的页面白板”的诡异现象。我用 STM32CubeProgrammer 命令行烧录比较多命令是固定的# 先烧固件SWD 接口通过 NRST 连接写入后校验并硬复位 STM32_Programmer_CLI.exe -c portSWD modeNRST -w build/Demo.hex -v -hardRst # 再烧外部 Flash 资源写到 FMC 映射的外部 Flash 起始地址 STM32_Programmer_CLI.exe -c portSWD modeNRST -w build/Demo_extflash.bin 0x90000000 -v -hardRst参数说明-c portSWD modeNRST指定调试接口和连接方式modeNRST是在芯片跑着 TouchGFX 动画时也能强拉复位进入烧录模式比单纯connect under reset稳-w是写入指定文件hex 自带起始地址bin 需要显式给出地址0x90000000是很多 H743 板上外部 NOR Flash 的映射地址具体要看硬件设计如果你的板子 FMC Bank1 映射地址不同要对应改-v是写完回读校验-hardRst是结束后的硬件复位。烧完如果触摸没反应先别怀疑代码用 I2C 扫描工具确认触摸 IC 的地址。综合 Demo 常见触控 IC 是 GT911 或 FT5336GT911 地址可能是 0x5D 或 0x14取决于 I2C 地址引脚电平看原理图最靠谱。4. 综合Demo的疑难排查五个必踩的坑和解决顺序4.1 白屏或黑屏背景、控件全看不到现象烧录成功后屏幕完全不亮或者亮但一片纯色没有任何 UI 内容。原因最典型的是 LTDC 的 Pixel Clock 没起来或者 LTDC 初始化没执行。综合 Demo 的.ioc在主频调整、引脚重映射后PLLSAI 的配置可能被打乱LTDC 时钟分频出错。另一种常见原因是HAL_LTDC_Init()在TouchGFX_Init()之后才被调用初始化顺序颠倒。解决回到 CubeMX 打开.ioc在 Clock Configuration 里找到 PLLSAI 与 LTDC 分频确认 Pixel Clock 输出值接近屏的额定值。然后查main()里的调用顺序标准顺序是先MX_LTDC_Init()、MX_DMA2D_Init()再TouchGFX_Init()。用调试器在HAL_LTDC_Init()里设断点看返回值是不是HAL_OK不是就看LTDC_InitTypeDef里的参数哪个被改歪了。4.2 画面闪烁、撕裂帧缓冲与 DMA2D 打架现象UI 能动但画面横向或纵向撕裂闪烁明显尤其在有大量图片切换的页面。原因绝大多数情况是帧缓冲放在了 Cacheable 区域。TouchGFX 用 DMA2D 搬运像素DMA2D 写内存不经过 CPU CacheCPU 读的时候却命中 Cache 里的旧数据两边看到的不是同一份内容表现出来就是撕裂。另一个常见原因是双缓冲时内存越界RGB565 双缓冲要 522KB内部 AXI SRAM 放不下配置里没改却硬开了双缓冲。解决如果帧缓冲在外部 SDRAM检查 MPU 对 SDRAM 区域的配置把这片区域设为 Shareable、Cacheable 关闭保证 DMA2D 和 CPU 看到一致的数据。如果帧缓冲在内部 SRAM确认型号和工程的链接设置匹配。我一般直接改成单缓冲验证确认是缓存一致性再做双缓冲避免一上来就排查错方向。4.3 触摸点不到按钮坐标方向与触摸驱动方向不对现象按钮位置看起来正常但点击无反应或者点上面的按钮响应的是下面那个。原因480x272 屏有些硬件是竖屏模组工程里按横屏布局LTDC 层面做了旋转但触摸驱动的原始坐标没同步做坐标变换X/Y 对调或镜像翻转。综合 Demo 原厂屏和你的屏模组方向不一致时这个问题必现。解决在 TouchGFX Designer 的 Target 配置里检查显示方向同时看触摸初始化代码里有没有坐标变换。没有现成 API 的话在TouchEvent上报前手动做一次变换// 假设触摸原始坐标 (raw_x, raw_y)显示做了一次 90 度旋转 touch_x raw_y; touch_y 479 - raw_x; // 480 分辨率索引从 0 开始逻辑说明旋转 90 度后触摸原始 Y 变成显示 X原始 X 变成显示 Y 再取镜像。参数说明479对应 480 像素宽的最大索引如果你的屏是 272 宽这个值要换成 271。改完这个桥接函数按钮点击的位置基本能对齐。4.4 重新生成 CubeMX 代码后 UI 资源丢失或编译不过现象在 CubeMX 里改了引脚或时钟重新生成再编译出现一坨 undefined reference或者 UI 图片全丢。原因CubeMX 重新生成时把 TouchGFX 相关的中间层代码也覆盖了尤其是App和generated目录下的集成文件。很多人喜欢直接在生成文件里改显存地址或加外设代码这些改动一次重新生成就全部清零。解决手写代码只放gui/和Target/对应位置放其它目录默认会被当生成代码处理。重新生成前备份整个工程目录生成后对比generated/的变化确认 TouchGFX 中间件版本没被 CubeMX 版本强制回退。这条是我自己的血泪经验现在每次生成前都先打 tar 包。4.5 Keil 编译报 No space in execution regions现象编译明明过了链接时报内存溢出错误信息里出现region RAM overflowed。原因UI 代码、帧缓冲、DMA 缓冲全挤在 AXI SRAM 或 DTCM 里H743 虽然内存不小但 TouchGFX 的综合 Demo 默认把大量缓存堆在 RAM一旦加了页面或图片链接器就装不下了。解决把帧缓冲移到外部 SDRAM或者把链接脚本里的 TouchGFX 缓冲区域挪到 SRAM4 这类空闲段。在 Keil 里右键目标工程Options - Target确认 External Memory 的 SDRAM 地址范围和大小被正确填写同时检查starting.xcl或.sct文件里是否有LR分区遗漏。一般解决这个报错后整机运行流畅度会明显变好因为内部 SRAM 带宽比外部 SDRAM 高。5. 把综合Demo改成自己的界面页面、回调与数据刷新5.1 理解 Screen/View/Presenter/Model 四层再动手删页面TouchGFX 的每个页面不是一个孤立的文件而是一整套分层结构。Model 存全局数据一个工程一个实例Presenter 管页面逻辑和数据中转View 管控件显示和事件Screen 是这三者的连接器由生成代码自动绑定。综合 Demo 里有好几个 Screen你看到的“页面”在gui/里是一组同名目录。想删掉一个页面正确做法是在 TouchGFX Designer 里删除对应 Screen再回到工程目录把残留的gui/xxx_screen文件夹清理掉同时检查Application.hpp里生成的 screen transition 代码是否还引用旧页面。直接在代码里删Designer 一保存删除的东西可能又回来了。新增页面我一般从现成 Screen 复制最快。在 Designer 里右键 Screen 选择 duplicate改名字再把素材替换成自己的图片和文本。生成的代码会自动更新 Screen 跳转链表这一步比从空白页开始省掉大量接线工作。5.2 数值实时刷新handleTickEvent 与 TextArea 联动综合 Demo 里有不少纯展示页面真正要改成自己的应用时最常见的需求是把传感器值、计数值实时刷到界面上。TouchGFX 提供的机制是handleTickEvent它随 TouchGFX 调度器周期性回调典型实现如下// MainView.cpp每 tick 从 Model 拿最新值刷到文本控件 void MainView::handleTickEvent() { uint16_t value model-getLiveValue(); Unicode::snprintf(textAreaBuffer1, 8, %u, value); textArea1.setWildcard(textAreaBuffer1); textArea1.invalidate(); }逻辑说明handleTickEvent是 View 层的虚函数TouchGFX 每帧都会调用适合放轻量级数据刷新。model-getLiveValue()从外部接口拿到最新数值snprintf把数字转成字符串放进预先声明的缓冲区setWildcard把这个字符串绑定到 TextArea 的通配符最后invalidate()标记这块区域需要重绘。参数说明缓冲区textAreaBuffer1的大小在头文件里定义为uint8_t textAreaBuffer1[8]8 字节能容纳 5 位数字加结束符如果显示整数超过 65535 或要带小数缓冲区要对应加大%u是无符号十进制有负数场景要改成%dinvalidate()只刷新这个控件区域比全屏刷新高效得多但调用的频率也别太高避免 CPU 占用过猛。5.3 串口或 GPIO 数据进 UI中断只收数据界面只做显示综合 Demo 里 UI 和底层数据是分开的。外部设备通过 UART、SPI 或 ADC 进来最常见做法是底层中断只管把数据放进缓冲区UI 层通过 Model 的接口去取。不要在中断回调里直接调setWildcard或invalidate()TouchGFX 的渲染调用不是线程安全的这样做会导致随机死机。// 串口中断回调4 字节一帧前两字节拼接成一个 16 位数值 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { static uint8_t buf[4]; static uint8_t idx 0; uint8_t ch; if (huart ! huart1) return; ch rx_byte; buf[idx] ch; if (idx 4) { idx 0; uint16_t value (buf[0] 8) | buf[1]; model-setLiveValue(value); } HAL_UART_Receive_IT(huart1, rx_byte, 1); }逻辑说明每收一个字节进数组凑满 4 字节当作一帧取前两个字节拼成 16 位数值通过model-setLiveValue()存入全局数据。UI 的handleTickEvent下一帧就能从getLiveValue()读到新值。拼接时buf[0]是高字节还是低字节取决于发送端的字节序常见单片机对单片机是小端也就是低字节在前如果数值明显对不上把两个字节调换一下。参数说明rx_byte是每次进入中断前由HAL_UART_Receive_IT()指定的接收变量4 字节帧协议里没用到的后两字节可以用来做校验和或帧尾。huart1换成你的实际串口句柄。这套模式的好处是中断里绝不碰 UI触摸和逻辑互不干扰。5.4 中文字体是另一个坑默认字体里没有汉字综合 Demo 的默认字体是英文数字直接往 TextArea 里写中文字符串显示出来是方框。原因很直接字体缓存里没包含汉字的字形。TouchGFX 用 Unicode 索引查字形默认字体范围只覆盖 ASCII汉字在 0x4E00 之后自然查不到。解决方法是进入 TouchGFX Designer 的 Text 或 Font 配置给对应字体添加 Unicode 范围比如0x4E00-0x9FFF重新生成。这个操作会让字体缓存明显膨胀480x272 屏上如果整屏中文缓存可能增加几百 KB。我一般只把实际用到的汉字加进范围或者干脆用图片代替固定中文标题省掉字体内存。还有一招是用 Google Fonts 或思源黑体的精简子集导入 designer常见做法但要注意授权个人项目无所谓商业产品要查字体授权。6. 验证与进阶帧率检查、PC模拟器与交付前的收尾习惯6.1 帧率与 CPU 占用三行代码确认没跑偏改完界面下载前先验证两件事帧率和内存余量。帧率验证最简单的方法是统计每秒进入handleTickEvent的次数在 View 里加一个计数变量每秒打印一次。TouchGFX 调度器每帧都会触发 tick计数除以运行秒数就是真实帧率。480x272 这个分辨率、RGB565 单缓冲H743 跑四五十帧很正常如果掉到二十帧以下优先查是不是在handleTickEvent里做了耗时操作或者帧缓冲被放到了慢速的 SDRAM。内存余量用链接器的 map 文件看找到IMAGE_RAM区域的占用比例低于八成比较安全。我是吃过亏的UI 逻辑全挤在内部 SRAM看似编译过了跑起来偶尔死机查了半天才发现是动态分配溢出。6.2 用 TouchGFX Simulator 在 PC 上快速调 UI综合 Demo 拿到手我建议先别急着烧板。TouchGFX Designer 自带模拟器点 Run 或 Simulator 可以直接在 PC 窗口里跑整个 UI。改布局、改颜色、调页面跳转全在模拟器里验证几十秒一轮比烧一次板子快一个数量级。串口数据在模拟器里没有可以用虚拟按键或脚本注入模拟值Mock 一下 Model 层的数据源。要注意的是模拟器用的是 PC 的渲染能力不涉及 LTDC 和 DMA2D所以它只能验证 UI 逻辑和布局验证不了底层性能。凡是牵涉到帧缓冲、触摸方向、SDRAM 时序的问题最终还是得回到板子上复测。6.3 交付前的收尾一份检查清单避免低级返工现在每次从综合 Demo 改成自己的交付物我都会强制走一遍固定动作解压后先备份原始.ioc和.touchgfx用 CubeMX 生成一次用 Designer 生成一次两次都无报错才动gui/改完界面用模拟器跑页面跳转上板前用脚本算一遍帧缓冲内存占用烧录后先测 SDRAM 回读再测 LTDC 白屏最后测触摸对齐。这套检查顺序帮我挡掉了至少十次改完代码发现是底层配置被带跑的返工。综合 Demo 的价值就是帮你把底层那些配置、生成、烧录的隐形链条理顺剩下的 UI 创意和逻辑才是你真正要投入的地方。希望帮到你。本文还有配套的精品资源点击获取