简介:该资源是面向STM32H743IIT6高端微控制器的Cubemx与Touchgfx整合工程,适合已掌握HAL库基础、希望进阶嵌入式GUI开发的工程师与学生。工程以4.3英寸800×480液晶屏为显示载体,完成触摸驱动移植,可构建带动画、图标与菜单的复杂交互界面,适用于工业控制、消费电子及物联网设备的人机交互场景。压缩包共1172个文件,约71.44MB,以hpp、h、cpp、c等源码文件为主,辅以d、o、crf等编译中间产物,以及a、lib静态库、png图片资源、uvprojx与uvoptx工程配置、ioc与mxproject初始化文件,另含hex烧录文件与map映射表,完整保留从CubeMX配置到Touchgfx界面工程的目录结构。目前已有2220人学习下载。读者可据此快速搭建可编译运行的Touchgfx工程模板,理解HAL层与图形框架的衔接方式,并参考其资源组织与库文件配置,减少从零移植触摸屏与GUI的试错成本。
1. 拿到一块 H743 板子,怎么把 TouchGFX 跑出丝滑感
手里有块 STM32H743IIT6 的板子,屏幕也焊好了,供电正常,但屏幕就是一片白或者卡在某个画面不动——这种场景搞嵌入式 GUI 的应该都不陌生。正点原子这套 STM32H743IIT6 的 CubeMX + TouchGFX 工程,解决的就是从「裸机点灯」到「带触摸交互的图形界面」之间那段最磨人的路。它把 CubeMX 的时钟树、SDRAM 配置、LTDC 显示接口、DMA2D 加速、I2C 触摸驱动,以及 TouchGFX 的 Designer 工程和生成代码全部串好了。适合谁?适合已经会 STM32 基础外设、想上手 TouchGFX 但被底层配置卡住的开发者,也适合拿 H743 做产品原型、需要快速验证 UI 交互的团队。下面按「工程怎么搭起来 → 核心模块怎么跑 → 怎么改」的顺序拆。
2. 工程结构拆解:CubeMX 配置与 TouchGFX 的对接点
2.1 目录分层与生成物关系
这套工程的核心逻辑是「CubeMX 管底层,TouchGFX Designer 管 UI,Keil/IAR 管编译链接」。目录上通常长这样:
H743_TouchGFX/ ├── Core/ │ ├── Inc/ # main.h, stm32h7xx_hal_conf.h 等 │ └── Src/ # main.c, stm32h7xx_it.c, 外设初始化 ├── Drivers/ │ ├── STM32H7xx_HAL_Driver/ │ └── CMSIS/ ├── Middlewares/ │ └── ST/ │ └── TouchGFX/ # TouchGFX 库、OS 抽象层 ├── TouchGFX/ │ ├── App/ # 应用层:Model, Presenter, View │ ├── gui/ # Designer 生成的界面代码 │ ├── generated/ # 生成代码:fonts, images, texts │ └── target/ # 目标平台适配:TouchGFXHAL, TouchGFXGeneratedHAL └── STM32H743IITx_FLASH.ld关键点在于TouchGFX/target/这个目录。CubeMX 生成的TouchGFXHAL.cpp继承自TouchGFXGeneratedHAL,里面重写了initialize()、flushFrameBuffer()、touchGfxSignalVSync()这些函数。很多人第一次跑 TouchGFX 翻车,就是没搞清「谁调谁」——CubeMX 初始化 LTDC 和 DMA2D 之后,TouchGFX 的 HAL 层再接管帧缓冲刷新和触摸事件分发。
2.2 CubeMX 里必须配对的外设
在 CubeMX 的.ioc文件里,下面这几个外设不是随便勾上就行,参数配错一个,屏幕要么不亮要么撕裂:
| 外设 | 关键配置项 | 典型值(以 800×480 RGB 屏为例) |
|---|---|---|
| LTDC | Pixel Clock | 33 MHz 左右,按屏手册算 |
| LTDC | HSW/HBP/HFP | 根据屏时序填,常见 48/88/40 |
| LTDC | VSW/VBP/VFP | 常见 3/32/13 |
| DMA2D | 使能 | 用于加速填充和混合 |
| FMC/SDRAM | 时钟、行列地址 | 按板载 SDRAM 型号配 |
| I2C | 速率 | 100kHz 或 400kHz,看触摸芯片 |
| GPIO | 触摸中断引脚 | 下降沿触发 |
LTDC 的像素时钟不是随便给的。H743 的 LTDC 时钟源来自 PLL3,CubeMX 里要先把 PLL3 的 R 分频算对,否则像素时钟偏差超过 5%,屏幕就会出现水波纹或者偏移。我一般会先用示波器量一下 LTDC_CLK 引脚,确认频率和屏手册一致再往下走。
2.3 TouchGFX Designer 工程怎么嵌进来
TouchGFX Designer 生成的.touchgfx文件不直接参与编译,它导出的是TouchGFX/gui/和TouchGFX/generated/下的 C++ 代码。CubeMX 工程里通过Middlewares/ST/TouchGFX/touchgfx.mk或者 Keil 的.uvprojx把这几层源码加进编译。
一个容易忽略的点:TouchGFX 的Application::create()是在main.c的MX_TouchGFX_Init()之后、MX_TouchGFX_Process()之前调用的。如果你在 CubeMX 里改了 FreeRTOS 的堆大小,但没同步改 TouchGFX 的OSWrappers配置,跑起来就会在osMessageQueueNew那里直接 HardFault。
3. 核心模块跑通:从 SDRAM 到触摸事件的完整链路
3.1 SDRAM 初始化与帧缓冲分配
H743IIT6 片内 SRAM 只有 1MB 左右,800×480×2 字节的双缓冲就要 1.5MB,所以帧缓冲必须放外部 SDRAM。CubeMX 里 FMC 配好之后,代码里要手动做一次 SDRAM 初始化序列:
// fmc.c 中 SDRAM 初始化关键片段 void MX_FMC_Init(void) { FMC_SDRAM_TimingTypeDef SdramTiming = {0}; hsdram1.Instance = FMC_SDRAM_DEVICE; hsdram1.Init.SDBank = FMC_SDRAM_BANK1; hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9; hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13; hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16; hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3; hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2; hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE; hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1; HAL_SDRAM_Init(&hsdram1, &SdramTiming); // 发送 SDRAM 初始化命令序列 FMC_SDRAM_CommandTypeDef Command; Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1; Command.AutoRefreshNumber = 1; Command.ModeRegisterDefinition = 0; HAL_SDRAM_SendCommand(&hsdram1, &Command, 0xFFFF); HAL_Delay(1); Command.CommandMode = FMC_SDRAM_CMD_PALL; HAL_SDRAM_SendCommand(&hsdram1, &Command, 0xFFFF); Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; Command.AutoRefreshNumber = 8; HAL_SDRAM_SendCommand(&hsdram1, &Command, 0xFFFF); Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE; Command.ModeRegisterDefinition = 0x220; // CAS=3, 突发长度 4 HAL_SDRAM_SendCommand(&hsdram1, &Command, 0xFFFF); }这段代码里SdramTiming的TRCD、TWR、TRP这些参数必须按 SDRAM 芯片手册填,填大了没事,填小了数据读写会随机出错。ModeRegisterDefinition的0x220对应 CAS 延迟 3、突发长度 4,和Init里的CASLatency要一致。初始化完之后,帧缓冲地址一般定在0xC0000000开始的位置,TouchGFX 的TouchGFXGeneratedHAL::initialize()里会通过setFrameBufferStartAddresses()把两个缓冲的地址传进去。
3.2 LTDC 层配置与 DMA2D 加速
LTDC 配两层:一层背景层,一层 UI 层。背景层可以关掉或者填纯色,UI 层指向 SDRAM 里的帧缓冲。CubeMX 里 LTDC 的 Layer 配置要注意Alpha和BlendingFactor,如果 UI 层不需要透明,把 blending 关掉能省带宽。
DMA2D 在 TouchGFX 里主要干两件事:矩形填充和图像混合。TouchGFX 的flushFrameBuffer()默认走 LTDC 的垂直消隐中断,但如果你在 Designer 里用了大量半透明控件,DMA2D 的混合性能就关键了。H743 的 DMA2D 支持 RGB565 和 ARGB8888 互转,TouchGFX 的LCD24bpp和LCD16bpp配置要和 LTDC 的像素格式对齐。
// TouchGFXHAL.cpp 中 flushFrameBuffer 的典型实现 void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect) { // 等待 DMA2D 空闲 while (DMA2D->CR & DMA2D_CR_START); // 配置 DMA2D 做矩形填充或拷贝 DMA2D->CR = DMA2D_R2M; // 寄存器到内存模式 DMA2D->OPFCCR = DMA2D_OUTPUT_RGB565; DMA2D->OCOLR = 0x0000; // 填充色 DMA2D->OMAR = (uint32_t)frameBuffer + rect.y * 800 * 2 + rect.x * 2; DMA2D->OOR = 800 - rect.width; DMA2D->NLR = (rect.height << 16) | rect.width; DMA2D->CR |= DMA2D_CR_START; // 通知 TouchGFX 这一帧已提交 touchgfx::HAL::flushFrameBuffer(rect); }这里OOR是行偏移,NLR的高 16 位是行数、低 16 位是列数。如果屏幕出现花屏或者错位,先查OMAR的地址计算有没有按像素字节数乘对。RGB565 每像素 2 字节,RGB888 是 3 字节,算错一个字节整个画面就斜了。
3.3 触摸事件从 I2C 到 Presenter 的传递
触摸芯片一般是 GT911 或 FT6236,走 I2C。CubeMX 里配好 I2C 和外部中断引脚后,中断服务函数里读触摸坐标,然后通过 TouchGFX 的OSWrappers::signalTouch()或者直接调HAL::injectTouchEvent()把事件塞进去。
// stm32h7xx_it.c 中触摸中断处理 void EXTI15_10_IRQHandler(void) { if (__HAL_GPIO_EXTI_GET_IT(TOUCH_INT_PIN) != RESET) { __HAL_GPIO_EXTI_CLEAR_IT(TOUCH_INT_PIN); // 读取触摸坐标(伪代码,具体寄存器看芯片手册) uint16_t x, y; if (GT911_ReadPoint(&x, &y)) { touchgfx::TouchEvent evt(x, y, touchgfx::TouchEvent::PRESSED); touchgfx::HAL::getInstance()->injectTouchEvent(evt); } } }注意injectTouchEvent是线程安全的,但如果你在中断里直接调,要确保 TouchGFX 的任务优先级和中断优先级不冲突。常见做法是在中断里发一个信号量,让 TouchGFX 的任务去读 I2C,避免在中断里做慢速 I2C 传输。
4. 避坑与排查:H743 + TouchGFX 最容易翻车的几个点
4.1 屏幕白屏或背光亮但无图像
现象:上电后背光亮,但屏幕全白或全黑,没有 UI 画面。原因通常是 LTDC 的像素时钟没配好,或者帧缓冲地址没指向 SDRAM 的正确位置。解决:先用 CubeMX 的 LTDC 配置生成一个纯色填充测试,确认 LTDC 本身能出图;再检查setFrameBufferStartAddresses()传的地址是不是 SDRAM 映射区,以及 MPU 配置有没有把 SDRAM 区域设成 Write-Through 或 Write-Back。
4.2 TouchGFX 任务跑不起来,卡在 osMessageQueueNew
现象:调试器停在OSWrappers::initialize()里的osMessageQueueNew返回 NULL。原因一般是 FreeRTOS 堆不够,或者 CubeMX 里没使能 FreeRTOS 的USE_COUNTING_SEMAPHORES。解决:把configTOTAL_HEAP_SIZE调到 60KB 以上,TouchGFX 默认要几个队列和信号量,堆太小直接分配失败。
4.3 触摸坐标偏移或反向
现象:点屏幕左边,UI 响应在右边;或者上下颠倒。原因:触摸芯片的坐标原点和屏幕原点不一致,或者 I2C 读出来的 x/y 需要交换。解决:在TouchGFXHAL::initialize()之后加一个坐标变换,或者在 Designer 的TouchCalibration里做映射。常见做法是先在屏幕上画一个十字,手动点四个角校准一次,把偏移量写死到代码里。
4.4 DMA2D 和 LTDC 抢带宽导致撕裂
现象:UI 刷新时画面上下半截不一致,或者有横向撕裂线。原因:DMA2D 在刷帧缓冲的同时 LTDC 也在读同一块内存,SDRAM 带宽不够。解决:开双缓冲,TouchGFX 的setFrameBufferStartAddresses传两个地址,让 DMA2D 写后台缓冲、LTDC 读前台缓冲,在 VSYNC 中断里交换。H743 的 SDRAM 如果跑在 100MHz 以上,双缓冲基本能消掉撕裂。
4.5 编译通过但下载后 HardFault
现象:Keil 里编译零错误,下载后一运行就进 HardFault_Handler。原因:MPU 配置和 SDRAM 地址冲突,或者栈溢出。H743 的默认 MPU 配置可能把 0xC0000000 区域设成 Strongly Ordered,DMA2D 访问会触发总线错误。解决:在main.c的MPU_Config()里把 SDRAM 区域设成 Normal, Non-cacheable 或 Write-Back,并且确保栈指针指向 DTCM 而不是 SDRAM。
5. 二次开发:改 UI、加逻辑、调性能的入手点
拿到这套工程之后,最常改的三个地方是:Designer 里的界面布局、TouchGFX/App/下的 Model 和 Presenter、以及TouchGFX/target/里的 HAL 适配。
改 UI 最简单,在 TouchGFX Designer 里拖控件、配交互,点 Generate Code 之后gui/和generated/会自动更新。但要注意:Designer 生成的代码会覆盖你手动改过的gui/文件,所以自定义逻辑要写在App/层,通过 Model 和 Presenter 跟 View 通信。
加业务逻辑的入口在Model.cpp的tick()或者自定义的action回调里。比如你要加一个串口收数据的界面刷新,就在 Model 里起一个定时器或者消息队列,收到数据后调presenter->notifyDataUpdated(),Presenter 再调 View 的updateXXX()。这套 MVP 架构的好处是 UI 和逻辑解耦,但坑在于 Presenter 和 View 的指针生命周期——Designer 重新生成代码后,View 的类名可能变,Presenter 里的view.setXXX()就要跟着改。
调性能的话,先看 TouchGFX 的Performance面板,里面会显示每帧的渲染时间和 DMA2D 占用率。如果帧率掉到 30fps 以下,优先查三件事:SDRAM 的刷新周期是不是太长、DMA2D 有没有开中断模式、以及 LTDC 的层数是不是开多了。H743 的 LTDC 支持两层,但每层都占 SDRAM 带宽,如果 UI 层不需要透明,把背景层关掉能省不少。
还有一个血泪经验:TouchGFX 的Application::create()里如果调了setUseDRAM(true)但 SDRAM 没初始化好,整个 UI 会直接卡死,而且不报错。从那以后我每次改完 CubeMX 的 FMC 配置,都强制先跑一遍 SDRAM 的读写测试,确认0xC0000000开始的内存能读能写,再往下调 TouchGFX。希望帮到你。
本文还有配套的精品资源,点击获取