ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

考虑老化的修正EKF电池SOC估计:Simulink复现与踩坑记录

考虑老化的修正EKF电池SOC估计:Simulink复现与踩坑记录

先提醒一句:这篇帖子标题里的“修正EKF”,如果读论文不够细,很容易被当成“改进的扩展卡尔曼滤波”这种泛泛的说法带过去。但真正动手复现之后你会发现,整篇论文的核心其实落在“考虑老化”四个字上——SOC估计的难点从来不是滤波公式本身,而是电池在一轮一轮充放电之后,模型参数不再匹配新电池出厂参数时,怎么让算法还有精度可言。这篇我把从论文拆解到Simulink模型落地、再到调参和踩坑的完整过程整理出来,给准备做同类复现的同学一个可以照抄的作业。

1. 标题拆解:三个关键词决定了整套仿真结构

1.1 “修正EKF”到底修的是什么

论文标题里“修正EKF”很容易让人以为是在EKF基础上改进了某个数学步骤,比如换了协方差更新方式或者改了增益计算公式。但我把论文反复读了两遍之后确认,这里的“修正”更接近“对模型失配的补偿修正”——也就是说,算法框架依然是标准EKF,只不过在状态方程和观测方程里加入了老化带来的参数变化修正项。换个更直白的说法:标准EKF假设电池模型参数是固定的,而这篇论文让EKF用上了一套“会随时间缓慢变化”的模型参数,再配合自适应噪声协方差调整,让滤波器在电池老化后仍然保持较好的估计精度。

这个理解直接影响Simulink的建模思路。如果只是把EKF写成一个函数块,那复现过程很快就结束;但“修正”意味着必须额外搭一套老化参数更新模块,把容量衰减量、内阻增量这些信息实时注入到EKF的预测和更新方程里去。仿真图的拓扑结构,从一开始就和单纯跑EKF不一样。

1.2 “考虑老化”涉及电池模型的两个层面

老化对锂电池的影响,从模型角度看集中在两块。

一是容量衰减。新电池标称容量比如50Ah,循环400次之后可能掉到42Ah,而传统SOC定义中分母(可用容量)如果用50Ah代进去,真实SOC一定偏大。这是老化的第一个坑,也是最直观的一个。

二是模型参数漂移,主要是欧姆内阻和极化内阻随循环次数增加而增大,并且OCV-SOC曲线形态也会略微变化。内阻增大最直接的后果是端电压在相同电流下的压降变大,观测方程里预测的端电压和实测端电压偏差变大。如果EKF还在用出厂时的内阻值,新息(残差)里就会混入大量由参数失配引起的偏差,滤波器会被带着跑偏。

所以“考虑老化”不是简单地把一个老化因子乘上去就行,而是要在电池模型层面同时解决容量、内阻、OCV三个参数的时变问题。论文里的做法通常是把这些参数写成老化程度(比如循环次数、累计安时吞吐量)的函数,Simulink里最自然的实现方式就是查表+插值。

1.3 SOC估计的“天花板”不在算法而在模型精度

做仿真之前我对这一点体会不深,总觉得EKF的数学模型写得漂亮就能出好结果。但复现之后我越来越认同一个观点:SOC估计算法能到达的精度上限,基本由电池模型的精度决定。滤波器只是在模型和测量值之间做折中,模型本身如果偏差大,滤波器的折中结果也不会准。

这也是为什么论文复现时,很多人把精力全放在EKF公式推导上,却忽略了电池模型参数辨识这一步。以我做的二阶RC等效电路模型为例,如果OCV-SOC曲线采样点稀疏、内阻辨识用的数据不覆盖全SOC区间,那么修正EKF算法本身写得再完美,仿真波形里的误差照样在5%以上。所以这篇帖子里我会花不少篇幅讲讲电池模型搭建,因为这一块才是复现成败的关键。

2. 标准EKF在电池SOC估计中的局限:从公式到仿真

2.1 五步迭代里的三个隐含假设

