逻辑视图:模块划分原则、接口定义规范、层与分区设计
好,咱们接着聊逻辑视图。这东西说白了,就是系统的骨架图。你想想看,一个嵌入式系统动辄几十万行代码,如果没有清晰的逻辑结构,那维护起来简直就是噩梦。我个人习惯,拿到需求后第一件事不是写代码,而是画逻辑视图。嗯,这一步省了,后面全是坑。
模块划分原则:高内聚、低耦合的落地
模块划分,教科书上天天讲「高内聚、低耦合」。但怎么落地?我总结了几条实战经验。
- 单一职责原则:一个模块只做一件事。比如驱动层就管硬件操作,别把业务逻辑塞进去。我在项目中遇到过有人把PID控制算法直接写在GPIO中断里,结果后来想复用驱动,发现根本拆不开。
- 接口最小化:暴露给外部的接口越少越好。能隐藏的细节全部隐藏。你想想看,接口多了,耦合就重了,改一个地方可能牵动全身。
- 依赖方向稳定:高层模块不要依赖低层模块的具体实现。说白了,业务逻辑应该依赖抽象接口,而不是具体的硬件寄存器操作。
核心原则:模块之间的通信,尽量通过定义好的接口进行,不要直接访问对方的内部数据结构。这是血的教训换来的。
接口定义规范:别让接口成为瓶颈
接口定义,我见过太多团队在这上面翻车。有的接口参数多达十几个,有的返回值含义模糊。嗯,这里要注意几点。
- 命名要自解释:函数名、参数名要让人一看就懂。比如
adc_read_channel(uint8_t ch)就比get_data(int a)好得多。 - 参数数量控制:我个人习惯,参数超过4个就要考虑封装成结构体了。否则调用者很容易搞错顺序。
- 返回值统一:建议统一返回错误码,而不是混合返回数据和状态。我曾经在一个项目里看到有的函数返回-1表示错误,有的返回NULL,有的返回false,调试起来简直想砸键盘。
// 好的接口示例
typedef struct {
uint8_t channel;
uint16_t sample_rate;
uint8_t resolution;
} adc_config_t;
int32_t adc_init(adc_config_t *config);
int32_t adc_read(uint16_t *value);
int32_t adc_deinit(void);
个人技巧:接口文档一定要和代码同步更新。我见过太多接口文档还写着V1.0的接口,代码已经改到V3.0了。建议用Doxygen这类工具从代码注释自动生成文档。
层与分区设计:把系统切成蛋糕
分层设计,说白了就是把系统按职责切分成若干层。常见的三层架构:硬件抽象层(HAL)、中间件层、应用层。但实际项目中,我建议再加一个「服务层」。
| 层次 | 职责 | 典型内容 |
|---|---|---|
| 应用层 | 业务逻辑、用户交互 | 状态机、任务调度、UI |
| 服务层 | 系统服务、资源管理 | 日志、内存管理、任务间通信 |
| 中间件层 | 协议栈、算法库 | TCP/IP、FATFS、加密算法 |
| 硬件抽象层 | 屏蔽硬件差异 | GPIO、UART、SPI驱动接口 |
| 硬件层 | 物理硬件 | MCU、传感器、执行器 |
分区设计呢,则是把同一层内的功能再切分成更小的区块。比如硬件抽象层,可以按外设类型分区:GPIO分区、定时器分区、通信接口分区。每个分区独立编译、独立测试。
避坑指南:我曾经在一个项目里,把所有驱动都写在一个文件里,结果后来换MCU,那个文件改了两周还没改完。从那以后,我坚持每个外设一个文件,每个文件不超过500行。
层间通信:规矩要定好
层与层之间怎么通信?我建议遵循「单向依赖」原则。上层可以调用下层,下层绝对不能反向调用上层。如果下层需要通知上层事件,用回调函数或者消息队列。
// 下层向上层通知事件的正确姿势
typedef void (*event_callback_t)(uint32_t event_id, void *data);
void hal_register_callback(event_callback_t cb);
void hal_event_trigger(uint32_t event_id, void *data);
你想想看,如果下层直接调用上层函数,那分层就失去了意义。改上层代码时,下层也得跟着改,这不又回到一锅粥的状态了吗?
实际案例:一个温控系统的逻辑视图
拿一个温控系统举例。应用层负责温度设定和显示逻辑;服务层负责温度数据的滤波和报警判断;中间件层跑着PID算法库;硬件抽象层封装了温度传感器和加热器的驱动。每个层之间通过定义好的接口通信,互不干扰。
我记得有一次,客户要求把温度传感器从DS18B20换成NTC热敏电阻。我只需要改硬件抽象层的驱动代码,上层逻辑完全不用动。这就是分层设计的好处——改一层,不影响其他层。
总结一下:逻辑视图不是画完就完事的。它需要你在编码过程中不断迭代优化。模块划分、接口定义、分层分区,这三件事做好了,你的系统架构就稳了一半。另一半,嗯,留到后面的章节再聊。