3、工程结构解析:TouchGFX项目的文件目录结构,HAL层、平台层、应用层的职责划分

拿到一个TouchGFX工程,很多人第一反应是「这么多文件夹,从哪下手?」

说实话,我刚开始接触时也懵过。目录里又是 Target 又是 generated,看着就头大。但摸清规律后你会发现,这套结构其实很清晰——它把硬件、平台、应用三层剥开了,各管各的事。

这一章,我就带你把这棵「目录树」从头捋一遍。

3.1 顶层目录:一眼看穿骨架

一个典型的TouchGFX项目,顶层大概长这样:

MyProject/
├── Core/               # 应用层核心代码
├── TouchGFX/           # TouchGFX 框架相关
├── Drivers/            # 硬件驱动(HAL层)
├── Middlewares/         # 中间件(RTOS、文件系统等)
├── project.eww         # IAR 工程文件
└── .cproject / .project # 其他IDE配置

嗯,这里要注意:TouchGFX 文件夹才是我们的主战场。它里面又细分了几个子目录:

子目录 作用 我常改它吗?
app/ 应用层代码(屏幕、逻辑、Model) 天天改
generated/ TouchGFX Designer 自动生成的代码 几乎不动
gui/ UI 视图层(View、Presenter) 经常改
target/ 平台层与HAL层适配代码 偶尔改
我的习惯: 每次新建项目,先把 generated/ 目录加入.gitignore。因为它是自动生成的,多人协作时容易冲突,而且你根本不需要手动改它。

3.2 三层架构:HAL层、平台层、应用层

TouchGFX 把整个项目拆成了三层。说白了,就是「硬件—中间件—业务」的经典套路。但它的边界划得特别清楚,我画了张图帮你理解:

TouchGFX三层架构图 TouchGFX 三层架构 应用层(Application Layer) View / Presenter / Model / Screens 业务逻辑、UI交互、数据绑定 你每天写代码的地方 平台层(Platform Layer) OSAL / 文件系统 / 内存管理 / 时钟 抽象操作系统与中间件 连接应用与硬件的桥梁 HAL层(Hardware Abstraction Layer) LCD驱动 / 触摸驱动 / DMA / 帧缓冲 直接操作寄存器与硬件外设 数据流 用户 输入 事件 回调 渲染 指令 帧 缓冲 硬件 刷新

3.3 HAL层:跟硬件打交道的「翻译官」

HAL层在 TouchGFX/target/ 目录下。它的任务很简单:把TouchGFX的通用接口,翻译成你板子上的具体硬件操作。

举个例子,TouchGFX要刷屏时,会调用一个叫 flushFrameBuffer() 的函数。HAL层就得把这个函数实现成「往LTDC数据寄存器里写像素」。不同的MCU,写法完全不同。

HAL层核心文件:

  • STM32F7HAL.cpp / STM32H7HAL.cpp — 具体芯片的HAL实现
  • TouchGFXHAL.cpp — TouchGFX框架与HAL的胶水代码
  • OSWrappers.cpp — 操作系统抽象(信号量、互斥锁等)
我曾经踩过的坑: 有一次移植到新板子,屏幕死活不亮。查了两天,最后发现是HAL层里的 setFrameBufferStartAddress() 没正确配置——帧缓冲地址指向了SRAM的无效区域。从那以后,我每次移植都会先打印帧缓冲地址,确认它落在有效的内存区间。

3.4 平台层:操作系统的「遮羞布」

平台层在 TouchGFX/target/ 里也有份,但它更偏向软件抽象。如果你用了FreeRTOS,平台层会帮你封装好任务创建、队列、信号量等接口。

TouchGFX本身不依赖任何特定RTOS,它通过 OSWrappers 这个抽象层来调用底层的OS服务。你换RTOS时,只需要重写 OSWrappers.cpp 里的几个函数就行。

OSWrappers 函数 作用 对应RTOS实现
initialize() 初始化OS资源 创建信号量、队列
takeFrameBufferSemaphore() 获取帧缓冲访问权 xSemaphoreTake()
giveFrameBufferSemaphore() 释放帧缓冲访问权 xSemaphoreGive()
signalVSync() 通知垂直同步事件 xQueueSend()

说白了,平台层就是给TouchGFX套了一层「OS无关」的外壳。你换RTOS时,改这一个文件就够了。

3.5 应用层:你的业务代码大本营

应用层在 TouchGFX/app/ 和 TouchGFX/gui/ 里。这是你每天打交道最多的地方。

  • Model: 数据模型,负责与后端(传感器、通信等)交互。我习惯把所有的业务逻辑都塞在Model里,View只负责显示。
  • View: 界面显示,由TouchGFX Designer生成。你可以在里面添加自定义控件,但核心布局建议用Designer拖出来。
  • Presenter: 中间人,接收View的事件,调用Model的接口。说白了,就是「View不知道Model,Model不知道View,Presenter两头传话」。
我的个人习惯: 在Presenter里尽量不写业务逻辑。只做两件事:把View的请求转发给Model,把Model的结果推给View。这样万一要换UI框架(虽然不太可能),业务代码几乎不用动。

3.6 避坑指南:目录结构常见问题

  1. 不要手动修改 generated/ 目录下的文件。 你改了,Designer一重新生成,全没了。我见过有人直接在 generated/Screen1ViewBase.cpp 里加代码,结果下次改UI时全部丢失。
  2. HAL层的配置在 TouchGFX/target/ 里,别去 Drivers/ 里乱翻。 Drivers/ 是STM32CubeMX生成的底层驱动,TouchGFX的HAL层会调用它们,但你不应该直接改。
  3. 平台层的 OSWrappers.cpp 一定要跟你的RTOS匹配。 如果你用FreeRTOS,却用了ThreadX的封装,编译不会报错,但运行时帧缓冲访问会死锁。
  4. 应用层的 Model.hpp 是全局单例。 所有屏幕共享同一个Model实例。我早期犯过错误:在每个View里各自维护一份数据,结果屏幕切换时数据不同步。

嗯,目录结构就这些。你只要记住「HAL管硬件、平台管OS、应用管业务」这个口诀,以后找文件就不会迷路了。