课程导论与项目全景:为什么需要分层?
大家好,欢迎来到这门实战课。
先聊个实在的。你写单片机代码多久了?有没有遇到过这种情况:项目刚开始时,代码跑得挺顺。三个月后,加一个新功能要改三个文件。半年后,改一个 bug 能引出两个新 bug。一年后,没人敢动那坨代码了。
嗯,这就是典型的「代码腐烂」。我见过太多项目死在这上面。
一个真实项目的腐烂史
几年前我带过一个智能家居网关项目。硬件平台是 STM32F407,外挂 WiFi、蓝牙、LCD 屏、温湿度传感器、继电器阵列。刚开始团队 3 个人,两周出了原型。
但问题很快就来了。
| 时间节点 | 代码规模 | 典型问题 |
|---|---|---|
| 第 1 个月 | ~3000 行 | 所有代码写在 main.c 里,全局变量满天飞 |
| 第 3 个月 | ~12000 行 | WiFi 驱动和业务逻辑混在一起,换个 ESP8266 固件要改 5 个文件 |
| 第 6 个月 | ~35000 行 | LCD 显示函数里居然有继电器操作,没人说得清依赖关系 |
| 第 9 个月 | ~60000 行 | 新同事入职两周,改了一个延时函数,整个 UI 卡死——因为那个延时被 17 个地方调用 |
为什么会这样?说白了,就是没有分层。硬件操作、业务逻辑、协议解析、用户界面,全搅在一起。你想想看,一个函数里既有 I2C 读写,又有状态机跳转,还有串口打印——这代码怎么维护?
我曾经踩过的坑:有一次客户要求把温湿度传感器从 SHT30 换成 DHT22。我以为只是改个驱动文件,结果发现传感器读取逻辑散落在 8 个模块里,改了整整三天,还引入了一个临界区 bug。从那以后,我强制团队所有项目必须做硬件抽象层。
分层到底解决了什么?
我个人的理解很简单:分层就是把「做什么」和「怎么做」分开。
- 硬件驱动层:只管「怎么读写寄存器」。比如 SPI 发一个字节,I2C 读一个寄存器。
- 硬件抽象层:把具体芯片的操作封装成通用接口。比如「读温度」,不管底下是 SHT30 还是 DHT22。
- 中间件层:协议解析、数据缓存、状态机。不关心数据从哪来,只关心怎么处理。
- 应用层:业务逻辑。比如「温度超过 30 度就开风扇」。
你可能会问:分层会不会增加代码量?会。但增加的是可复用的代码,不是重复的代码。我在项目中算过一笔账:
| 对比项 | 不分层 | 分层 |
|---|---|---|
| 首次开发时间 | 快(省了接口设计) | 慢 20%~30%(接口设计耗时) |
| 换硬件平台 | 重写 60%~80% 代码 | 只改驱动层,约 10%~15% |
| 新功能添加 | 平均 3~5 天 | 平均 1~2 天 |
| Bug 定位时间 | 2~8 小时 | 0.5~1 小时 |
| 团队新人上手 | 2~4 周 | 3~7 天 |
数据是我从三个项目里统计的。虽然不绝对精确,但趋势很明显。
课程目标:你要带走什么?
这门课不是讲理论。30 章内容,每一章都有可运行的代码。最终你会拿到一个完整的嵌入式软件框架,包含:
- 一套通用的 MCU 硬件抽象层接口(GPIO、UART、SPI、I2C、Timer)
- 一个 传感器驱动封装范例(以 SHT30 和 MPU6050 为例)
- 一个 状态机框架(可移植到任何单片机)
- 一个 消息队列与事件总线(模块间解耦的核心)
- 完整的 项目工程模板(Keil / IAR / CMake 三套)
核心交付物:一个经过三个实际项目验证的「单片机软件分层参考架构」。你学完可以直接套在自己的项目上。
知识体系全景
下面这张图,是我做这门课之前画的架构总图。你第一眼看可能觉得复杂,别急,我们一章一章拆。
这张图贯穿整个课程。每一章都会对应到图上的某一层或某几个层之间的接口。你学完一章,就能在图上找到它的位置。
最终交付物预览
课程结束时,你会拿到一个完整的工程仓库。结构大概是这样:
project_root/
├── drivers/ # 芯片驱动层
│ ├── gpio_stm32f4.c
│ ├── uart_stm32f4.c
│ └── i2c_stm32f4.c
├── hal/ # 硬件抽象层接口
│ ├── hal_gpio.h
│ ├── hal_uart.h
│ └── hal_i2c.h
├── middleware/ # 中间件
│ ├── event_bus.c/h
│ ├── msg_queue.c/h
│ └── state_machine.c/h
├── sensors/ # 传感器驱动(基于 HAL)
│ ├── sht30.c/h
│ └── mpu6050.c/h
├── app/ # 应用层
│ ├── temp_control.c/h
│ └── display_manager.c/h
├── port/ # 移植层(不同 MCU 的适配)
│ └── stm32f407/
└── build/ # 构建脚本
├── keil/
├── iar/
└── cmake/
我的建议:不要等到学完再动手。每章结束后,把代码下载到你的开发板上跑一遍。哪怕只改一个引脚配置,也比只看不动强十倍。