ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

锂电池参数辨识实战:FFRLS+卡尔曼滤波+遗传算法,Matlab代码全解

锂电池参数辨识实战:FFRLS+卡尔曼滤波+遗传算法,Matlab代码全解

搞电池的人,尤其是做BMS算法或者电芯测试这一行的,手里估计都囤着好几个电池模型。模型建好了,参数怎么来?靠厂商给一份常温下的标称值,仿真一跑,低温或者大倍率工况下电压偏差能到几十毫伏甚至更多,SOC估计跟着飘。这时候就需要做参数辨识:根据实测的电流电压数据,把电池等效电路模型里的内阻、极化电容电阻这些参数给“拟合”出来。这篇文章我准备把手头这套常用方案完整拆开讲——递推最小二乘、带遗忘因子的FFRLS、卡尔曼滤波估计SOC、遗传算法离线寻优,核心代码都是Matlab的,覆盖离线标定和在线更新两条路。项目适合正在做电池建模、SOC/SOH估计,或者刚接手BMS算法想快速上手的工程师参考。

锂电参数辨识这个话题,说难不难,说简单也真不简单。难的地方在于你要同时处理好三个层面的问题:模型结构选得对不对、辨识算法用哪种、数据质量能不能支撑你的算法。这三件事任何一件掉链子,结果都很难看。我过去做过一段时间电池管理系统的算法开发,把这套东西从头到尾踩了一遍,今天把流程和经验整理出来,尽量讲得实操一点,代码能直接改改用。

1. 先搞懂参数辨识到底在干什么:模型、参数与数据三道关

1.1 等效电路模型选型:从物理意义到数学描述

参数辨识的第一步不是找算法,而是先确定你要辨识对象的结构。锂电池最常用的就是戴维南等效电路模型,也就是Rint模型加RC网络。Rint最简,一个欧姆内阻串一个电压源,只适合静态场景。二阶戴维南模型精度高,但参数多,在线辨识的收敛压力大。我这里推荐先把一阶RC模型跑通,也就是一个欧姆内阻R0,串一组极化电阻R1和极化电容C1的并联网络,再加一个开路电压源OCV。

一阶RC模型的数学描述是这样的:端电压 U_t = OCV(SOC) - I·R0 - U1,其中U1是极化电压,动态方程是 dU1/dt = -U1/(R1·C1) + I/C1。离散化之后可以写成差分方程形式,变成适合最小二乘的回归结构。

为什么从一阶模型入手?因为参数辨识本质上是在解一个最优化问题,参数越多,搜索空间越大,对数据激励的要求就越高。二阶模型在高倍率脉冲下有优势,但在普通工况下,一阶模型的精度差异可能也就十几毫伏。先用一阶把流程打通,后面需要再往二阶升级,代码框架都不用动。

把RC网络的微分方程做离散化推下去,一阶模型能写成这种线性回归形式:y(k) = φ(k)^T·θ。具体推导各家文献都有,核心思想是把包含U1的项消掉,用端电压、电流的历史值和当前值构造回归量。这个形式直接决定了你能不能套最小二乘——这也是为什么模型选型要在算法之前确定下来。

1.2 OCV-SOC曲线的获取与拟合

模型里还有个关键部分:OCV(SOC)函数。参数辨识的自变量不只是I和U_t,OCV查表误差会直接污染辨识结果。做HPPC实验的一个隐藏目的就是把OCV-SOC曲线给标出来:静置足够长时间,让电池内部到达近似平衡状态,测得的端电压就是该SOC点下的OCV。

实际工程中很少用解析式来表达OCV-SOC,基本都是多项式拟合或者分段线性插值。SOC要覆盖0到100%,间隔5%或10%取点,然后拿Matlab的polyfit或者interp1去套。多项式阶数别太高,7到9阶就够了,高了反而过拟合,两头翘得厉害。

注意OCV的滞后效应:充电状态的OCV和放电状态的OCV并不完全重合,锂电池存在迟滞电压。第一版做参数辨识的时候可以忽略迟滞,直接用平均OCV曲线,误差能控制在辨识允许范围内。如果后面做高精度SOC估计,再考虑引入迟滞模型。

