动力电池二阶RC等效电路模型及EKF:原理、公式推导

文章目录

文章摘要

本文全面解析了动力电池二阶RC等效电路模型,这是当前新能源汽车和储能系统BMS(电池管理系统)中最主流的建模方案。文章从电化学机理出发,详细阐述了二阶RC模型的电路拓扑、各元件物理意义,并完整推导了连续时间域的状态空间方程和离散化差分方程。重点介绍了模型参数辨识的两种工业方法(HPPC脉冲实验离线辨识和在线递推辨识),以及该模型在BMS高精度SOC估算、电池故障诊断、SOH健康状态评估、整车仿真和充电控制优化等五大工程场景中的全链路应用。最后总结了二阶RC模型的优势与局限性,并展望了行业优化方向。本文为动力电池算法工程师提供了从理论到实践的完整技术参考。

前言

锂电池内部包含复杂电化学反应、离子扩散、欧姆损耗,无法直接测量内部极化、锂离子浓度等微观状态。工程上采用等效电路模型(ECM),用电阻、电容、电压源等效复现电池外部电压-电流动态特性,是BMS电池状态估算、故障诊断、仿真分析的核心基础。

主流ECM分为零阶(理想电压源)、一阶Thevenin、二阶RC、PNGV、高阶电化学模型。二阶RC模型是车企、储能行业的最优平衡点:相比一阶模型可同时捕捉快慢双尺度极化,电压拟合误差降低50%以上;相比高阶电化学模型计算量小、参数易辨识、可实时嵌入车载MCU,广泛用于电动车、储能电站BMS算法开发。

一、二阶RC等效电路拓扑与元件物理意义

1. 电路拓扑图

二阶RC电路拓扑图

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 R1C1快极化RC支路 τ 1 = R 1 C 1 \tau_1=R_1C_1 τ1=R1C1,0.1~10s电化学活化极化:电极界面电荷转移反应阻力,脉冲电流下电压快速升降
R 2 、 C 2 R_2、C_2 R2C2慢极化RC支路 τ 2 = R 2 C 2 \tau_2=R_2C_2 τ2=R2C2,10~100s浓差扩散极化:锂离子在电解液、电极孔隙扩散形成浓度梯度,电压缓慢弛豫恢复
U 1 、 U 2 U_1、U_2 U1U2两支路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)IR0U1U2(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 U1U2:快、慢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)R0ux2x3

三、离散化差分方程(车载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=SOCk1QnΔtIk1U1,k=exp(τ1Δt)U1,k1+R1[1exp(τ1Δt)]Ik1U2,k=exp(τ2Δt)U2,k1+R2[1exp(τ2Δt)]Ik1

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)IkR0U1,kU2,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 R0R1C1R2C2,主流两类工业方案:

4.1 HPPC脉冲实验离线辨识(标定阶段使用)

HPPC混合脉冲功率特性测试标准流程:

  1. 固定SOC点(0.05、0.1、0.2…0.95),静置30min以上,记录稳定 U O C V U_{OCV} UOCV
  2. 施加3C放电脉冲10s,采集瞬时电压跌落;
  3. 静置40s,记录电压双指数回弹曲线;
    参数提取逻辑:
  4. 脉冲通电瞬间电压跳变 Δ V 0 = I ⋅ R 0 \Delta V_0=I\cdot R_0 ΔV0=IR0,直接计算欧姆内阻 R 0 R_0 R0
  5. 电压快速回弹段(0~30s)拟合得到 τ 1 、 R 1 、 C 1 \tau_1、R_1、C_1 τ1R1C1
  6. 慢速回弹段(30~120s)拟合得到 τ 2 、 R 2 、 C 2 \tau_2、R_2、C_2 τ2R2C2
    使用双指数函数拟合静置恢复电压:
    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)=UOCVAet/τ1Bet/τ2

4.2 在线递推辨识(车载实时修正)

