第二章:软件架构设计原则:分层架构、模块化设计、高内聚低耦合

各位同学,咱们今天聊点实在的。

做电机控制软件,说白了就是跟硬件打交道。你写的每一行代码,最终都要变成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模块不受影响。

怎么做到低耦合?我总结了三条铁律:

  1. 用接口通信,别用全局变量。 模块A想获取电流值,调用CurrentLoop_GetIq(),而不是直接读一个全局变量g_iq。
  2. 单向依赖。 应用层可以调用控制算法层,但控制算法层不要反过来调用应用层。
  3. 用回调函数解耦。 比如故障处理模块,不直接调用具体硬件操作,而是注册一个回调函数,由上层决定怎么处理。

避坑指南:我曾经在一个项目中,为了图方便,让电流环模块直接调用了通信模块的发送函数。结果后来通信协议改了,电流环模块也得跟着改。这就是典型的耦合过紧。正确的做法是:电流环只负责计算,计算完把结果放在一个结构体里,由上层决定要不要发送。

你想想看,如果每个模块都像这样「各扫门前雪」,那维护起来得多轻松?改一个模块,其他模块完全不用动。

2.4 实战:一个简单的电机控制架构示例

说了这么多理论,咱们看个实际例子。这是一个典型的FOC电机控制软件架构:

层级 模块 职责
应用层 状态机模块 处理启动、停止、故障复位等状态切换
应用层 通信模块 解析CAN/RS485指令,转发给控制层
控制算法层 电流环模块 PI调节、Park/Clark变换
控制算法层 速度环模块 速度PI调节、速度估算
硬件抽象层 PWM模块 配置定时器、输出PWM波
硬件抽象层 ADC模块 采集相电流、母线电压
硬件抽象层 编码器模块 读取编码器脉冲,计算角度和速度

你看,每一层、每个模块的职责都清清楚楚。如果我要换一种编码器,只需要改硬件抽象层的编码器模块,控制算法层的速度估算完全不用动。

总结一下:

  • 分层架构:让代码有层次感,上层依赖下层。
  • 模块化设计:把大系统拆成小零件,每个零件独立开发、独立测试。
  • 高内聚低耦合:模块内部拧成一股绳,模块之间保持距离。

这三个原则,说白了就是一句话:写代码的时候,多想想「如果这里改了,其他地方会不会受影响?」

好了,这一章就到这里。下一章咱们聊聊具体怎么设计一个可复用的电机控制模块,到时候我会拿一个实际项目中的代码来拆解。嗯,保证干货满满。