ESP32 项目目录如何长大而不失控
补档说明:本文整理自一次原型重构。重点不是推荐唯一目录,而是说明每个边界解决了什么问题。
很多 ESP32 项目都从 main.c 开始,这没有错。问题出现在第二块传感器、第二种板型和第一位协作者加入之后:头文件互相包含,GPIO 数字散落各处,构建选项只存在某个人的电脑上。那次重构的目标不是追求“企业级架构”,而是让新功能不会修改十个不相干的文件。
目录按变化原因划分
最终目录大致如下:
project/
├─ main/ # 组装应用与生命周期
├─ components/
│ ├─ board/ # 引脚、电源与板型差异
│ ├─ sensor_service/ # 传感器抽象和采样策略
│ ├─ ble_protocol/ # 数据模型、编解码、传输
│ ├─ display_ui/ # 显示状态,不直接读硬件
│ └─ storage/ # 配置与日志持久化
├─ configs/ # 各环境的 sdkconfig.defaults
├─ tools/ # 量产、抓日志与数据转换脚本
└─ tests/ # 主机侧纯逻辑测试
board 组件只回答“这块板如何操作”,不会决定采样间隔;sensor_service 决定何时采样,但不知道数据会显示还是通过蓝牙发送。BLE 组件接收稳定的数据结构,不直接访问 I2C。这样更换屏幕或传感器时,变化停在对应边界内。
公共接口要小
每个组件的 include 目录只放调用方真正需要的声明。内部结构体留在源文件,避免其他组件依赖字段布局。返回值统一为错误码,日志里同时打印组件名和关键参数。
esp_err_t sensor_service_start(const sensor_cfg_t *cfg);
esp_err_t sensor_service_read(sensor_sample_t *out, TickType_t wait);
void sensor_service_stop(void);
这里刻意不暴露任务句柄、队列句柄和具体驱动。测试替身只需实现同样的三个动作,应用层就能在主机上演练状态机。
配置分三层
不再把所有差异塞进 menuconfig。构建期能力用 Kconfig,例如是否启用屏幕;板级常量在 board;用户可变参数存 NVS,并提供版本号与迁移函数。敏感值不提交仓库,而是在发布流程中注入。
配置读取遵循“默认值—持久化值—运行时覆盖”的顺序,启动日志会输出最终配置摘要,但掩去密钥。这样现场说“采样频率不对”时,可以先比较事实,而不是猜测烧录了哪一版。
依赖方向比文件夹更重要
我画了一条简单规则:main 可以依赖所有业务组件,业务组件可以依赖基础设施,但基础设施不能反向调用界面。跨组件通知通过事件或窄接口完成。构建时开启循环依赖检查,代码评审看到跨层 include 就先问原因。
重构验收清单
- [x] 全新目录执行一条命令即可构建
- [x] 两种板型只切换配置,不修改源码
- [x] 协议编解码可在电脑上跑单元测试
- [x] 每个组件有负责人、入口文档和错误码说明
- [x] 固件打印版本、提交号与配置摘要
- [x] 删除某个可选组件后,其余工程仍可编译
重构之后代码行数略有增加,但“理解一次修改会影响哪里”的时间明显缩短。目录结构不是为了好看,它是团队对变化方式的共同约定。原型阶段可以简单,准备长期维护时则要尽早固定依赖方向、配置来源和可验证入口。
评论区