ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于EKF的锂电池SOC估计Simulink仿真实现

基于EKF的锂电池SOC估计Simulink仿真实现

直接以程序员/嵌入式工程师的口吻来写这篇博文,避免任何AI痕迹,从实际开发者的第一视角出发。不添加任何前置说明。 ## 1. 方案整体拆解:为什么偏偏是EKF,而不是查表法或粒子滤波

锂电池SOC估计这个题目,做BMS或者电池仿真的人都不陌生。它不像电压、电流那样可以直接用传感器测回来,只能通过间接手段去推。行业内公认的难点在于:SOC没办法直接测量,而且电池本身是个强非线性系统,充放电倍率、温度、老化程度都会让参数漂移。

先说结论:EKF(扩展卡尔曼滤波)在工程上是一个“性价比最优解”。它把卡尔曼滤波的线性假设扩展到了非线性系统,通过对状态方程和观测方程做泰勒展开,只保留一阶项,用雅可比矩阵完成线性化。这种做法虽然在强非线性场景下会有轻微精度损失,但对于电池SOC估计这种工况来说,精度完全够用,而且计算量远小于粒子滤波。

我做这个Simulink仿真模型时,核心目标有三条:一是能够把“真实电池”和“估计算法”完全分成两个独立子系统;二是能从错误初始SOC开始收敛到真值;三是能够模拟传感器噪声、电流激励变化对估计过程的影响。最终模型用一阶RC等效电路模型作为被控对象,用EKF作为估计器,两者在同一个Simulink工程里协同工作。

为什么不直接用安时积分?安时积分法本身没有闭环反馈,电流传感器一旦有直流偏置或标定误差,SOC误差会随时间累积且无法自愈。查表法依赖OCV-SOC映射,但在充放电过程中端电压包含欧姆压降和极化压降,OCV很难在线直接测得。EKF好就好在它天然是个闭环结构:用测量到的端电压去修正状态预测,即使初始SOC给错了,只要工况持续激励,状态会逐步收敛到真实值附近。

2. Simulink模型搭建核心细节

2.1 一阶RC等效电路模型的数学表达

用一阶RC模型来模拟锂离子电池,是最常见的工程折中。它用一个受控电压源(OCV)串联欧姆内阻R0,再串联一个RC并联网络来表征极化效应。模型背后的物理直觉可以这样理解:R0代表电解液、隔膜和电极接触电阻的瞬时压降,RC网络则代表锂离子浓度梯度建立的过程,也就是极化现象的暂态特性。

模型的状态方程可以写为:

  • 荷电状态递推:SOC(k+1) = SOC(k) - (η·Δt/Capacity)·i(k)
  • 极化电压递推:Vp(k+1) = exp(-Δt/τ)·Vp(k) + R1·(1 - exp(-Δt/τ))·i(k)

其中τ = R1·C1,是极化时间常数。这个公式的推导很直观:RC网络的时域响应是一阶指数过程,离散化后就是当前值按指数衰减,同时新电流通过电阻产生新的压降。我把容量单位统一为Ah,电流单位统一为A,时间单位统一为秒,所以SOC更新项中的系数是Δt/(3600·Capacity),这一步很多人容易漏掉3600,导致SOC变化速度快得离谱。

端电压输出方程为:Vt(k) = OCV(SOC(k)) + Vp(k) + R0·i(k)。

2.2 OCV-SOC曲线的获取与拟合

OCV-SOC映射是EKF观测方程的灵魂。没有一条足够准确的映射曲线,整个滤波器的观测矩阵H就是错的。我用的方法是:以0.02C的低倍率对电池进行完整放电,静置足够长时间让极化充分恢复,再记录每一小段的开路电压。最小二乘拟合用7阶多项式,阶数太低中间段拟合不好,阶数太高两端容易出现过冲(龙格现象)。

拟合完成后,需要把多项式系数作为结构体传给EKF模块,同时还要计算OCV对SOC的导数表达式。这一步很多人忽略,认为EKF只需要OCV值,实际上观测矩阵H矩阵第一列就是这个导数,它决定了卡尔曼增益的更新方向。我习惯用符号工具箱求导,再将导数表达式代入代码,避免手工求导出错。

