4. 工程目录结构设计
在STM32产品级工程中,目录结构不是“把文件放整齐”这么简单,它直接决定了代码的可移植性、可测试性和团队协作效率。一个没有分层约束的工程,往往在半年后演变为“所有文件堆在Core和User下、驱动和应用互相调用、换一颗MCU就要重写业务逻辑”的泥潭。本节从分层思想出发,给出可直接落地的目录组织方案。
一、核心原理:分层与依赖方向
分层设计的本质是控制依赖方向。上层可以调用下层,下层绝不能反向依赖上层。在STM32工程中,通常划分为五层:应用层、中间件层、驱动层、硬件抽象层(HAL/LL)、配置层。配置层与文档层、脚本层属于支撑性目录,不参与运行时调用链,但贯穿整个工程生命周期。
依赖方向应严格自上而下:应用层 → 中间件层 → 驱动层 → HAL/LL → 寄存器。任何跨层反向调用(例如驱动层直接调用应用层的业务函数)都会破坏可移植性。判断分层是否合格,有一个简单标准:把应用层整体删除后,驱动层和中间件层仍应能独立编译通过。
二、各层职责与目录组织
驱动层(Drivers)负责把芯片外设和板载器件封装成统一接口。建议按器件类型再分子目录,例如 Drivers/Inc 与 Drivers/Src,或按模块划分 drv_uart.c、drv_spi_flash.c、drv_oled.c。驱动层只暴露句柄和操作函数,不包含业务判断。
中间件层(Middleware)放置与具体硬件无关的通用能力,如 Modbus 协议栈、环形缓冲区、状态机框架、日志组件、参数管理。中间件层通过驱动层提供的接口访问硬件,自身不直接调用 HAL。
应用层(App)是产品逻辑的唯一归属地。建议按功能域拆分,如 App/Sensor、App/Comm、App/Control,每个功能域内部再分 Inc 与 Src。应用层可以调用中间件和驱动,但不得被下层调用。
配置层(Config)集中管理所有可变参数:引脚定义、功能宏开关、通信地址、版本号、校准系数。典型文件包括 board_config.h、app_config.h、version.h。配置层应做到“改配置不改逻辑”,换板子时只动这一层。
文档层(Doc)与脚本层(Scripts)是工程可维护性的保障。文档层存放硬件说明、协议文档、测试报告、变更记录;脚本层存放编译、烧录、格式化、版本生成等自动化脚本。
三、目录结构清单
| 目录 | 职责 | 典型内容 | 依赖方向 |
|---|---|---|---|
| App/ | 产品业务逻辑 | 任务、状态机、控制算法 | 可依赖 Middleware、Drivers |
| Middleware/ | 通用软件能力 | 协议栈、日志、参数管理 | 可依赖 Drivers |
| Drivers/ | 外设与器件驱动 | UART、SPI Flash、传感器 | 可依赖 HAL |
| HAL/ | 芯片抽象 | ST HAL/LL 库 | 仅依赖寄存器 |
| Config/ | 集中配置 | 引脚、宏、版本、参数表 | 被各层引用 |
| Doc/ | 工程文档 | 硬件说明、协议、测试报告 | 不参与编译 |
| Scripts/ | 自动化脚本 | build.bat、flash.py、version.py | 不参与编译 |
四、案例:从“一锅粥”到分层重构
某工业采集器最初把 UART 解析、Modbus 组包、传感器读取全部写在 main.c 中,换用 STM32F4 后无法复用。重构后目录如下:
Project/
├─ App/
│ ├─ Inc/ app_main.h app_sample.h
│ └─ Src/ app_main.c app_sample.c
├─ Middleware/
│ ├─ Modbus/ modbus_rtu.c modbus_rtu.h
│ └─ RingBuf/ ringbuf.c ringbuf.h
├─ Drivers/
│ ├─ Inc/ drv_uart.h drv_adc.h
│ └─ Src/ drv_uart.c drv_adc.c
├─ Config/
│ ├─ board_config.h
│ ├─ app_config.h
│ └─ version.h
├─ Doc/
│ ├─ hardware.md
│ └─ protocol_modbus.md
└─ Scripts/
├─ build.bat
└─ gen_version.py
重构后,Modbus 中间件不再直接操作寄存器,而是调用 drv_uart_send();应用层只关心“采集—组包—上报”的业务流程。更换 MCU 时,只需替换 HAL 与驱动层实现,应用层和中间件层几乎零修改。
README.md,用一张依赖图说明各层关系;在 Config/version.h 中统一维护版本号,由脚本自动生成,避免手工修改遗漏。
五、常见误区与复盘
- 误区一:驱动层包含业务判断。例如在 UART 中断里直接解析协议并执行控制。正确做法是驱动只负责收发字节,协议解析交给中间件。
- 误区二:配置散落各处。引脚宏定义在驱动里、功能开关在应用里,导致换板时到处搜索。应统一收敛到 Config 层。
- 误区三:文档与脚本缺失。工程能编译但没人知道如何烧录、如何生成版本。脚本层应至少包含一键构建与版本生成。
- 误区四:目录层级过深。超过四级的嵌套会增加 include 路径维护成本,建议每层不超过两级子目录。
总结来看,STM32 产品级工程的目录结构应以“依赖单向、职责单一、配置集中、文档脚本配套”为原则。驱动层管硬件、中间件层管通用能力、应用层管业务、配置层管差异、文档与脚本层管可维护性。把这七层组织清楚,工程才具备从样机走向量产的骨架。