基于EKF的SOC/SOH联合估计:Simulink二阶RC电池模型实现
2026/9/13 13:19:28 网站建设 项目流程

简介:面向电动汽车电池管理系统研发与仿真工程师,也适合新能源汽车相关专业学生,聚焦基于荷电状态估计的动力电池健康状态建模。压缩包内共四个文件,包含两个Simulink模型与两个MATLAB脚本,分别用于卡尔曼滤波及电池仿真、SOH计算与运行控制,整体大小约29KB,便于快速下载研读。目前已有483人参与学习,可作为BMS专题研究、毕业设计或课程设计的参考资料。资源完整呈现了从SOC估算到SOH评估的建模链路,涵盖电池模型选择、SOC估计、参数老化、数据驱动预测与模型验证等关键环节;读者可通过模型文件与脚本进行仿真复现,结合实验数据完成标定,也可在此基础上扩展安时积分、开路电压与扩展卡尔曼滤波的对比分析,为后续集成至电池管理系统提供可移植的基础框架。

1. 为什么SOH估算要绑定SOC估计这条链路

动力电池健康状态(State of Health)不是用传感器直接测出来的量,它是基于容量、内阻等可观测量推断出的老化指标。同一个电池在SOC 30%和SOC 80%时,端电压和内阻都不一样,如果不先把SOC估计准,后面所有的SOH计算都会跟着偏。反过来,SOH又影响总容量和可用能量,两个状态必须放到同一个模型链路里闭环。

常见做法是用Simulink搭一个二阶RC等效电路,配合扩展卡尔曼滤波EKF把SOC估计到1%以内,再在SOC估计输出上叠加容量估计通道。SOC是快变量,SOH是慢变量,时间尺度不同,但共享同一组OCV、内阻和温度数据。这样模型既能离线跑,也能生成C代码或导出FMU做硬件在环。适合做BMS算法、整车仿真的工程师,也适合做模型在环测试的人。

2. 在Simulink里搭电池等效电路模型与SOC估计器

2.1 从OCV-SOC曲线到二阶RC等效电路

锂电池的端电压并不能直接代表SOC,因为内阻分压和极化效应会在充放电瞬间把电压抬高或压低。工程上最常用的折中是戴维南等效电路:一个电压源OCV(Z,t)与欧姆内阻R0串联,再挂上两个RC网络,分别模拟电化学极化和浓度极化。SOC通过安时积分定义:

Z(t) = Z(0) - (1/Q)∫η·i dt

其中Q是当前最大可用容量,η是库仑效率。这个公式里Q会在老化过程中不断变小,如果拿新电池的Q去做积分,SOC会在使用后期逐渐漂移,这正是SOH要解决的耦合问题。

二阶RC模型的连续状态方程取状态量x=[Z, V1, V2]:

dZ/dt = -η·i/Q
dV1/dt = -V1/(R1·C1) + i/C1
dV2/dt = -V2/(R2·C2) + i/C2

其中R1C1代表电化学极化,R2C2代表浓度极化。在Simulink里不需要从头搭微分方程,RC状态部分可以直接用Continuous库的State-Space模块,把A、B矩阵由工作区变量传入;OCV的查表部分单独用Lookup Table模块做,最后把OCV(Z)、V1、V2和R0·i相加得到端电压。

搭建顺序我一般按四条线走:

  1. 先放一个积分器作为SOC状态,初值接工作区变量SOC0;
  2. 用State-Space模块搭建V1、V2两个RC状态,输入是电流i;
  3. 用Lookup Table模块读OCV-SOC表,SOC来自第一步的积分器;
  4. 用Sum模块把OCV、V1、V2、R0·i加起来输出端电压U_terminal。

这样做的原因是把线性的RC网络和非线性的OCV查表分开。数组读在Simulink里默认通过Lookup Table内部线性插值,不需要自己写下标判断,省掉很多边界条件处理。后面做代码生成时,Lookup Table会自动变成等间距查表函数,仿真和C代码的结果能保持一致。

2.2 用MATLAB脚本初始化参数,避免Simulink查表到处填数

在Simulink中做电池模型最让人头疼的不是模块连不通,而是参数散落在各个Mask里。我通常建一个初始化脚本init_battery_model.m,在模型PreLoadFcn回调里调用它,这样每次打开或仿真模型都会自动加载参数。