1.3 一个容易被忽视的问题:数据质量决定辨识上限

很多入门者把精力全部压在算法上,对数据预处理不上心,结果同样的算法,别人辨识出来的参数就是比你的稳。数据这里有三件事必须做到:

第一,端电压采样要稳定。辨识算法对端电压的噪声非常敏感,尤其是算差分项的时候,噪声会被放大。采样前做滑动平均滤波或者用精度高一点的采集通道。

第二,电流激励必须“丰富”。最小二乘辨识本质上需要输入信号持续激励,如果电流一直恒定,回归矩阵接近奇异,参数就辨识不出来,或者辨识出来方差特别大。HPPC的脉冲电流,或者动态工况电流(比如某个标准工况的前几百秒),都是不错的激励信号。

第三,时间对齐。电压和电流必须严格同步,哪怕差一个采样周期,辨识结果的偏差都可能大得离谱。采集系统要统一时钟,后处理时也要再三确认。

数据质量决定辨识上限,算法只是逼近这个上限。这句话是我做了多次辨识以后最深的体会。

2. 离线辨识与在线辨识两条路线:最小二乘家族与全局优化

2.1 批处理最小二乘:最朴素的起点

最原始的做法是把一整段实验数据拿来做一次最小二乘回归。把所有采样时刻的回归方程堆起来,形成超定方程组Y = Φ·θ,解就是 θ = (Φ^T·Φ)^(-1)·Φ^T·Y。

批处理最小二乘的优点是实现简单,一个矩阵求逆就完事,算出来的结果在数据质量好的时候非常准。缺点是没法在线用——你必须攒完全部数据才能算,而且计算量随数据量增长。

实际应用场景是在实验室标定阶段。把电芯放在恒温箱里跑完HPPC,数据导出来,用批处理最小二乘算一组“离线最优参数”,当作后续在线算法的初值和对照组。

