文章目录
-
- 文章摘要
- 前言
- 一、二阶RC等效电路拓扑与元件物理意义
-
- 1. 电路拓扑图
- 2. 各元件对应电池内部电化学机理
- 3. 二阶模型对比一阶Thevenin核心优势
- 二、连续时间域完整公式推导(基尔霍夫KVL/KCL)
-
- 2.1 端电压观测方程(KVL电压回路)
- 2.2 极化电压微分方程(KCL电流分流)
- 2.3 SOC安时积分状态方程
- 2.4 连续时间状态空间标准形式
- 三、离散化差分方程(车载MCU实时计算必备)
-
- 3.1 离散状态转移方程
- 3.2 离散观测方程
- 四、模型参数辨识方法(工程落地核心)
-
- 4.1 HPPC脉冲实验离线辨识(标定阶段使用)
- 4.2 在线递推辨识(车载实时修正)
- 五、二阶RC模型-BMS高精度SOC估算
- 六、二阶RC模型优缺点总结
-
- 优势
- 局限性
- 七、标准卡尔曼滤波(KF)基础原理
-
- 7.1.1 核心思想
- 7.2 离散线性系统标准模型
-
- (1)状态转移方程(系统模型)
- (2)观测方程(测量模型)
- 7.3 卡尔曼滤波5条核心公式(完整推导)
-
- 步骤1:状态先验预测(时间更新)
- 步骤2:计算卡尔曼增益
K
k
K_k
Kk(权重平衡) - 步骤3:状态后验更新(观测修正)
- 步骤4:更新后验协方差矩阵
- 7.4 标准KF局限性(电池场景无法直接使用)
- 八、扩展卡尔曼滤波EKF原理(适配电池非线性)
-
- 8.1 核心思路
- 8.2 非线性离散系统通用模型
- 8.3 雅可比矩阵定义
- 8.4 EKF完整迭代五公式
- 九、二阶RC等效电路ECM与EKF完整结合(工程核心)
-
- 9.1 二阶RC电路模型结构
- 9.2 构建EKF状态向量
- 9.3 连续微分方程离散化(核心状态转移
f
(
x
,
u
)
f(x,u)
f(x,u)) - 9.4 观测方程
h
(
x
)
h(\\boldsymbol{x})
h(x)(非线性) - 9.5 求解雅可比矩阵(代入EKF)
-
- (1)状态转移雅可比
F
(
3
×
3
)
\\boldsymbol{F}(3\\times3)
F(3×3) - (2)观测雅可比矩阵
H
(
1
×
3
)
\\boldsymbol{H}(1\\times3)
H(1×3)
- (1)状态转移雅可比
- 9.6 ECM-EKF完整迭代工程流程
-
- 步骤1:初始化参数
- 步骤2:EKF预测步(对应代码`_ekf_predict`)
- 步骤3:EKF更新步(对应代码`_ekf_update`)
- 步骤4:循环迭代
- 十、ECM+EKF工程优势与传统方案对比
- 十一、工程调参关键点
- 十二、SOC估算工程实现-python代码
文章摘要
本文全面解析了动力电池二阶RC等效电路模型,这是当前新能源汽车和储能系统BMS(电池管理系统)中最主流的建模方案。文章从电化学机理出发,详细阐述了二阶RC模型的电路拓扑、各元件物理意义,并完整推导了连续时间域的状态空间方程和离散化差分方程。重点介绍了模型参数辨识的两种工业方法(HPPC脉冲实验离线辨识和在线递推辨识),以及该模型在BMS高精度SOC估算、电池故障诊断、SOH健康状态评估、整车仿真和充电控制优化等五大工程场景中的全链路应用。最后总结了二阶RC模型的优势与局限性,并展望了行业优化方向。本文为动力电池算法工程师提供了从理论到实践的完整技术参考。
前言
锂电池内部包含复杂电化学反应、离子扩散、欧姆损耗,无法直接测量内部极化、锂离子浓度等微观状态。工程上采用等效电路模型(ECM),用电阻、电容、电压源等效复现电池外部电压-电流动态特性,是BMS电池状态估算、故障诊断、仿真分析的核心基础。
主流ECM分为零阶(理想电压源)、一阶Thevenin、二阶RC、PNGV、高阶电化学模型。二阶RC模型是车企、储能行业的最优平衡点:相比一阶模型可同时捕捉快慢双尺度极化,电压拟合误差降低50%以上;相比高阶电化学模型计算量小、参数易辨识、可实时嵌入车载MCU,广泛用于电动车、储能电站BMS算法开发。
一、二阶RC等效电路拓扑与元件物理意义
1. 电路拓扑图