% init_battery_model.m % 电池参数定义,供Simulink模型直接引用 Q_new = 94; % 新电池额定容量 Ah SOC0 = 0.95; % 初始SOC eta = 1.0; % 充电时库仑效率取1.0 R0 = 0.003; % 欧姆内阻 Ohm R1 = 0.0018; C1 = 1250; % 电化学极化 RC,单位 F R2 = 0.0025; C2 = 5200; % 浓度极化 RC,单位 F % 定义OCV-SOC查表点,SOC从0到1,间隔0.05 SOC_LUT = (0:0.05:1)'; OCV_LUT = [2.85 3.35 3.52 3.63 3.71 3.78 3.84 3.88 3.91 3.94 3.97 3.99 ... 4.02 4.05 4.08 4.12 4.17 4.22 4.27 4.32 4.38]';

每个参数的单位都写在注释里,因为后续做参数辨识时最容易错的是把欧姆内阻和RC电阻混在一起。OCV_LUT是21个点的离散表,仿真时通过Lookup Table模块线性插值。注意SOC_LUT最后一个点是1,OCV_LUT最后一个值4.38对应满充电压,不要拿电芯平台电压4.2V直接替代。

为什么用脚本定义而不是在Mask里填?因为后面做参数扫描或遗传算法辨识时,只需要循环改变脚本中的R0、R1等变量,再调用set_param或直接sim()运行,不用手工打开每个模块。如果是新下载安装的MATLAB环境,Simulink和System Identification Toolbox务必选上,否则EKF相关函数会缺工具箱。

2.3 用EKF做SOC估计:状态方程与观测方程怎么落到模块

SOC估计不能只靠安时积分。电流传感器有偏置噪声,初始SOC可能错到10%以上,积分时间长了会越积越偏。扩展卡尔曼滤波的思路是用端电压观测来修正SOC,把SOC、V1、V2作为状态,用端电压方程作为观测方程。

离散预测步:

x_k|k-1 = A_d x_{k-1} + B_d i_{k-1}
P_k|k-1 = A_d P A_d^T + Q_noise

更新步:

K = P H^T (H P H^T + R_noise)^(-1)
x_k = x_k|k-1 + K (y_k - h(x_k|k-1))
P = (I - K H) P_k|k-1

其中h(x) = OCV(Z) + V1 + V2 + R0·i,H是h对x的雅可比矩阵。我一般在Simulink里用MATLAB Function模块写这个更新逻辑,配合Unit Delay保存上一时刻的协方差矩阵。

function [SOC_est, Pout] = ekf_step(SOC_pred, P_pre, Vpred, U_meas, i_meas, params) % 简化的EKF单步更新 % 状态顺序固定为 [SOC, V1, V2] % 观测只有端电压,因此H是1x3行向量 dZ = 0.001; dOCV = (interp1(params.SOC_LUT, params.OCV_LUT, SOC_pred+0.001) - ... interp1(params.SOC_LUT, params.OCV_LUT, SOC_pred-0.001)) / (2*dZ); H = [dOCV, 1, 1]; % 对V1、V2的偏导都是1 K = P_pre * H' / (H * P_pre * H' + params.R_noise); SOC_est = SOC_pred + K(1) * (U_meas - Vpred); Pout = (eye(3) - K * H) * P_pre + params.Q_noise; end

这段代码里,K是一个3×1矩阵,SOC修正量只取第一行。如果维度对不上,多半是H的形状问题,务必先做size检查。参数说明:Q_noise不能设成零,否则EKF会过度相信SOC预测,端电压反馈几乎不起作用;R_noise如果太小,端电压噪声会被误当成SOC变化,估计结果会非常毛。初值P_pre用单位阵放大到1左右,表示初始SOC不确定度约为1%。想快速跑通时先用实验室静态工况,动态工况最后再调这两个噪声参数。

3. 基于SOC估计的SOH建模:容量与内阻双通道估计

3.1 为什么容量法SOH不够,要加内阻

业界对SOH最直观的定义是当前最大可用容量与额定容量之比:

SOH_Q = Q_aged / Q_new × 100%

这是BMS里常用来显示健康度的值。但容量法在实车上有个问题:容量很难在任意时刻直接测出来。你必须等一次完整的充放电区间,或者用很小电流从满充放到空,这在日常驾驶中往往等不到。同时,容量衰减到80%时,内阻往往已经增加30%到50%,功率性能明显下降,但容量百分比看起来还行。所以工程上常做双通道SOH:

SOH_R = (R0_EOL - R0_aged) / (R0_EOL - R0_BOL) × 100%

R0_BOL是初始欧姆内阻,R0_EOL是寿命终止时的内阻。这两个SOH最终取最小值或按场景加权融合。从这个角度看,SOH建模实际上是在估计两个慢时变量:容量Q_aged和欧姆内阻R0_aged。它们都依赖SOC估计提供的荷电状态参考点,所以模型内部不能把SOC估计器单独摘开。

