2. 分层架构总览:经典三层架构(HAL、MAL、AL)的定义与职责边界
好,咱们正式开始聊分层架构。
做电机控制这么多年,我见过太多人一上来就写业务逻辑,把PWM配置、电流采样、速度环计算全揉在一个文件里。结果呢?换个电机型号,代码重写一半。换个MCU,直接重来。这种痛苦,我经历过不止一次。
所以,分层架构不是花架子。它是让你从「能用」走向「好用」的关键一步。
2.1 为什么是三层?不是两层,也不是四层?
你可能会问:为什么非得三层?两层不行吗?
我个人的习惯是:三层刚刚好。两层的话,硬件和业务逻辑耦合太紧。四层呢?对于大多数电机控制项目来说,有点过度设计了。
这三层分别是:
- HAL(硬件抽象层):直接跟寄存器、外设打交道。
- MAL(电机抽象层):封装电机控制的核心算法和状态机。
- AL(应用层):处理用户指令、通信协议、系统逻辑。
说白了,就是「硬件-算法-应用」三个世界,各管各的。
2.2 HAL:硬件抽象层——把MCU的脾气藏起来
HAL的职责很纯粹:屏蔽硬件差异。
你用的是STM32还是GD32?是TI的C2000还是国产的AT32?HAL层把这些差异全部封装掉。上层代码根本不需要知道你在用哪个定时器、哪个DMA通道。
HAL层典型接口示例:
// HAL层:PWM输出接口
void HAL_PWM_SetDuty(uint8_t ch, uint16_t duty);
void HAL_PWM_Start(uint8_t ch);
void HAL_PWM_Stop(uint8_t ch);
// HAL层:ADC采样接口
uint16_t HAL_ADC_GetValue(uint8_t ch);
void HAL_ADC_StartConversion(void);
uint8_t HAL_ADC_IsConversionDone(void);
嗯,这里要注意:HAL层不要做任何业务判断。它就是个「工具人」,你让它读电流,它就老老实实读。别在HAL层里算转速,那是MAL的事。
我的经验:我曾经在一个项目里,把电流滤波算法放在了HAL层。结果换MCU时,滤波参数全得重调。后来我学乖了,HAL层只做最原始的读写,所有数据处理都往上提一层。
2.3 MAL:电机抽象层——算法的家
MAL层是电机控制的核心。FOC算法、六步换向、PID调节、速度环、电流环……这些都在这里。
MAL层不关心你用的是什么MCU,也不关心用户按了哪个按钮。它只关心一件事:给定目标值,算出正确的控制量。
MAL层典型接口示例:
// MAL层:电机控制接口
void MAL_Motor_Init(Motor_Type_t type);
void MAL_Motor_SetSpeed(int16_t rpm);
void MAL_Motor_SetTorque(int16_t torque);
void MAL_Motor_Run(void); // 主循环调用
void MAL_Motor_Stop(void);
你想想看,MAL层就像一个黑盒子。你告诉它「我要转1000转」,它自己算好PWM占空比,然后调用HAL层的接口去设置。至于HAL层用的是哪个定时器,MAL层完全不知道。
避坑指南:我曾经犯过一个错误——在MAL层里直接操作GPIO。结果换板子时,GPIO引脚号变了,MAL层代码改得面目全非。记住:MAL层只能调用HAL层的接口,不能直接碰寄存器。
2.4 AL:应用层——跟人打交道
AL层是离用户最近的一层。它处理的事情包括:
- 解析CAN总线或串口发来的指令
- 处理按键输入
- 管理系统状态机(启动、停止、故障恢复)
- 记录日志、上报状态
说白了,AL层就是个「翻译官」。它把用户的「我要加速」翻译成MAL层的「SetSpeed(2000)」,再把MAL层的「当前转速1800」翻译成「发送CAN报文0x181」。
AL层典型接口示例:
// AL层:应用逻辑接口
void AL_ProcessCommand(uint8_t* cmd, uint8_t len);
void AL_UpdateSystemState(void);
void AL_ReportStatus(void);
2.5 三层之间的调用关系
调用方向是单向的:AL → MAL → HAL。绝对不能反向调用。
为什么?因为一旦MAL层能直接调用AL层,你的代码就变成了意大利面条。我见过一个项目,MAL层里居然调用了串口打印函数。结果调试时没问题,量产时串口被占用了,整个电机控制就崩了。
| 层级 | 依赖关系 | 典型内容 | 可移植性 |
|---|---|---|---|
| AL | 依赖MAL | 通信协议、状态机、用户逻辑 | 高(与硬件无关) |
| MAL | 依赖HAL | FOC、PID、速度环、电流环 | 中(算法通用,接口依赖HAL) |
| HAL | 无依赖 | PWM、ADC、GPIO、定时器 | 低(与MCU强相关) |
2.6 一个实际例子:启动电机
咱们走一遍流程,你就明白了。
- AL层收到CAN报文「启动电机,目标转速3000」。
- AL层调用
MAL_Motor_SetSpeed(3000)。 - MAL层启动速度环PID计算,算出需要输出50%占空比。
- MAL层调用
HAL_PWM_SetDuty(1, 500)(500对应50%)。 - HAL层直接写定时器比较寄存器,PWM波形输出。
你看,每一层只做自己的事。出了问题也很好定位:电机不转?先查HAL层PWM有没有波形,再查MAL层PID有没有输出,最后查AL层指令有没有发对。
我的习惯:调试时,我会在每一层加一个「自检函数」。比如HAL层自检:PWM输出引脚是否有电平变化?MAL层自检:PID输出值是否在合理范围?AL层自检:通信链路是否正常?这样分层排查,效率极高。
2.7 什么时候可以打破分层?
说实话,没有绝对的金科玉律。我偶尔也会打破分层,但有两个前提:
- 性能瓶颈:比如电流环需要在中断里直接操作寄存器,绕过HAL层。但我会用宏或条件编译明确标记出来。
- 原型验证:快速出Demo时,先写在一起。但量产前必须重构。
警告:「先这样写,后面再改」是最大的谎言。我见过太多项目,原型代码直接变成了量产代码。如果你决定分层,就从第一行代码开始分层。
2.8 小结
三层架构不是什么高深理论。它就是一套规矩:
- HAL层:只管硬件,别想业务。
- MAL层:只管算法,别碰硬件。
- AL层:只管应用,别算电流。
遵守这套规矩,你的代码就能轻松移植、容易调试、方便扩展。下一章,咱们会深入HAL层的具体设计,聊聊怎么把MCU的寄存器封装得既高效又易用。