EKF用于SOC估计,套路大家都熟:状态量取SOC和极化电压,观测方程输出端电压,每一步先做状态预测和协方差预测,然后算卡尔曼增益、更新状态和协方差。公式不写了,Simulink里每一步对应一个运算模块就行,关键是要理解这五步背后的三个隐含假设。

第一个假设是系统噪声和观测噪声都是零均值高斯白噪声,且协方差矩阵Q和R固定不变。电池工况千变万化,电流大时模型误差大,静置时模型误差小,固定的Q/R协方差本质上是在“平均化”地描述噪声水平,不可能在所有工况下都最优。

第二个假设是模型参数准确。但电池老化的必然性决定了参数不可能一直准确。内阻变大、容量变小,这些变化如果不在模型里体现,EKF的新息就会被系统误差污染。

第三个假设是状态误差和观测误差近似线性。EKF能做的是在每个时间点对非线性函数做一阶泰勒展开,遇到OCV-SOC曲线曲率大的区间(比如SOC低于20%或者高于90%),线性化误差会明显变大。

2.2 固定Q/R的典型症状:滤波发散和滞后

我在初始复现时先用固定Q/R跑了一遍,电池模型用出厂参数,把仿真时间拉到20次模拟循环等效老化之后,结果出现了两个典型症状。

一个症状是SOC估计结果整体偏高。原因前面说过了,分母容量没变,但真实可用容量已经在衰减,换算出来的SOC自然偏大。这个偏大不是随机噪声,是系统性偏差,EKF的增益矩阵再怎么调整也修正不了,因为同样的偏差在每个时刻都存在,滤波器会把它当成“真实值”的一部分。

另一个症状是端电压预测残差里出现明显的时变漂移。固定R矩阵意味着滤波器对观测量的信任度保持不变,但老化后的传感器残差里既有测量噪声,也有模型失配误差,这两者合在一起让新息的统计特性严重偏离零均值假设。此时卡尔曼增益按原定调子做加权,导致SOC估计出现额外的滞后和震荡。

2.3 一个最容易被忽略的问题:老化让“参考SOC”都不对了

复现过程中还必须解决一个方法论问题:用什么来评价估计算法的好坏?一般仿真里用真实SOC做参考,而“真实SOC”是怎么来的?安时积分法在模型准确、初值准确的前提下是可靠的,但老化之后容量本身在变,安时积分必须以“当前实际容量”为准。如果参考SOC还是按老容量算,那EKF估计得再好也会被对比成“不准”。

这个问题的常规解法是:在仿真模型里给电池模型一个独立的容量衰退计算模块,安时积分的分母也从这个模块取当前容量。这样参考SOC本身能反映老化状态,和修正EKF在同一套老化体系下比较,才算公平。这一步虽然简单,但论文初读时很容易忽略,直接导致评估结果失真。

3. 修正策略的落地逻辑:更新模型比改滤波器公式更管用

3.1 修正方向一:让Q/R矩阵随工况自适应

“修正EKF”在论文里最常见的第一处改动,就是不再让Q和R固定。具体做法可以走两条路:一条是基于新息序列实时估计噪声协方差,另一条是按工况查表切换Q/R数值。

我这次复现选用的是前者,思路和自适应滤波里的Sage-Husa方法接近。简单说就是开一个长度为N的滑窗,收集最近N步的新息值,用新息的样本方差去修正观测噪声协方差R。系统噪声协方差Q同理,用状态预测残差的样本统计去调整。这种方法在Matlab里实现非常顺手,Simulink里加一个buffer模块就能完成滑窗。

不过要提醒的是,自适应Q/R不是越“自适应”越好。噪声协方差更新本质上是一个随机估计问题,滑窗太短则协方差估计方差大,滤波容易抖动;滑窗太长则自适应效果变差,响应迟钝。我的实践结果是窗口长度取50到100之间比较均衡,具体数值要看仿真步长。

3.2 修正方向二:把老化参数直接写进状态方程

比调整Q/R更符合论文“考虑老化”主旨的改动,是在EKF的状态方程和观测方程里显式加入老化修正项。具体实现方式是把电池模型的内阻R0、极化电阻Rp1/Rp2、极化电容Cp1/Cp2和容量Qn都做成当前老化程度的函数。