这里提一句实测下来的经验:OCV曲线中间平台区(SOC 20%~80%)斜率很小,EKF在这个区域的可观测性天然偏弱,SOC估计收敛速度会比两端慢一些。这是电池特性决定的,不是算法缺陷,不需要过度紧张。

2.3 Simulink模型结构:真实电池与EKF估计器解耦

整体模型被我拆成三大部分:真实电池子系统、EKF估计子系统、波形观测区。

真实电池子系统输入是电流信号,输出是真实SOC和端电压。端电压会叠加高斯白噪声(模拟电压传感器噪声),然后送入EKF估计器;真实SOC则送入波形观测区作为真值用于对比。EKF估计子系统的输入是带噪端电压和电流信号,输出是SOC估计值和端电压估计值。

关键设计决策:不要让EKF模块使用真实SOC值。否则仿真就失去意义了。我特意让EKF的初始SOC设为80%,同时把真实电池的初始SOC设为50%,中间隔着30%的偏差,用来展示EKF的收敛能力。如果初始SOC给得太离谱,比如和真值相差50%以上,EKF依然能收敛,只不过动态过程会稍微猛一些,P矩阵初值需要相应放大。

2.4 离散化步长的选择逻辑

Simulink模型里我设置了固定步长0.01s。为什么不是变步长?变步长求解器在电池模型这种刚度不大的系统中也能跑,但EKF模块是基于固定采样周期推导离散方程的,采样时间参数会直接进状态转移矩阵。如果求解器自适应变步长,很难保证EKF内部的时间假设和实际执行步长一致。固定步长才是稳妥的做法。

传感器噪声我用了Simulink自带的Band-Limited White Noise模块,噪声方差设置为1e-6 V,这个量级相当于一个中等精度电压采集芯片的噪声水平。真正做硬件在环时,噪声方差应该从传感器数据手册或者实测数据中获取,仿真阶段用一个合理量级即可。

3. EKF算法核心实现与MATLAB Function代码

3.1 EKF的五个标准步骤在电池场景下的展开

EKF的原理在很多书里都有,但把公式落到电池SOC估计这个具体问题,需要明确每一步的实际含义。

第一步是状态先验估计,就是只依赖系统模型,不考虑当前测量值,从上一时刻的后验状态推出当前时刻的预测状态。电池场景下,这一步本质上是“用安时积分法先粗算一遍SOC”,同时用RC网络模型预测极化电压。第二步是误差协方差先验估计,这一步量化模型预测的不确定性,模型越不准,这个协方差就应该越大,滤波器越倾向相信测量值。

第三步是卡尔曼增益计算。它本质上是一个加权系数,决定当端电压预测值和实测值不一致时,多大的修正力量用来校正状态。第四步是状态后验估计,先用实际端电压和预测端电压做差得到新息,再乘以卡尔曼增益,修正先验状态。这一步体现了闭环反馈的核心价值:如果端电压测量值显示电池实际电压比模型预测的低,说明SOC估计可能偏高了,就往下修正。第五步是误差协方差后验更新,为下一时刻迭代做准备。

3.2 MATLAB Function代码实现与参数传递

EKF模块我用MATLAB Function实现,而不是用Simulink基础模块一个个搭。基础模块搭EKF会特别繁琐,而且遇到矩阵运算就要用一堆矩阵运算模块,可读性和可维护性都很差。MATLAB Function在Simulink里可以直接写循环和矩阵运算,编译成嵌入式代码也方便,是实际工程中更常见的选择。

function [socHat, vHat, pUpdate] = ekfSOC(u, i, dt, params) % u: 端电压测量值(含噪声) % i: 电流测量值(放电为负) % dt: 采样周期 % params: 电池参数结构体 persistent x P Q R if isempty(x) x = [params.soc0; 0]; % 初始SOC与初始极化电压 P = diag([params.p0Soc, params.p0Vp]); Q = diag([params.qSoc, params.qVp]); R = params.rNoise; end % 1. 状态先验预测 socPred = x(1) - (dt / (3600 * params.capacity)) * params.eta * i; vpPred = exp(-dt / params.tau) * x(2) + params.r1 * (1 - exp(-dt / params.tau)) * i; xPred = [socPred; vpPred]; % 2. 误差协方差先验预测 A = [1, 0; 0, exp(-dt / params.tau)]; Ppred = A * P * A' + Q; % 3. 观测预测与观测矩阵(H矩阵) socPred = max(0, min(1, socPred)); ocvPred = polyval(params.ocvPoly, socPred); vPred = ocvPred + vpPred + params.r0 * i; dOCV_dSOC = polyval(params.ocvDerivPoly, socPred); H = [dOCV_dSOC, 1]; % 4. 卡尔曼增益 S = H * Ppred * H' + R; K = Ppred * H' / S; % 5. 状态后验更新 innov = u - vPred; x = xPred + K * innov; % 6. 误差协方差后验更新 P = (eye(2) - K * H) * Ppred; % 输出 socHat = max(0, min(1, x(1))); vHat = vPred; pUpdate = diag(P); end