还有个小细节:构造Φ矩阵的时候,如果用的都是原始电压电流,矩阵的条件数可能比较大,数值上不稳定。可以先对数据进行归一化,或者用QR分解替代直接求逆。Matlab里用左除运算符“\”比直接inv(Φ'*Φ)更稳,这就是工程细节。

2.2 递推最小二乘(RLS)与遗忘因子(FFRLS)

在线辨识的思路是:每来一个新采样点,就把参数更新一次,不需要重新处理历史数据。递推最小二乘的更新逻辑是——上一拍的参数加上一个修正项,修正项增益由协方差矩阵决定。

标准RLS的递推公式是这样的,假设回归方程是 y = φ^T·θ,则:

增益向量 K(k) = P(k-1)·φ(k) / [λ + φ(k)^T·P(k-1)·φ(k)]

参数更新 θ(k) = θ(k-1) + K(k)·[y(k) - φ(k)^T·θ(k-1)]

协方差更新 P(k) = [P(k-1) - K(k)·φ(k)^T·P(k-1)] / λ

λ就是遗忘因子,取值0.95到1.0之间。λ=1时算法退化成标准RLS,所有历史数据同样权重;λ越小,新数据权重越大,算法跟踪参数变化的能力越强,但对噪声也更敏感。

为什么电池辨识必须带遗忘因子?因为电池参数本身是时变的:温度变了,SOC变了,内阻就会跟着变。如果所有历史数据权重相同,参数估计就变成“平均”效果,跟不上实际变化。遗忘因子的本质是给历史数据加一个指数衰减的权重,让算法“忘掉”太久以前的信息。

实际取值经验:常温动态工况下λ设0.98到0.995之间比较稳。λ设太低(比如0.9),参数虽然跟得快,但估计值会抖得很厉害;λ接近1,跟得慢但平稳。你可以先离线拿数据扫一遍λ,看哪个值在追踪速度和波动幅度之间平衡最好。

2.3 遗传算法为什么在离线辨识里值得用

说到遗传算法(GA),很多人第一反应是“这也太老派了”。但在电池参数离线辨识这个场景下,GA其实很实用。因为FFRLS之类的方法本质上是局部优化——你得把模型写成线性回归形式才能套。如果模型结构复杂(比如要考虑温度内插系数,或者同时辨识多个温度点下的参数),回归形式就不容易构造,这时直接用GA做全局寻优反而方便。

遗传算法的思路不复杂:维护一群候选解(种群),每个解就是一组参数(R0、R1、C1),用模型输出和实测电压之间的误差构造适应度函数,然后通过选择、交叉、变异反复迭代,最终收敛到适应度最好的解。

GA最大的好处是不需要初值猜得准,对非线性目标函数也能搜。代价是计算量大,一次辨识可能要跑几代、每代上百个个体,适合离线场景。

有个工程技巧:GA跑完的结果不用白不用,把它的最优参数直接拿来做FFRLS的初值。FFRLS这种递归算法对初值敏感,初值离真值太远时收敛时间长,甚至一开始会震荡。GA先给一个全局较优的起点,FFRLS在线再精调,两段式策略配合实测非常好使。

3. Matlab代码实战之一:带遗忘因子的递推最小二乘辨识

3.1 数据准备与仿真信号生成

动手写代码之前,先准备好数据。最稳妥的办法是用仿真数据验证算法正确性:自己设定一组“真实参数”,用模型生成电压数据,再叠加一点高斯噪声,然后用辨识算法去把参数反推回来。能反推回来,说明代码逻辑没问题,再上实测数据。

我一般这样生成仿真数据:设定SOC初值从80%开始,用一个包含脉冲和动态波形的电流序列激励,电流幅值控制在±2C以内,采样频率1Hz,时长500秒以上。

clear; clc; close all; % 仿真参数(真实值) R0_true = 0.02; % 欧姆内阻 R1_true = 0.008; % 极化内阻 C1_true = 1200; % 极化电容 Q_true = 2.0; % 容量Ah % 电流序列:脉冲+动态段 t = (0:499)'; I = [ones(1,20)*2, zeros(1,10), ones(1,20)*(-2), zeros(1,10), ... ones(1,20)*1, zeros(1,10), ones(1,20)*(-1), zeros(1,10), ... sin((0:339)'/20)*1.5, zeros(1,40)]'; N = length(t); % 这里注意维度,实际建议用行向量转置统一

3.2 FFRLS核心递推代码

拿到电压电流序列后,核心就是这个递推循环。构造回归向量时,一阶RC模型的差分方程整理成标准形式后,需要用到当前电压、上一拍电压、当前电流、上一拍电流。实现如下:

