1. 课程导论与分层思想:为什么需要分层架构?
大家好,我是你们这门课的老朋友。做了十几年单片机开发,踩过的坑比吃过的盐还多。今天咱们开篇第一讲,不急着上代码,先聊聊一个根本问题——为什么你的代码总是写着写着就成了一团乱麻?
说白了,就是“面条代码”在作祟。我刚入行那会儿,写一个温控器项目,所有逻辑全塞在一个 main.c 里。按键扫描、温度读取、PID计算、LCD显示……全搅在一起。结果呢?客户要改个显示格式,我改了三天,还引入三个新bug。那滋味,真不好受。
1.1 从“面条代码”说起
什么是面条代码?你想想看,一个函数里既有硬件寄存器操作,又有业务逻辑判断,还夹杂着延时等待。就像一碗面条,你夹起一根,带出一整碗。改一处,全盘都得动。
我见过最夸张的项目,一个 while(1) 循环里写了 2000 多行。没有函数,没有模块,全靠全局变量传参。后来换了个新人接手,直接崩溃——不是人崩溃,是代码崩溃,编译都过不了。
| 特征 | 面条代码 | 模块化代码 |
|---|---|---|
| 函数长度 | 几百上千行 | 几十行,职责单一 |
| 耦合度 | 高,改一处动全身 | 低,接口清晰 |
| 可测试性 | 几乎无法单元测试 | 可以 mock 硬件做测试 |
| 复用性 | 复制粘贴改参数 | 直接调用接口 |
| 维护成本 | 指数级增长 | 线性增长 |
1.2 分层架构的核心思想
分层架构,说白了就是“各司其职,各管一摊”。我们把代码按职责切分成几个层次,每一层只关心自己的事,通过定义好的接口跟上下层打交道。
我个人习惯把单片机软件分成三层:
- 硬件抽象层(HAL):直接跟寄存器、外设打交道。比如 GPIO 置高置低、SPI 收发一个字节。
- 中间层(Middleware):封装一些通用功能。比如按键消抖、环形缓冲区、协议解析。
- 应用层(Application):业务逻辑。比如“温度超过 50 度就打开风扇”。
这样做的好处很明显:硬件换了,只改 HAL 层;业务逻辑变了,只改应用层。中间层几乎不用动。
HAL_XXX_Read() / HAL_XXX_Write() 的形式。应用层永远不直接操作寄存器。这样即使换 MCU,应用层代码一行都不用改。
1.3 接口抽象:让代码“活”起来
分层只是第一步。真正让代码灵活的是接口抽象。什么意思呢?就是定义一套通用的操作接口,底层具体怎么实现,上层不关心。
举个例子。你有一个温湿度传感器,可能是 DHT11,也可能是 SHT30。如果应用层直接调用 DHT11_Read(),那换传感器就得改应用层代码。但如果你定义一个抽象接口:
// 抽象传感器接口
typedef struct {
int8_t (*init)(void);
int8_t (*read_temperature)(float *temp);
int8_t (*read_humidity)(float *humi);
} sensor_t;
// 应用层只调用这个接口
sensor_t my_sensor;
my_sensor.init();
my_sensor.read_temperature(&temp);
这样,底层换传感器,只需要换一个 sensor_t 实例,应用层代码纹丝不动。我在一个智能家居项目里用过这个模式,后来客户要求从 DHT11 换成 SHT30,我只花了半小时改驱动,应用层一行没动。
1.4 从“能跑”到“好维护”的演进路径
很多初学者觉得:“我的代码能跑就行,搞那么复杂干嘛?”嗯,这话我年轻时也说过。直到项目规模超过 5000 行,bug 开始像雨后春笋一样冒出来,我才明白:能跑只是及格线,好维护才是硬道理。
我建议的演进路径是这样的:
- 第一阶段:把所有功能写在一个文件里,先跑通。这叫“原型验证”。
- 第二阶段:按功能拆成多个 .c/.h 文件。比如
led.c、key.c、uart.c。这叫“模块化”。 - 第三阶段:引入分层思想,把硬件操作和业务逻辑分开。这叫“架构化”。
- 第四阶段:定义抽象接口,支持多平台、多硬件。这叫“平台化”。
这门课,我们会从第二阶段开始,一路走到第四阶段。每一章都会配合实战代码,让你亲手感受从“面条”到“模块”再到“架构”的蜕变。
1.5 本章小结
这一讲,我们聊了:
- 面条代码的典型特征和危害
- 分层架构的三层模型:HAL、中间层、应用层
- 接口抽象的核心思想:面向接口编程,而不是面向实现
- 从“能跑”到“好维护”的四个演进阶段
下一章,我们会动手搭建第一个分层项目——一个带按键和 LED 的简单系统。你会亲眼看到,同样的功能,用分层写和用面条写,差别有多大。
嗯,咱们下章见。