有一点需要特别注意:代码里我加了SOC限幅操作,把socPred限制在[0,1]区间。如果不加这个限制,在极端工况下SOC先验估计可能出现负值或大于100%,多项式外推会导致OCV预测严重偏离物理规律,进而让滤波器状态震荡甚至发散。但限幅操作在代码里会给函数带来不可微的“拐点”,严格来说会破坏EKF的线性化假设。在实际工程中,这种做法的收益远大于理论缺陷,所以保留,但要心里有数。

3.3 状态协方差矩阵Q与R的整定心得

Q矩阵和R矩阵是EKF手册里写的最少、但实际影响最大的参数。Q是过程噪声协方差,代表对模型本身的信任程度:Q越大,滤波器越“怀疑”模型预测,越依赖测量值修正;R是观测噪声协方差,代表对传感器的信任程度:R越大,滤波器越“怀疑”测量值,越依赖模型递推。

我建议的整定顺序是先定R,再定Q。R通常从传感器数据手册或者实测噪声方差确定,1e-6量级对应电压传感器的RMS噪声大约1mV,这个取值比较合理。Q则作为可调参数,Q中SOC对应的元素决定SOC收敛速度,极化电压对应的元素决定极化电压跟踪能力。一般来说SOC对应的Q在1e-6到1e-4之间,极化电压对应的Q在1e-5到1e-3之间,需要根据仿真结果微调。

一个常见误区是Q和R同时设得很小,结果滤波器“过于自信”,新息对状态修正作用几乎为零,SOC估计变成纯开环安时积分,误差越积越大。另一个误区是把Q设得过大,滤波器疯狂跟随噪声,SOC估计曲线毛刺多、抖动大。整定的目标是在响应速度和噪声抑制之间取平衡,这没有解析解,只能经验性调试。

4. 仿真验证与故障注入:从错误初始值到收敛全过程

4.1 初始SOC偏差下的收敛性验证

仿真启动时,真实电池SOC为50%,EKF的初始SOC却被我设为80%。从t=0开始,真实电流信号为一个1C恒流放电(100A)。端电压在放电瞬间有一个明显的欧姆压降,这完全符合物理规律。

EKF在启动后的第一拍就会因为电压测量值和模型预测值之间出现巨大偏差而产生很大的新息。由于初始P矩阵被我设定为一个相对较大的值(P0SOC=0.1),卡尔曼增益较大,SOC估计值会迅速向真值方向修正。整个过程大约在200秒内完成主要收敛,之后误差稳定在1%以内。如果你把初始P设得更小,比如1e-4,那么初始信任模型较多,收敛就会慢很多,可能要几千秒。这就是第一节提到的“初始P决定初始收敛速度”的体现。

4.2 脉冲工况下的动态跟踪表现

为了验证在动态工况下EKF能否区分“真正的SOC变化”和“极化电压的瞬态响应”,我特意构造了脉冲电流激励:周期性地以1C放电10秒,然后静置20秒。放电阶段SOC总量实际消耗很少,但电压变化很大;静置阶段欧姆压降瞬间消失,极化电压按指数衰减。

这种激励最考验EKF的观测方程是否准确。如果OCV-SOC多项式拟合不平滑,会导致dOCV/dSOC在曲线转折处产生毛刺,滤波估计也会跟着抖动。根源在于观测矩阵H对应位置不准确,卡尔曼增益方向出现偏差。我把拟合多项式从5阶改成7阶后,这个问题明显缓解。

