资讯详情

资讯详情

双扩展卡尔曼滤波在锂电池SOC估计中的Matlab实现与避坑指南

简介面向锂电池SOC估计研究者的MATLAB代码包完整实现基于双扩展卡尔曼滤波的荷电状态估计算法。代码附带锂离子电池实验数据包含SOC-OCV曲线与实验室实测电流电压无需自行准备数据即可直接复现结果。算法采用两个扩展卡尔曼滤波器协同估计SOC并与单一卡尔曼滤波方案进行对比清晰展示不同方法的精度差异。资源共135个文件压缩包大小约58.58MB内有64个.m源码文件、58个.mat实验数据文件以及少量PDF、Word说明文档和辅助脚本代码结构清楚从电池等效模型建立、参数识别到双滤波器实现均有详细中文注释便于阅读和二次开发。此外还提供仿真验证脚本可观察滤波收敛过程支持按需调整模型参数并对比估计效果。运行前须先将实验数据导入MATLAB工作空间。已有3162人学习下载适合电池管理系统、状态估计算法方向的学生与工程师作为课程设计或科研验证的参考实现。1. 双扩展卡尔曼滤波的锂电池SOC估计matlab代码为什么两条卡尔曼通道比一条更可靠做锂电池SOC估计的matlab代码时我最早只写一个扩展卡尔曼滤波器直到在内阻随温度明显漂移的工况下连续翻车才开始认真把双扩展卡尔曼滤波DEKF落实到代码里。所谓双扩展卡尔曼滤波是把电池的状态量和模型参数拆成两个EKF并行运行一个快滤波器估计SOC和极化电压一个慢滤波器跟踪内阻等缓慢变化的参数。对BMS算法、电化学储能系统或毕业设计来说这套代码解决的核心问题是即使电池老化、温度变化导致模型参数不再准确SOC估计仍能保持稳定。适合那些已经会搭一阶RC模型但被单EKF参数失配、估计漂移困扰的工程师。下面直接给出一份可运行思路和matlab代码结构并把我踩过的初始化、噪声协方差和可观测性坑一起说清楚。2. 从电池等效电路到双EKF结构两个滤波器各自在看什么2.1 一阶RC等效电路模型状态量与输出方程做DEKF之前先要把电池模型固定下来。常见做法是用一阶RC等效电路因为它比纯库仑积分多出极化电压的响应又比二阶RC少两个待辨识参数。对SOC估计来说一阶RC在动态工况下的端电压拟合精度已经够用参数也不至于多到让双滤波器都陷入不可观测。状态量选两个SOC和极化电压V1。离散状态方程写成SOC_{k1} SOC_k - (η·Δt / Cn) · I_kV1_{k1} a·V1_k b·I_k其中 a e^{-Δt/τ}b R1·(1 - a)τ R1·C1。η是库仑效率放电时取1.0Cn是电池容量单位安时Δt是采样周期。输出方程是V_t OCV(SOC) - V1 - R0·I这里OCV与SOC的关系不是简单解析式而是查表。很多电池厂家直接提供电压容量对照表磷酸铁锂的曲线比三元锂更平平台区的dOCV/dSOC很小查表点必须加密三元锂的4.2V截止附近斜率很陡查表点可以稀疏一点但也要保证插值单调。我在代码里一般把OCV表放在一个函数里用interp1做线性插值。千万不要直接把离散表当阶梯函数用否则EKF的增益会在平台区来回震荡后面避坑章节会细讲。2.2 状态EKF与参数EKF的耦合谁给谁喂估计值双扩展卡尔曼滤波的核心是两个EKF交替工作但互相喂结果。状态EKF负责快速变化量也就是SOC和极化电压参数EKF负责慢变量通常先只估内阻R0因为R0直接出现在输出方程里和端电压残差的关系最干净。两个滤波器的分工可以用下面这个表概括滤波器估计量更新频率主要输入输出给谁状态EKFSOC、V1每个采样周期电流、电压、当前R0给参数EKF提供SOC和V1估计参数EKFR0每个采样周期或条件触发端电压残差、电流、状态估计给状态EKF提供最新R0运行时序是先用上一拍的R0做状态预测再用电压测量更新SOC和V1然后把更新后的SOC和V1当成已知量用同一拍端电压残差去更新R0。不要让两个EKF同时改同一个变量否则两个滤波器都会以为自己已经把误差消掉了实际是互相掩盖。参数EKF的随机游走模型一般写成θ_{k1}θ_kww的方差Qθ决定参数跟踪速度。θ变化慢Qθ就该很小。如果Qθ给得大R0会追着电压噪声抖反而让SOC估计更差。2.3 为什么SOC和参数不能放进同一个状态向量不少新手会问与其开两个EKF不如把SOC、V1、R0拼成一个三维状态向量一个EKF全估了。理论可行但实际跑起来问题很多。首先是时间尺度差异。SOC和V1是毫秒到秒级动态R0是分钟到小时级缓慢漂移。联合状态EKF必须用同一个过程噪声矩阵去描述这三者快变量需要Q大慢变量需要Q小折中之后协方差矩阵容易病态。双EKF把两个时间尺度分开各自给各自的Q调试起来直观很多。其次是线性化误差。EKF对非线性函数做一阶线性化OCV曲线本来就是强非线性如果状态向量里再混入参数雅可比矩阵的维度变高增益计算容易出现互相干扰的项。实际表现就是滤波器不发散但也不收敛SOC和R0一起缓慢漂移。第三是排错。联合EKF一旦发散很难判断是SOC更新错了还是参数更新错了。双EKF可以分别看两个残差状态残差大说明模型动态不准参数残差大说明参数更新条件有问题。这个工程上的优势比理论上的“最优性”更值钱。3. 在Matlab落地可运行的DEKF代码模型函数、主循环和初始化3.1 电池模型函数输入电流返回端电压先写模型函数。这个函数只做一件事给一个状态向量x、电流I、采样周期dt、参数R0返回下一拍状态和端电压估计。function [Vt, x_next] batteryModel(x, I, dt, R0) % 一阶RC模型 % x [SOC; V1]SOC单位1V1单位V % R0是内阻容量Cn固定为3.0Ah SOC x(1); V1 x(2); Cn 3.0; % 电池容量Ah eta 1.0; % 库仑效率 R1 0.0015; % 极化电阻欧姆 C1 1000; % 极化电容F tau R1 * C1; % 时间常数秒 a exp(-dt / tau); b R1 * (1 - a); SOC_next SOC - eta * dt / Cn * I; V1_next a * V1 b * I; Vt OCV_interp(SOC_next) - V1_next - R0 * I; x_next [SOC_next; V1_next]; end这个函数把容量Cn写死成3.0Ah是刻意的。第一版DEKF只让R0做参数容量先固定否则参数EKF容易发散。如果你要估计容量需要额外加一个观测方程到避坑章节再展开。OCV_interp函数单独写用查找表加interp1。SOC点是0:0.05:1电压点按电池厂家给的电压容量对照表填。线性插值比polyfit稳polyfit在高次时容易在端点振荡。3.2 核心DEKF循环状态滤波与参数滤波怎么串起来主循环是整个代码的核心。下面这段代码可以直接粘到Matlab脚本里跑前提是你已经有电流序列I和电压测量序列Vt_meas并且都按相同采样周期dt对齐。N length(I); dt 1; % 采样周期1秒 x [1; 0]; % SOC初值1V1初值0 Px diag([1e-3, 0.1]); % 状态协方差初值 Qx diag([5e-6, 1e-5]);% 状态过程噪声 Rx 1e-4; % 电压测量噪声方差对应10mV rms theta 0.005; % R0初值5mΩ Ptheta 1e-4; % 参数协方差初值 Qtheta 1e-8; % 参数随机游走噪声 soc_est zeros(N, 1); r0_est zeros(N, 1); for k 1:N % ---- 状态EKF预测 ---- [~, x_pred] batteryModel(x, I(k), dt, theta); a exp(-dt / (0.0015 * 1000)); Fx [1, 0; 0, a]; Px_pred Fx * Px * Fx Qx; % ---- 状态EKF更新 ---- Hx [dOCV_dSOC(x_pred(1)), -1]; Vt_hat OCV_interp(x_pred(1)) - x_pred(2) - theta * I(k); Kx Px_pred * Hx / (Hx * Px_pred * Hx Rx); x x_pred Kx * (Vt_meas(k) - Vt_hat); Px (eye(2) - Kx * Hx) * Px_pred; % ---- 参数EKF预测与更新 ---- Ptheta_pred Ptheta Qtheta; Htheta -I(k); Vt_hat_theta OCV_interp(x(1)) - x(2) - theta * I(k); Ktheta Ptheta_pred * Htheta / (Htheta * Ptheta_pred * Htheta Rx); theta theta Ktheta * (Vt_meas(k) - Vt_hat_theta); Ptheta (1 - Ktheta * Htheta) * Ptheta_pred; soc_est(k) x(1); r0_est(k) theta; end这段代码里状态EKF先工作得到x后参数EKF再工作。参数EKF的Htheta只有一项因为端电压对R0的偏导就是-I。电流放电台I为负值所以Htheta为正残差为正时会把R0调大。dOCV_dSOC需要实现成OCV曲线的数值导数。可以用差分在SOC附近取±0.001算两遍OCV再求斜率。不要用polyfit的解析导数因为查表插值后的曲线导数本身不光滑数值差分更符合EKF的离散框架。3.3 初始化与数据结构Q、R和协方差矩阵该给多大初始化看似只是拍脑袋填几个数实际上是DEKF能不能收敛的第一道关口。下面的初始化参数表是我在1秒采样、5A恒流放电工况下常用的起点参数典型初值说明Px(1,1)1e-3对应SOC初始不确定性约3%Px(2,2)0.1极化电压的初始不确定度Qx(1,1)5e-6主要由库仑积分误差决定Qx(2,2)1e-5模型简化误差Rx1e-4电压传感器噪声10mV rms时Rx1e-4Ptheta1e-4R0初始不确定度约10mΩQtheta1e-8R0慢变给太大会抖动Q和R的绝对大小不重要比例才重要。如果Qx(1,1)/Rx偏大SOC会积极跟随电压测量噪声大偏小则SOC只信任库仑积分电压观测几乎没有修正作用。调试时先固定Rx再逐个调Q不要同时动多个旋钮。还有一个常见坑Ptheta初值给很大比如1表示对R0毫无先验。这样前几步参数EKF增益会特别大R0可能一步就从0.005跳到0.02。正确的做法是先跑一段离线数据把R0初值设到肉眼可见的合理区间Ptheta只给初值附近的不确定度。4. 跑通后怎么验证和整定SOC误差、放电曲线和噪声协方差4.1 用模拟数据闭环从25℃恒流放电看估计效果DEKF写完后第一件事不是接真电池而是用已知模型生成模拟数据。这样真值在手能直接判断算法本身有没有写错。下面这段代码生成一组5A恒流放电数据电流序列是负数持续1800秒。dt 1; N 1800; I -5 * ones(1, N); % 5A恒流放电 x_true [1; 0]; R0_true 0.005; % 真实内阻5mΩ Vt_true zeros(1, N); for k 1:N a exp(-dt / (0.0015 * 1000)); x_true(1) x_true(1) - dt / 3.0 * I(k); x_true(2) a * x_true(2) 0.0015 * (1 - a) * I(k); Vt_true(k) OCV_interp(x_true(1)) - x_true(2) - R0_true * I(k); end Vt_meas Vt_true 0.003 * randn(1, N);这里真实模型和DEKF里的batteryModel用的是同一个一阶RC结构属于算法自洽验证。它能确认DEKF的增益计算、协方差更新和参数更新逻辑没有符号错误但不能验证模型本身是否符合真实电池。真机验证换一批数据模型误差会作为过程噪声进入Qx。跑完DEKF后把soc_est和x_true的第一行画在同一张图上再把残差画出来。如果残差里还有明显趋势比如SOC估计始终领先或落后0.5%先不要调Q去看OCV查表是否偏了。4.2 SOC误差的两个判据RMSE与最大绝对误差验证DEKF不能只盯着曲线“看起来像”要用两个数字说话。我一般固定用RMSE和最大绝对误差MAE。err soc_est - x_true(1, :); RMSE sqrt(mean(err.^2)); MAE max(abs(err)); fprintf(RMSE %.4f, MAE %.4f\n, RMSE, MAE);RMSE反映整体精度MAE抓极端情况。恒流放电工况下一阶RC模型加DEKF的RMSE一般能压到0.01以内动态工况因为有极化瞬态RMSE会到0.02甚至0.03。这不是算法不行而是模型阶数不够。另外注意SOC低段误差容易放大。SOC低于20%时OCV曲线斜率变陡电压对SOC的敏感度高如果查表点不够密增益会被放大MAE往往出现在这个区间。所以看误差时要按SOC区间分段统计不要被总RMSE掩盖问题。4.3 现场调试三件套Q/R比例、OCV查表、电流噪声真机调试时最常改的三处是Q/R比例、OCV查表密度和电流噪声。Q/R比例的症状很典型SOC估计曲线平滑到看不出电压修正说明Qx给太小SOC跟着电压测量剧烈抖动说明Qx给的相对Rx太大。调法固定先把Rx按电压传感器手册里的噪声方差填好再调Qx(1,1)每次乘或除10试一轮。OCV查表的问题主要出现在磷酸铁锂。它的放电曲线平台区很平dOCV/dSOC在SOC 30%~70%可能只有三元锂的十分之一此时状态EKF的Hx变小SOC几乎完全靠库仑积分撑住。如果这时OCV表本身还有台阶电压残差会在平台区反复变号SOC估计就会出现锯齿。解决方法是把电压容量对照表加密到SOC每1%一个点并且用平滑后的曲线算数值导数。电流噪声是很多工程实现容易忽略的点。DEKF只处理电压测量噪声电流偏置会直接进入SOC状态方程做积分等价于一个巨大的过程噪声。如果电流传感器存在固定偏置DEKF无法从电压测量里完全恢复SOC。解决方式是在参数EKF里增加一个电流偏置参数或者在硬件层面做零漂校准。这个扩展会带来可观测性问题需要更多工况激励不推荐第一版就做。5. 避坑清单双EKF在锂电池SOC估计上最容易翻车的5个细节5.1 现象SOC在电压平台区来回跳变恒流放电跑到磷酸铁锂平台区SOC估计本该平滑下降实际却在5%范围内振荡。原因几乎都是OCV查表不平滑或者dOCV_dSOC数值差分步长太大。电压平台区斜率接近0残差里的微小电压波动会被放大成SOC修正。解决方法是把OCV表离散点加密插值方式从linear改成pchip保证一阶导数连续。数值差分求dOCV_dSOC时步长不要固定可以用中心差分步长取SOC表间隔的一半。调完再看SOC曲线锯齿一般会消失。5.2 现象参数EKF把R0越估越大甚至发散恒流放电时R0估计值一路往上飘SOC也跟着偏。原因是激励不足恒流工况下端电压的变动大部分来自SOC下降和极化电压缓慢建立R0和V1等参数存在混叠。参数EKF在这种工况下没有新的信息可以区分它们增益不减反增。解决方法是给参数更新加触发条件。只有电流变化幅度超过阈值时才更新R0否则只做预测不更新参数。代码里加一个判断if abs(I(k) - I(k-1)) 0.5 % 电流激励充分更新参数 Ktheta Ptheta_pred * Htheta / (Htheta * Ptheta_pred * Htheta Rx); theta theta Ktheta * (Vt_meas(k) - Vt_hat_theta); Ptheta (1 - Ktheta * Htheta) * Ptheta_pred; else % 激励不足保留预测协方差 theta theta; Ptheta Ptheta_pred; end同时对theta做边界限制比如R0只能落在0.001到0.02之间。卡尔曼滤波本身不关心物理边界但真值一定在合理区间内边界裁剪是工程上最后的防线。5.3 现象状态EKF和参数EKF同时漂移表现在SOC和R0都缓慢偏离真值但两个残差都不大。原因通常是初始协方差Px和Ptheta给得过大前几十步增益过高把错误的修正量灌进了滤波器之后协方差收缩错误被“焊死”在状态里。解决方法是故意跑一个错数据把当前修好的状态当成真值重新初始化然后人为把R0设偏50%看滤波器能否在100步内拉回来。如果拉不回来说明增益已经收缩到不更新的状态需要适当增大Qtheta让参数EKF保持“永远能接受新信息”的活力。5.4 现象想估计电池容量结果端电压观测量不响应有人把容量Cn也放进参数EKF用端电压残差更新它结果Cn几乎不动。这不是代码写错而是可观测性问题端电压对Cn没有直接偏导Cn只通过SOC状态方程的积分间接影响电压单靠瞬时电压残差根本喂不进去。解决方法是把参数EKF的观测量改成SOC增量。先用状态EKF输出的SOC估计值做差分再和安时积分公式做残差ΔSOC_est ≈ -η·Δt / Cn · I这样Cn就能直接从SOC变化率里被观测出来。实际工程里我习惯把容量估计拆出来做另一个滤波器而不是硬塞进同一套电压观测方程。顺带说一句这个方向再往前走一步就是电池剩余寿命预测的入口。5.5 现象Matlab脚本跑一遍要几分钟无法实时代码里每次循环都调用OCV_interp和dOCV_dSOC数值差分每个SOC点要算两遍OCV数据量一大就慢。解决方法是把OCV曲线和导数曲线预先算成一个高分辨率数组循环里只做索引查表不用插值。更彻底的做法是把DEKF主循环封装成函数用Matlab Coder生成C代码或者直接在Simulink里跑。对BMS原型验证来说先把OCV表向量化已经能把速度提升一个数量级。6. 把DEKF封装成一键重跑的matlab脚本一个值得养成的工程习惯前面所有代码都是平铺在脚本里换一组数据就要改一堆变量。我后来养成的习惯是把DEKF封装成一个函数输入电流、电压、采样周期和初始SOC输出结果结构体。这样不同工况的数据比如25℃恒流、动态脉冲、低温放电都能一键重跑。函数头可以这样写function result run_DEKF(I, Vt_meas, dt, soc0) % 输入电流序列I(A)电压序列Vt_meas(V)采样周期dt(s)初始SOC % 输出result.soc, result.R0, result.RMSE, result.MAE % 内部完成初始化、DEKF循环、误差统计 end这么做的好处是验证新数据时不用再去翻初始化代码只需要关心数据本身。另一个好处是当你想对比单EKF和双EKF时只要再写一个run_EKF函数两个结果放在一起比较说服力比空口解释强得多。我的血泪经验是最早做DEKF时参数全写死在脚本里换工况十几处要改结果某一次漏改容量整个验证结论都是错的。封装之后初始化和循环逻辑只写一次后面所有坑都在数据侧排查速度明显提升。如果你准备把DEKF用到实际项目里建议也从这个函数入口开始先跑仿真数据再跑实测数据最后再考虑代码生成。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →