首页 / 资讯中心 / 文章详情

基于Simulink的电池等效电路模型搭建与参数辨识实战指南

基于Simulink的电池等效电路模型搭建与参数辨识实战指南 ★ FEATURED ARTICLE
我一开始搭电池仿真模型的时候纯粹是被BMS算法逼的——总不能每次验证SOC估算都去拆真电池、做满充满放的破坏性实验。后来用Matlab/Simulink从头搭了一套电池等效电路模型从单体到电池组从常温到变温踩过的坑比写过的代码还多。这篇文章就把我这段搭建之旅的完整思路、关键步骤和排查心得写出来给正在搞电池建模、BMS仿真或者毕业设计的朋友做个参考。你不需要刚开始就能搭出商用电芯级精度的模型但跟着这条路线走至少能有一个可靠、可扩展、能对自己的算法说清道理的Simulink电池仿真模型。1. 为什么选等效电路模型而不是电化学模型或纯查表模型1.1 模型复杂度和精度的平衡电池仿真模型大致有三条路电化学模型、等效电路模型和纯查表模型。电化学模型从锂离子在电极内部的嵌入/脱出、扩散和电解液浓度分布出发物理意义最强但参数动辄几十个还要解偏微分方程放到Simulink里仿真速度很慢也不适合做控制算法的实时验证。纯查表模型就是把OCV、内阻和温度、SOC的关系建一张大表输入电流和SOC直接插值出电压简单到不能再简单但它没有“记忆效应”电流突变的瞬间电压跳变得特别生硬无法反映电池极化弛豫的过程。等效电路模型刚好卡在中间——用几个RC并联网络表示电池内部极化的动态特性既能复现电压的瞬态响应参数又可以用常规充放电实验辨识出来计算量也小。这正是我最终选它的原因。1.2 我在选型时的一个实际对比当时我手上有两个备选直接用Simulink自带Simscape Electrical库里的Battery模块或者自己用积分器搭一个数学模型。自带模块确实方便打开库拖出来就能仿真内部包含了热效应、老化寿命这些高级特性适合系统级电能分析。但如果你像我一样想控制算法、观察每个状态变量甚至后期要改模型方程自带模块就像一个黑盒子出了问题你只能干瞪眼。所以我决定自己搭一个透明的等效电路模型。后面证明这个决定对了因为我在调参数和理解OCV-SOC曲线之间的关系时每一步都知道值是怎么算出来的。2. 动手前的两件事环境准备与数据来源2.1 需要哪些工具箱和库我的环境是MATLAB R2022bSimulink是必须的。如果模型里用到更底层电学模块再装Simscape Electrical但我自己搭等效电路模型只用基本Simulink模块就够了。参数辨识阶段需要用lsqcurvefit或者nlinfit这两个分别来自Optimization Toolbox和Statistics and Machine Learning Toolbox建议装上。如果你要用SIMULINK仿真后自动生成C代码还需要Simulink Coder但那是后话初期可以跳过。安装这块没什么好说的正常安装就完了。但是我建议在开始搭模型前先在Matlab命令行跑一遍ver确认一下工具箱是否完全识别尤其是Optimization Toolbox否则后面辨识时会报“Undefined function lsqcurvefit”。2.2 实验数据怎么组织才便于参数辨识模型搭出来没有数据就是空中楼阁。最常用的电池建模测试是HPPC混合脉冲功率特性测试把电池放在恒温箱里先静置很久然后用一定倍率电流放电脉冲几秒再静置再充脉冲逐个小SOC点重复最后记录时间、电流、电压。如果你没有实验条件也可以用公开的电池数据集比如马里兰大学CALCE电池数据集或者网上常见的Panasonic 18650测试数据。数据格式我强烈建议统一为三列CSV时间(秒)、电流(安培)、电压(伏特)。电流正负号按习惯定义我用正表示放电负表示充电。SOC曲线单独用一个Excel表存放横轴是SOC(0到1)纵轴是OCV(伏特)。表头必须清晰因为后面要脚本批量读取。我当时是直接从实验室的充放电柜导出的原始数据里面有很多噪声和异常跳点。建议导入Matlab后第一时间画图核对剔除明显不合理的点并做一次移动平均滤波但注意不要过度平滑掉电流突变的响应否则参数辨识中的R0会被低估。3. 在Simulink里搭出第一个2RC等效电路模型3.1 模型方程与模块布局2RC等效电路模型的端电压方程是这样的V_t OCV(SOC) - I * R0 - V1 - V2其中V1和V2是两个RC网络的极化电压各自满足一阶微分方程dV1/dt I/C1 - V1/(R1C1)dV2/dt I/C2 - V2/(R2C2)更常用的是写成传递函数形式每个RC网络相当于一个一阶惯性环节V1/I R1/(tau1s 1)tau1 R1C1。两个RC并联网络串联起来就能同时表达快极化和慢极化——一个时间常数几秒另一个几十到几百秒。只用1个RC网络拟合精度往往不够特别是长弛豫阶段电压回不到基线所以直接上2RC是工程上的主流选择。在Simulink里布局的时候我从左到右排电流输入端、容量积分和SOC计算、OCV查表、两个RC支路、求和输出。电流作为模型的输入这个非常关键因为电压是状态变量的函数而不是直接由代数方程求出来的这样能避开代数环问题。3.2 逐步搭建的实操细节我按照下面这个顺序搭建放一个Constant模块作为初始SOC设成0.8。放一个Integrator模块计算SOC变化。积分器的输入是电流除以容量再除以3600因为容量是Ah电流是A时间是s换算后得到SOC的变化率。例如2Ah电池增益就是1/(2*3600)。把Integrator输出接到一维Lookup Table查OCV-SOC表得到当前开路电压。在电流通路并联两条RC支路。我习惯用“Transfer Fcn”模块每个支路设置成R/(tau*s1)的形式比如快支路R10.02欧姆tau110秒慢支路R20.03欧姆tau2200秒。这样比手动搭积分器省事但注意Transfer Fcn初值默认是0如果不希望有初始极化这样刚刚好。用Sum块做加减V_t OCV - I*R0 - V1 - V2。R0用Gain模块乘以电流。最后用Scope或者To Workspace将电压输出。搭完之后用Signal Editor或From Workspace加载一个脉冲电流信号跑一下你应该能看到电压在电流突变时先瞬间跳变R0产生然后缓慢指数过渡RC产生。这个瞬态形状对不对一眼就能看出来。4. 参数辨识用最小二乘把RC参数“喂”准4.1 参数辨识的原理和步骤模型搭好了里面的参数还是拍脑袋给的必须从实验数据里提出来。基本思路是分段辨识第一步辨识R0。在脉冲电流突变的瞬间电压会有一个阶跃跳变跳变幅值除以电流变化量就是欧姆内阻R0。比如从0A跳到-10A放电电压从4.0V降到3.95V那么R0 (4.0-3.95)/10 0.005欧姆。注意要读取突变瞬间那个采样点的电压而不是突变后几秒的电压因为RC极化马上就会叠加进去。第二步辨识RC参数。脉冲结束后进入弛豫阶段电流为0端电压会指数回升回升过程由两个RC网络组成。这段电压响应可以用双指数函数拟合V_relax OCV a1 * exp(-t/tau1) a2 * exp(-t/tau2)这里的a1和a2代表弛豫开始时两个RC网络的极化电压值。如果用最小二乘法拟合出a1、a2、tau1、tau2再结合刚才脉冲放电时的电流大小就能反算R1和R2。如果放电脉冲足够长极化接近稳态则稳态极化电压约等于IR1和IR2所以R1 a1/IR2 a2/I注意符号和方向。更严谨的做法是结合脉冲施加阶段的完整响应一起拟合但双指数拟合对初值敏感需要多次尝试。4.2 在Matlab里做辨识的代码片段下面是我用的一个简单拟合代码框架直接使用弛豫段数据% 加载HPPC数据 data readtable(hppc_data.csv); t data.Time; I data.Current; V data.Voltage; % 常数找弛豫段起始索引 idx_start find(t t_relax_start, 1); idx_end find(t t_relax_end, 1); t_relax t(idx_start:idx_end); V_relax V(idx_start:idx_end); % OCV为弛豫结束后的电压取最后几十个点的均值 OCV mean(V_relax(end-50:end)); % 定义双指数函数V OCV a1*exp(-t/tau1) a2*exp(-t/tau2) fitfun (p, t) OCV p(1)*exp(-t/p(2)) p(3)*exp(-t/p(4)); % 初值电阻极化0.02Vtau快速5s慢极化0.04Vtau慢速100s p0 [0.02, 5, 0.04, 100]; % 上下界限制 lb [0, 0.5, 0, 10]; ub [1, 100, 1, 1000]; % 最小二乘拟合 pfit lsqcurvefit(fitfun, p0, t_relax, V_relax, lb, ub); a1 pfit(1); tau1 pfit(2); a2 pfit(3); tau2 pfit(4);拟合完之后根据电流方向求出电阻初值。如果放电电流为正弛豫起点极化电压为负那么实际上a1和a2的绝对值就是放电时产生的极化电压所以R1 abs(a1)/I_pulseR2 abs(a2)/I_pulse。这样每个SOC点都做一次就能得到一组随SOC变化的R0、R1、R2、C1、C2参数表。C tau/R。这里有个很容易犯的错弛豫段OCV必须取足够长的静置后的电压否则SOC还没有稳定估算的OCV有偏差拟合出的双指数曲线就会整体偏移。我一般至少静置15分钟以上再取OCV。5. 让模型具备“智慧”SOC、温度与老化的耦合5.1 SOC估算引入方式前面的模型核心是电流积分计算SOC这就是安时积分法SOC(t) SOC0 - (1/(3600*Q)) * ∫ I dt在Simulink里用一个积分器实现初始条件设成SOC0。但是安时积分有累积误差尤其是长时间仿真或者电流传感器有偏置时SOC会越漂越远。因此模型内部要能够接受外部修正方便后面和BMS算法对接。我通常在SOC积分器旁边加一个“修正输入”端口当外部估算算法给出权威SOC时可以把这个值以很小的增益反馈到积分器保持模型和真实状态同步。如果你做的是纯算法验证也可以把SOC作为直接输入不通过积分器这样省事且不会因为SOC漂移影响OCV输出。但如果你要模拟真实情况下SOC估算不准时的电压响应就必须保留积分器并允许SOC误差的存在。5.2 温度修正通过查表或解析式实现电池参数受温度影响非常大冬天和夏天的内阻能差两倍。最简单的温度修正方式是在前面得到的参数表基础上再加一个温度维度。比如把HPPC测试在0℃、10℃、25℃、40℃都跑一遍建立四个温度下的参数表。然后在Simulink里使用二维Lookup Table横轴SOC纵轴温度输出R0、R1等参数。我实际用过一种更省事的方法只测25℃基准参数表然后用一个温度修正系数查表或解析式。常用的阿伦尼乌斯修正形式是R(T) R_ref * exp(A * (1/T - 1/T_ref))其中T和T_ref是绝对温度A是活化能常数。这个公式用在锂离子电池内阻修正上还挺有效。但要小心温度范围过大时单指数修正不够准确最好还是多温度测试。把温度耦合进模型时注意两条第一温度信号必须也是连续信号不能突变否则电压也会有阶跃第二仿真中温度通常来自热模型或实测数据如果没有热模型就干脆固定温度别让温度成为假动态。老化的引入更简单把容量改成SOH乘以标称容量内阻也可以加一个随循环增长的修正因子。对模型搭建来说不用追求精确老化模型重点是理解容量和内阻变化对端电压、SOC估算的影响。6. 仿真验证误差、鲁棒性与步长敏感性6.1 与实测数据对比的流程模型参数辨识完必须拿到一组没参与辨识的动态工况数据来做交叉验证。我常用一个自定义的混合工况几分钟大电流放电再平台小电流再脉冲叠加温度变化如果有温度数据。把这个工况的电流信号导入Simulink的Signal Editor跑完仿真后把输出的电压序列和实测电压序列画在一起计算误差。误差指标我习惯看这几个平均绝对误差MAE、均方根误差RMSE、最大绝对误差。对于2RC模型在25℃下动态工况电压RMSE做到20mV以内就算不错最大误差不要超过50mV。如果误差超过这个值首先检查参数表在极端SOC区间是否平滑其次检查模型里R0是否取的采样点过晚。注意对比时要把仿真的初始SOC和真实SOC对齐。如果仿真从SOC1开始实际数据电池从SOC0.9开始那前面电压会一直差一个OCV偏移。这个不算模型误差是初始化问题。6.2 步长选择和求解器设置Simulink默认变步长求解器 ode45在大多数情况下没问题但电池模型里有快速RC时间常数和查表插值如果步长控制不好电压响应会失真。我的经验是设置求解器为变步长容差RelTol1e-4AbsTol1e-6最大步长限制在0.1s。原因是电流突变后RC快速支路可能只有几秒时间常数步长太大不够采样查表在SOC变化剧烈时也可能有插值尖峰。如果模型要跑硬件在环HIL或代码生成必须转成定步长离散模型。我建议从一开始就用定步长梯形法或者四阶龙格库塔步长10ms或1ms。这样结果可重复也方便与外部离散算法交互。查表模块里设置“平滑”插值不要用线性插值。锂离子电池OCV-SOC曲线在低SOC段有个拐点线性插值会出现折点影响微分阶跃。用平滑插值例如三次样条能让电压响应更平缓。7. 踩坑实录代数环、单位混乱和初始状态7.1 代数环问题及解法我第一次搭模型时就遇到红颜色的“Algebraic Loop”警告虽然仿真也能跑但速度慢而且偶尔死循环。原因是我在电压输出端用了“受控电压源”和“电流传感器”把电压再反馈到输入端导致电流和电压形成一个瞬间闭合的因果环。解决方法是改变建模思路保证“电流进电压出”电流是输入电压是状态变量的线性组合而不是电压决定电流。如果你确实需要闭环可以在反馈路径加一个有延迟的Memory模块但更推荐重构信号流。7.2 单位混乱是个大坑容量单位换算是我见过最多人翻车的地方。电池容量是Ah而Simulink里积分时间是秒所以1Ah 3600As。如果你的电池容量是2Ah积分增益实际是1/(2*3600)不是1/2。我曾经写成1/2结果SOC每秒就归零整个模型完全不正常。另外一个常见错误是把电压毫伏当伏特或者参数内阻用毫欧直接填欧姆。建议在Simulink模型里给每个信号加上单位标签电流(A)、电压(V)、SOC(0-1)在模型文档里写清楚每个参数的物理量纲。如果使用Bus还可以用Simulink.BusElement定义单位属性至少能防止自己后面忘掉。7.3 初始状态与“零漂移”问题假如你要仿真的工况是从电池满电荷但已静置很久的状态开始那RC极化电压初始为0没有问题。但如果你从正在大电流放电的状态开始V1、V2的初值不设的话模型开头会出现一个虚假的过渡过程。我一般在参数辨识完之后额外跑一遍“预仿真”先给模型加载一个恒定电流跑足够长的时间让RC极化电压稳定再把这个稳定状态的V1、V2以及SOC值作为正式仿真的初始化条件。这在Simulink里可以通过工作区传递初始状态或者直接把积分器初始条件设成变量。还有一个小坑OCV-SOC曲线从0到1如果你用线性插值在SOC0和1的位置会把OCV拉得很夸张影响仿真收敛。所以我建议给OCV曲线两端各外插一小段让导数平滑。8. 从单体到电池包扩展模型时的架构建议8.1 打包成子系统还是Simscape当你需要仿真几十串电芯的电池组时一个个搭建肯定不可行。我采用的办法是把上面的2RC模型封装成一个带Mask的子系统参数表通过Mask参数传递。每个子系统有电流输入、SOC输出、电压输出还有温度输入端口。然后用总线箭头把这些子系统的输入输出串联起来或者做成一个单独的“BatteryCube”子系统内部再铺网络。这样做的好处是BMS算法可以直接读取每一串的SOC和电压方便做均衡策略仿真。如果你用的是Simscape Electrical它有自己的Battery模块支持串并联甚至能和热网络联合仿真。但用Simscape的代价是仿真步长要求更严格且状态变量不好直接观测。我的建议是先做纯Simulink版本验证算法等到需要做热-电耦合高精度仿真时再把底层换成Simscape外面留一样的接口。8.2 批量仿真和自动代码生成注意点做完电池组模型我经常要扫参数比如扫容量、内阻、OCV曲线偏移量来测试BMS算法鲁棒性。在脚本里用Simulink.SimulationInput批量跑非常方便比如for k 1:length(SOC_init_list) simIn(k) Simulink.SimulationInput(battery_model); simIn(k) setVariable(simIn(k), SOC_init, SOC_init_list(k)); simOut(k) sim(simIn(k)); end注意变量名一定要和工作区中的模型参数一致否则静默不生效。如果要生成C代码我的经验是先转成定步长离散模型把连续积分器换成离散积分器再把查表模块设置好确保支持代码生成。生成的代码里你可能会发现有些数学函数在嵌入式环境没有完全等效所以要做好验证。不过这个阶段比较进阶初期不需要考虑。最后再啰嗦一句模型搭建的核心不是堆模块而是每一步物理意义要清楚。你先用一个最简单的1RC模型跑通全流程再逐步加慢RC、温度、老化你会发现每次加复杂度之前都要问自己一个问题这个复杂度真的能改善误差吗如果加了温度修正误差反而变大那就说明你的温度数据或修正公式有问题而不是模型越复杂越好。我后来做电池模型最大的感受是稳扎稳打永远比追求花哨模型更高效。希望这篇经验能让你少走点弯路。
阅读完成 · 觉得有帮助?
咨询建站