1. 架构思维筑基:为什么嵌入式需要架构设计?从单体到模块化的演进之路
1.1 一个让我翻车的项目
先讲个真事。
几年前我接手过一个智能家居网关项目。代码量大概 8 万行,全部写在一个 main.c 里。对,你没看错——一个文件。
当时团队的想法很简单:「单片机资源有限,搞什么架构?能跑就行。」
结果呢?
- 加一个新传感器,要改 6 个函数,动 3 个全局变量
- 修一个 Wi-Fi 重连的 bug,把 LCD 驱动搞崩了
- 新人入职两周,连编译都过不了
- 最要命的是——OTA 升级时死锁,产品已经量产了
那次之后我彻底想明白一件事:嵌入式不是不需要架构,而是太需要了。资源越受限,架构的容错空间就越小。
1.2 单体架构:为什么它曾是「默认选择」?
咱们先看看最原始的状态——单体架构。
说白了就是:所有代码揉在一起,函数随便调,全局变量随便用。
| 特征 | 表现 | 后果 |
|---|---|---|
| 函数调用 | A 函数直接调 B 的内部变量 | 牵一发动全身 |
| 全局变量 | 30+ 个全局变量散落各处 | 改一个,崩一片 |
| 模块边界 | 没有,或者说没人遵守 | 代码像意大利面条 |
| 测试难度 | 几乎无法单元测试 | 全靠硬件联调 |
| 复用性 | 几乎为零 | 换平台就得重写 |
你想想看,为什么早期嵌入式项目都这么写?
原因其实很现实:
- 硬件资源太紧:ROM/RAM 按 KB 算,函数调用开销都心疼
- 功能简单:一个 8051 跑个 LED 流水灯,确实不需要架构
- 工期短:老板说「下周出样机」,谁还管设计
- 工具链落后:很多 IDE 连代码折叠都没有
但问题在于——项目会变大。今天加个传感器,明天加个通信协议,后天要支持 OTA。单体架构的复杂度是指数级增长的,不是线性。
1.3 模块化:从「一团乱麻」到「乐高积木」
那怎么办?
答案就是——模块化。
模块化不是啥高深理论。说白了就是:把系统拆成若干独立单元,每个单元只干一件事,单元之间通过明确的接口通信。
我习惯用「乐高积木」来比喻:
- 每个模块是一块积木
- 接口是积木的凸点和凹槽
- 你可以自由组合,也可以随时替换某一块
来看一个具体例子。这是单体架构的典型代码:
// 单体架构:所有逻辑混在一起
void system_run(void) {
// 读传感器
adc_value = HAL_ADC_Read();
temp = adc_value * 3.3 / 4096 * 100;
// 处理数据
if (temp > 50) {
HAL_GPIO_WritePin(FAN_GPIO, 1);
}
// 显示
sprintf(buf, "Temp: %.1f", temp);
LCD_DisplayString(buf);
// 通信
UART_Send(buf, strlen(buf));
}
再看模块化之后的版本:
// 模块化:每个模块只关心自己的事
void system_run(void) {
float temp = sensor_read_temperature();
if (temp > TEMP_THRESHOLD) {
actuator_control(FAN, ON);
}
display_show_temperature(temp);
comm_send_temperature(temp);
}
看出区别了吗?
- sensor_read_temperature() 只负责读传感器,不管显示和通信
- actuator_control() 只负责控制执行器,不关心温度怎么来的
- display_show_temperature() 只负责显示,不碰硬件
- comm_send_temperature() 只负责通信,不碰传感器
每个模块内部可以随便改,只要接口不变,外面完全不受影响。
1.4 模块化的三个核心原则
光说「拆模块」还不够。怎么拆?拆到什么粒度?
我总结了三个原则:
原则一:单一职责
一个模块只做一件事。比如:
- ✅ 温度传感器驱动模块:只负责读取原始数据
- ❌ 温度传感器驱动模块:既读数据,又做滤波,还负责显示
原则二:接口稳定
模块之间的接口要像「法律条文」一样稳定。一旦定下来,尽量不改。我习惯用 .h 文件来定义接口,.c 文件里随便折腾。
原则三:依赖单向
模块之间的依赖关系应该是单向的,不能有循环依赖。比如:
- ✅ 应用层 → 服务层 → 驱动层
- ❌ 应用层 → 驱动层 → 应用层(循环依赖)
1.5 从单体到模块化的演进路径
很多团队不是不想模块化,而是不知道怎么从一团乱麻里走出来。
我建议分四步走:
- 先画现状图:把当前代码的调用关系、全局变量依赖画出来。这一步很痛苦,但必须做。
- 识别「坏味道」:找那些被到处调用的函数、被多个模块修改的全局变量、超过 500 行的函数。
- 从最痛的地方下手:别想着一次重构完。先挑一个最影响开发效率的模块拆出来。
- 逐步替换:新功能用模块化写,旧功能保持不动。等旧功能需要改的时候,再顺便重构。
这个过程可能持续几个月。别急,架构演进是马拉松,不是百米冲刺。
1.6 本章知识体系
下面这张图总结了从单体到模块化的核心逻辑:
1.7 小结:架构思维是「投资」不是「成本」
很多人觉得做架构设计浪费时间。我刚开始也这么想。
但后来我算了一笔账:
- 一个没有架构的项目,前 3 个月开发速度很快,但第 6 个月开始断崖式下跌
- 一个有架构的项目,前 3 个月慢一些,但第 6 个月之后速度反而越来越快
为什么?因为架构设计是在「还技术债」之前先把债还了。
你想想看,一个模块化的系统:
- 新人上手快——只看自己负责的模块就行
- 测试容易——每个模块可以单独测
- 复用方便——这个项目的驱动,下个项目直接拿来用
- 改需求不怕——只要接口不变,内部随便改
所以我的建议是:从第一个项目就开始用模块化思维。哪怕只是一个 LED 灯的程序,也把驱动、逻辑、应用分开。养成习惯之后,你会发现——架构设计不是负担,而是让你少加班的利器。
- 单体架构在项目初期「快」,后期「慢」
- 模块化的本质是「分而治之」——拆成独立单元,通过接口通信
- 三个原则:单一职责、接口稳定、依赖单向
- 演进路径:画现状 → 识坏味 → 拆痛点 → 逐步替
- 架构思维是长期投资,不是短期成本