☰
锂电池SOC估算实战:基于戴维南一阶RC模型与EKF的完整实现
2026/10/15 9:37:20 网站建设 项目流程

B站/技术论坛的老哥们经常私信问我锂电池SOC估算到底怎么落地,不是看论文看得头大,就是GitHub代码跑起来结果稀烂。今天干脆把我做单节锂电池EKF估SOC的整套流程翻出来,从戴维南一阶RC建模、参数辨识,到扩展卡尔曼滤波的代码实现、踩坑记录,一次说清楚。这套方案我实测用在18650电芯上,SOC误差能稳定压在3%以内,收敛速度也不错,适合做BMS原型验证或者入门电池状态估计。

全文不绕弯子,直接按项目推进的顺序讲,包括每个公式是怎么来的、每个参数是怎么定的、代码里每一行在干什么,以及我调参时踩过的那些文档里绝对不会写的坑。

1. 项目概述:为什么单节锂电池SOC要用EKF加一阶RC模型

1.1 SOC估算为什么是个麻烦事

SOC(State of Charge,荷电状态)说白了就是电池还剩多少电,通常用百分比表示。听起来简单,但做起来非常恶心,因为SOC没法直接测量,只能靠模型去“猜”。你唯一能拿到的硬件信息就是电压、电流、温度这三个量,而SOC和它们之间的关系受温度、老化、放电倍率、静置时间影响,漂移很严重。

最早大家用安时积分法,就是把电流对时间积分再除以额定容量。这个方法简单粗暴,但有两个致命问题:一是电流传感器有零点漂移和噪声,积分误差会无限累积;二是电池实际可用容量会随温度和老化变化,你写死一个额定容量算出来的SOC,用久了必然越来越不准。开路电压法倒是准,但必须等电池静置足够久,电压稳定下来才能查表,动态工况下完全没法用。所以业内主流方案就是用状态观测器,其中扩展卡尔曼滤波(EKF)凭借计算量适中、精度高、实时性好的特点,成为最经典的选择。

1.2 常见SOC估算方法对比,EKF强在哪

我把常用方案摊开对比一下,大家就清楚为什么选EKF而不是上更复杂的无迹卡尔曼滤波(UKF)或粒子滤波(PF):

方法精度计算量实现难度主要问题
安时积分低极低极简误差累积无法消除
开路电压法中低简只能静态用,无法在线估计
查表+安时修正中低中修正时机难把握,动态精度差
EKF高低中需要线性化,强非线性下精度下降
UKF更高中中高需要调参sigma点参数
粒子滤波最高极高高计算量爆炸,嵌入式跑不动

对单节电池、MCU资源有限的场景来说,EKF是性价比天花板。戴维南一阶RC模型有一个状态变量是RC网络的极化电压,加上SOC总共两个状态,EKF的矩阵运算规模小到微控制器都能跑。而且一阶RC模型本身对电压动态的描述已经覆盖了电池主要的极化特性,比纯内阻模型准不少,又不像二阶RC那样参数多、辨识麻烦、状态维数高。对大多数实际工况,一阶RC加EKF足够用。

有人可能会杠说UKF精度更高。没错,但在单节电芯、模型本身误差占主导的场景下,把EKF的噪声协方差调好,精度差距基本可以忽略。EKF调试简单许多,而且上手快,所以我强烈建议新手从EKF+一阶RC起步。

2. 电池建模:戴维南一阶RC模型的完整推导

2.1 模型构成与物理含义

戴维南一阶RC模型的结构,可以理解成“电压源+内阻+一坨电容电阻”的串联电路:

  • OCV(SOC):开路电压源,是SOC的非线性函数,代表电池平衡态下的电动势。
  • R0:欧姆内阻,描述电解液、极耳、集流体等对电流的即时电阻效应,电流一加上去立刻产生压降。
  • R1和C1并联:描述电池的极化现象。R1是极化电阻,C1是极化电容,它们的并联组合模拟电压在加载/卸载后缓慢爬升或回落的动态过程,时间常数τ = R1 * C1,通常几十秒到几百秒。

这个模型虽然简单,但抓住了电池在动态工况下最重要的两个电压特性:瞬时跳变(R0决定)和渐近过渡(RC网络决定)。这也是它能在EKF里用的关键——状态方程可以写成线性离散形式,只有观测方程中的OCV-SOC映射是非线性的,逻辑非常干净。