在Simulink里我会用一维查找表或者MATLAB Function块来计算这些参数。以容量为例,它的老化函数可以写成:

Qn_now = Qn_new * (1 - k_q * Ah_throughput / Ah_total)

其中Ah_throughput是累计安时吞吐量,Ah_total是寿命周期对应的总安时吞吐量,k_q是容量衰减系数。内阻同样类似:

R0_now = R0_new * (1 + k_r * Ah_throughput / Ah_total)

这两个式子看上去简单,但放进EKF之后有一个关键细节:状态转移矩阵A需要对时变参数求偏导。因为参数随着安时吞吐量在变化,严格来说系统已经不是线性时不变系统,如果要严谨,A矩阵的每个元素都得跟随当前参数重新计算。我在复现时的做法是每个仿真步更新一次A矩阵对应的参数部分,效果比一次性算好固定A矩阵更接近论文结果。

3.3 修正方向三:状态扩维,把内阻也“估计”出来

第三种更高级的做法是把内阻直接扩成状态变量,让它和SOC一起被EKF在线估计。这样做的好处是不需要事先知道老化函数的形式,模型自己会跟踪内阻的实际变化;代价是状态方程从二阶变成了三阶,且内阻的动态方程需要额外假设(通常设置为随机游走模型)。

我对比过状态扩维和参数查表两种方式,在Simulink里查表方式更容易调通,因为不需要额外处理内阻估计值和真实值之间的耦合问题。状态扩维方式虽然更接近“真正在线估计老化参数”,但会引入新的调参维度——内阻的噪声方差怎么定,直接决定估计速度和稳定性。论文如果明确写了状态扩维,那就必须按论文来;如果只提到“考虑老化”,优先选择查表方式,复现成本低很多。

4. Simulink仿真模型搭建:从模块划分到信号流

4.1 总体结构分成四块

先说我搭的模型整体拓扑,按信号流方向依次是:

  1. 工况输入模块:负责产生充放电电流序列,我用的是动态工况(类似简化版的城市循环工况),包含加速、匀速、制动回充、静置几个阶段。这个模块输出电流I和采样时间戳。

  2. 电池模型模块:二阶RC等效电路模型,输入是电流I和当前温度,输出是端电压Vt和真实SOC。模型内部已经把容量衰减、内阻增加的老化逻辑做进去。

  3. 修正EKF模块:核心算法区。输入是端电压实测值Vt、电流I、当前老化参数,输出是SOC估计值以及估计误差。这里需要特别注意的是,算法区内所有参数必须和电池模型区内当前老化程度保持一致,否则滤波器估计的是“另一块电池”。

  4. 评估与显示模块:负责把真实SOC和估计SOC做差,计算误差百分比,生成波形。

这四块划分清晰之后,调试可以分模块进行,哪个环节出问题直接看该模块的输出波形就能定位。

4.2 二阶RC等效电路模型的Simulink实现细节

二阶RC等效电路模型可以表达为:

  • Vt = OCV(SOC) - I*R0 - V1 - V2
  • dV1/dt = I/C1 - V1/(R1*C1)
  • dV2/dt = I/C2 - V2/(R2*C2)
  • dSOC/dt = -I/Qn

实现时我用了积分模块加增益模块组合。一个常见坑是SOC的积分器可能会跑到负值或者超过1,所以必须在积分器后面加饱和限幅,同时把限幅后的SOC反馈用于OCV查表,避免查表超出边界。

OCV-SOC曲线我用的是Lookup Table模块,曲线数据从论文里对应型号电池的实验数据摘录。这里要强调一下查表精度:SOC从0到100%之间均匀取了41个点,线性插值方式,效果还凑合;如果取点太少,OCV曲线曲率较大的平台区会产生明显误差,直接影响观测方程精度。

4.3 修正EKF的Simulink实现:MATLAB Function块更省心

EKF核心我建议直接用MATLAB Function块写,而不是拆成一堆Simulink基本模块。原因很简单:矩阵运算在Simulink基本模块里连线极其痛苦,而且公式写错不好排查;MATLAB Function里可以直接抄论文里的矩阵公式,逐行对照,出问题也好加断点。

