2、系统架构总览:分层架构设计
好,咱们直接进入正题。电池均衡系统的软件架构,说白了就是一套「分而治之」的思路。你想想看,一个BMS要管几十甚至上百节电芯,还要实时监控、计算、通信、执行均衡动作,如果所有代码都揉在一起,那调试起来绝对是一场噩梦。
我个人习惯把系统拆成三层:应用层、算法层、驱动层。每一层各司其职,层与层之间通过清晰的接口通信。这样做的好处是——哪一层出了问题,你只需要动那一层,不用把整个系统翻个底朝天。
2.1 为什么一定要分层?
我记得刚入行那会儿,接手过一个老项目。代码里直接操作寄存器做均衡控制,ADC采样和均衡策略写在一个函数里,足足两千行。每次改需求,我都得抱着代码啃三天,生怕改崩了别的地方。
分层架构就是为了解决这种「牵一发而动全身」的痛点。它的核心原则就三条:
- 高内聚:每一层只关心自己的事。驱动层只管读写硬件,算法层只管算均衡策略,应用层只管调度和交互。
- 低耦合:层与层之间只通过接口函数通信。驱动层换了芯片,算法层代码一行都不用改。
- 可替换:每一层都可以独立测试、独立升级。比如你想把均衡算法从「被动均衡」换成「主动均衡」,只需要替换算法层,其他两层纹丝不动。
核心观点:分层不是花架子,是让你少加班的真功夫。
2.2 应用层:管调度、管逻辑、管人机
应用层是离用户最近的一层。它不关心电芯电压具体是多少毫伏,也不关心均衡电流怎么控制。它只关心三件事:
- 状态机管理:系统什么时候该均衡?什么时候该休眠?什么时候报故障?
- 通信协议:把均衡结果、电芯状态打包成CAN报文或者Modbus帧,发给整车控制器或者上位机。
- 日志与诊断:记录均衡历史、异常事件,方便后期排查。
举个例子,应用层里会有一个主循环,大概长这样:
void App_MainLoop(void)
{
while(1)
{
App_StateMachine_Run(); // 状态机调度
Bal_Algorithm_Execute(); // 调用算法层
Drv_Actuator_Control(); // 调用驱动层
App_Comm_SendStatus(); // 发送状态
App_Delay(100); // 100ms周期
}
}
嗯,这里要注意:应用层不要直接调用驱动层的寄存器操作函数。中间必须经过算法层或者一个抽象接口。我曾经见过一个项目,应用层里直接写GPIO_SetLow()来控制均衡MOS管,结果换了MCU型号,整个应用层代码全废了。
2.3 算法层:算策略、算阈值、算时间
算法层是整个系统的「大脑」。它从驱动层拿到原始数据(电压、温度、电流),然后算出:
- 哪些电芯需要均衡?
- 均衡电流多大?
- 均衡多长时间?
- 要不要暂停均衡去充电?
我个人习惯把算法层再拆成两个子模块:
| 子模块 | 职责 | 典型算法 |
|---|---|---|
| 均衡判定模块 | 判断哪些电芯需要均衡 | 电压排序法、SOC差值法 |
| 均衡执行模块 | 计算均衡时长和电流 | PID控制、查表法 |
这里有个避坑指南:算法层一定要做输入校验。我曾经遇到过驱动层因为ADC干扰,传上来一个-5V的电压值。算法层没做校验,直接拿这个值去算均衡时间,结果把电芯过放了。从那以后,我在算法层入口加了一个IsVoltageValid()函数,所有数据先过一遍「安检」。
警告:算法层不要直接操作硬件!它只负责算,不负责做。算出来的结果通过结构体或者回调函数传给应用层,再由应用层调度驱动层去执行。
2.4 驱动层:管硬件、管时序、管隔离
驱动层是系统的最底层,直接跟硬件打交道。它的职责非常纯粹:
- ADC采样:读取电芯电压、温度、电流。
- 均衡控制:打开或关闭均衡MOS管,设置PWM占空比。
- 通信接口:SPI、I2C、CAN底层收发。
- 故障检测:硬件看门狗、过流检测、短路保护。
驱动层的设计原则是「一次封装,到处调用」。比如均衡控制函数,我通常会写成这样:
// 驱动层接口
void Drv_Bal_Enable(uint8_t cellIndex, bool enable);
void Drv_Bal_SetCurrent(uint8_t cellIndex, uint16_t currentMa);
// 算法层调用时,完全不知道底层是GPIO还是PWM
Bal_Algorithm_Control(cellIndex, 100); // 100mA均衡电流
你想想看,如果将来从被动均衡换成主动均衡,驱动层只需要改Drv_Bal_Enable和Drv_Bal_SetCurrent的实现,算法层和应用层完全不用动。这就是分层带来的好处。
小技巧:驱动层里建议加一个「硬件抽象层(HAL)」。比如把Drv_GPIO_Write()再包一层HAL_GPIO_Write(),这样换MCU时只需要改HAL层,驱动层代码几乎不用动。我现在的项目都是这么干的,省心不少。
2.5 模块划分与职责总结
好了,咱们把三层架构的模块划分和职责整理一下。我习惯用一张表来概括:
| 层级 | 模块名称 | 核心职责 | 典型接口 |
|---|---|---|---|
| 应用层 | 状态机管理 | 系统状态切换、均衡启停 | App_StateMachine_Run() |
| 应用层 | 通信管理 | CAN/Modbus报文收发 | App_Comm_Send() |
| 应用层 | 日志管理 | 均衡历史记录、故障存储 | App_Log_Write() |
| 算法层 | 均衡判定 | 电芯电压/SOC比较、筛选 | Bal_Judge_NeedBalance() |
| 算法层 | 均衡执行 | 计算均衡时长、电流 | Bal_Calc_Time() |
| 算法层 | 数据校验 | 输入数据合法性检查 | Bal_Validate_Data() |
| 驱动层 | ADC采样 | 电芯电压/温度采集 | Drv_ADC_Read() |
| 驱动层 | 均衡控制 | MOS管/PWM控制 | Drv_Bal_Control() |
| 驱动层 | 通信驱动 | SPI/I2C/CAN底层收发 | Drv_CAN_Send() |
2.6 我的一点经验之谈
最后说几句掏心窝子的话。分层架构听起来很美好,但实际落地时容易犯两个错误:
- 过度分层:有人恨不得分七八层,结果每层就几行代码,调用链绕来绕去,调试时跳转得头晕。我个人建议,嵌入式系统三层就够了,最多四层(加一个硬件抽象层)。
- 层间耦合:最常见的是算法层偷偷调用驱动层的函数。比如算法层里直接写
HAL_GPIO_WritePin()来控制均衡灯。这种「越级操作」一旦多了,分层就形同虚设。
我的做法是:在代码评审时,专门检查层间调用关系。如果发现算法层里出现了Drv_或者HAL_开头的函数,直接打回重写。规矩立好了,后面的人就不敢乱来了。
好,这一章就到这里。下一章咱们深入算法层,聊聊具体的均衡策略——电压排序法、SOC差值法,还有我踩过的那些坑。