2、BMS软件架构总览:分层架构、模块划分、数据流与任务调度
好,咱们进入正题。这一章我打算聊聊BMS软件的整体骨架——架构。你想想看,一个BMS少说也有几十个功能模块,采集、均衡、通信、诊断……如果没有一个清晰的架构,代码写起来就是一团乱麻。我个人习惯,在动手写第一行代码之前,先把架构图在白板上画清楚。
2.1 分层架构:各司其职
BMS软件我倾向于采用经典的三层架构:应用层、中间件层、驱动层。为什么这么分?说白了就是为了解耦。每一层只关心自己的事,不越界。
| 层级 | 职责 | 典型内容 |
|---|---|---|
| 应用层 | 业务逻辑 | 状态机、SOC/SOH估算、均衡策略、故障诊断 |
| 中间件层 | 数据管理与服务 | 任务调度、数据缓存、协议解析、日志管理 |
| 驱动层 | 硬件抽象 | ADC驱动、GPIO控制、SPI/I2C/CAN收发 |
驱动层直接跟硬件打交道。比如采集芯片的SPI读写、温度传感器的I2C读取,都在这一层。我建议驱动层只做一件事:把硬件寄存器的值读出来,转成软件能理解的数值。别在这里做滤波、做校准,那是上层的事。
中间件层是个“粘合剂”。它把驱动层拿到的原始数据缓存起来,按优先级分发给应用层。同时,任务调度也在这里完成。嗯,这里要注意,中间件层不要掺和业务逻辑,否则后面改需求你会很痛苦。
应用层是核心。状态机、均衡策略、故障诊断……所有跟电池管理相关的“大脑”都在这里。应用层只调用中间件层提供的接口,不直接操作硬件。这样做的好处是:换硬件平台时,只需要重写驱动层,应用层几乎不用动。
核心原则:上层依赖下层,下层不依赖上层。驱动层不知道中间件层是谁,中间件层不知道应用层在干什么。各层之间通过接口通信,而不是直接调用函数。
2.2 模块划分:五大核心模块
分层是纵向的切分,模块是横向的划分。我一般把BMS拆成五个核心模块:采集模块、状态机模块、均衡模块、通信模块、诊断模块。
2.2.1 采集模块
负责电压、电流、温度的采集。这里有个坑——采集时序。我曾经在一个项目里,电压和温度采集用了同一个ADC通道,结果因为切换不及时,导致数据错位。后来我强制要求:每个采集通道独立配置采样周期,并且用DMA传输,避免CPU干预。
2.2.2 状态机模块
这是BMS的灵魂。从休眠、唤醒、预充电、运行、故障到休眠,每个状态都有明确的进入条件和退出条件。我习惯用枚举定义状态,用二维表管理状态转移。这样代码可读性高,也方便后期维护。
2.2.3 均衡模块
被动均衡还是主动均衡?这个看成本。但不管哪种,均衡策略一定要放在应用层。我见过有人把均衡逻辑写在驱动层的中断里,结果调试时差点崩溃。均衡模块需要跟采集模块配合:先采集,再判断,最后执行均衡。
2.2.4 通信模块
CAN、RS485、以太网……通信协议五花八门。我建议通信模块只负责收发数据,不解析业务。比如收到一条CAN报文,通信模块把它放到缓冲区,然后通知应用层来取。解析报文的事,交给应用层去做。
2.2.5 诊断模块
故障检测、故障记录、故障恢复。诊断模块要独立于其他模块运行。为什么?因为即使采集模块挂了,诊断模块也要能报出“采集故障”。我习惯给诊断模块一个独立的定时器,定期检查所有模块的健康状态。
个人经验:模块之间尽量用消息队列通信,不要用全局变量。全局变量虽然方便,但调试时你根本不知道谁改了它。消息队列可以记录谁发了什么消息,排查问题快得多。
2.3 数据流:数据是怎么跑的?
数据流其实很简单,就三步:采集 → 处理 → 输出。但细节决定成败。
- 采集阶段:驱动层从硬件读取原始数据,比如电压值(ADC码值)。然后通过回调函数或消息,把数据传给中间件层。
- 处理阶段:中间件层对原始数据进行转换(比如ADC码值转成毫伏)、滤波(滑动平均或卡尔曼滤波),然后存入共享数据区。应用层从共享数据区读取处理后的数据,执行状态机、均衡策略等逻辑。
- 输出阶段:应用层生成控制指令(比如闭合继电器、启动均衡),通过中间件层下发到驱动层,驱动层执行硬件操作。
这里有个关键点:数据一致性。我曾经遇到一个问题:采集线程在更新电压数据时,状态机线程正在读取同一块内存,结果读到了半新半旧的数据。解决方案很简单——加锁,或者用双缓冲。我倾向于双缓冲,因为锁在高频采集场景下会影响实时性。
避坑指南:千万不要在中断服务函数里做数据处理。中断里只做“搬数据”,把数据从硬件寄存器搬到内存缓冲区。数据处理放到任务里做。我曾经因为在中断里做滤波,导致中断响应时间过长,系统直接跑飞了。
2.4 任务调度:谁先谁后?
BMS是个实时系统,任务调度至关重要。我常用的调度策略是:时间片轮转 + 优先级抢占。
| 任务名称 | 优先级 | 周期 | 说明 |
|---|---|---|---|
| 采集任务 | 最高 | 10ms | 必须准时,否则数据丢失 |
| 状态机任务 | 高 | 50ms | 状态切换不能延迟太久 |
| 通信任务 | 中 | 100ms | 收发报文,允许一定延迟 |
| 均衡任务 | 低 | 500ms | 均衡是慢过程,不着急 |
| 诊断任务 | 中 | 200ms | 定期检查,但不能漏检 |
采集任务优先级最高,因为它是所有数据的源头。状态机任务次之,因为安全相关的状态切换必须及时。通信任务和诊断任务优先级中等,均衡任务优先级最低。
我建议用操作系统(比如FreeRTOS)来管理任务调度,而不是自己写超级循环。自己写调度器容易出问题,尤其是当任务数量增多时,优先级反转、死锁这些坑会接踵而至。用现成的RTOS,省心又可靠。
总结一下:分层架构让代码清晰,模块划分让功能独立,数据流让信息有序,任务调度让系统实时。这四个方面做好了,BMS软件就成功了一半。剩下的,就是细节和测试了。
嗯,这一章就到这里。下一章咱们深入状态机,聊聊怎么设计一个健壮的状态转移表。到时候我会拿一个实际项目中的例子来拆解,保证干货满满。