我写的核心结构大致是:

function [SOC_est, P_out] = ekf_update(I, Vt, SOC_pred_prev, P_prev, params) % 状态预测 SOC_pred = SOC_pred_prev - (I * dt) / params.Qn_now; V1_pred = V1_prev * exp(-dt/(params.R1*params.C1)) + I*params.R1*(1-exp(-dt/(params.R1*params.C1))); V2_pred = V2_prev * exp(-dt/(params.R2*params.C2)) + I*params.R2*(1-exp(-dt/(params.R2*params.C2))); x_pred = [SOC_pred; V1_pred; V2_pred]; % 观测预测 OCV = interp1(params.SOC_OCV(:,1), params.SOC_OCV(:,2), SOC_pred, 'linear', 'extrap'); Vt_pred = OCV - I*params.R0_now - V1_pred - V2_pred; % 线性化 H = [dOCV_dSOC(SOC_pred, params), -1, -1]; % 新息与增益 innovation = Vt - Vt_pred; S = H * P_pred * H' + R; K = P_pred * H' / S; % 更新 x_est = x_pred + K * innovation; P_est = (eye(3) - K*H) * P_pred; SOC_est = x_est(1); end

这段代码只是核心逻辑示意,实际写的时候状态预测和观测更新是分成两个函数块的,但写法就是这样,和论文公式一一对应。

4.4 老化注入模块:时变参数要送进电池模型和EKF两个地方

我单独做了一个“老化参数计算”模块,输入是累计安时吞吐量,输出是当前容量Qn_now、当前内阻R0_now、当前RC参数。它的输出同时连接到电池模型和修正EKF模块。这样做的好处是仿真过程中如果发现SOC估计误差变大,可以快速确认是电池模型的参考值变了、还是EKF使用的参数没跟上。

老化参数的更新周期不需要和仿真步长一样,为了减少计算量,我用了一个触发子系统的方案:每100秒更新一次老化参数,更新间隔内保持参数不变。实测下来在动态工况里效果和每步更新几乎一致,但仿真速度提升了约30%。这个优化技巧在长周期老化仿真里非常实用。

5. 复现路上的五个“坑”和排查链路

5.1 代数环问题:MATLAB Function块里的直通路径

第一次运行模型直接报错“algebraic loop detected”,定位到EKF模块。原因是MATLAB Function块里当前步的输出SOC_est直接参与了当前步端电压的计算(观测矩阵H依赖SOC),而端电压又作为输入反馈回来,这就形成了代数环。

排查之后确认代数环的根源在于观测方程里SOC和端电压的耦合。解决方案有两种:一种是给MATLAB Function块加一个单位延迟,让状态预测部分先算,观测更新用上一时刻的SOC计算H矩阵;另一种是把H矩阵的计算独立出来,用查表方式预先算好dOCV/dSOC。我最终选的是延迟方案,因为它改动最小,只需要在反馈回路里加一个Unit Delay模块。

5.2 初值敏感性问题:P矩阵的“信任度”预设

刚开始我按论文给的初始协方差P0设成单位矩阵乘以一个小值,结果前20秒SOC估计误差一度冲到8%,后面才慢慢收敛。后来我把P0调大了一两个数量级,误差明显改善,前几个周期就收敛到2%以内。

这里面的逻辑是:P0代表算法对初始状态估计的不信任程度。你如果一开始就有较准确的SOC初值,P0可以设小一些;如果初始处于未知状态,P0设大一点让滤波器大胆修正。论文一般不会告诉你这个细节,需要在仿真里自己试验。

5.3 采样时间的噩梦:连续模块和离散模块交界处

二阶RC模型我用连续积分器,EKF模块我用的是离散更新(步长0.1秒),连接处没有加采样保持的话,仿真器会自己插值,容易出现意料之外的数值震荡。后来把电池模型的连续积分器也改成离散积分器,步长0.1秒,两者完全同步,震动消失。

这个坑在Simulink里非常隐蔽,因为模型能跑、不报错,但波形里总是有莫名其妙的毛刺。排查方式是在关键信号线上加Scope,对比连续采样和离散采样的差异。