3.2 在线容量估计公式:库仑计数与SOC差分

在线容量估计常用的方法是SOC差分法。选取两个时刻t1和t2,记录安时积分ΔAh和SOC估计变化ΔSOC,两者关系是:

Q_aged = ∫_{t1}^{t2} η·i dt / (SOC_est(t2) - SOC_est(t1))

这个公式不需要电池完全放空,只要ΔSOC足够大,比如超过30%,且区间内避免持续大电流脉冲即可。分母是两个SOC估计值之差,如果SOC估计存在固定偏置,容量估计时两端偏置会部分抵消,所以容量估计的精度比单点SOC要稳。

在Simulink中,用带Reset的Integrator做安时积分,用触发信号捕捉充放电结束的稳定点。触发条件可以设置为电流绝对值小于C/50持续5秒,且SOC变化超过阈值,避免在动态工况中取到瞬态值。

function Q_aged = capacity_update(SOC_t1, SOC_t2, Ah_integrated) % 容量估计更新函数 % SOC_t1, SOC_t2 必须来自同一个SOC估计器 dSOC = SOC_t2 - SOC_t1; if abs(dSOC) > 0.3 && abs(Ah_integrated) > 0.1 Q_aged = Ah_integrated / dSOC; end

这里关键参数是dSOC阈值0.3。当SOC估计误差为1%时,容量估计的相对误差约为3%左右;如果把阈值设到0.1,噪声占比会明显增大。Ah_integrated在Simulink里需要和一个“估计窗口有效”的使能信号联动,否则区间外积分会污染结果。

3.3 Simulink模型实现与参数表

把容量通道和内阻通道拼在一起后,我一般整理成清晰参数表,方便代码生成和后续维护。

模块路径参数名物理含义典型初值更新方式
SOC积分器SOC0初始SOC0.95上电读取
EKF模块Q_noiseSOC过程噪声方差1e-6温度查表
EKF模块R_noise端电压观测噪声1e-4标定
容量估计器dSOC_min最小SOC变化窗口0.30固定
容量估计器I_thr触发电流阈值0.05C固定
内阻估计器R0_FIR欧姆内阻滤波系数0.05随老化调
SOH融合alpha容量权重0.8标定

“更新方式”一列很重要。Q_noise不能固定,低温下模型失配加剧,需要适当调大;R_noise在电流剧烈变化时也要调大。SOH融合的alpha建议在电池健康初期取0.8偏容量,后期偏内阻,因为快充场景对内阻越来越敏感。

内阻估计通道的做法是:在电流产生阶跃时,用端电压的瞬时跳变除以电流跳变得到R0。Simulink里可以用Unit Delay记录上一时刻的电流和端电压,触发信号是电流微分的绝对值大于阈值。这样得到的R0很粗,需要再接一阶低通滤波器平滑,滤波器系数设为0.05比较合适。这个通道与SOC估计关系较弱,但依然共用同一个等效电路参数,必须和EKF放在同一个模型中跑。

4. 参数辨识与仿真验证:从MATLAB脚本到Simulink联合跑

4.1 用实验数据辨识OCV曲线和RC参数

要让上面的SOC/SOH模型可信,必须先把模型参数标定到某个真实电芯。OCV-SOC曲线的辨识相对简单,在25℃恒温室中用C/20小电流充满再放空,记录电压平台和SOC对应关系。工业界常用脉冲充放电法,每段电流脉冲后静置1小时,静置结束的端电压当作该SOC点的OCV。

RC参数辨识用最小二乘拟合:

% 用脉冲放电数据辨识二阶RC参数 data = load('pulse_25C.mat'); t = data.t; v = data.v; i = data.i; model = @(p) predict_voltage(p, t, i, SOC0, OCV_LUT, SOC_LUT); p0 = [0.003, 0.0018, 1250, 0.0025, 5200]; options = optimoptions('lsqnonlin', 'Display', 'off', 'TolFun', 1e-6); p_opt = lsqnonlin(@(p) model(p) - v, p0, [], [], options); R0 = p_opt(1);

model函数把电压响应按二阶RC递推计算,减去实测电压得残差。p0初值只要数量级对,就不会收敛到离谱的局部最优。辨识完成后,把p_opt写回init_battery_model.m,再重新跑Simulink模型验证脉冲末尾的静置电压。如果电压残差大于20mV,优先怀疑OCV表在低SOC段插值不够密,而不是RC参数有问题。

4.2 仿真工况与初始SOC设置