2.2 状态空间方程与离散化

我定义两个状态量:

  • 状态1:SOC(记作z)
  • 状态2:RC网络电压(记作V1或者u1)

连续时间电气方程如下(放电电流为正):

  • C1 * d(V1)/dt + V1/R1 = i
  • d(SOC)/dt = -i / Q_n

整理后:

d(V1)/dt = -V1 / (R1 * C1) + i / C1 d(SOC)/dt = -i / Q_n

其中Q_n是电池最大可用容量,单位跟上电流一致(电流用A,时间用s,容量用As;如果容量用Ah,记得乘3600换算)。

端电压观测方程:

V_t = OCV(SOC) - V1 - R0 * i

注意符号:我全程约定放电电流为正,所以OCV减去欧姆压降和极化电压。如果你用充电为正的约定,符号要整体翻转,后面代码会乱。

接下来离散化。用前向欧拉法,步长Δt:

SOC[k+1] = SOC[k] - (Δt / Q_n) * i[k]

V1[k+1] = (1 - Δt / (R1 * C1)) * V1[k] + (Δt / C1) * i[k]

为了数值稳定性和减少计算量,引入常系数a和b:

a = exp(-Δt / (R1 * C1)) b = R1 * (1 - a)

于是:

V1[k+1] = a * V1[k] + b * i[k]

这个形式比欧拉离散更稳,而且物理意义清晰:a是上一时刻极化电压的衰减系数,b是当前电流对极化电压的贡献。时间常数越大,a越接近1,极化电压衰减越慢,符合直觉。

于是状态空间矩阵如下:

x_k = [SOC_k, V1_k]^T A = [[1, 0], [0, a]] B = [-Δt/Q_n, b]^T u_k = i_k

2.3 OCV-SOC映射曲线,模型的心脏

整个EKF观测更新环节里,最关键的查表函数就是OCV = f(SOC)。这条曲线把SOC变成电压,再由电压反推SOC,精度直接影响最终估算结果。

获得OCV-SOC曲线的方法是:对充满电的电池,每放电一定SOC百分比(比如5%),然后静置1小时以上,记录稳定电压,最后把数据点连成曲线。注意两点:第一,放电电流要小(0.1C甚至0.05C),减少极化影响;第二,静置时间要够长,确保电压真正稳定。

实测下来,磷酸铁锂的OCV-SOC曲线中间平台非常平坦,SOC从30%到70%电压变化还不到0.1V,EKF在这种区间容易因为电压分辨率不足而收敛慢甚至发散;三元锂电池曲线坡度稍好。针对这个问题,我建议在代码里对OCV-SOC查表做一阶线性插值,并且保存足够密的标定点(至少每5%一个点,最好2%一个点)。还要注意OCV-SOC曲线不是唯一的,充电和放电过程存在迟滞,严格来说要分别标定。对单节电芯做入门级SOC估算,可以取充放电曲线的平均值作为查表依据,误差在可接受范围。

3. EKF算法设计:五个公式如何套进电池系统

3.1 从KF到EKF,非线性问题的线性化思路

经典卡尔曼滤波(KF)只适用于线性系统,状态方程和观测方程都是矩阵乘法形式。但电池系统里观测方程是OCV(SOC),这玩意儿跟SOC是非线性关系,KF直接套会崩。EKF的思路是说:每个时刻,我在当前估计值附近对非线性函数做一阶泰勒展开,用Jacobian矩阵替代线性模型里的系数矩阵,然后照搬KF的更新流程。

只要非线性不太强,展开点离真值不太远,EKF的近似效果就很好。电池系统恰好满足这个条件——OCV-SOC曲线整体单调,局部线性化偏差可控。所以EKF是电池SOC估计的绝配。

3.2 核心公式与Jacobian矩阵推导

EKF的每一步包括预测和更新两个阶段。预测阶段:

x_pred = A * x_est + B * u P_pred = A * P * A^T + Q

更新阶段先算卡尔曼增益:

K = P_pred * H^T * (H * P_pred * H^T + R)^(-1)

然后修正状态和协方差:

x_est = x_pred + K * (y_meas - h(x_pred, u)) P = (I - K * H) * P_pred

