第二章:软件架构设计原则:分层架构、模块化设计、高内聚低耦合
各位同学,咱们今天聊点实在的。
做电机控制软件,说白了就是跟硬件打交道。你写的每一行代码,最终都要变成PWM波、电流采样、速度计算。如果代码架构一塌糊涂,那调试起来就是灾难。我见过太多项目,前期图快,代码全塞在一个文件里,结果后期改一个参数要翻半天,还容易改出bug。
所以,这一章咱们把地基打牢。三个核心原则:分层架构、模块化设计、高内聚低耦合。你把这三点吃透了,后面写代码会顺手很多。
2.1 分层架构:把代码像洋葱一样剥开
分层架构,说白了就是「各司其职」。每一层只关心自己的事,不越界。
我习惯把电机控制软件分成三层:
- 硬件抽象层(HAL):直接跟寄存器、外设打交道。比如PWM输出、ADC采样、编码器读取。
- 控制算法层(CAL):做核心运算。比如PID计算、FOC变换、速度估算。
- 应用层(APP):处理用户指令、状态机、故障保护。
为什么要这么分?你想想看,如果我把PID计算和ADC采样代码混在一起,哪天换个MCU,ADC寄存器全变了,我是不是得把PID也重写一遍?
核心思想:上层依赖下层,但下层绝不依赖上层。硬件抽象层改了,控制算法层代码一行都不用动。
举个例子,我做过一个项目,客户要求从STM32换到GD32。因为硬件抽象层封装得好,我只改了HAL层的几个寄存器配置,控制算法和应用层代码直接复制粘贴,两天就搞定了。要是当初没分层,估计得折腾两周。
2.2 模块化设计:把大问题拆成小零件
模块化,就是「分而治之」。一个复杂的电机控制系统,拆成几个独立的模块:电流环模块、速度环模块、位置环模块、通信模块、故障处理模块。
每个模块只做一件事,并且把这件事做好。
我建议每个模块都遵循这样的结构:
// 模块头文件示例:current_loop.h
#ifndef CURRENT_LOOP_H
#define CURRENT_LOOP_H
// 模块初始化
void CurrentLoop_Init(void);
// 模块主函数,周期性调用
void CurrentLoop_Run(void);
// 模块参数设置接口
void CurrentLoop_SetTarget(float iq_ref, float id_ref);
// 模块状态获取接口
float CurrentLoop_GetIq(void);
float CurrentLoop_GetId(void);
#endif
你看,这个模块对外只暴露了四个接口。内部怎么算PID、怎么限幅,外面根本不用管。这就是模块化的好处——接口清晰,内部随便改。
我的习惯:每个模块一个.c和一个.h文件。模块内部函数用static修饰,不让外部调用。这样别人想乱用你的内部变量都做不到。
我曾经遇到一个同事,把所有函数都写在main.c里,全局变量满天飞。结果调试时发现,一个变量在三个地方被修改,查了三天才找到bug。嗯,从那以后,我坚持模块化设计,再也没犯过这种低级错误。
2.3 高内聚低耦合:让模块之间「君子之交淡如水」
这两个词听起来有点玄乎,其实很简单:
- 高内聚:一个模块内部的东西,彼此关系紧密。比如电流环模块里,所有变量和函数都跟电流控制有关,别把温度检测的代码塞进来。
- 低耦合:模块之间依赖越少越好。A模块改了,B模块不受影响。
怎么做到低耦合?我总结了三条铁律:
- 用接口通信,别用全局变量。 模块A想获取电流值,调用CurrentLoop_GetIq(),而不是直接读一个全局变量g_iq。
- 单向依赖。 应用层可以调用控制算法层,但控制算法层不要反过来调用应用层。
- 用回调函数解耦。 比如故障处理模块,不直接调用具体硬件操作,而是注册一个回调函数,由上层决定怎么处理。
避坑指南:我曾经在一个项目中,为了图方便,让电流环模块直接调用了通信模块的发送函数。结果后来通信协议改了,电流环模块也得跟着改。这就是典型的耦合过紧。正确的做法是:电流环只负责计算,计算完把结果放在一个结构体里,由上层决定要不要发送。
你想想看,如果每个模块都像这样「各扫门前雪」,那维护起来得多轻松?改一个模块,其他模块完全不用动。
2.4 实战:一个简单的电机控制架构示例
说了这么多理论,咱们看个实际例子。这是一个典型的FOC电机控制软件架构:
| 层级 | 模块 | 职责 |
|---|---|---|
| 应用层 | 状态机模块 | 处理启动、停止、故障复位等状态切换 |
| 应用层 | 通信模块 | 解析CAN/RS485指令,转发给控制层 |
| 控制算法层 | 电流环模块 | PI调节、Park/Clark变换 |
| 控制算法层 | 速度环模块 | 速度PI调节、速度估算 |
| 硬件抽象层 | PWM模块 | 配置定时器、输出PWM波 |
| 硬件抽象层 | ADC模块 | 采集相电流、母线电压 |
| 硬件抽象层 | 编码器模块 | 读取编码器脉冲,计算角度和速度 |
你看,每一层、每个模块的职责都清清楚楚。如果我要换一种编码器,只需要改硬件抽象层的编码器模块,控制算法层的速度估算完全不用动。
总结一下:
- 分层架构:让代码有层次感,上层依赖下层。
- 模块化设计:把大系统拆成小零件,每个零件独立开发、独立测试。
- 高内聚低耦合:模块内部拧成一股绳,模块之间保持距离。
这三个原则,说白了就是一句话:写代码的时候,多想想「如果这里改了,其他地方会不会受影响?」
好了,这一章就到这里。下一章咱们聊聊具体怎么设计一个可复用的电机控制模块,到时候我会拿一个实际项目中的代码来拆解。嗯,保证干货满满。