5.4 容量衰减参数的“过拟合”:老化函数太激进

第一次做老化参数注入时,我参考某篇文献给了一个偏激进的容量衰减系数,结果仿真跑到后半段,电池容量掉了20%,而EKF的参数更新模块在同一老化体系下修正不够及时,误差后半段快速增大到6%以上。这个问题的本质不是算法问题,而是老化函数本身不可信。

解决办法是把容量衰减系数改成软函数形式,比如线性衰减但在接近寿命终点时斜率放缓,更接近真实电池表现。老化只是一种趋势,过分追求拟合精度反而让算法在边界工况下失真。

5.5 参考SOC的“公平性问题”

前面提到过,参考SOC用安时积分法获得时,必须和老化容量同步。我在实现时走了些弯路,最初参考SOC直接用固定容量算,结果EKF算法的表现曲线一直“不好看”,弄得我一度怀疑是滤波增益算错了。后来把参考SOC改为从老化注入模块获取当前容量做分母,误差走势一下就合理了。

这是复现论文时最容易让人怀疑人生的一个坑,强烈建议先检查参考基准的算法逻辑,再怀疑自己的滤波器公式。基准错了,后面一切优化都是白费。

6. 波形分析:什么结果才算复现成功

6.1 四类波形必须全部画出来

复现完成后,我习惯性画了四类波形来验证结果:

  1. 真实SOC与估计SOC对比曲线
  2. SOC估计误差曲线(百分比)
  3. 端电压实测与端电压预测对比曲线
  4. 新息序列(残差)波形

前两类直接体现估计精度,后两类用来判断滤波器是否健康。尤其是新息序列,如果它长期偏离零均值,说明模型偏差没有被正确补偿,即使SOC曲线误差不大,这个结果也是“纸面好看”。

6.2 论文结论的量化标准

一个相对合理的复现结果应该是:动态工况下SOC估计误差稳定在2%以内,偶发尖峰不超过3%,并且老化到中后期(容量衰减10%以上)时误差没有明显发散趋势。如果误差在老化后继续增大到超过3%,多半是老化参数注入没做好,或者自适应Q/R窗口设置不合理。

我自己的复现结果在老化到10%时误差约1.6%,老化到20%时误差约2.1%,整体呈现缓慢增大但有界的状态,和论文给出的趋势图吻合得比较好。

6.3 一个容易被忽视的细节:静置段的误差变化

动态工况里会穿插静置段,静置时电流为零,极化电压逐渐消退,端电压逐渐趋近OCV。这时观测方程里OCV是主要信息来源,如果OCV-SOC曲线在平台区分辨力差,静置段的SOC估计误差可能反而比动态段更大。实测中我把OCV采样点加密后,静置段误差显著下降。

这也提醒我们:论文里的OCV-SOC曲线数据质量直接决定复现效果的天花板,别把心思全花在滤波器调参上。

7. 从复现到优化的一点思考

复现本身不是目的,通过复现理解算法思路才是。我做完这个项目以后,最大的感受是修正EKF的精髓不在于滤波公式本身有什么魔法,而在于它承认了“电池模型永远是错的”这个事实,然后想办法让滤波器在模型偏差面前保持稳定。

如果接下来要在这个方向上继续深化,我可能会尝试两个方向:一是把老化参数在线辨识加到EKF里,让修正系数自动跟随实际老化状态,而不是依赖离线老化模型;二是考虑温度和环境对老化速率的影响,把Arrhenius类的温度修正公式做进去。这些都是对“考虑老化”更广义的解读,也能让仿真结果更贴近实车场景。

最后给准备动手复现的同学一个建议:先花半天时间把论文里的电池模型参数、老化模型函数、EKF初始化参数全部整理成一张参数表,再开始搭Simulink模型。我在这个过程前期吃过亏,边查论文边建模,被参数单位、符号定义各种小问题反复打断思路,整理完参数表之后,后面两天内就基本拉通了整个仿真链路。这种准备工作看起来琐碎,却是整个复现过程性价比最高的一件事。

RELATED READING

延伸阅读

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