文章目录
- 文章摘要
- 前言
- 一、二阶RC等效电路拓扑与元件物理意义
- 二、连续时间域完整公式推导(基尔霍夫KVL/KCL)
- 三、离散化差分方程(车载MCU实时计算必备)
- 四、模型参数辨识方法(工程落地核心)
- 五、二阶RC模型-BMS高精度SOC估算
- 六、二阶RC模型优缺点总结
- 七、标准卡尔曼滤波(KF)基础原理
- 八、扩展卡尔曼滤波EKF原理(适配电池非线性)
- 九、二阶RC等效电路ECM与EKF完整结合(工程核心)
- 十、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混合脉冲功率特性测试标准流程:
- 固定SOC点(0.05、0.1、0.2…0.95),静置30min以上,记录稳定 U O C V U_{OCV} UOCV;
- 施加3C放电脉冲10s,采集瞬时电压跌落;
- 静置40s,记录电压双指数回弹曲线;
参数提取逻辑: - 脉冲通电瞬间电压跳变 Δ V 0 = I ⋅ R 0 \Delta V_0=I\cdot R_0 ΔV0=I⋅R0,直接计算欧姆内阻 R 0 R_0 R0;
- 电压快速回弹段(0~30s)拟合得到 τ 1 、 R 1 、 C 1 \tau_1、R_1、C_1 τ1、R1、C1;
- 慢速回弹段(30~120s)拟合得到
τ
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采集端电压;
- 利用离散状态方程做时间预测步,安时积分初步更新SOC、极化电压;
- 利用观测电压残差做测量更新步,修正SOC误差,抑制安时积分累积漂移;
- 二阶模型精准复现极化压降,消除动态工况下电压偏差,SOC稳态误差≤3%,远优于一阶模型5%~8%误差。
配套落地:电动车续航里程计算、充电截止控制、均衡策略触发。
六、二阶RC模型优缺点总结
优势
- 物理意义清晰,每个元件对应真实电化学过程,参数可通过实验标定;
- 精度与算力平衡,双RC支路完美拟合快慢极化,误差远低于一阶模型;
- 离散化方程简单,无复杂微分运算,低成本MCU可实时运行;
- 适配全场景:SOC、SOH、故障诊断、系统仿真全覆盖;
- 参数可离线标定+在线实时修正,适配温度、老化变工况。
局限性
- 属于等效黑盒模型,无法描述内部锂离子浓度、SEI膜生长微观机理,极致精度不如P2D电化学模型;
- 忽略滞回电压(充放电OCV不重合),高精度场景需叠加滞回修正项;
- 超低温、超大倍率工况下RC参数非线性变强,需建立温度-SOC多维参数查表。
七、标准卡尔曼滤波(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
- SOC离散递推(安时积分,
η
\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(电压传感器噪声);
- HPPC参数插值函数: 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),区分充放电两套参数;
- OCV插值函数: 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;
- 实时插值当前SOC对应的 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)
- 判断充放电,调取对应OCV插值函数,计算预测端电压 h ( x ^ k ∣ k − 1 ) h(\hat{x}_{k|k-1}) h(x^k∣k−1);
- 计算OCV曲线导数 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参数 |
十一、工程调参关键点
- 噪声矩阵Q/R调试
- R R R增大:信任模型,电压修正权重降低,适合噪声大的廉价电压采集芯片;
- Q Q Q增大:信任传感器,SOC收敛速度更快,但易受电压毛刺扰动;
- HPPC参数分离
充电、放电极化特性不对称,必须两套插值表,共用参数会导致端电压RMSE>70mV; - OCV滞回处理
充放电OCV曲线分开存储,分别计算雅可比导数,消除充放电切换时SOC跳变; - 数值限幅
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
2168

被折叠的 条评论
为什么被折叠?