这里A、B是状态方程的系数矩阵,和前面推导的一致;h是非线性观测函数,也就是端电压模型;H是h在x_pred处的Jacobian矩阵。

观测函数是:

h(x, u) = OCV(SOC) - V1 - R0 * i

求偏导得:

H = [∂h/∂SOC, ∂h/∂V1] = [d(OCV)/d(SOC), -1]

注意d(OCV)/d(SOC)这个导数需要从OCV-SOC曲线上数值求,可以预先算好每个标定点的斜率存成表,或者实时用查表插值算。我建议预处理斜率表,省得每个周期做数值微分,还能避免插值抖动。

R是观测噪声协方差,反映电压传感器噪声水平;Q是过程噪声协方差,反映模型本身的不确定性。它们的具体取法后面单独说,这两个矩阵调好了,EKF就成功了大半。

3.3 噪声协方差Q和R到底怎么调

这是几乎所有人第一次做EKF都会被卡住的地方。Q和R不是随便填的单位矩阵,它们之间的相对大小直接决定卡尔曼增益,控制你对模型的信任程度和对传感器测量的信任程度。

R相对越小,说明你越信任电压测量,卡尔曼增益越大,状态会被传感器拉到更激进的位置;Q相对越小,说明你越信任模型,滤波结果更平滑但不跟测量。

我实际调试时的做法是:先用统计方法估R。记录电池静置时的电压噪声,用一串采样值算方差。我的采集板ADC是12位的,满量程对应电压噪声标准差大概5mV,R直接取2.5e-5到1e-4(单位V^2)之间,够用。

Q是两阶方阵,对应SOC和V1两个状态的过程噪声方差。Q(1,1)放的是SOC过程噪声,Q(2,2)放的是V1过程噪声。这两个值主要反映模型误差——比如容量误差、温度造成的参数漂移。我建议从小的值试起,比如Q = diag([1e-6, 1e-5]),然后做动态电流测试看观测残差。如果残差均值明显不为零且滤波结果抖动大,可以小幅加大Q;如果收敛慢、SOC被“粘”在初始值附近,可能Q设太小了。

有个很实用的技巧:对比新息序列(innovation,即y_meas - h(x_pred, u))的均值。理想情况下新息应该是零均值的。如果新息长期偏正或偏负,说明模型有系统性偏差,不是调Q/R能补回来的,要先回去查参数辨识。

4. 实操过程:从HPPC参数辨识到代码落地

4.1 离线参数辨识:HPPC测试与拟合

上代码之前必须先做完参数辨识,不然模型参数都是乱填的,EKF再漂亮也白搭。最常用的辨识方法是HPPC(Hybrid Pulse Power Characterization)测试。

流程是这样的:把电池恒温在25摄氏度,充满电,静置1小时,记录OCV;然后用1C电流放电10秒,静置40秒,再用1C电流充电10秒,静置40秒,记录整个过程的电压响应曲线;接着放掉5%或10%容量,再静置1小时,记录OCV,重复脉冲测试。这样测完整个SOC区间,就得到每个SOC点对应的OCV、R0和RC参数。

从脉冲电压响应里怎么把参数抠出来:

  • R0 = 电流加载瞬间的电压跳变值 / 电流。加载瞬间电压从稳定值跳变,只隔了采样周期,这个跳变主要就是欧姆内阻造成的。
  • R1和C1:电流撤掉之后,电压不是瞬间回到OCV,而是以指数形式爬升。把这个爬升段拟合为V(t) = V_ocv - V1_0 * exp(-t / τ),就能拿到时间常数τ,再结合稳态压降算R1,最后C1 = τ / R1。

我提醒一句:脉冲测试的采样率不能太低,建议至少10Hz,否则电流加载瞬间的电压跳变会被采样点漏掉,R0会严重低估。我第一次做图省事用1Hz采样,算出来的R0只有真实值的一半,后来重测才纠正。

拟合工具用MATLAB的cftool或者Python的scipy.optimize.curve_fit都行。模型输出是端电压,输入是电流序列,拟合目标是让端电压曲线和实测曲线误差平方最小。拟合之前把OCV曲线上每个SOC点标好,作为已知输入,不要让它作为自由参数去拟合,否则会严重过拟合。

4.2 核心代码实现(MATLAB风格)