2. 各元件对应电池内部电化学机理
|
U O C V ( S O C ) U_{OCV}(SOC) UOCV(SOC) |
开路电压,SOC非线性函数 | 静态无时间特性 | 正负极电极平衡电势,仅由剩余电量决定 |
|
R 0 R_0 R0 |
欧姆内阻 | 毫秒级瞬时响应 | 集流体、极耳、电解液、隔膜的电子/离子传导阻力;通电瞬间立刻产生压降 |
|
R 1 、 C 1 R_1、C_1 R1、C1 |
快极化RC支路 |
τ 1 = R 1 C 1 \\tau_1=R_1C_1 τ1=R1C1,0.1~10s |
电化学活化极化:电极界面电荷转移反应阻力,脉冲电流下电压快速升降 |
|
R 2 、 C 2 R_2、C_2 R2、C2 |
慢极化RC支路 |
τ 2 = R 2 C 2 \\tau_2=R_2C_2 τ2=R2C2,10~100s |
浓差扩散极化:锂离子在电解液、电极孔隙扩散形成浓度梯度,电压缓慢弛豫恢复 |
|
U 1 、 U 2 U_1、U_2 U1、U2 |
两支路RC电容两端极化电压 | – | 电流作用下积累的极化压降,断电后缓慢释放、电压回弹 |
3. 二阶模型对比一阶Thevenin核心优势
一阶仅单RC支路,只能拟合单一时间尺度极化;真实电池同时存在快速界面反应、慢速离子扩散两种动态过程,脉冲充放电、动态工况下电压拟合误差大。二阶双RC支路分离快慢极化,HPPC脉冲、城市道路动态工况下端电压拟合误差可控制在30~50mV内,满足车载SOC、压差故障预警精度要求。
二、连续时间域完整公式推导(基尔霍夫KVL/KCL)
2.1 端电压观测方程(KVL电压回路)
沿电路闭合回路电压求和:开路电压减去欧姆压降、两级极化压降等于外部输出端电压:
U
L
=
U
O
C
V
(
S
O
C
)
−
I
R
0
−
U
1
−
U
2
(1)
U_L = U_{OCV}(SOC) – I R_0 – U_1 – U_2 \\tag{1}
UL=UOCV(SOC)−IR0−U1−U2(1)
-
U
L
U_L
UL:电池外部采集端电压(BMS可直接测量) -
I
I
I:充放电电流,放电I
>
0
I>0
I>0,充电I
<
0
I<0
I<0 -
U
1
、
U
2
U_1、U_2
U1、U2:快、慢RC支路电容极化电压
2.2 极化电压微分方程(KCL电流分流)
对
R
1
C
1
R_1C_1
R1C1并联支路:总电流分为电阻支路电流、电容充放电电流:
I
=
U
1
R
1
+
C
1
d
U
1
d
t
I = \\frac{U_1}{R_1} + C_1 \\frac{dU_1}{dt}
I=R1U1+C1dtdU1 移项整理得到一阶微分方程,描述极化电压随时间变化规律:
d
U
1
d
t
=
−
1
R
1
C
1
U
1
+
1
C
1
I
(2)
\\frac{dU_1}{dt} = -\\frac{1}{R_1 C_1} U_1 + \\frac{1}{C_1} I \\tag{2}
dtdU1=−R1C11U1+C11I(2) 同理,慢极化支路:
d
U
2
d
t
=
−
1
R
2
C
2
U
2
+
1
C
2
I
(3)
\\frac{dU_2}{dt} = -\\frac{1}{R_2 C_2} U_2 + \\frac{1}{C_2} I \\tag{3}
dtdU2=−R2C21U2+C21I(3) 时间常数定义:
τ
1
=
R
1
C
1
,
τ
2
=
R
2
C
2
\\tau_1=R_1C_1,\\tau_2=R_2C_2
τ1=R1C1,τ2=R2C2,代入简化:
d
U
1
d
t
=
−
1
τ
1
U
1
+
I
C
1
,
d
U
2
d
t
=
−
1
τ
2
U
2
+
I
C
2
\\frac{dU_1}{dt} = -\\frac{1}{\\tau_1} U_1 + \\frac{I}{C_1},\\quad \\frac{dU_2}{dt} = -\\frac{1}{\\tau_2} U_2 + \\frac{I}{C_2}
dtdU1=−τ11U1+C1I,dtdU2=−τ21U2+C2I
2.3 SOC安时积分状态方程
剩余电量SOC随充放电电流变化,额定容量
Q
n
Q_n
Qn(单位A·s):
d
S
O
C
d
t
=
−
I
Q
n
(4)
\\frac{dSOC}{dt} = -\\frac{I}{Q_n} \\tag{4}
dtdSOC=−QnI(4) 放电时
I
>
0
I>0
I>0,SOC下降;充电
I
<
0
I<0
I<0,SOC上升,符合电量守恒。
2.4 连续时间状态空间标准形式
定义三维状态向量(系统内部不可直接观测状态):
x
=
[
S
O
C
U
1
U
2
]
\\boldsymbol{x} = \\begin{bmatrix} SOC \\\\ U_1 \\\\ U_2 \\end{bmatrix}
x=
SOCU1U2
输入量:
u
=
I
u=I
u=I;观测输出:
y
=
U
L
y=U_L
y=UL 状态微分方程:
d
x
d
t
=
[
0
0
0
0
−
1
τ
1
0
0
0
−
1
τ
2
]
x
+
[
−
1
Q
n
1
C
1
1
C
2
]
u
\\frac{d\\boldsymbol{x}}{dt} = \\begin{bmatrix} 0 & 0 & 0 \\\\ 0 & -\\displaystyle\\frac{1}{\\tau_1} & 0 \\\\ 0 & 0 & -\\displaystyle\\frac{1}{\\tau_2} \\end{bmatrix} \\boldsymbol{x} + \\begin{bmatrix} -\\displaystyle\\frac{1}{Q_n} \\\\ \\displaystyle\\frac{1}{C_1} \\\\ \\displaystyle\\frac{1}{C_2} \\end{bmatrix} u
dtdx=
0000−τ11000−τ21
x+
−Qn1C11C21
u 观测方程(非线性,
U
O
C
V
U_{OCV}
UOCV是SOC非线性函数):
y
=
U
O
C
V
(
x
1
)
−
R
0
⋅
u
−
x
2
−
x
3
y = U_{OCV}(x_1) – R_0 \\cdot u – x_2 – x_3
y=UOCV(x1)−R0⋅u−x2−x3
三、离散化差分方程(车载MCU实时计算必备)
BMS芯片为数字离散系统,采样周期
Δ
t
\\Delta t
Δt(通常100ms/1s),采用零阶保持ZOH离散化,假设一个采样周期内电流
I
I
I恒定。
3.1 离散状态转移方程
{
S
O
C
k
=
S
O
C
k
−
1
−
Δ
t
Q
n
I
k
−
1
U
1
,
k
=
exp
(
−
Δ
t
τ
1
)
U
1
,
k
−
1
+
R
1
[
1
−
exp
(
−
Δ
t
τ
1
)
]
I
k
−
1
U
2
,
k
=
exp
(
−
Δ
t
τ
2
)
U
2
,
k
−
1
+
R
2
[
1
−
exp
(
−
Δ
t
τ
2
)
]
I
k
−
1
\\begin{cases} SOC_k = SOC_{k-1} – \\displaystyle\\frac{\\Delta t}{Q_n} I_{k-1} \\\\[4pt] U_{1,k} = \\exp\\left(-\\displaystyle\\frac{\\Delta t}{\\tau_1}\\right) U_{1,k-1} + R_1 \\left[1-\\exp\\left(-\\displaystyle\\frac{\\Delta t}{\\tau_1}\\right)\\right] I_{k-1} \\\\[4pt] U_{2,k} = \\exp\\left(-\\displaystyle\\frac{\\Delta t}{\\tau_2}\\right) U_{2,k-1} + R_2 \\left[1-\\exp\\left(-\\displaystyle\\frac{\\Delta t}{\\tau_2}\\right)\\right] I_{k-1} \\end{cases}
⎩
⎨
⎧SOCk=SOCk−1−QnΔtIk−1U1,k=exp(−τ1Δt)U1,k−1+R1[1−exp(−τ1Δt)]Ik−1U2,k=exp(−τ2Δt)U2,k−1+R2[1−exp(−τ2Δt)]Ik−1
3.2 离散观测方程
U
L
,
k
=
U
O
C
V
(
S
O
C
k
)
−
I
k
R
0
−
U
1
,
k
−
U
2
,
k
U_{L,k} = U_{OCV}(SOC_k) – I_k R_0 – U_{1,k} – U_{2,k}
UL,k=UOCV(SOCk)−IkR0−U1,k−U2,k 离散方程组是EKF/UKF SOC估计算法、在线参数辨识的底层代码数学基础,可直接写成C语言嵌入BMS。
四、模型参数辨识方法(工程落地核心)
模型共5个待辨识参数:
R
0
、
R
1
、
C
1
、
R
2
、
C
2
R_0、R_1、C_1、R_2、C_2
R0、R1、C1、R2、C2,主流两类工业方案:
4.1 HPPC脉冲实验离线辨识(标定阶段使用)
HPPC混合脉冲功率特性测试标准流程:
U
O
C
V
U_{OCV}
UOCV;
Δ
V
0
=
I
⋅
R
0
\\Delta V_0=I\\cdot R_0
ΔV0=I⋅R0,直接计算欧姆内阻
R
0
R_0
R0;
τ
1
、
R
1
、
C
1
\\tau_1、R_1、C_1
τ1、R1、C1;
τ
2
、
R
2
、
C
2
\\tau_2、R_2、C_2
τ2、R2、C2; 使用双指数函数拟合静置恢复电压:
U
(
t
)
=
U
O
C
V
−
A
e
−
t
/
τ
1
−
B
e
−
t
/
τ
2
U(t) = U_{OCV} – A e^{-t/\\tau_1} – B e^{-t/\\tau_2}
U(t)=UOCV−Ae−t/τ1−Be−t/τ2
4.2 在线递推辨识(车载实时修正)
车辆运行过程中,电池老化、温度变化会改变全部参数,采用带遗忘因子递推最小二乘FFRLS、多新息辨识,实时更新
R
0
、
R
1
、
C
1
、
R
2
、
C
2
R_0、R_1、C_1、R_2、C_2
R0、R1、C1、R2、C2,适配SOH衰减、高低温工况。
五、二阶RC模型-BMS高精度SOC估算
SOC无法直接硬件测量,依靠二阶RC模型+扩展卡尔曼滤波EKF实现闭环估算:
S
O
C
、
U
1
、
U
2
SOC、U_1、U_2
SOC、U1、U2;观测值:BMS采集端电压;
六、二阶RC模型优缺点总结
优势
局限性
七、标准卡尔曼滤波(KF)基础原理
7.1.1 核心思想
卡尔曼滤波是线性最小均方差递归估计器,仅保存上一时刻状态,无需存储历史数据,适合嵌入式实时迭代。 核心逻辑分为两步:时间预测(Predict)+观测更新(Update)
- 预测:依靠系统状态转移方程,推算当前状态先验值;
- 更新:利用传感器实测观测值,计算卡尔曼增益修正先验,输出最优后验状态。
假设条件:系统线性、噪声零均值高斯白噪声、过程噪声
w
k
w_k
wk、观测噪声
v
k
v_k
vk互不相关。
7.2 离散线性系统标准模型
(1)状态转移方程(系统模型)
x
k
=
A
x
k
−
1
+
B
u
k
−
1
+
w
k
−
1
\\boldsymbol{x}_k = \\boldsymbol{A}\\boldsymbol{x}_{k-1} + \\boldsymbol{B}\\boldsymbol{u}_{k-1} + \\boldsymbol{w}_{k-1}
xk=Axk−1+Buk−1+wk−1
-
x
k
\\boldsymbol{x}_k
xk:k时刻n维状态向量 -
A
\\boldsymbol{A}
A:状态转移矩阵;B
\\boldsymbol{B}
B:控制输入矩阵;u
\\boldsymbol{u}
u:系统输入(电池电流) -
w
k
−
1
\\boldsymbol{w}_{k-1}
wk−1:过程噪声,w
∼
N
(
0
,
Q
)
w\\sim N(0,\\boldsymbol{Q})
w∼N(0,Q),Q
\\boldsymbol{Q}
Q为过程噪声协方差矩阵
(2)观测方程(测量模型)
z
k
=
H
x
k
+
v
k
\\boldsymbol{z}_k = \\boldsymbol{H}\\boldsymbol{x}_k + \\boldsymbol{v}_k
zk=Hxk+vk
-
z
k
\\boldsymbol{z}_k
zk:传感器实测观测值(电池端电压) -
H
\\boldsymbol{H}
H:观测矩阵;v
k
\\boldsymbol{v}_k
vk:观测噪声,v
∼
N
(
0
,
R
)
v\\sim N(0,\\boldsymbol{R})
v∼N(0,R),R
\\boldsymbol{R}
R观测噪声协方差
7.3 卡尔曼滤波5条核心公式(完整推导)
目标:最小化状态估计误差协方差
P
=
E
[
(
x
^
−
x
)
(
x
^
−
x
)
T
]
\\boldsymbol{P}=E[(\\hat{x}-x)(\\hat{x}-x)^T]
P=E[(x^−x)(x^−x)T]
步骤1:状态先验预测(时间更新)
用上一时刻最优估计
x
^
k
−
1
∣
k
−
1
\\hat{x}_{k-1|k-1}
x^k−1∣k−1预测当前先验状态:
x
^
k
∣
k
−
1
=
A
x
^
k
−
1
∣
k
−
1
+
B
u
k
−
1
\\hat{\\boldsymbol{x}}_{k|k-1} = \\boldsymbol{A}\\hat{\\boldsymbol{x}}_{k-1|k-1} + \\boldsymbol{B}\\boldsymbol{u}_{k-1}
x^k∣k−1=Ax^k−1∣k−1+Buk−1 同步预测误差协方差:
P
k
∣
k
−
1
=
A
P
k
−
1
∣
k
−
1
A
T
+
Q
\\boldsymbol{P}_{k|k-1} = \\boldsymbol{A}\\boldsymbol{P}_{k-1|k-1}\\boldsymbol{A}^T + \\boldsymbol{Q}
Pk∣k−1=APk−1∣k−1AT+Q
P
k
∣
k
−
1
\\boldsymbol{P}_{k|k-1}
Pk∣k−1代表先验状态的不确定度,
Q
\\boldsymbol{Q}
Q越大,模型可信度越低。
步骤2:计算卡尔曼增益
K
k
K_k
Kk(权重平衡)
增益作用:平衡模型预测与传感器测量可信度
K
k
=
P
k
∣
k
−
1
H
T
(
H
P
k
∣
k
−
1
H
T
+
R
)
−
1
\\boldsymbol{K}_k = \\boldsymbol{P}_{k|k-1}\\boldsymbol{H}^T \\left( \\boldsymbol{H}\\boldsymbol{P}_{k|k-1}\\boldsymbol{H}^T + \\boldsymbol{R} \\right)^{-1}
Kk=Pk∣k−1HT(HPk∣k−1HT+R)−1
-
R
\\boldsymbol{R}
R大:传感器噪声大,K
k
K_k
Kk变小,更信任模型预测; -
Q
\\boldsymbol{Q}
Q大:模型误差大,K
k
K_k
Kk变大,更信任电压测量值。
步骤3:状态后验更新(观测修正)
残差(创新):实测电压与模型预测电压差值
e
k
=
z
k
−
H
x
^
k
∣
k
−
1
e_k=\\boldsymbol{z}_k-\\boldsymbol{H}\\hat{\\boldsymbol{x}}_{k|k-1}
ek=zk−Hx^k∣k−1
x
^
k
∣
k
=
x
^
k
∣
k
−
1
+
K
k
e
k
\\hat{\\boldsymbol{x}}_{k|k} = \\hat{\\boldsymbol{x}}_{k|k-1} + \\boldsymbol{K}_k e_k
x^k∣k=x^k∣k−1+Kkek
步骤4:更新后验协方差矩阵
P
k
∣
k
=
(
I
−
K
k
H
)
P
k
∣
k
−
1
\\boldsymbol{P}_{k|k} = \\left( \\boldsymbol{I} – \\boldsymbol{K}_k \\boldsymbol{H} \\right)\\boldsymbol{P}_{k|k-1}
Pk∣k=(I−KkH)Pk∣k−1
I
\\boldsymbol{I}
I为单位矩阵,迭代后协方差持续收敛,估计误差逐步降低。
7.4 标准KF局限性(电池场景无法直接使用)
锂电池系统是强非线性系统:
O
C
V
=
f
(
S
O
C
)
OCV=f(SOC)
OCV=f(SOC)为高度非线性曲线;
R
0
、
R
1
、
R
2
R_0、R_1、R_2
R0、R1、R2随SOC、温度动态变化;
U
t
=
O
C
V
(
S
O
C
)
−
R
0
I
−
U
1
−
U
2
U_t=OCV(SOC)-R_0I-U_1-U_2
Ut=OCV(SOC)−R0I−U1−U2非线性。 标准KF仅适用于线性系统,因此电池SOC必须使用扩展卡尔曼滤波EKF。
八、扩展卡尔曼滤波EKF原理(适配电池非线性)
8.1 核心思路
对非线性状态函数
f
(
⋅
)
f(\\cdot)
f(⋅)、观测函数
h
(
⋅
)
h(\\cdot)
h(⋅)在当前先验状态
x
^
k
∣
k
−
1
\\hat{x}_{k|k-1}
x^k∣k−1处一阶泰勒展开局部线性化,用雅可比矩阵替代线性矩阵
A
、
H
\\boldsymbol{A}、\\boldsymbol{H}
A、H,其余KF迭代框架完全不变。
8.2 非线性离散系统通用模型
{
x
k
=
f
(
x
k
−
1
,
u
k
−
1
)
+
w
k
−
1
z
k
=
h
(
x
k
)
+
v
k
\\begin{cases} \\boldsymbol{x}_k = f(\\boldsymbol{x}_{k-1},\\boldsymbol{u}_{k-1}) + \\boldsymbol{w}_{k-1} \\\\ \\boldsymbol{z}_k = h(\\boldsymbol{x}_k) + \\boldsymbol{v}_k \\end{cases}
{xk=f(xk−1,uk−1)+wk−1zk=h(xk)+vk
8.3 雅可比矩阵定义
F
k
\\boldsymbol{F}_k
Fk(替代
A
\\boldsymbol{A}
A)
F
k
=
∂
f
∂
x
∣
x
^
k
−
1
∣
k
−
1
\\boldsymbol{F}_k = \\left. \\frac{\\partial f}{\\partial \\boldsymbol{x}} \\right|_{\\hat{x}_{k-1|k-1}}
Fk=∂x∂f
x^k−1∣k−1
H
k
\\boldsymbol{H}_k
Hk(替代
H
\\boldsymbol{H}
H)
H
k
=
∂
h
∂
x
∣
x
^
k
∣
k
−
1
\\boldsymbol{H}_k = \\left. \\frac{\\partial h}{\\partial \\boldsymbol{x}} \\right|_{\\hat{x}_{k|k-1}}
Hk=∂x∂h
x^k∣k−1
8.4 EKF完整迭代五公式
x
^
k
∣
k
−
1
=
f
(
x
^
k
−
1
∣
k
−
1
,
u
k
−
1
)
\\hat{\\boldsymbol{x}}_{k|k-1} = f(\\hat{\\boldsymbol{x}}_{k-1|k-1},\\boldsymbol{u}_{k-1})
x^k∣k−1=f(x^k−1∣k−1,uk−1)
P
k
∣
k
−
1
=
F
k
P
k
−
1
∣
k
−
1
F
k
T
+
Q
\\boldsymbol{P}_{k|k-1} = \\boldsymbol{F}_k \\boldsymbol{P}_{k-1|k-1} \\boldsymbol{F}_k^T + \\boldsymbol{Q}
Pk∣k−1=FkPk−1∣k−1FkT+Q
K
k
=
P
k
∣
k
−
1
H
k
T
(
H
k
P
k
∣
k
−
1
H
k
T
+
R
)
−
1
\\boldsymbol{K}_k = \\boldsymbol{P}_{k|k-1}\\boldsymbol{H}_k^T \\left( \\boldsymbol{H}_k\\boldsymbol{P}_{k|k-1}\\boldsymbol{H}_k^T + \\boldsymbol{R} \\right)^{-1}
Kk=Pk∣k−1HkT(HkPk∣k−1HkT+R)−1
x
^
k
∣
k
=
x
^
k
∣
k
−
1
+
K
k
(
z
k
−
h
(
x
^
k
∣
k
−
1
)
)
\\hat{\\boldsymbol{x}}_{k|k} = \\hat{\\boldsymbol{x}}_{k|k-1} + \\boldsymbol{K}_k \\left( \\boldsymbol{z}_k – h(\\hat{\\boldsymbol{x}}_{k|k-1}) \\right)
x^k∣k=x^k∣k−1+Kk(zk−h(x^k∣k−1))
P
k
∣
k
=
(
I
−
K
k
H
k
)
P
k
∣
k
−
1
\\boldsymbol{P}_{k|k} = \\left( \\boldsymbol{I} – \\boldsymbol{K}_k \\boldsymbol{H}_k \\right)\\boldsymbol{P}_{k|k-1}
Pk∣k=(I−KkHk)Pk∣k−1
九、二阶RC等效电路ECM与EKF完整结合(工程核心)
9.1 二阶RC电路模型结构
二阶RC精准模拟电池电化学极化
R
1
C
1
R_1C_1
R1C1、浓差极化
R
2
C
2
R_2C_2
R2C2,电路组成:
-
O
C
V
(
S
O
C
)
OCV(SOC)
OCV(SOC):开路电压源,SOC非线性映射 -
R
0
R_0
R0:欧姆内阻;R
1
,
C
1
R_1,C_1
R1,C1、R
2
,
C
2
R_2,C_2
R2,C2两级RC极化支路 -
I
I
I:电流(放电正、充电负);U
t
U_t
Ut:模组端电压
基尔霍夫电压方程:
U
t
=
O
C
V
(
S
O
C
)
−
I
R
0
−
U
1
−
U
2
U_t = OCV(SOC) – I R_0 – U_1 – U_2
Ut=OCV(SOC)−IR0−U1−U2
U
1
、
U
2
U_1、U_2
U1、U2分别为两级RC电容极化电压。
9.2 构建EKF状态向量
选取3维状态变量:
x
=
[
S
O
C
U
1
U
2
]
\\boldsymbol{x} = \\begin{bmatrix} SOC \\\\ U_1 \\\\ U_2 \\end{bmatrix}
x=
SOCU1U2
输入控制量:
u
=
I
u=I
u=I(充放电电流);观测值
z
=
U
t
,
m
e
a
s
z=U_{t,meas}
z=Ut,meas(实测端电压)
9.3 连续微分方程离散化(核心状态转移
f
(
x
,
u
)
f(x,u)
f(x,u))
采样周期
Δ
t
\\Delta t
Δt,时间常数
τ
1
=
R
1
C
1
,
τ
2
=
R
2
C
2
\\tau_1=R_1C_1,\\tau_2=R_2C_2
τ1=R1C1,τ2=R2C2
η
\\eta
η库伦效率,
Q
n
Q_n
Qn额定容量Ah)
S
O
C
k
=
S
O
C
k
−
1
−
η
⋅
I
k
−
1
⋅
Δ
t
3600
⋅
Q
n
SOC_{k} = SOC_{k-1} – \\frac{\\eta \\cdot I_{k-1} \\cdot \\Delta t}{3600 \\cdot Q_n}
SOCk=SOCk−1−3600⋅Qnη⋅Ik−1⋅Δt
{
U
1
,
k
=
e
−
Δ
t
/
τ
1
U
1
,
k
−
1
+
R
1
(
1
−
e
−
Δ
t
/
τ
1
)
I
k
−
1
U
2
,
k
=
e
−
Δ
t
/
τ
2
U
2
,
k
−
1
+
R
2
(
1
−
e
−
Δ
t
/
τ
2
)
I
k
−
1
\\begin{cases} U_{1,k} = e^{-\\Delta t/\\tau_1} U_{1,k-1} + R_1(1-e^{-\\Delta t/\\tau_1})I_{k-1} \\\\ U_{2,k} = e^{-\\Delta t/\\tau_2} U_{2,k-1} + R_2(1-e^{-\\Delta t/\\tau_2})I_{k-1} \\end{cases}
{U1,k=e−Δt/τ1U1,k−1+R1(1−e−Δt/τ1)Ik−1U2,k=e−Δt/τ2U2,k−1+R2(1−e−Δt/τ2)Ik−1 合并得到非线性状态转移函数
f
(
x
,
I
)
f(\\boldsymbol{x},I)
f(x,I):
f
(
x
k
−
1
,
I
k
−
1
)
=
[
S
O
C
k
−
1
−
η
I
k
−
1
Δ
t
3600
Q
n
e
−
Δ
t
/
τ
1
U
1
,
k
−
1
+
R
1
(
1
−
e
−
Δ
t
/
τ
1
)
I
k
−
1
e
−
Δ
t
/
τ
2
U
2
,
k
−
1
+
R
2
(
1
−
e
−
Δ
t
/
τ
2
)
I
k
−
1
]
f(\\boldsymbol{x}_{k-1},I_{k-1})= \\begin{bmatrix} SOC_{k-1} – \\displaystyle\\frac{\\eta I_{k-1}\\Delta t}{3600 Q_n} \\\\ e^{-\\Delta t/\\tau_1}U_{1,k-1} + R_1(1-e^{-\\Delta t/\\tau_1})I_{k-1} \\\\ e^{-\\Delta t/\\tau_2}U_{2,k-1} + R_2(1-e^{-\\Delta t/\\tau_2})I_{k-1} \\end{bmatrix}
f(xk−1,Ik−1)=
SOCk−1−3600QnηIk−1Δte−Δt/τ1U1,k−1+R1(1−e−Δt/τ1)Ik−1e−Δt/τ2U2,k−1+R2(1−e−Δt/τ2)Ik−1
9.4 观测方程
h
(
x
)
h(\\boldsymbol{x})
h(x)(非线性)
h
(
x
k
)
=
O
C
V
(
S
O
C
k
)
−
I
k
R
0
−
U
1
,
k
−
U
2
,
k
h(\\boldsymbol{x}_k) = OCV(SOC_k) – I_k R_0 – U_{1,k} – U_{2,k}
h(xk)=OCV(SOCk)−IkR0−U1,k−U2,k 输出为模型预测端电压,与实测电压
z
k
=
U
t
,
m
e
a
s
z_k=U_{t,meas}
zk=Ut,meas做残差修正。
9.5 求解雅可比矩阵(代入EKF)
(1)状态转移雅可比
F
(
3
×
3
)
\\boldsymbol{F}(3\\times3)
F(3×3)
对
f
f
f分别求
S
O
C
、
U
1
、
U
2
SOC、U_1、U_2
SOC、U1、U2偏导,输入电流
I
I
I仅为外部控制量,无偏导:
F
=
[
1
0
0
0
e
−
Δ
t
/
τ
1
0
0
0
e
−
Δ
t
/
τ
2
]
\\boldsymbol{F}= \\begin{bmatrix} 1 & 0 & 0 \\\\ 0 & e^{-\\Delta t/\\tau_1} & 0 \\\\ 0 & 0 & e^{-\\Delta t/\\tau_2} \\end{bmatrix}
F=
1000e−Δt/τ1000e−Δt/τ2
(2)观测雅可比矩阵
H
(
1
×
3
)
\\boldsymbol{H}(1\\times3)
H(1×3)
对
h
(
x
)
h(\\boldsymbol{x})
h(x)求各状态偏导,
d
O
C
V
d
S
O
C
\\displaystyle\\frac{dOCV}{dSOC}
dSOCdOCV为OCV-SOC曲线在当前SOC处斜率:
H
=
[
d
O
C
V
d
S
O
C
,
−
1
,
−
1
]
\\boldsymbol{H}= \\left[ \\frac{dOCV}{dSOC},\\ -1,\\ -1 \\right]
H=[dSOCdOCV, −1, −1]
工程关键点:充电、放电OCV曲线存在滞回,必须分开插值、分开计算
d
O
C
V
/
d
S
O
C
dOCV/dSOC
dOCV/dSOC,否则电压拟合误差超80mV。
9.6 ECM-EKF完整迭代工程流程
步骤1:初始化参数
x
^
0
=
[
S
O
C
0
,
0
,
0
]
\\hat{x}_0=[SOC_0,\\ 0,\\ 0]
x^0=[SOC0, 0, 0],静置时SOC由OCV查表得到;
P
0
=
d
i
a
g
(
[
0.01
,
1
e
−
3
,
1
e
−
3
]
)
\\boldsymbol{P}_0=\\mathrm{diag}([0.01,\\ 1e-3,\\ 1e-3])
P0=diag([0.01, 1e−3, 1e−3]);
Q
=
d
i
a
g
(
[
1
e
−
6
,
1
e
−
3
,
1
e
−
3
]
)
\\boldsymbol{Q}=\\mathrm{diag}([1e-6,\\ 1e-3,\\ 1e-3])
Q=diag([1e−6, 1e−3, 1e−3]);
R
=
1
e
−
3
R=1e-3
R=1e−3(电压传感器噪声);
R
0
(
S
O
C
)
、
R
1
(
S
O
C
)
、
R
2
(
S
O
C
)
、
τ
1
(
S
O
C
)
、
τ
2
(
S
O
C
)
R_0(SOC)、R_1(SOC)、R_2(SOC)、\\tau_1(SOC)、\\tau_2(SOC)
R0(SOC)、R1(SOC)、R2(SOC)、τ1(SOC)、τ2(SOC),区分充放电两套参数;
O
C
V
c
h
a
r
g
e
(
S
O
C
)
、
O
C
V
d
i
s
c
h
a
r
g
e
(
S
O
C
)
OCV_{charge}(SOC)、OCV_{discharge}(SOC)
OCVcharge(SOC)、OCVdischarge(SOC)。
步骤2:EKF预测步(对应代码_ekf_predict)
x
^
k
−
1
\\hat{x}_{k-1}
x^k−1、当前电流
I
k
I_k
Ik,代入二阶RC离散公式计算先验
x
^
k
∣
k
−
1
\\hat{x}_{k|k-1}
x^k∣k−1;
R
0
、
R
1
、
R
2
、
τ
1
、
τ
2
R_0、R_1、R_2、\\tau_1、\\tau_2
R0、R1、R2、τ1、τ2;
F
\\boldsymbol{F}
F;
P
k
∣
k
−
1
=
F
P
k
−
1
F
T
+
Q
\\boldsymbol{P}_{k|k-1}=\\boldsymbol{F}\\boldsymbol{P}_{k-1}\\boldsymbol{F}^T+\\boldsymbol{Q}
Pk∣k−1=FPk−1FT+Q。
步骤3:EKF更新步(对应代码_ekf_update)
h
(
x
^
k
∣
k
−
1
)
h(\\hat{x}_{k|k-1})
h(x^k∣k−1);
d
O
C
V
/
d
S
O
C
dOCV/dSOC
dOCV/dSOC,构造观测雅可比
H
\\boldsymbol{H}
H;
y
=
U
m
e
a
s
−
U
p
r
e
d
y=U_{meas}-U_{pred}
y=Umeas−Upred;
K
K
K;
x
^
k
∣
k
=
x
^
k
∣
k
−
1
+
K
⋅
y
\\hat{x}_{k|k} = \\hat{x}_{k|k-1} + K\\cdot y
x^k∣k=x^k∣k−1+K⋅y;
P
k
∣
k
=
(
I
−
K
H
)
P
k
∣
k
−
1
\\boldsymbol{P}_{k|k}=(I-KH)P_{k|k-1}
Pk∣k=(I−KH)Pk∣k−1;
S
O
C
∈
[
0
,
1
]
SOC\\in[0,1]
SOC∈[0,1]、极化电压
U
1
/
U
2
∈
[
−
1
,
1
]
U_1/U_2\\in[-1,1]
U1/U2∈[−1,1]防止数值发散。
步骤4:循环迭代
读取下一帧电流、电压采样,重复预测-更新,逐帧输出最优SOC、极化电压、模型估算端电压。
十、ECM+EKF工程优势与传统方案对比
| 纯安时积分 | 计算简单、无电压依赖 | 电流噪声长期累积漂移,无自校准能力 |
| 开路电压法 | 静态SOC精度高 | 动态工况失效,必须长时间静置 |
| 一阶RC+EKF | 计算量小 | 无法区分两级极化,动态电压误差大 |
| 二阶RC+EKF | 1. 两级极化精准拟合充放电动态2. EKF实时修正积分误差3. 抑制电流/电压传感器噪声4. 支持初始SOC自收敛校准5. 适配动态DST、FUDS车载工况 | 计算量略大,需预辨识HPPC参数 |
十一、工程调参关键点
-
R
R
R增大:信任模型,电压修正权重降低,适合噪声大的廉价电压采集芯片; -
Q
Q
Q增大:信任传感器,SOC收敛速度更快,但易受电压毛刺扰动;
十二、SOC估算工程实现-python代码
用二阶RC等效电路模型估算的端电压
不包含内短路的等效电路模型、SOC估算
"""
import random
import os
import pandas as pd
import time
import matplotlib.pyplot as plt
from Funcs import *
import warnings
import seaborn as sns
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文显示为黑体
plt.rcParams['axes.unicode_minus'] = False # 正确显示负号
# 全局设置字体大小
plt.rcParams.update({'font.size': 24}) # 设置全局字体大小为 14
warnings.filterwarnings('ignore')
import plotly.graph_objects as go
from matplotlib.ticker import MultipleLocator, MaxNLocator
from openpyxl import load_workbook
from openpyxl.drawing.image import Image
import io # 内存缓存图片,避免生成临时文件
from datetime import datetime
import numpy as np
from scipy.interpolate import interp1d
class Record(object):
def __init__(self, output_path, fig_title):
super(Record, self).__init__()
self.normal_cap = 50
self.output_path = output_path
self.fig_title = fig_title
self.capacity = 50 # 额定容量 (Ah)
self.coulomb_efficiency = 1 # 库伦效率
def pipeline(self, df_raw, param_data, ocv_data_d, ocv_data_c, file_name, down_sample_flag):
"""
:param df_raw: 原始测试的模组循环数据
:param param_data: HPPC参数数据
:param ocv_data_d: 放电的OCV
:param ocv_data_c: 充电的OCV
:param file_name: 模组文件名
:param down_sample_flag: true则执行降采样,否则不降采样
:return:
"""
print('———————————-数据清洗、打标志位—————————–')
df_clean = df_raw_cleaning(df_raw) # 数据清洗
df_clean = raw_status_mark(df_clean)
df_clean = interval_mark(df_clean)
df_clean = self.del_index(df_clean)
print('———————————-选择HPPC参数、OCV参数———————-')
hppc_param_data_c = param_data[param_data['type'] == 'c'].reset_index(drop=True)
hppc_param_data_d = param_data[param_data['type'] == 'd'].reset_index(drop=True)
hppc_numbers = param_data['number'].unique().tolist() # 总共有几组HPPC参数数据
ocv_d_numbers = ocv_data_d['number'].unique().tolist() # 放电OCV参数数据组数
ocv_c_numbers = ocv_data_c['number'].unique().tolist() # 充电OCV参数数据组数
ocv_data_d = ocv_data_d[ocv_data_d['number'] == 1].reset_index(drop=True)
ocv_data_c = ocv_data_c[ocv_data_c['number'] == 1].reset_index(drop=True)
df_clean_copy = df_clean.copy()
if down_sample_flag: # true则执行,否则不降采样
print('———————————-数据降采样————————-')
drop_rows_c = random.randint(200, 300) # 0.2-0.4
df_clean_copy = self.down_sampling(df_clean, drop_rows_c)
estimation_df_all = pd.DataFrame()
count = 1
for hppc_number in hppc_numbers: # 3 * 7 * 3
if hppc_number not in [1]:
continue
print('———————————–hppc_number:', hppc_number)
param_data_c = hppc_param_data_c[hppc_param_data_c['number'] == hppc_number].reset_index(drop=True)
param_data_d = hppc_param_data_d[hppc_param_data_d['number'] == hppc_number].reset_index(drop=True)
# 存储模型参数并创建插值函数
self.param_data_c = param_data_c.sort_values('SOC')
self.param_data_d = param_data_d.sort_values('SOC')
self.soc_values = self.param_data_c['SOC'].values
# 创建各参数的SOC插值函数
self.R0_func_c = interp1d(self.soc_values, self.param_data_c['R0'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.R1_func_c = interp1d(self.soc_values, self.param_data_c['R1'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.R2_func_c = interp1d(self.soc_values, self.param_data_c['R2'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.tau1_func_c = interp1d(self.soc_values, self.param_data_c['tau1'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.tau2_func_c = interp1d(self.soc_values, self.param_data_c['tau2'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.R0_func_d = interp1d(self.soc_values, self.param_data_d['R0'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.R1_func_d = interp1d(self.soc_values, self.param_data_d['R1'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.R2_func_d = interp1d(self.soc_values, self.param_data_d['R2'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.tau1_func_d = interp1d(self.soc_values, self.param_data_d['tau1'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.tau2_func_d = interp1d(self.soc_values, self.param_data_d['tau2'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
# 处理OCV-SOC映射关系
self.ocv_soc_data_d = ocv_data_d.sort_values('SOC')
self.ocv_func_d = interp1d(self.ocv_soc_data_d['SOC'].values, self.ocv_soc_data_d['OCV'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.soc_from_ocv_func_d = interp1d(self.ocv_soc_data_d['OCV'].values, self.ocv_soc_data_d['SOC'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
# 处理OCV-SOC映射关系
self.ocv_soc_data_c = ocv_data_c.sort_values('SOC')
self.ocv_func_c = interp1d(self.ocv_soc_data_c['SOC'].values, self.ocv_soc_data_c['OCV'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.soc_from_ocv_func_c = interp1d(self.ocv_soc_data_c['OCV'].values, self.ocv_soc_data_c['SOC'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
# ——————————————record数据生成——————————————
estimation_df, true_df = self.process_data(df_clean_copy)
estimation_df = estimation_df.iloc[10:, :].reset_index(drop=True) # 删除前面10行数据
estimation_df = self.static_data_process(estimation_df) # 充电后静置几帧处理,放电后静置几帧处理
estimation_df = self.run_key_info(estimation_df)
final_cols = ['数据序号', '循环号', '工步号', '工步序号', '工步类型', '工步时间', '绝对时间', '总时间', '电压(V)', '电流(A)',
'容量(Ah)', '充电容量(Ah)', '放电容量(Ah)', '功率(mW)', '能量(Wh)', '充电能量(Wh)', '放电能量(Wh)',
'V1辅助通道电压(V)', 'V2辅助通道电压(V)', 'V3辅助通道电压(V)', 'V4辅助通道电压(V)', 'V5辅助通道电压(V)',
'V6辅助通道电压(V)', 'V7辅助通道电压(V)', 'V8辅助通道电压(V)', 'V9辅助通道电压(V)', 'V10辅助通道电压(V)',
'V11辅助通道电压(V)', 'V12辅助通道电压(V)', 'V13辅助通道电压(V)', 'V14辅助通道电压(V)', 'V15辅助通道电压(V)',
'V16辅助通道电压(V)', 'V17辅助通道电压(V)', 'V18辅助通道电压(V)', 'V19辅助通道电压(V)', 'V20辅助通道电压(V)',
'V21辅助通道电压(V)', 'V22辅助通道电压(V)', 'T1辅助通道温度(℃)', 'T2辅助通道温度(℃)',
'T3辅助通道温度(℃)', 'T4辅助通道温度(℃)', 'T5辅助通道温度(℃)', 'T6辅助通道温度(℃)']
estimation_df = estimation_df[final_cols]
return df_clean, estimation_df_all
def pipeline_cut(self, df_raw, param_data, ocv_data_d, ocv_data_c, file_name, down_sample_flag):
"""循环片段随机裁切,重组生成多样化仿真数据"""
print('———————————-数据清洗、打标志位—————————–')
df_clean = df_raw_cleaning(df_raw) # 数据清洗
df_clean = raw_status_mark(df_clean)
df_clean = interval_mark(df_clean)
df_clean = self.del_index(df_clean) # 删除充电后3帧静置,放电后3帧静置
print('———————————-选择HPPC参数、OCV参数———————-')
hppc_param_data_c = param_data[param_data['type'] == 'c'].reset_index(drop=True)
hppc_param_data_d = param_data[param_data['type'] == 'd'].reset_index(drop=True)
hppc_numbers = param_data['number'].unique().tolist() # 总共有几组HPPC参数数据
ocv_d_numbers = ocv_data_d['number'].unique().tolist() # 放电OCV参数数据组数
ocv_c_numbers = ocv_data_c['number'].unique().tolist() # 充电OCV参数数据组数
ocv_data_d = ocv_data_d[ocv_data_d['number'] == 1].reset_index(drop=True)
ocv_data_c = ocv_data_c[ocv_data_c['number'] == 1].reset_index(drop=True)
# 遍历每一个电芯
estimation_df_all, true_df_all = pd.DataFrame(), pd.DataFrame()
count = 1
print('—————————循环数据截取组合————————–')
groups = self.cut_cycle_dict()
for group_key, group_value in groups.items():
data_copy = df_clean[df_clean['循环号'].isin(list(group_value))].reset_index(drop=True)
last_cycle = data_copy['循环号'].max()
data_copy = pd.concat([data_copy[data_copy['循环号'] != last_cycle],
data_copy[(data_copy['循环号'] == last_cycle) &
(data_copy["容量(Ah)"] <= 15) &
(data_copy["工步类型"].str.contains("充电"))]])
if down_sample_flag: # true则执行,否则不降采样
print('———————————-数据降采样————————-')
drop_rows_c = random.randint(50, 100)
data_copy = self.down_sampling(df_clean, drop_rows_c)
hppc_number_ls = [1, 2, 3, 4, 5, 7, 8]
hppc_number = random.choice(hppc_number_ls)
print('———————————–hppc_number:', hppc_number)
param_data_c = hppc_param_data_c[hppc_param_data_c['number'] == hppc_number].reset_index(drop=True)
param_data_d = hppc_param_data_d[hppc_param_data_d['number'] == hppc_number].reset_index(drop=True)
# 存储模型参数并创建插值函数
self.param_data_c = param_data_c.sort_values('SOC')
self.param_data_d = param_data_d.sort_values('SOC')
self.soc_values = self.param_data_c['SOC'].values
# 创建各参数的SOC插值函数
self.R0_func_c = interp1d(self.soc_values, self.param_data_c['R0'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.R1_func_c = interp1d(self.soc_values, self.param_data_c['R1'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.R2_func_c = interp1d(self.soc_values, self.param_data_c['R2'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.tau1_func_c = interp1d(self.soc_values, self.param_data_c['tau1'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.tau2_func_c = interp1d(self.soc_values, self.param_data_c['tau2'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.R0_func_d = interp1d(self.soc_values, self.param_data_d['R0'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.R1_func_d = interp1d(self.soc_values, self.param_data_d['R1'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.R2_func_d = interp1d(self.soc_values, self.param_data_d['R2'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.tau1_func_d = interp1d(self.soc_values, self.param_data_d['tau1'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.tau2_func_d = interp1d(self.soc_values, self.param_data_d['tau2'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
# 处理OCV-SOC映射关系
self.ocv_soc_data_d = ocv_data_d.sort_values('SOC')
self.ocv_func_d = interp1d(self.ocv_soc_data_d['SOC'].values, self.ocv_soc_data_d['OCV'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.soc_from_ocv_func_d = interp1d(self.ocv_soc_data_d['OCV'].values, self.ocv_soc_data_d['SOC'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
# 处理OCV-SOC映射关系
self.ocv_soc_data_c = ocv_data_c.sort_values('SOC')
self.ocv_func_c = interp1d(self.ocv_soc_data_c['SOC'].values, self.ocv_soc_data_c['OCV'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
self.soc_from_ocv_func_c = interp1d(self.ocv_soc_data_c['OCV'].values, self.ocv_soc_data_c['SOC'].values, kind='linear', bounds_error=False, fill_value="extrapolate")
estimation_df, true_df = self.process_data(data_copy)
estimation_df = estimation_df.iloc[10:, :].reset_index(drop=True) # 删除前面10行数据
estimation_df = self.static_data_process(estimation_df) # 充电后静置几帧处理,放电后静置几帧处理
estimation_df = self.run_key_info(estimation_df)
final_cols = ['数据序号', '循环号', '工步号', '工步序号', '工步类型', '工步时间', '绝对时间', '总时间', '电压(V)', '电流(A)',
'容量(Ah)', '充电容量(Ah)', '放电容量(Ah)', '功率(mW)', '能量(Wh)', '充电能量(Wh)', '放电能量(Wh)',
'V1辅助通道电压(V)', 'V2辅助通道电压(V)', 'V3辅助通道电压(V)', 'V4辅助通道电压(V)', 'V5辅助通道电压(V)',
'V6辅助通道电压(V)', 'V7辅助通道电压(V)', 'V8辅助通道电压(V)', 'V9辅助通道电压(V)', 'V10辅助通道电压(V)',
'V11辅助通道电压(V)', 'V12辅助通道电压(V)', 'V13辅助通道电压(V)', 'V14辅助通道电压(V)', 'V15辅助通道电压(V)',
'V16辅助通道电压(V)', 'V17辅助通道电压(V)', 'V18辅助通道电压(V)', 'V19辅助通道电压(V)', 'V20辅助通道电压(V)',
'V21辅助通道电压(V)', 'V22辅助通道电压(V)', 'T1辅助通道温度(℃)', 'T2辅助通道温度(℃)',
'T3辅助通道温度(℃)', 'T4辅助通道温度(℃)', 'T5辅助通道温度(℃)', 'T6辅助通道温度(℃)']
estimation_df = estimation_df[final_cols]
volt_col = ['V1辅助通道电压(V)', 'V2辅助通道电压(V)', 'V3辅助通道电压(V)', 'V4辅助通道电压(V)', 'V5辅助通道电压(V)',
'V6辅助通道电压(V)', 'V7辅助通道电压(V)', 'V8辅助通道电压(V)', 'V9辅助通道电压(V)', 'V10辅助通道电压(V)',
'V11辅助通道电压(V)', 'V12辅助通道电压(V)', 'V13辅助通道电压(V)', 'V14辅助通道电压(V)', 'V15辅助通道电压(V)',
'V16辅助通道电压(V)', 'V17辅助通道电压(V)', 'V18辅助通道电压(V)', 'V19辅助通道电压(V)', 'V20辅助通道电压(V)',
'V21辅助通道电压(V)', 'V22辅助通道电压(V)']
estimation_df = estimation_df.reset_index(drop=True)
def _get_parameters_c(self, soc):
soc_clamped = np.clip(soc, 0, 1)
return {
'R0': float(self.R0_func_c(soc_clamped)),
'R1': float(self.R1_func_c(soc_clamped)),
'R2': float(self.R2_func_c(soc_clamped)),
'tau1': float(self.tau1_func_c(soc_clamped)),
'tau2': float(self.tau2_func_c(soc_clamped))
}
def _get_parameters_d(self, soc):
soc_clamped = np.clip(soc, 0, 1)
return {
'R0': float(self.R0_func_d(soc_clamped)),
'R1': float(self.R1_func_d(soc_clamped)),
'R2': float(self.R2_func_d(soc_clamped)),
'tau1': float(self.tau1_func_d(soc_clamped)),
'tau2': float(self.tau2_func_d(soc_clamped))
}
def _ekf_predict(self, current, dt):
"""EKF预测步:二阶RC离散递推"""
soc_prev, up1_prev, up2_prev = self.x
soc_pred = soc_prev – (self.coulomb_efficiency * current * dt / 3600) / self.capacity
soc_pred = np.clip(soc_pred, 0, 1)
if current <= 0:
params = self._get_parameters_c(soc_pred)
else:
params = self._get_parameters_d(soc_pred)
alpha1 = np.exp(-dt / params['tau1']) if params['tau1'] > 0 else 0
alpha2 = np.exp(-dt / params['tau2']) if params['tau2'] > 0 else 0
up1_pred = alpha1 * up1_prev + params['R1'] * (1 – alpha1) * current
up2_pred = alpha2 * up2_prev + params['R2'] * (1 – alpha2) * current
up1_pred = np.clip(up1_pred, -1, 1)
up2_pred = np.clip(up2_pred, -1, 1)
self.x_pred = np.array([soc_pred, up1_pred, up2_pred])
A = np.array([
[1, 0, 0],
[0, alpha1, 0],
[0, 0, alpha2]
])
self.P_pred = A @ self.P @ A.T + self.Q
def _ekf_update(self, voltage_meas, current):
"""EKF更新步:端电压残差修正状态"""
soc_pred, up1_pred, up2_pred = self.x_pred
if current <= 0:
params = self._get_parameters_c(soc_pred)
ocv_pred = self.ocv_func_c(soc_pred)
self.voltage_pred = ocv_pred – current * params['R0'] – up1_pred – up2_pred
y = voltage_meas – self.voltage_pred
soc_idx = np.searchsorted(self.ocv_soc_data_c['SOC'].values, soc_pred)
if soc_idx <= 0 or soc_idx >= len(self.ocv_soc_data_c) – 1:
d_ocv_d_soc = 0
else:
d_ocv_d_soc = (self.ocv_soc_data_c['OCV'].iloc[soc_idx] – self.ocv_soc_data_c['OCV'].iloc[soc_idx – 1]) / (self.ocv_soc_data_c['SOC'].iloc[soc_idx] – self.ocv_soc_data_c['SOC'].iloc[soc_idx – 1])
else:
params = self._get_parameters_d(soc_pred)
ocv_pred = self.ocv_func_d(soc_pred)
self.voltage_pred = ocv_pred – current * params['R0'] – up1_pred – up2_pred
y = voltage_meas – self.voltage_pred
soc_idx = np.searchsorted(self.ocv_soc_data_d['SOC'].values, soc_pred)
if soc_idx <= 0 or soc_idx >= len(self.ocv_soc_data_d) – 1:
d_ocv_d_soc = 0
else:
d_ocv_d_soc = (self.ocv_soc_data_d['OCV'].iloc[soc_idx] – self.ocv_soc_data_d['OCV'].iloc[soc_idx – 1]) / (self.ocv_soc_data_d['SOC'].iloc[soc_idx] – self.ocv_soc_data_d['SOC'].iloc[soc_idx – 1])
H = np.array([d_ocv_d_soc, -1, -1])
S = H @ self.P_pred @ H.T + self.R
K = self.P_pred @ H.T / S
self.x = self.x_pred + K * y
self.x[0] = np.clip(self.x[0], 0, 1)
self.x[1] = np.clip(self.x[1], -1, 1)
self.x[2] = np.clip(self.x[2], -1, 1)
self.P = (np.eye(3) – np.outer(K, H)) @ self.P_pred
def estimate(self, current, voltage, dt, true_soc=None):
"""单次EKF估算,返回估计SOC"""
self._ekf_predict(current, dt)
self._ekf_update(voltage, current)
estimated_soc = self.x[0]
return estimated_soc
def process_data(self, data):
"""二阶RC-EKF批量生成电芯估算电压"""
volt_cols = ['V1辅助通道电压(V)', 'V2辅助通道电压(V)', 'V3辅助通道电压(V)', 'V4辅助通道电压(V)', 'V5辅助通道电压(V)',
'V6辅助通道电压(V)', 'V7辅助通道电压(V)', 'V8辅助通道电压(V)', 'V9辅助通道电压(V)', 'V10辅助通道电压(V)',
'V11辅助通道电压(V)', 'V12辅助通道电压(V)', 'V13辅助通道电压(V)', 'V14辅助通道电压(V)', 'V15辅助通道电压(V)',
'V16辅助通道电压(V)', 'V17辅助通道电压(V)', 'V18辅助通道电压(V)', 'V19辅助通道电压(V)', 'V20辅助通道电压(V)',
'V21辅助通道电压(V)', 'V22辅助通道电压(V)']
initial_voltages = data.loc[0, volt_cols].tolist()
init_soc = []
for volt in initial_voltages:
soc = self.soc_from_ocv_func_d(volt)
init_soc.append(np.clip(soc, 0, 1))
data['time_diff'] = data['time_diff'].replace(0, 0.1)
data['time_diff'] = data['time_diff'].clip(upper=30)
data['绝对时间'] = pd.to_datetime(data['绝对时间'])
data.loc[:, 'current'] = -1 * data['电流(A)']
data = data.rename(columns={'容量(Ah)': 'cap', 'time_stamp': 'time'})
R_values = [1e-5, 1e-6]
a = random.choice(R_values)
b = random.choice(R_values)
c = random.choice(R_values)
estimation_df, true_df = pd.DataFrame(), pd.DataFrame()
cell_number = 0
for col in volt_cols:
self.x = np.array([init_soc[cell_number], 0.0, 0.0])
self.P = np.diag([0.01, 1e-3, 1e-3])
self.Q = np.diag([1e-6, a, b])
self.R = c
cell_number += 1
self.results_true = {
'volt_V' + str(cell_number): [],
}
self.results_simulation = {
f'V{cell_number}辅助通道电压(V)': [],
}
for _, row in data.iterrows():
dt = row['time_diff']
estimated_soc = self.estimate(row['current'], row[col], dt)
self.results_true['volt_V' + str(cell_number)].append(row[col])
self.results_simulation[f'V{cell_number}辅助通道电压(V)'].append(round(self.voltage_pred, 6))
df_cell_simulation = pd.DataFrame(self.results_simulation)
estimation_df = pd.concat([estimation_df, df_cell_simulation], axis=1)
df_cell_true = pd.DataFrame(self.results_true)
true_df = pd.concat([true_df, df_cell_true], axis=1)
true_df["delta_volt"] = true_df[true_df.columns.tolist()].max(axis=1) – true_df[true_df.columns.tolist()].min(axis=1)
estimation_df["delta_volt"] = estimation_df[estimation_df.columns.tolist()].max(axis=1) – estimation_df[estimation_df.columns.tolist()].min(axis=1)
estimation_df['电流(A)'] = data['current'].tolist()
estimation_df['charge_status'] = data['charge_status'].tolist()
return estimation_df, true_df