车辆运行过程中,电池老化、温度变化会改变全部参数,采用带遗忘因子递推最小二乘FFRLS、多新息辨识,实时更新 R 0 、 R 1 、 C 1 、 R 2 、 C 2 R_0、R_1、C_1、R_2、C_2 R0R1C1R2C2,适配SOH衰减、高低温工况。

五、二阶RC模型-BMS高精度SOC估算

SOC无法直接硬件测量,依靠二阶RC模型+扩展卡尔曼滤波EKF实现闭环估算:

  1. 状态变量: S O C 、 U 1 、 U 2 SOC、U_1、U_2 SOCU1U2;观测值:BMS采集端电压;
  2. 利用离散状态方程做时间预测步,安时积分初步更新SOC、极化电压;
  3. 利用观测电压残差做测量更新步,修正SOC误差,抑制安时积分累积漂移;
  4. 二阶模型精准复现极化压降,消除动态工况下电压偏差,SOC稳态误差≤3%,远优于一阶模型5%~8%误差。
    配套落地:电动车续航里程计算、充电截止控制、均衡策略触发。

六、二阶RC模型优缺点总结

优势

  1. 物理意义清晰,每个元件对应真实电化学过程,参数可通过实验标定;
  2. 精度与算力平衡,双RC支路完美拟合快慢极化,误差远低于一阶模型;
  3. 离散化方程简单,无复杂微分运算,低成本MCU可实时运行;
  4. 适配全场景:SOC、SOH、故障诊断、系统仿真全覆盖;
  5. 参数可离线标定+在线实时修正,适配温度、老化变工况。

局限性

  1. 属于等效黑盒模型,无法描述内部锂离子浓度、SEI膜生长微观机理,极致精度不如P2D电化学模型;
  2. 忽略滞回电压(充放电OCV不重合),高精度场景需叠加滞回修正项;
  3. 超低温、超大倍率工况下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=Axk1+Buk1+wk1

  • 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} wk1:过程噪声, w ∼ N ( 0 , Q ) w\sim N(0,\boldsymbol{Q}) wN(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}) vN(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^k1∣k1预测当前先验状态:
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^kk1=Ax^k1∣k1+Buk1
同步预测误差协方差:
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} Pkk1=APk1∣k1AT+Q
P k ∣ k − 1 \boldsymbol{P}_{k|k-1} Pkk1代表先验状态的不确定度, 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=Pkk1HT(HPkk1HT+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=zkHx^kk1
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^kk=x^kk1+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} Pkk=(IKkH)Pkk1
I \boldsymbol{I} I为单位矩阵,迭代后协方差持续收敛,估计误差逐步降低。

7.4 标准KF局限性(电池场景无法直接使用)

锂电池系统是强非线性系统

  1. O C V = f ( S O C ) OCV=f(SOC) OCV=f(SOC)为高度非线性曲线;
  2. 内阻 R 0 、 R 1 、 R 2 R_0、R_1、R_2 R0R1R2随SOC、温度动态变化;
  3. 观测方程 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)R0IU1U2非线性。
    标准KF仅适用于线性系统,因此电池SOC必须使用扩展卡尔曼滤波EKF

八、扩展卡尔曼滤波EKF原理(适配电池非线性)

8.1 核心思路

对非线性状态函数 f ( ⋅ ) f(\cdot) f()、观测函数 h ( ⋅ ) h(\cdot) h()在当前先验状态 x ^ k ∣ k − 1 \hat{x}_{k|k-1} x^kk1一阶泰勒展开局部线性化,用雅可比矩阵替代线性矩阵 A 、 H \boldsymbol{A}、\boldsymbol{H} AH,其余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(xk1,uk1)+wk1zk=h(xk)+vk

8.3 雅可比矩阵定义

  1. 状态转移雅可比矩阵 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=xf x^k1∣k1
  2. 观测雅可比矩阵 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=xh x^kk1

