3. 工程结构解析:TouchGFX 生成的目录到底长什么样?
好,上一章我们把 TouchGFX Designer 跑通了,生成了第一个工程。你双击打开那个 .ioc 或者 .uvprojx 文件的时候,有没有一瞬间觉得「哇,这么多文件夹,我该改哪个?」
别急,我当年第一次看到这个目录结构,也愣了几秒。后来做过的项目多了,发现这个结构其实非常清晰,只是它把「框架代码」和「用户代码」分得很开。说白了,TouchGFX 帮你搭好了舞台,你只管在上面演戏就行。
这一章,我们就来拆解这个舞台。我会带着你,把 App、Gui、Target 这几个核心文件夹一个一个看明白。
3.1 顶层目录:一眼看穿骨架
打开你的工程根目录,正常情况下你会看到类似这样的结构(我用的是 STM32CubeIDE 生成的,Keil 或 IAR 也大同小异):
MyProject/
├── Core/
├── Drivers/
├── Middlewares/
├── TouchGFX/
├── .cproject
├── .project
├── *.ioc
└── Src/ (部分IDE会有)
这里面,Core 和 Drivers 是 STM32CubeMX 生成的,跟 TouchGFX 没有直接关系。真正的主角是 TouchGFX 这个文件夹。
我个人习惯,拿到一个新工程,第一件事就是打开 TouchGFX 目录。因为所有跟界面相关的代码,都藏在这里面。
核心原则:TouchGFX 生成的代码,你 不要手动修改。你改的代码,都应该放在 App 和 Target 里。这是避免升级冲突的关键。
3.2 TouchGFX 目录:三层架构
展开 TouchGFX 文件夹,你会看到三个子目录:
TouchGFX/
├── App/
├── Gui/
└── Target/
这三个文件夹,对应了 TouchGFX 框架的「三层架构」:
- App:应用层。你写业务逻辑的地方。
- Gui:界面层。Designer 生成的 UI 代码全在这里。
- Target:硬件层。板级支持包,跟硬件打交道的代码。
嗯,这里要注意:Gui 文件夹里的东西,你几乎不需要碰。但 App 和 Target 是你频繁出入的地方。
3.3 App 文件夹:你的主战场
先看 App 文件夹。它里面通常只有两个文件:
TouchGFX/App/
├── app_touchgfx.c
└── app_touchgfx.h
这两个文件,是 TouchGFX 跟你的主循环(main loop)之间的桥梁。说白了,它负责启动 TouchGFX 的调度器。
打开 app_touchgfx.c,你会看到类似这样的代码:
#include "app_touchgfx.h"
#include "touchgfx.h"
void MX_TouchGFX_Init(void)
{
// 初始化 TouchGFX 框架
touchgfx_init();
}
void MX_TouchGFX_Process(void)
{
// 主循环里不断调用这个函数
touchgfx_taskEntry();
}
我在项目中遇到过一个问题:有人把 MX_TouchGFX_Process 放在了一个低优先级的任务里,结果界面卡顿。后来发现,这个函数必须在主循环或者高优先级任务里频繁调用,否则 UI 刷新会跟不上。
小技巧:如果你用 FreeRTOS,建议把 touchgfx_taskEntry() 放在一个独立任务里,优先级设高一点。我曾经因为优先级设低了,导致触摸响应延迟了 200ms,用户体验很差。
3.4 Gui 文件夹:Designer 的「自留地」
接下来是 Gui 文件夹。这个目录结构,完全由 TouchGFX Designer 生成和维护。你想想看,你在 Designer 里拖了一个按钮,它就会在这里生成对应的 C++ 代码。
典型的 Gui 结构如下:
TouchGFX/Gui/
├── include/
│ └── gui/
│ ├── common/
│ ├── screen1_screen/
│ └── screen2_screen/
└── src/
└── gui/
├── common/
├── screen1_screen/
└── screen2_screen/
每个屏幕(Screen)对应一个文件夹。比如 screen1_screen 里,会有:
Screen1View.hpp/Screen1View.cpp:视图层,负责显示。Screen1Presenter.hpp/Screen1Presenter.cpp:主持人层,负责逻辑。
这是 TouchGFX 的 MVP(Model-View-Presenter)架构。View 只管画界面,Presenter 管业务逻辑。我刚开始学的时候,总觉得这两个东西分不清,后来发现一个简单的判断方法:凡是跟 UI 控件直接打交道的,放 View;凡是跟数据、通信、计算有关的,放 Presenter。
警告:千万不要手动修改 Gui 文件夹里的文件!如果你改了,下次在 Designer 里重新生成代码,你的修改会被覆盖。正确的做法是:在 App 或 Target 里写你的逻辑,然后通过回调或接口跟 Gui 交互。
3.5 Target 文件夹:跟硬件打交道的地方
最后是 Target 文件夹。这个文件夹里,放的是跟具体硬件平台相关的代码。
TouchGFX/Target/
├── BoardConfiguration.cpp
├── BoardConfiguration.hpp
├── TouchGFXGPIO.cpp
├── TouchGFXGPIO.hpp
├── TouchGFXHAL.cpp
├── TouchGFXHAL.hpp
├── STM32DMA.cpp
├── STM32DMA.hpp
├── TouchGFXDataReader.cpp
├── TouchGFXDataReader.hpp
└── ... (其他硬件相关文件)
这里的关键文件是 TouchGFXHAL.cpp。HAL 是 Hardware Abstraction Layer 的缩写,它负责把 TouchGFX 的绘制指令,转换成实际硬件能理解的信号。
举个例子,你的屏幕是 RGB 接口还是 SPI 接口?帧缓冲(Frame Buffer)放在内部 SRAM 还是外部 SDRAM?这些配置,都在 TouchGFXHAL.cpp 里设置。
我记得有一次移植到一款国产 MCU 上,屏幕一直花屏。查了两天,最后发现是 TouchGFXHAL.cpp 里的帧缓冲地址配置错了。嗯,从那以后,我每次移植都会先检查这个文件。
3.6 依赖关系:谁依赖谁?
搞清楚了三个文件夹的作用,我们来看看它们之间的依赖关系。我画了一张图,帮你理清思路:
从这张图可以看得很清楚:
- App 依赖 Gui:App 里的主循环调用 Gui 的 Presenter 来驱动界面更新。
- Gui 依赖 Target:Gui 里的 View 需要调用 Target 的 HAL 来操作屏幕硬件。
- Target 是独立的:它不依赖 App 和 Gui,只依赖 MCU 的底层驱动。
这种分层设计的好处是:如果你想换一个屏幕,只需要修改 Target 文件夹里的代码,Gui 和 App 完全不用动。反之,如果你想改界面布局,只需要在 Designer 里改,然后重新生成 Gui 文件夹,App 和 Target 不受影响。
3.7 实战建议:移植时该动哪些文件?
好了,理论讲完了,我们来点实际的。假设你要把 TouchGFX 从一个开发板移植到自己的板子上,你应该关注哪些文件?
| 文件夹 | 需要修改的文件 | 修改原因 |
|---|---|---|
| App | app_touchgfx.c |
调整初始化顺序、任务优先级 |
| Gui | 通常不改 | 除非你要手动调整布局(不推荐) |
| Target | TouchGFXHAL.cpp |
配置帧缓冲地址、屏幕接口、时钟 |
| Target | BoardConfiguration.cpp |
配置 GPIO、LCD 控制引脚 |
| Target | STM32DMA.cpp |
配置 DMA 传输(如果使用) |
我曾经在移植到 STM32F429 时,发现屏幕刷新特别慢。查了半天,发现是 TouchGFXHAL.cpp 里没有启用 DMA2D 硬件加速。打开后,帧率从 15fps 直接飙到了 60fps。所以,移植时一定要检查硬件加速是否开启。
避坑指南:如果你用的是外部 SDRAM 作为帧缓冲,记得在 TouchGFXHAL.cpp 里把帧缓冲地址改成 SDRAM 的地址。我曾经忘了改,结果画面一直显示乱码,还以为是屏幕坏了。
3.8 小结
这一章我们拆解了 TouchGFX 的工程结构。说白了,你只需要记住三句话:
- App 是你的业务逻辑入口,放主循环和初始化。
- Gui 是 Designer 的领地,别去动它。
- Target 是硬件适配层,移植时主要改这里。
搞清楚了这些,后面我们讲具体的移植步骤时,你就知道该往哪个文件夹里塞代码了。