下面给出一段简化但可直接运行的EKF核心循环代码,我用MATLAB风格写,方便大家对照公式理解。Python移植也容易,矩阵运算逻辑完全一致。

% EKF核心循环 % 已知参数:R0, R1, C1, Q_n(容量As), OCV_SOC表, dOCV_dSOC斜率表 % 输入:i_k(电流,放电为正), V_meas_k(实测端电压) % 状态:x = [SOC; V1] dt = 1; % 采样周期1秒 tau = R1 * C1; a = exp(-dt / tau); b = R1 * (1 - a); % 状态转移矩阵 A = [1, 0; 0, a]; B = [-dt / Q_n; b]; % 噪声协方差 Q = diag([1e-6, 1e-5]); R = 5e-5; % 初始状态 x = [1.0; 0]; % 假设满电启动 P = diag([0.01, 0.01]); % 初始协方差适当给大,加速收敛 N = length(current_data); % 数据点数 SOC_est = zeros(1, N); V1_est = zeros(1, N); for k = 1:N % 1. 预测 x_pred = A * x + B * current_data(k); P_pred = A * P * A' + Q; % 2. 计算观测和Jacobian ocv = interp1(SOC_table, OCV_table, x_pred(1), 'linear', 'extrap'); dOCV_dSOC = interp1(SOC_table, slope_table, x_pred(1), 'linear', 'extrap'); V_pred = ocv - x_pred(2) - R0 * current_data(k); H = [dOCV_dSOC, -1]; % 3. 卡尔曼增益 K = P_pred * H' / (H * P_pred * H' + R); % 4. 更新 innovation = voltage_data(k) - V_pred; x = x_pred + K * innovation; P = (eye(2) - K * H) * P_pred; % 5. 状态约束(防止SOC越界或极化电压不合理) x(1) = min(max(x(1), 0), 1); SOC_est(k) = x(1); V1_est(k) = x(2); end

看代码的时候有几个细节值得注意:

第一,初始协方差P设得比较大(比如diag([0.01, 0.01])),可以加快EKF从错误的初始SOC快速收敛到真值。如果你明确知道初始SOC近似值,P可以设小点;完全不知道,就给大点让它自己收敛。

第二,每一步做完之后要加状态约束。SOC越界在物理上不存在,但数值上EKF完全可能算出1.2或者-0.1。强制截断到[0,1]区间不会影响滤波稳定性,但能让后续查表环节不越界崩掉。V1的约束可以加在[-0.5, 0.5]V这样合理的极化电压范围。

第三,观测方程里的R0 * i项,符号取决于电流方向约定。我代码里用放电为正,所以端电压 = OCV - V1 - R0 * i。如果电流方向搞反,整体符号错误会让EKF新息符号反转,SOC会往错误方向修正,这个坑我踩过不止一次,务必先确认。

4.3 初始值与仿真验证

代码跑起来后,第一步要做的不是直接上实测数据,而是用模型自己生成一个仿真数据集来验证。说白了就是:给定一条电流曲线,用同样的模型方程正推出一条“真实”端电压,再叠加一点高斯白噪声模拟传感器,然后喂给EKF。因为这个时候真值是知道的,可以定量算SOC误差曲线。

我用的验证流程是:

  1. 生成一个混合电流工况,包含恒流放电、脉冲负载、回充等段。
  2. 用正模型算出SOC真值和端电压,叠加5mV的零均值高斯噪声。
  3. EKF从错误的初始SOC(比如90%)开始估计,而真值从100%开始放电。
  4. 观察SOC估计值收敛到真值附近需要多长时间,稳态误差多大。

实测下来,初始误差20%的情况下,大约100~200秒收敛到3%以内。收敛速度受Q/R比值影响很大,Q给大收敛快但噪声大,Q给小收敛慢但平滑。没有绝对最优,只有根据你的应用场景取舍。

我还建议跑一个“极端初始值”测试:把初始SOC设成0.5而真值是0.8,确认滤波器不会发散。如果这种情况都能快速收敛回来,说明Q/R比例在合理范围。

5. 常见问题与调试实录

5.1 滤波发散和震荡怎么处理

最典型的故障现象是SOC估计值剧烈震荡,甚至直接冲到0或1卡住。优先排查这几件事:

第一,检查采样时间dt和模型里的单位是否一致。容量Q_n如果你用的是Ah,电流是A,那么dt必须用小时而不是秒,否则状态方程里那个-Δt/Q_n会差3600倍,EKF直接疯狂发散。这个看起来弱智的问题,我见过不止一个群友栽在上面。

第二,检查OCV-SOC表的插值方向。查表必须用当前SOC预测值去查OCV,而不是用当前电压查SOC——那是开路电压法,不是EKF。搞错这个逻辑,观测方程的h和H就对不上,滤波必然乱。

第三,检查Jacobian矩阵H的符号。H第二列必须是-1,对应∂h/∂V1。如果写成+1,滤波器的修正方向就反了,SOC会被推向错误方向。

5.2 SOC不准,先查这几件事

如果EKF没发散但是SOC最终稳态误差很大,逐项排查:

  • 容量Q_n不准。容量误差直接反映为SOC误差,尤其是大电流积分段。建议用完整放电实验标定Q_n,不要直接抄电池标称容量。
  • R0在低温下会显著增大。如果测试工况有温度变化,建议预先建R0随温度变化的表,在EKF里按温度实时查表更新。温度从25度降到0度,R0可能翻一倍,观测方程就开始系统性偏差,SOC会被拉偏。
  • OCV-SOC曲线标定不准或过于粗糙。曲线中间段太平缓的区域(尤其是磷酸铁锂),斜率接近0,这时H矩阵第一个元素接近0,卡尔曼增益会变小,EKF对SOC的修正能力大打折扣。这种情况建议引入更多的激励,或者考虑多时间尺度滤波,但那是后话了。

5.3 温度、老化、传感器噪声的实际影响

温度的影响上面说了一部分,老化方面主要是容量衰减和內阻增加。容量衰减会让真实SOC比估算值低,长期使用误差会越拉越大。入门级方案可以每隔一段时间做一次容量在线修正,或者定期重新辨识参数。传感器噪声方面,使用12位ADC采集电压,噪声方差大约在1e-5 V^2量级,R取这个量级问题不大。如果ADC位数更低或者采集电路布局差,噪声可能到10mV以上,R要相应调大到1e-4量级。

有一点容易被忽略:电流传感器误差。如果电流采样偏置10mA,对单节18650来说就是持续往错误方向积分,一小时能偏出0.01Ah,对应2.5%的SOC。EKF虽然能通过电压观测修正一部分,但如果偏置恒定且模型没建模这个输入误差,稳态SOC还是会偏。遇到这种情况,可以考虑在状态向量里加一个电流偏置项去估计,或者对电流采样的零点进行校准。

我个人在实际调试中最深刻的体会是:EKF不是调出来的,是模型喂出来的。把电池模型参数整准了,Q/R随便填一套合理值都能工作;参数一塌糊涂,Q/R调到天荒地老也没用。所以顺序永远是:先HPPC把参数辨识做扎实,再做OCV曲线标定,最后才轮到调EKF的Q/R。

5.4 收敛速度不够快,能不能加速

如果目标是让EKF在启动阶段快速收敛,而稳态精度不能牺牲太多,我常用的做法是Cache出来的一个“转角”技巧:启动阶段的前20秒用大一点的Q。具体讲就是做一个时间衰减的过程噪声项,比如Q(t) = Q_base * (1 + 10 * exp(-t / 20))。启动时Q大,卡尔曼增益高,状态快速拉向真值;20秒后Q衰减到正常值,滤波重新平滑。这个方法对初始SOC未知的场景特别有效。

还有个配套技巧:在启动阶段,如果电压测量显著偏离预测,可以把新息做限幅处理,比如限制单步SOC修正量不超过5%,防止初始剧烈振荡。等滤波进入稳定状态后取消限幅。


最后再分享一个实战里很管用的小细节:把EKF输出的新息序列(innovation)持续记录下来,存成一条曲线。当你把这条曲线叠在电流曲线上观察时,会形成非常直观的“诊断图像”。如果新息在每次电流突变后总是朝同一方向偏离且恢复缓慢,说明RC参数没辨识准,时间常数估计得过长或过短,不用看数据分析就能拍板重做一段HPPC。顺手把这个习惯养成,比什么自动调参工具都好用。

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

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

立即咨询