8.4 EKF完整迭代五公式

  1. 先验状态预测: 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^kk1=f(x^k1∣k1,uk1)
  2. 先验协方差预测: 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} Pkk1=FkPk1∣k1FkT+Q
  3. 卡尔曼增益: 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=Pkk1HkT(HkPkk1HkT+R)1
  4. 状态更新: 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^kk=x^kk1+Kk(zkh(x^kk1))
  5. 协方差更新: 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} Pkk=(IKkHk)Pkk1

九、二阶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)IR0U1U2
U 1 、 U 2 U_1、U_2 U1U2分别为两级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

  1. 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=SOCk13600QnηIk1Δt
  2. 极化电压离散递推
    { 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,k1+R1(1eΔt/τ1)Ik1U2,k=eΔt/τ2U2,k1+R2(1eΔt/τ2)Ik1
    合并得到非线性状态转移函数 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(xk1,Ik1)= SOCk13600QnηIk1ΔteΔt/τ1U1,k1+R1(1eΔt/τ1)Ik1eΔt/τ2U2,k1+R2(1eΔt/τ2)Ik1

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)IkR0U1,kU2,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 SOCU1U2偏导,输入电流 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:初始化参数
  1. 状态初始值 x ^ 0 = [ S O C 0 ,   0 ,   0 ] \hat{x}_0=[SOC_0,\ 0,\ 0] x^0=[SOC0, 0, 0],静置时SOC由OCV查表得到;
  2. 误差协方差 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, 1e3, 1e3])
  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([1e6, 1e3, 1e3])
  4. 观测噪声 R = 1 e − 3 R=1e-3 R=1e3(电压传感器噪声);
  5. 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),区分充放电两套参数;
  6. 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
  1. 根据上一时刻 x ^ k − 1 \hat{x}_{k-1} x^k1、当前电流 I k I_k Ik,代入二阶RC离散公式计算先验 x ^ k ∣ k − 1 \hat{x}_{k|k-1} x^kk1
  2. 实时插值当前SOC对应的 R 0 、 R 1 、 R 2 、 τ 1 、 τ 2 R_0、R_1、R_2、\tau_1、\tau_2 R0R1R2τ1τ2
  3. 计算状态转移雅可比 F \boldsymbol{F} F
  4. 更新先验协方差 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} Pkk1=FPk1FT+Q
步骤3:EKF更新步(对应代码_ekf_update
  1. 判断充放电,调取对应OCV插值函数,计算预测端电压 h ( x ^ k ∣ k − 1 ) h(\hat{x}_{k|k-1}) h(x^kk1)
  2. 计算OCV曲线导数 d O C V / d S O C dOCV/dSOC dOCV/dSOC,构造观测雅可比 H \boldsymbol{H} H
  3. 计算残差 y = U m e a s − U p r e d y=U_{meas}-U_{pred} y=UmeasUpred
  4. 求解卡尔曼增益 K K K
  5. 修正状态 x ^ k ∣ k = x ^ k ∣ k − 1 + K ⋅ y \hat{x}_{k|k} = \hat{x}_{k|k-1} + K\cdot y x^kk=x^kk1+Ky
  6. 后验协方差更新 P k ∣ k = ( I − K H ) P k ∣ k − 1 \boldsymbol{P}_{k|k}=(I-KH)P_{k|k-1} Pkk=(IKH)Pkk1
  7. 钳位 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+EKF1. 两级极化精准拟合充放电动态
2. EKF实时修正积分误差
3. 抑制电流/电压传感器噪声
4. 支持初始SOC自收敛校准
5. 适配动态DST、FUDS车载工况
计算量略大,需预辨识HPPC参数

十一、工程调参关键点

  1. 噪声矩阵Q/R调试
    • R R R增大:信任模型,电压修正权重降低,适合噪声大的廉价电压采集芯片;
    • Q Q Q增大:信任传感器,SOC收敛速度更快,但易受电压毛刺扰动;
  2. HPPC参数分离
    充电、放电极化特性不对称,必须两套插值表,共用参数会导致端电压RMSE>70mV;
  3. OCV滞回处理
    充放电OCV曲线分开存储,分别计算雅可比导数,消除充放电切换时SOC跳变;
  4. 数值限幅
    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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值