3. 软件架构设计原则:分层架构、模块化设计、高内聚低耦合、设计模式在嵌入式中的应用
各位同学,咱们今天聊点实在的。
软件架构设计原则,说白了就是一套「怎么把代码写得不那么痛苦」的方法论。我做了十几年嵌入式,见过太多项目因为一开始架构没想清楚,后期改一个功能要动半套代码,那叫一个酸爽。今天我把几个核心原则掰开揉碎讲给你听。
3.1 分层架构:别让所有代码挤在一个文件里
分层架构,我个人的理解就是「各司其职」。你想想看,一个电池化成设备,从底层硬件到上层业务逻辑,跨度非常大。如果所有代码都混在一起,调试一个通信问题,结果发现跟充放电算法搅在一块,那排查起来简直要命。
我习惯把嵌入式软件分成这么几层:
- 硬件抽象层(HAL):直接跟寄存器、外设打交道。比如读写ADC、控制GPIO。
- 驱动层:封装具体芯片的操作。比如I2C驱动、SPI Flash驱动。
- 中间件层:提供通用服务。比如RTOS、文件系统、通信协议栈。
- 应用层:业务逻辑。比如充放电策略、告警处理、数据上报。
这样做的好处是什么?举个例子,假如我们要把MCU从STM32换成GD32,只需要改HAL层和驱动层,应用层的代码基本不用动。我在一个项目中就遇到过这种情况,客户临时要求换芯片,幸好当时分层做得干净,只花了两天就移植完了。
核心要点:每一层只依赖它的下一层,不要跨层调用。应用层不要直接操作寄存器,驱动层不要写业务逻辑。
3.2 模块化设计:把大问题拆成小问题
模块化设计,说白了就是「分而治之」。一个电池化成设备,功能那么多——电压采集、电流控制、温度监测、通信管理、日志记录……如果全写在一个.c文件里,那代码量轻松上万行,谁维护谁崩溃。
我建议按功能划分模块,每个模块有清晰的接口。比如:
- adc_module.c/h:负责所有ADC通道的采集和滤波
- charge_ctrl.c/h:负责充放电逻辑和PID控制
- comm_module.c/h:负责CAN/RS485通信
- log_module.c/h:负责日志存储和查询
每个模块对外暴露的接口要尽量少,内部实现细节全部隐藏。我见过有人把全局变量满天飞,一个模块改了另一个模块的变量,查bug查到怀疑人生。嗯,这里要注意:模块之间只通过函数调用和消息传递来交互,不要共享全局变量。
小技巧:每个模块的.h文件里只放必要的函数声明和结构体定义,内部使用的宏和静态函数都放在.c文件里。这叫「接口最小化原则」。
3.3 高内聚低耦合:让代码像乐高积木一样灵活
这两个词你可能听过很多次了。我用自己的话解释一下:
- 高内聚:一个模块内部的东西,相关性要强。比如温度采集模块,所有跟温度相关的逻辑——传感器读取、滤波、超限判断——都应该放在一起。
- 低耦合:模块之间的依赖要弱。改了一个模块,尽量不要影响其他模块。
为什么会这么重要?我举个例子。之前有个项目,通信模块和业务逻辑模块耦合得很紧。通信模块里直接调用了业务逻辑的函数,后来业务逻辑要重构,结果通信模块也得跟着改,牵一发动全身。后来我强制要求:所有模块之间通过回调函数或者消息队列通信,谁也别直接调用谁内部的函数。
你看,这样设计之后,每个模块都可以独立测试、独立升级。说白了,就是给自己留条后路。
避坑指南:我曾经在一个项目里看到,有人为了「方便」,在驱动层里直接调用了应用层的函数。结果后来换了驱动芯片,应用层也得跟着改。记住:依赖方向永远是单向的,从应用层指向底层,不要反过来。
3.4 设计模式在嵌入式中的应用:别为了用模式而用模式
设计模式不是Java的专利,嵌入式里同样用得上。但我要强调一点:别生搬硬套。嵌入式资源有限,有些模式会带来额外的开销,得权衡着用。
我常用的几个模式:
| 设计模式 | 适用场景 | 嵌入式示例 |
|---|---|---|
| 状态机模式 | 设备状态切换(空闲、充电、放电、故障) | 充放电流程控制 |
| 观察者模式 | 一个事件触发多个模块响应 | 告警事件通知日志、通信、显示模块 |
| 单例模式 | 全局唯一的资源(如ADC、日志系统) | 系统配置管理器 |
| 命令模式 | 将请求封装成对象,支持队列和撤销 | 上位机指令解析与执行 |
拿状态机模式来说,电池化成设备的状态切换非常典型。我习惯用函数指针表来实现,既高效又清晰:
// 状态机示例
typedef enum {
STATE_IDLE,
STATE_CHARGING,
STATE_DISCHARGING,
STATE_FAULT,
STATE_MAX
} sys_state_t;
typedef void (*state_handler_t)(void);
state_handler_t state_table[STATE_MAX] = {
[STATE_IDLE] = idle_handler,
[STATE_CHARGING] = charging_handler,
[STATE_DISCHARGING] = discharging_handler,
[STATE_FAULT] = fault_handler
};
void state_machine_run(void) {
state_table[current_state]();
}
你看,这样写,每个状态的处理逻辑都独立了,新增一个状态只需要加一个枚举值和对应的处理函数,其他代码不用动。这就是低耦合的体现。
我的建议:设计模式是工具,不是目的。在嵌入式里,优先考虑代码的可读性和资源占用。如果一个模式让代码变复杂了,那就别用。我见过有人为了用观察者模式,搞了一堆链表和回调注册,结果RAM不够用了,得不偿失。
3.5 总结一下
今天讲的这几个原则,其实就一句话:写代码的时候,多想想以后别人(包括你自己)怎么维护。
分层架构让你改得动,模块化让你找得到,高内聚低耦合让你拆得开,设计模式让你写得巧。这些不是理论空谈,是我在无数个加班debug的夜晚里总结出来的血泪教训。
下一章咱们聊聊具体的代码组织方式和命名规范,到时候我会拿一个实际的电池化成设备项目来拆解。今天就先到这里,有问题随时交流。