脉冲工况下,EKF能不能快速跟踪极化电压变化也十分关键。如果RC时间常数参数有误差,极化电压预测就会偏差,EKF会用SOC修正来“补偿”这个误差,导致SOC估计出现不必要的波动。我在仿真中发现,极化时间常数τ参数偏差超过50%时,SOC估计在脉冲切换处会出现明显过冲。这里建议永远不要只看平均误差,要看误差曲线的瞬时波动幅度。

4.3 传感器噪声与参数失配的鲁棒性测试

仿真中我分别在电压测量通道添加了1mV和3mV均方根的噪声,SOC估计的稳态误差变化不大,从0.8%涨到了1.5%左右。这说明EKF对量测噪声有天然抑制能力,加大R值可以进一步滤除噪声,代价是动态响应变慢。

另外我模拟了电池老化导致内阻R0增加30%的情况。此时模型内部参数和真实电池参数不一致,端电压预测出现系统偏差,EKF的SOC估计出现稳态偏差。这说明一个事实:EKF虽然抗噪声能力强,但抗参数失配能力有限。在实车BMS中,通常需要搭配在线参数辨识来配合使用,这也是近年来很多论文把EKF和递推最小二乘结合的原因。

5. 常见问题速查与调试经验记录

现象根因分析处理建议
SOC估计从启动后一直发散不收敛状态转移方程中时间单位不一致或容量单位错误,SOC变化速率与实际不符检查dt、Capacity、3600三个系数关系;打印前200步先验SOC变化量做对比
SOC收敛速度极慢,几百秒变化很小初始P矩阵设置过小,滤波器太信任模型增大P0SOC,比如设为0.05~0.1
SOC估计曲线噪声大,出现明显锯齿状Q矩阵设置过大或R矩阵设置过小,滤波器过度信任测量值适当减小Q中SOC对应项,或增大R值
脉冲电流切换瞬间SOC出现尖峰脉冲极化电压模型参数失配,EKF用SOC修正来补偿极化误差检查R1和C1参数;在OCV曲线拟合时确保导数平滑
静置完全零电流时SOC估计几乎不动且不收敛零电流状态下系统端电压中不含SOC观测信息,EKF长时间无新息更新,属于可观测性不足属正常现象;恢复电流激励后自动收敛
仿真报“代数环”错误Simulink内部输出通过同一时刻输入反馈形成闭环在反馈回路中插入Unit Delay模块或离散化状态块
OCV多项式在两端出现异样外推高次多项式端点出现过冲限制SOC适用范围,或者在拟合时对两端额外加权约束,增大两端采样密度

5.1 代数环问题详细说明

MATLAB Function内部如果不加状态,纯粹用当前时刻输入计算当前时刻输出,很容易碰到代数环问题。Simulink报错时提示循环依赖,实际原因可能是EKF的输入电流和输出状态被同一个表达式引用。解决办法是在EKF输出侧增加一个零阶保持或者Unit Delay,把反馈回路切断。代价是等效于引入一个采样周期的延迟,在0.01s步长下对SOC估计几乎无影响。

5.2 仿真速度与Inlining优化

MATLAB Function代码首次运行时会触发代码生成编译,比较慢。后续运行就快了。如果想要更快,可以在MATLAB Function中开启代码生成报告,确认代码是否被inlined到Simulink模型中。如果是纯解释型运行,循环内的polyval调用会成为性能瓶颈。我实测过一个500秒的工况,优化后仿真时间从120秒降到15秒,提升非常明显。

6. 总结与工程落地建议

以上这套模型做下来,我可以负责任地说:EKF估计SOC的完整链路在Simulink里实现其实没有想象中复杂,但对细节的把控决定成败。模型里最容易出错的就是单位换算、初值设定和噪声协方差整定三件事。

如果计划把这段代码最终部署到嵌入式MCU上,我建议提前确认目标平台是否支持MATLAB Coder生成C代码。我的经验是,代码中尽量少用动态数组分配和多层嵌套结构体,尽量将参数装配成扁平结构体,这样生成的C代码更简单也更高效。

另外,强烈建议把真实电池模块和EKF模块分别封装成独立的Subsystem,并添加注释。这样做的好处是后续更换电池等效电路模型时,只需要改真实电池子系统和对应的参数结构体,EKF核心代码完全不用动。这种解耦设计,在BMS算法开发中是最值得养成的习惯。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进