参数标定完,把模型放到更接近整车的动态电流工况里。可以用简化的US06或WLTP电流序列,也可以直接用MATLAB的drive cycle模块转换成电流输入。关键是Simulink仿真中初始SOC不能随意设。由于SOH估计依赖SOC差分,如果初始SOC差10%,前几百秒的SOH估计值会处于收敛振荡期。

设置初始SOC有两种常用方式。第一种是双击积分器,把Initial Condition设成SOC0;第二种是在MATLAB脚本里用set_param:

set_param('battery_model/SOC_Integrator', 'InitialCondition', num2str(SOC0)); sim('battery_model.slx');

跑容量估计仿真时,我一般用两个不同初始SOC,例如0.95和0.5,验证容量估计结果是否一致。如果Q_aged都在同一时刻收敛到接近值,说明算法没有把初始SOC偏置带入SOH。这个交叉验证步骤常常能发现EKF里雅可比矩阵写错的问题,因为SOC初始偏差过大时,EKF应在几十秒内拉到真实值。

4.3 结果怎么看,误差怎么分

仿真完成后,把SOC估计值与模型内部真实SOC画在同一张图上,先观察SOC绝对误差。若动态工况下峰值误差超过2%,先不要动SOH模块,而是回到EKF的Q_noise和R_noise调整。SOC误差的形态能帮你定位原因:

误差形态可能原因调整方向
恒定为正偏初始SOC错或容量Q偏小检查状态初值
大电流时脉冲状误差R0辨识不准重做R0辨识
长时间漂移Q_noise过小或OCV表失真增大Q_noise
低SOC段发散OCV曲线平坦导致可见性差提高OCV表密度

SOH误差看相对误差和收敛时间。容量估计误差来源中,安时积分偏置会随时间累积;SOC差分法用两个点相减,能抵消一部分偏置,但抵消程度取决于区间内电流方向是否一致。如果区间内既有充电又有放电,η充、η放要分别处理,Simulink里可以用乘法模块对电流做方向判断,充电时乘0.98,放电时乘1.0。

仿真时间太长时,把EKF模块采样时间从1ms放宽到10ms,SOC估计精度通常不会明显下降,仿真速度会快一个量级。SOH本身是秒级慢变量,完全不需要和电流采样同频。

5. 提高SOH估计精度的三个实测技巧

5.1 对SOC估计器的噪声矩阵做时变处理

固定Q_noise在高温和低温场景会带来不同结果。低温下电池极化增大,模型失配明显,此时把Q_noise乘以1.5可以给EKF更大修正自由度。经验做法是建立温度-噪声系数查找表,在Simulink里用Lookup Table接到EKF模块的Q_noise输入端口,效果立竿见影。最容易踩的坑是只调R_noise不调Q_noise,导致SOC估计低温下抖动但不收敛。正确顺序是先标定OCV和R0,再调Q_noise,最后调R_noise,不要同时改所有参数。

5.2 用simulink外部模式和SDF导出做快速验证

想验证SOH模型在真实环境下的行为,不必直接烧到控制器里。在Simulink里把EKF和容量估计器打包成一个子系统,使用simulink外部模式连接目标机,或用Desktop Real-Time跑实时数据回放。外部模式中可以直接看SOC和SOH_est的轨迹,问题定位速度快很多。

如果后续要与其他工具联合,可以把SOC估计器单独导出,容量估计器再包一层。这样在Python里测试不同老化场景时可以分别替换模块。导出FMU或SDF文件时,记得把触发电平、容量窗口阈值作为外部可调参数暴露出来,否则离线标定时每次都要重新导模型。simulink的数组读在导出时表现为查找表的输入输出端口,没必要全部改成信号线。

5.3 用C代码生成当基线,避免仿真与实车两套结果

最后我会把整个SOC/SOH模型生成C代码,嵌入一个简单的BMS算法测试工程。Simulink模型和手写C代码之间最容易有差异的是查表范围和除法保护,因此做代码生成前,在Model Settings中打开运行时检查,并在容量除法前加一个前置饱和模块。这样生成的代码里会带上除零保护,虽然多几条指令,但对硬件在环更稳。

拿一段记录的真实电流数据,分别跑sim()和生成的C代码,两个结果的SOH估计差应当控制在0.1%以内才算闭环。使用C代码生成时,要确保MATLAB Function模块里的interp1在生成代码时被替换为查表函数,避免生成动态内存分配。最简单的验证方法是在init脚本里把SOC_LUT改成双精度double类型,观察生成代码中是否有临时缓存变量,如果出现,优先在函数开头声明最大数组长度,而不是依赖运行时分配。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询