% 递推变量初始化 theta = [0.02; 0.005; 300]; % 初始值:R0, R1, C1的某种组合或直接对应回归系数 P = eye(3) * 1000; % 协方差矩阵初值,取大一点表示初始参数不太可信 lambda = 0.98; % 遗忘因子 theta_hist = zeros(N, 3); U_model = zeros(N, 1); for k = 3:N % 构造回归向量(具体形式取决于你的差分方程整理方式) phi = [U(k-1); I(k); I(k-1)]; y = U(k); % 增益向量 K = P * phi / (lambda + phi' * P * phi); % 参数更新 theta = theta + K * (y - phi' * theta); % 协方差更新 P = (P - K * phi' * P) / lambda; % 记录 theta_hist(k,:) = theta'; end

上面为了展示核心逻辑做了简化,实际构造回归向量的时候要根据你的差分方程严格推导,phi的形式不对,结果一定不对。

我重点解释一下协方差矩阵P的物理含义。P可以粗略理解为参数估计的不确定度:P数值大,说明对当前参数估计没信心,修正增益就大;随着数据积累,P逐渐变小,修正增益也变小,参数趋于收敛。遗忘因子λ放进分母,相当于每个周期把P放大1/λ,防止P衰减到太小,保持算法对参数变化的跟踪能力。

初值P取大对角阵,是刻意“不信任”初值,让算法前几步快速修正。如果你对初值很有信心,P取小一些;如果完全没谱,P取到1e4也不奇怪。

3.3 辨识结果分析与模型验证

跑完递推,先看参数轨迹。理想情况是前几十拍参数从初值快速向真值收敛,然后小幅波动。如果参数轨迹发散或者剧烈震荡,先查三件事:回归向量构造是否错误,遗忘因子是不是设太低,激励信号是否太弱。

辨识完参数必须验证。验证方法很简单:拿辨识出的参数重新跑一遍模型,用同样的电流输入,计算模型输出电压,再和实测电压对比。

% 用辨识结果重算端电压 U_sim = zeros(N,1); U1 = 0; % 假设已知OCV曲线,这里用常数为了简化 OCV_sim = 3.6; for k = 1:N U1 = U1 * exp(-1/(R1*C1)) + I(k) * R1 * (1 - exp(-1/(R1*C1))); U_sim(k) = OCV_sim - I(k)*R0 - U1; end % 画对比图 figure; plot(t, U, 'b', t, U_sim, 'r--', 'LineWidth', 1.5); legend('实测电压','模型输出电压'); xlabel('时间/s'); ylabel('电压/V');

模型输出电压和实测电压的RMSE能压到几个毫伏以内,说明辨识结果是可信的。注意验证要用辨识时没用过的数据段,比如跑HPPC的数据辨识完,换一段动态工况数据来验证,才能体现泛化能力。

4. Matlab代码实战之二:卡尔曼滤波做SOC联合估计

4.1 状态方程与观测方程的建立

参数辨识搞定了,下一个核心问题是怎么用上这些参数做SOC估计。这里卡尔曼滤波就登场了。虽然也有直接查表积分法这类朴素的SOC估算方式,但卡尔曼滤波的好处在于能把模型误差和测量噪声做融合,输出一个带方差估计的状态结果。

一阶RC模型下,状态向量设为 x = [SOC; U1],离散状态方程如下:

SOC(k+1) = SOC(k) - η·I(k)·Δt / Q

U1(k+1) = U1(k)·exp(-Δt/τ) + I(k)·R1·(1 - exp(-Δt/τ)),其中 τ = R1·C1

观测方程就是端电压方程:U_t(k) = OCV(SOC(k)) - I(k)·R0 - U1(k)

这里SOC和U1的更新都是线性的,问题出在观测方程:OCV(SOC)是个非线性函数,直接套经典卡尔曼滤波不行,需要扩展卡尔曼滤波(EKF),对OCV函数做一阶泰勒展开,求雅可比矩阵。

4.2 EKF核心迭代实现

EKF的核心步骤是预测和更新两个循环。预测阶段用状态方程算先验估计和协方差;更新阶段用观测方程算卡尔曼增益,修正状态估计。

% EKF参数 x = [0.8; 0]; % 初始状态[SOC; U1] P = diag([0.01, 0.0001]); % 初始协方差 Q = diag([1e-5, 1e-6]); % 过程噪声 R = 1e-4; % 测量噪声 SOC_est = zeros(N,1); % 已知参数(来自FFRLS辨识结果) % R0, R1, C1, Q_capacity, dt for k = 1:N % 预测 A = [1, 0; 0, exp(-dt/(R1*C1))]; x_pred = A * x + [0; I(k)*R1*(1-exp(-dt/(R1*C1)))]; P_pred = A * P * A' + Q; % 观测方程线性化:OCV函数对SOC求导 dOCV_dSOC = polyval(polyder(OCV_coeff), x_pred(1)); H = [dOCV_dSOC, -1]; % 更新 z_pred = OCV_func(x_pred(1)) - I(k)*R0 - x_pred(2); K = P_pred * H' / (H * P_pred * H' + R); x = x_pred + K * (U(k) - z_pred); P = (eye(2) - K * H) * P_pred; SOC_est(k) = x(1); end

OCV_coeff是前面拟合好的多项式系数,polyder和polyval是Matlab自带的求导和求值函数,用起来非常方便。

4.3 滤波器调参与防发散

EKF最折磨人的就是调Q和R。Q是过程噪声协方差,表示你对模型本身的信任程度;R是测量噪声协方差,表示你对采样电压的信任程度。Q大R小,滤波器更相信测量值,SOC估计会跟得快但噪声大;Q小R大,更相信模型,SOC轨迹平滑但可能跟不上真实变化。

实操经验:先把R按电压采集精度的平方来估,比如采集精度1mV,R取1e-6,再根据效果微调。Q的调节更玄学,我习惯先设大一点让滤波器能跟住数据,然后逐渐减小直到轨迹开始平滑。

EKF发散的表现是状态值剧烈跳变或者跑到物理不可能的范围(比如SOC超过1)。这时候优先检查雅可比矩阵H对不对,以及OCV函数在低SOC段是否过于平坦导致观测失效——如果dOCV/dSOC接近0,观测方程对SOC几乎没有修正作用,SOC就只能靠积分了。

5. Matlab代码实战之三:遗传算法离线寻优

5.1 适应度函数设计

GA离线辨识的思路是:直接把模型参数当基因,用遗传算法搜索让模型输出与实测电压误差最小的参数组合。适应度函数通常取误差平方和的倒数,误差越小适应度越高。

function fitness = batteryFitness(params, U, I, OCV, dt) R0 = params(1); R1 = params(2); C1 = params(3); % 用一阶RC模型仿真 N = length(I); U_sim = zeros(N,1); U1 = 0; for k = 1:N U1 = U1 * exp(-dt/(R1*C1)) + I(k) * R1 * (1 - exp(-dt/(R1*C1))); U_sim(k) = OCV(k) - I(k)*R0 - U1; end err = U - U_sim; fitness = 1 / (sum(err.^2) + eps); end

参数范围要给定,R0一般在0.001到0.1欧姆之间,R1在0.001到0.05之间,C1在100到5000法之间。范围太窄可能漏掉真值,太宽搜索效率低。

5.2 用ga实现参数辨识的完整流程

Matlab有自带的ga函数,直接调就行。下面是我常用的调用方式:

% 定义参数边界 lb = [0.001, 0.001, 100]; ub = [0.1, 0.05, 5000]; % 调用ga options = optimoptions('ga', ... 'PopulationSize', 100, ... 'MaxGenerations', 200, ... 'Display', 'iter', ... 'UseParallel', true); [best_params, best_fitness] = ga(@(p) batteryFitness(p, U, I, OCV, dt), ... 3, [], [], [], [], lb, ub, [], options);

UseParallel设为true可以用并行计算工具箱加速适应度评估。GA的随机性比较大,同一个数据跑两次结果会有细微差异,这是正常的。种群大小和迭代代数要看数据量定,我习惯先用100×200的配置看趋势,效果不够再翻倍。

GA的辨识速度比FFRLS慢得多,但优势在于不依赖初值和模型结构的线性化程度。你把目标函数换成二阶RC模型,甚至带温度修正的参数模型,GA适应度函数改几行就行,不用重新推导回归方程。

5.3 两段式策略:GA给FFRLS喂初值

GA和FFRLS不是竞争关系,是配合关系。实际工作流我是这么设计的:

第一步,用GA离线跑一遍,拿到全局较优的参数组,这一组参数涵盖了当前温度和SOC区间下的“平均水平”。

第二步,把这组参数作为FFRLS的初值theta_0,P初值可以设小一点(比如1),表示对初值比较有信心。

第三步,实车或者实测工况下FFRLS在线运行,参数跟随温度、老化等因素的变化做实时微调。

好处很明显:GA把FFRLS从“从零收敛”的困境里解救出来,在线运行前几个采样周期就不会出现离谱的参数跳变。FFRLS的初值敏感性这个老大难问题,用这种方式基本绕开了。

6. 实操中绕不开的坑:参数、数据与数值问题速查

6.1 遗忘因子与噪声的平衡

遗忘因子λ是FFRLS一个最敏感的旋钮。我之前做过一组数据对比:同一段动态数据,λ从0.95到0.99各跑一遍,λ=0.95时参数轨迹跟随很快,但R0的估计波动达到±15%;λ=0.99时参数轨迹稳定,但SOC变化剧烈的那段里R1的响应明显滞后。

推荐的调试步骤是:先把λ设0.98跑通全流程,模型验证的RMSE能接受以后,再根据波形微调。如果参数轨迹噪声大,增大λ;如果跟踪不上参数突变(比如冷启动时内阻快速变化),减小λ。

还有个细节,λ不是必须全程固定的。有些文献做“变遗忘因子”,在激励丰富的时候用较小的λ快速跟踪,激励弱的时候自动回退到接近1。实现不复杂,但建议先把定λ的方案跑熟了再探索。

6.2 数据激励条件与采样设置

持续激励条件是辨识算法收敛的前提。有个最容易踩的坑:拿一段静置数据——电流一直为0——去做辨识。电池静置时端电压只受OCV支配,回归矩阵的信息矩阵接近奇异,辨识出来的R0、R1、C1完全是噪声,毫无意义。

判断数据是否有效的一种方法:看电流序列是不是有足够的频段成分。恒流脉冲含DC和低频成分,动态工况含丰富的中高频成分。两种混合起来是最好的辨识信号,既有低频激励去辨识R0+极化内阻的总和,也有高频动态去分开欧姆内阻和极化内阻。

采样频率不要盲目追求高,要跟模型时间常数匹配。一阶RC模型的极化时间常数通常在几秒到几十秒之间,采样1Hz到10Hz足够。采样太高反而可能放大测量噪声对差分项的干扰。抗混叠滤波则要看采集硬件,软件层面对每周期做一次滑动平均就够。

6.3 数值稳定性与协方差矩阵维护

RLS长期运行会遇到一个经典问题:协方差矩阵P可能因为数值误差失去正定性,导致参数更新出现异常跳变。尤其是λ接近1,长时间运行时P的元素可能溢出。

处理办法有几种,按实用性排序:

第一,对P做对称性保持,每次更新后执行P = (P + P')/2,防止数值误差破坏对称性。

第二,给P设置下限和上限,比如限制对角线元素不低于1e-8、不高于1e8,避免零或溢出。

第三,定期检查P的特征值,如果出现负特征值就重置P为单位阵乘一个较小值。

我在实际代码里用的第一个加第二个的组合,简单有效,跑几百分钟在线数据没出现过问题。

6.4 结果验证的三种手段

最后说一下辨识结果怎么验证才算信得过。我用三个手段交叉确认:

第一种是残差分析。计算实测电压和模型输出电压的残差序列,观察残差的均值是否接近零、残差是否包含明显的结构成分。如果残差有规律性波动,说明模型结构没有完全捕获电池动态特性,考虑提高模型阶数。

第二种是参数物理一致性。辨识出的参数要符合物理常识:R0在零点几毫欧到几十毫欧之间,R1比R0小或者同数量级,C1在几百到几千法拉别太离谱。参数跑出合理区间,不要强行用,先回去查数据和代码。

第三种是泛化数据验证。用辨识数据训练,用另一段独立工况数据验证。这个最可靠,因为参数辨识本质上是个数据拟合问题,贴合训练数据不等于泛化好。我用这个办法抓到过不少问题——有些参数在训练段表现完美,换一段低温数据就露馅。

最后

我自己的体会是,锂电池参数辨识这套东西,算法本身没有多深奥,真正的壁垒在于对数据和对模型的直觉。你会踩的坑,大概率都是别人踩过的;而你在代码里积累的那些小改动——初值给法、遗忘因子选值、P矩阵保护——才是让一套算法从“论文能跑”变到“实车能跑”的关键。项目后续你想往深了做,可以考虑二阶RC模型的辨识、带温度内插的参数化模型、或者把FFRLS和EKF拧成一个联合估计器。先把一阶模型这整条链路跑通,后面的路自然就清楚了。

RELATED READING

延伸阅读

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