ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于EKF/UKF的锂电池SOC估算与Simulink仿真实现

基于EKF/UKF的锂电池SOC估算与Simulink仿真实现 新能源车用户最怕看到的就是仪表盘上的剩余里程像坐过山车一样跳。前几个月我做一个车用BMS算法项目核心任务就一句话把锂离子电池的SOC荷电状态State of Charge估算准。我从EKF扩展卡尔曼滤波和UKF无迹卡尔曼滤波两条路线入手在Simulink环境里完整做了电池建模、参数辨识、SOC估算和充电控制策略仿真坑踩了不少也沉淀了一套可以复用的实现思路。这篇文章把整个过程摊开讲从原理到建模再到Simulink实现细节都有适合正在做毕设、或者准备入坑BMS算法想找落地路径的同学参考。这里先说明一点如果搜过资料会看到“SOC”在芯片领域也常出现意思是片上系统System on Chip容易和电池的荷电状态搞混。这篇文章里的SOC一律指荷电状态——就是电池里还剩多少电通常用0~100%表示。搞清楚这个区别查资料时能省不少时间。1. 为什么非要上卡尔曼滤波来算SOC1.1 SOC估算的三大流派怎么选先说说SOC为什么难算。锂电池在充电和放电过程中内部状态没法直接测量能测到的只有端电压、电流、温度这三个物理量。SOC是个不可直接观测的内部状态只能靠“模型算法”去估计。目前主流的SOC估算方法大致分三类。第一类是安时积分法也叫库仑计数法。原理就是电流对时间积分拿标称容量做除法得出剩余电量。这方法实现极其简单但有两个致命伤一是电流传感器有偏置误差积分时间长了误差会一直累积漂移二是初始SOC必须给准给不准后面全错。实际车载场景里电流传感器零漂和温漂很难彻底消除所以光靠安时积分冬天和夏天同一块电池估值能差出10个百分点以上。第二类是开路电压法。把电池静置足够长时间测它的端电压再通过OCV-SOC标定曲线反查SOC。问题在于动力电池在运行状态下几乎不会给你“静置几小时”的机会动态工况下根本没法实时用这招。而且磷酸铁锂电池的OCV-SOC曲线在中段特别平电压差只有几毫伏查表误差直接放大所以很多做铁锂的工程师宁愿用别的方法。第三类是基于模型的状态估计方法比如卡尔曼滤波家族、H∞滤波、滑模观测器等。这类方法的核心思路是把电池看成一组状态方程SOC作为状态向量的一员通过实时的电压电流测量值去“校正”对状态的预测形成一个闭环估计。它既能修正初值偏差也不会像安时积分那样无休止漂移。EKF和UKF就属于这一大类。我选择EKF和UKF深入研究很重要的原因是它们在学术界和工程界都有大量验证算法逻辑清晰计算量相对可控好做嵌入式移植。后来看到不少论文用BiLSTM、CNN这类数据驱动方法做SOC精度确实能打但问题是可解释性差、训练依赖大量数据、模型动辄几MB车规MCU根本跑不动阶段不同路线选择也不同。1.2 电池模型两个RC网络的微妙平衡卡尔曼滤波的前提是“你有一个还算靠谱的模型”。所以我跟所有入坑的同学说模型没建好滤波器白搭。电池模型粗细程度差异很大。最粗的是单电阻模型Rint只有开路电压串联一个内阻动态特性完全没法体现用来做SOC估计精度太差。往细了走有电化学模型P2D模型、等效电路模型ECM、神经网络黑盒模型等。我最终选的是二阶RC等效电路模型这也是BMS工程里最常见的选择。为什么是二阶RC不是一阶也不是三阶一阶RC模型只有一个极化网络对电池“极化响应”的描述太粗糙尤其是动态工况下电压预测误差偏大直接影响滤波器的观测更新。三阶RC模型精度确实更高一点点但参数更多、辨识难度大、计算资源占用水涨船高在实时性要求高的VCU/BMS控制器上并不划算。二阶RC是两个RC串联分别描述电化学极化高频段和浓度差极化低频段算是一个精度和复杂度的平衡点。里面R0是欧姆内阻R1C1模拟SEI膜和电荷转移效应R2C2模拟锂离子扩散效应。这里有个工程细节二阶RC模型在实际运行中会有参数时变问题——温度一变、老化程度一变R0、R1C1、R2C2全都跟着变。如果模型参数和真实电池偏差太远卡尔曼滤波的“校正”作用就会失效甚至越估越偏。所以正规工程做法是定期做参数在线辨识或者用带遗忘因子的递推最小二乘法FFRLS在线更新参数。我这次仿真里先用离线辨识拿到一组基准参数后面再做变参测试验证滤波器的鲁棒性。后面会详细说这个。2. EKF和UKF都是什么怎么选2.1 EKF线性化硬算的状态估计卡尔曼滤波本身是给线性系统用的公式很优美状态预测、协方差预测、增益计算、状态更新、协方差更新五个公式循环跑。但电池系统偏偏是强非线性的因为OCV-SOC曲线是一条明显弯曲、且有滞回特性的曲线直接用标准卡尔曼没有理论依据。EKF的做法是“既然非线性不行我就先在当前状态点把它线性化”。具体手段是泰勒展开取一阶项把非线性函数在当前估计点处求偏导得到雅可比矩阵F和H然后形式上套用标准卡尔曼滤波那套递推。以二阶RC模型为例状态向量取 x [SOC; U1; U2] 其中U1、U2是两个RC网络的极化电压。观测方程是 U_t OCV(SOC) - R0 * i - U1 - U2 OCV(SOC)是那个非线性函数所以这个观测方程就是非线性的。EKF需要在每个采样周期对OCV(SOC)求导雅可比矩阵H的对应元素就是这一点的dOCV/dSOC。这个dOCV/dSOC算起来要小心。OCV-SOC曲线一般用插值表或者多项式拟合成你如果用三次样条插值在曲线拐点处导数变化会很剧烈滤波增益就会跟着抖。我后来改成分段多项式拟合并且在代码里加了导数的平滑处理收敛过程明显稳定。EKF的优点很明确实现简单逻辑大家都熟MATLAB原型很快能跑通。缺点也同样明确它只取了一阶泰勒展开非线性越强误差越大雅可比矩阵的计算有时候很头疼尤其在模型改成带滞回特性的时候求导就不是一件愉快的事情。还有一个坑如果初始化协方差矩阵给得不合理EKF会出现协方差矩阵非正定、滤波发散。2.2 UKF用一堆点去逼近非线性UKF的理念和EKF完全不一样。它不去算雅可比矩阵而是“无迹变换”Unscented Transform的思想在状态分布上选取一组Sigma点一般2n1个n是状态维度这些点经过非线性函数传播后用加权统计量的方式重构出新的均值和协方差。简单说EKF是“把非线性函数线性化”UKF是“把概率分布采样后直接穿过非线性函数”后者对这个非线性的把握更准。实际参数选择上涉及三个超参数alpha、beta、kappa。alpha决定Sigma点离均值多远一般取一个比较小的正数比如1e-3beta和状态分布有关高斯分布下取2kappa通常取0。这三个参数调起来有一定的经验成分但不像EKF那样容易因为雅可比矩阵计算错误直接发散。我实际跑下来的直观感受是对于电池SOC这种观测方程带有明显非线性OCV-SOC曲线弯曲且测量噪声不算太小的系统UKF的估计精度和收敛速度都比EKF好一些尤其是初始SOC给偏的情况下UKF恢复真实值更快。代价是计算量大约是EKF的三到五倍仿真在PC上完全没压力但如果后续考虑移植到MCU这个开销就要认真评估了。2.3 一张表看明白EKF和UKF的差异对比项EKFUKF非线性处理方式泰勒展开一阶线性化无迹变换Sigma点采样是否需计算雅可比矩阵需要不需要估计精度较好非线性强时误差偏大较好尤其非线性强场景更优计算量小大约为EKF的3~5倍实现难度简单中等典型适用场景线性度中等、资源受限强非线性、精度优先有一个容易忽略的点是UKF虽然不需要算雅可比矩阵但需要做矩阵开方运算比如对协方差矩阵做Cholesky分解来生成Sigma点。如果协方差矩阵因为数值问题失去了正定性这个Cholesky分解会直接报错。所以UKF在代码里必须加异常保护检测到非正定时要能自动重置或做正则化处理否则整个仿真会突然崩溃。这个坑我后面在第五章会细说。3. 参数辨识没有准模型的滤波器就是摆设3.1 HPPC实验流程与数据采集二阶RC模型里有R0、R1、C1、R2、C2这五个参数不能靠拍脑袋定要靠实验数据辨识。最经典的实验方法是HPPCHybrid Pulse Power Characterization这是PNGV电池测试手册里的标准流程核心是给电池交替施加短时脉冲电流和静置激发出欧姆内阻和极化效应然后用电压响应曲线来反推参数。我做这个仿真项目时虽然模型本身是虚拟的但为了让学生和工程人员能复现我按真实HPPC流程构造了脉冲验证数据。基本流程是电池充满电比如100% SOC、静置1小时以上然后施加一个10s左右的放电脉冲再静置40s再来一个10s的充电回充脉冲然后静置足够长时间让电压恢复稳定。每完成一组就把SOC往下调10%重复操作直到SOC降到0。这个流程背后对应的是模型里不同时间尺度的响应脉冲瞬间电压突变对应R0欧姆内阻脉冲结束后几十秒内的慢慢恢复对应R1C1和R2C2两个RC网络的驰豫过程。把这个曲线抓下来参数就能用拟合算法提取出来。实际操作里有几个细节影响很大。第一脉冲幅值别太大也别太小一般取1C附近太大会触发电池的极化饱和太小信号淹没在噪声里。第二静置时间要足够尤其是低SOC区域极化恢复慢静置不够会导致OCV取值偏小后面查表全偏。第三环境温度要稳定最好是在25度的恒温箱里做温度不稳参数全白测。3.2 从数据里回归出二阶RC参数拿到HPPC数据后参数辨识一般用最小二乘拟合进阶一点可以用MATLAB的曲线拟合工具箱或者自己写RLS递推最小二乘在线辨识。以离线拟合为例思路是把电压响应分为几段分别处理。脉冲加载的一瞬间端电压出现一个阶跃这个阶跃高度除以脉冲电流就是R0。然后看脉冲结束后的电压驰豫曲线它是由两个指数衰减项叠加组成的对应公式 U(t) U_ocv - R1Iexp(-t/tau1) - R2Iexp(-t/tau2) 用非线性最小二乘比如lsqcurvefit对这个衰减曲线做拟合就能同时得到R1、C1、R2、C2因为tau R*C拟合出tau就能算C。这里有个我反复踩过的坑两个RC网络的时间常数如果太接近拟合会陷入病态参数辨识结果不唯一。解决的办法是尽量让两个时间常数拉开差距一般让tau1在几秒到几十秒对应电化学极化tau2在几十秒到几百秒对应浓度差极化拟合前可以对两个RC初始值做合理约束。仿真里的参数辨识完成后一定要做模型验证拿一组没有参与辨识的动态工况数据比如DST工况、UDDS工况把模型预测电压和实测电压对比误差应控制在几十毫伏以内。如果电压误差超过0.1V别急着调滤波器先把模型参数校准否则卡尔曼滤波之后的精度上限已经被模型误差锁死了。这个校验环节我建议做成自动化脚本每次改完参数跑一遍能节省大量排查时间。4. Simulink里从零搭建SOC估算充电控制4.1 模块划分哪些用Simulink积木块哪些写M函数有人一上来就在Simulink里疯狂拖模块把卡尔曼滤波的一堆矩阵运算全用加法器和乘法器搭出来。我强烈不建议这么干——矩阵运算是卡尔曼滤波的天然主场用Gain和Add积木搭10个公式简直就是灾难改一个m的值要找半天线。常规做法是混合建模电池模型、充电控制策略用Simulink模块搭EKF/UKF算法封装在MATLAB Function或者S-Function里。这里说一下S-Function和MATLAB Function的区别。S-Function性能好、能处理离散连续混合逻辑适合做嵌入式代码生成MATLAB Function更轻量写起来快调试也方便。我这次用的是MATLAB Function加一个小的Level-2 S-Function包装主要是为了方便后面做C代码生成验证。如果你只是做毕设或者算法验证直接用MATLAB Function就够了。整体架构分四层第一层是输入层提供工况电流和温度第二层是电池模型层用二阶RC等效电路算真实端电压并按时加入测量噪声第三层是算法层EKF和UKF分别估计SOC第四层是充电控制策略层根据当前SOC和电压状态输出充电电流指令。4.2 EKF/UKF核心代码思路与离散化卡尔曼滤波要落地到Simulink第一步就是把连续时间状态空间方程离散化。二阶RC模型的连续状态方程是 dSOC/dt -I/(3600 * Q_nom) dU1/dt -U1/(R1C1) I/C1 dU2/dt -U2/(R2C2) I/C2这里Q_nom是电池标称容量单位Ah除以3600是为了把Ah换算成安秒这样SOC才是无量纲的0到1之间数值。注意符号约定放电时电流为正SOC下降。离散化直接用前向欧拉法就够了采样时间Delta_t我取1s。仿真过程中试过0.1s精度没明显改善但计算量上去了取5s的话动态工况下极化电压的跟踪就明显滞后。所以1s是一个经验上比较划算的折中。EKF的核心流程用伪代码写出来大概是这样% 状态预测 x_pred f(x_est, I); % 状态转移方程 P_pred F * P_est * F Q; % 协方差预测F为雅可比矩阵 % 观测更新 y_res U_meas - h(x_pred, I); % 新息 K P_pred * H * (H * P_pred * H R); % 卡尔曼增益 x_est x_pred K * y_res; P_est (eye(n) - K * H) * P_pred;UKF的核心差别是Sigma点生成和统计量重构% 生成Sigma点 [Xi, wm, wc] ut_sigma_points(x_est, P_est, alpha, beta, kappa); % 通过非线性函数传播 Yi f(Xi, I); % 计算预测均值和协方差 x_pred sum(wm .* Yi, 2); P_pred sum(wc .* (Yi - x_pred) * (Yi - x_pred), 3) Q;写完算法函数之后我需要强调一个非常容易踩的坑MATLAB Function里定义的矩阵维度如果随输入变化Simulink会报“variable-size matrix”错误。比如一开始P矩阵是3×3但你不小心让某个中间变量变成了3×1代码生成阶段直接过不了。解决办法是在函数开头用coder.varsize或者直接固定矩阵维度别偷懒。另外状态向量的单位要一致。很多人喜欢SOC用百分数0~100U1U2用伏特结果系统矩阵里单位混着来协方差矩阵里的数量级差距会有几个数量级滤波器数值稳定性很差。我习惯统一把SOC归一化到0~1和电压差的单位就相对接近Q和R矩阵也更好调。4.3 充电控制策略状态机搭法充电控制这块我做的是两层的策略顶层是充电模式调度底层是电流电压闭环。主流的动力锂电池充电是CC-CV即恒流恒压两阶段。恒流阶段以设定电流恒流充电电池电压会持续上升当电压升到截止电压比如4.2V或4.25V时切换为恒压模式电流逐渐下降当电流降到截止电流通常0.05C~0.1C时充电结束。这套策略在Simulink里用Stateflow搭状态机最方便状态切换条件就两条电压到没到截止值、电流降到没降到截止值。在此基础上我还实现了一个基于当前SOC的阶段电流修正策略。出发点是温度对电池充电能力的影响很大低温下电池析锂风险高不适合大电流充电。所以我在状态机里额外加了温度—电流查表逻辑温度低于5度时电流降到0.2C5到15度时0.5C高于15度才允许1C电流。这种策略在真实BMS里很常见叫冷充电降额。充电控制不光是状态机还要考虑和SOC估算的联动。我仿真里特意做了一个场景初始SOC故意设错比如真实是30%滤波器给的是50%然后观察充电控制策略在错误的SOC下会不会出现过充。结论是如果只用安时积分且初值错电压控制和SOC控制会打架可能导致电池过充到4.4V以上这种情况下EKF/UKF的修正能力就体现出来了——它能很快把SOC拉回真实值附近策略才能正确动作。这其实也回答了一个问题为什么充电策略里还需要SOC算法直接电压保护不就好了吗因为电压保护是最后一层兜底SOC算法让策略能提前预判充电阶段和充电时间用户体验和电池保护都更优。4.4 工况与噪声注入仿真逼真的关键算法在理想环境下跑得再好也不代表真实车载环境里能用。所以仿真环境里必须注入噪声和扰动。电流噪声我用了band-limited white noise模块幅值设在电流的1%左右模拟电流传感器的测量误差电压噪声设在10mV量级左右。SOC的真实值由电池模型内部计算而观测值只有带噪声的端电压这样EKF/UKF就处在和真实系统一样的信息条件下。工况输入方面我首选用UDDS或NEDC这类标准工况因为它们跑出来的功率需求曲线带有真实的刹车再生、急加速等瞬态特征对电池模型的考验比较全面。当然没有整车的同学也可以直接在Simulink里构造一个随机脉冲电流序列原则是包含放电、回充、静置等多种状态。如果你本地有CarSim和Simulink联合仿真的环境直接从整车模型拿车速和功率需求再除以电池电压得到电流这样SOC估算的输入就更贴近实车了。4.5 仿真结果怎么评估仿真跑完不是看一眼曲线“差不多”就完事要用指标说话。我常用的三个指标是最大绝对误差MAE、均方根误差RMSE、收敛时间。SOC真实值和估计值之间的RMSE能反映整体估计精度MAE能看出有没有瞬时发散的尖峰。收敛时间则是指从错误的初始SOC到误差收敛进2%以内所需的时间。EKF和UKF在同样的初始条件下跑完我用一个脚本自动生成对比表格和曲线直观看到两条算法的差异。在放电倍率1C、初始SOC误差20%的场景里我这边UKF大约50秒左右就能把误差收敛到2%以内EKF大约需要200秒上下而且UKF的RMSE总体上比EKF低了将近一半。但如果把Q矩阵调得很激进EKF也能追上去一些代价是稳态波动变大。所以算法之间没有绝对的“谁比谁好”关键是结合应用场景做权衡。5. 仿真和HIL实测中踩过的坑5.1 滤波器发散排查三步法卡尔曼滤波发散是每个人都会遇到的事不是你一个人倒霉。现象就是估计值突然跳到天边去或者协方差矩阵对角线变成负值。我这边总结了一个三步排查法第一步检查模型方程和雅可比矩阵是不是一致。EKF线性化用的F矩阵和H矩阵应当是对非线性函数在当前状态点求导的结果很多人的方程写对了但导数求错了导致算法静下来就发散。第二步检查Q和R矩阵的数量级。Q给太小滤波器过度信任模型跟不上真实状态变化R给太小滤波器过度信任测量值噪声全灌进来了。第三步检查采样时间。采样时间过大会破坏离散化精度尤其极化电压状态对时间步长敏感发散的时候试着把Delta_t从1s改成0.1s往往能看出端倪。还有一招比较好用把过程噪声和测量噪声设置成可调的常量在Simulink外面用Manual Switch在线切换几组Q/R组合观察哪组能让滤波不发散。这个方法调试效率极高比自己改代码重新跑快得多。5.2 SOC初值不准怎么办刚开始做时我把SOC初始值设成0.8模型真实值是0.5EKF一开始的估计曲线要挣扎很久才能回到真实值附近期间充电策略会误判充电阶段。一个有效的优化是给初始协方差矩阵P0设置足够大的值。P0本质上是“你对初始状态的信任程度”P0越大表示越不信任初始猜测滤波器前期就会给测量值更大权重更快校正初值。我一般把P0设成对角线为1×10^2或1×10^3的矩阵如果知道初值大概准可以把P0调小一点避免前期抖得太厉害。另外一个工程技巧是在充电策略里加初值检查系统上电后先做一小段时间的静置测一个开路电压用OCV-SOC表粗查一个SOC初始值再把初值丢给EKF/UKF。这样即使没有精确初值也能大幅缩短收敛时间。5.3 Q和R矩阵到底怎么调这是问得最多的问题。Q矩阵是过程噪声协方差对应模型不确定性R矩阵是测量噪声协方差对应传感器噪声水平。如果模型就很粗糙Q要设大一点因为模型预测本身不可信如果传感器质量差R要设大一点因为测量值不可信。两者的比值决定了滤波增益的水平本质是在“模型预测”和“测量校正”之间找个平衡。我先说一个保守的起步值。R可以根据电压传感器的标称精度来估比如电压噪声方差约1e-4R就取1e-4量级。Q矩阵通常取对角阵SOC那项对应电流积分不确定性可以取1e-5到1e-4U1、U2两项的取值和它们的时间常数有关量级也在1e-5到1e-3之间。调的时候先固定R再逐渐加大Q观察估计曲线从“偏慢”到“偏抖”的转折点然后选一个离“抖”还有距离的位置。这个方法需要点耐心但效果很稳。个人经验是别指望一组参数打天下。放电大电流和高SOC区间、充电末端和低SOC区间、温度突变时最优的Q/R其实不完全一样。感兴趣的同学可以进一步做自适应调参——实时监测新息序列的方差然后动态调整R这是比较进阶的玩法但仿真验证的阶段先不要急着上把固定参数跑明白再说。5.4 充放电切换瞬间的电压尖峰另一个容易被忽略的坑是电流方向切换瞬间。比如工况里某一步还是3C放电下一步突然变成2C回充电池模型的端电压会因为R0上的压降方向突变而出现一个阶跃尖峰。这个尖峰对卡尔曼滤波来说是巨大的“新息”会让滤波器误以为自己状态估计错了从而做出过激修正。我遇到这个问题时第一反应是加大R来平滑结果发现收敛变慢。后来排查发现问题出在模型层面R0在充放电切换时本身就不是恒定值真实电池的充放电阻抗有不对称性而我用的固定R0在切换瞬间误差特别大。解决方法是把R0按方向分开处理充电和放电分别用对应方向的欧姆内阻甚至在模型中串一个小时间常数的低通环节来平滑切换瞬间的端电压突变。滤波算法不用动太多但这个措施让整体误差立竿见影地降下来。5.5 代码生成和实时性评估的提醒最后说一点关于工程落地的。Simulink模型跑通了后面如果要往嵌入式方向走一定会碰到C代码生成的问题。生成前要在MATLAB Coder和Embedded Coder里做数据类型的定点化评估或者至少先用float测试实时性。我实际测过UKF跑一遍大约需要EKF三到四倍的时间如果在STM32这类中等性能MCU上做已经有点紧张了。如果目标是量产的BMS控制器建议先评估MCU主频、Flash空间、RAM空间再定算法路线。6. 写在最后的一点体会整个项目从电池建模到滤波器实现再到充电控制策略跑通只是第一步真正有价值的是把每一步的原理搞清楚。我后来再看同事们用UKF做在线参数辨识、用EKF做SOH估算就会发现卡尔曼滤波这套框架在BMS里有一整套连贯的应用学会了它的数学模型和Simulink实现方法后续做电池状态估计的很多问题都是相通的。如果让我给新手一个操作建议就是先把二阶RC模型和HPPC参数辨识跑通再上EKF最后再切UKF每一步都保留对比曲线。这样出问题时能准确判断到底是模型的问题、参数的问题还是算法的问题。仿真项目最忌讳的就是一步到位把整个系统搭起来然后出了问题到处找原因。另外提醒一下仿真参数再漂亮也要经过实际电池数据或者至少HIL测试验证才能放心。我个人在这些试验中感受到EKF和UKF在仿真里的性能差距放到真实环境下会因为传感器噪声特性、温度变化、电池老化等因素发生明显变化所以仿真阶段的Q/R参数只是起点不是终点。希望这篇梳理能帮你少走些弯路。后面如果有机会我再单独把UKF的Sigma点参数自适应方法以及充电策略和SOC估计联动优化的做法整理成另一篇分享。
返回列表