电池 SOC 估算算法完整深度解析

摘要


电池荷电状态 SOC 是电池管理系统 BMS 的核心状态量,表征电池剩余可用容量,无法直接传感器测量,只能通过观测电压、电流、温度进行状态估计。本文首先阐述 SOC 基础理论,对比安时积分法的漂移缺陷,重点讲解基于二阶 RC 等效电路模型 + 扩展卡尔曼滤波 EKF的 SOC 估算算法。全部采用 C# 原生代码实现,无任何第三方依赖。从系统状态空间建模、雅可比矩阵求解、时间更新、量测更新到边界约束,完整复现 BMS 中 SOC 估算逻辑;同时完成算法性能仿真分析,剖析误差来源、优缺点以及工程落地适用场景,可为 BMS 开发、仿真学习提供参考。

电池状态估计基础概念详解


SOC(电池荷电状态)定义与测量原理

SOC(State Of Charge)表示电池当前剩余能量,定义为剩余容量与额定容量的百分比:

其中:

  • :t时刻剩余容量(Ah)
  • :电池额定容量(Ah)

SOC取值含义:

  • 1:电池充满
  • 0:电池放空

关键特性:

  • SOC是内部状态变量,需通过可测量参数间接估算
  • 测量参数包括:端电压(V)、充放电电流(A)、温度(℃)
  • 实际估算受温度、老化、充放电倍率等因素影响

SOH(电池健康状态)简介

SOH表征电池老化程度,与SOC相关但有区别:

主要类型:

  • 容量衰减型:
  • 内阻增加型:

对SOC的影响:

  • 老化导致额定容量降低
  • 内阻随循环次数增加
  • 上述变化会影响SOC计算精度

注:实际系统通常需要SOC和SOH联合估计(本文不深入讨论SOH)

二阶RC等效电路模型

模型结构

主要组件:

  • 开路电压源

    • 表征电池电动势
    • 与SOC呈非线性关系
    • 需通过实验测定OCV-SOC曲线
  • 欧姆内阻

    • 反映瞬时电压降
    • 包含集流体、电极材料等电阻
  • 双RC并联回路:

    • 快极化回路(RC1):
      • :电荷转移电阻
      • :双电层电容
      • 时间常数(秒级)
    • 慢极化回路(RC2):
      • :扩散电阻
      • :扩散电容
      • 时间常数(分钟级)
数学模型

状态方程:

输出方程:

符号说明:

  • :电池电流(A),放电为正,充电为负
  • :可测量的端电压(V)
  • 3600:小时转秒系数
模型参数获取

OCV-SOC曲线测定:

  • 采用低倍率(如0.1C)充放电实验
  • 记录静置后的稳定电压与SOC关系
  • 使用分段线性插值或多项式拟合

参数辨识方法:

  • HPPC测试
  • 最小二乘法
  • 遗传算法优化

离散化处理:

  • BMS基于微控制器实现
  • 采样周期通常为0.1-1秒
  • 常用离散化方法:前向欧拉法、双线性变换

扩展卡尔曼滤波(EKF)原理

基本概念

标准KF局限:

  • 仅适用于线性系统
  • 电池系统中为非线性

EKF解决方案:

  • 在当前状态点进行一阶泰勒展开
  • 通过雅可比矩阵实现局部线性化
  • 应用标准KF框架
状态空间定义

状态向量:

系统输入:(电池电流) 系统输出:(端电压)

EKF实现步骤
  • 状态预测:基于前序状态和当前输入预测当前状态
  • 协方差预测:预测状态估计不确定性
  • 卡尔曼增益计算:平衡预测和测量可信度
  • 状态更新:结合预测和测量更新状态
  • 协方差更新:更新状态估计不确定性

注意事项:

  • 需合理设置过程噪声和测量噪声协方差矩阵
  • 强非线性系统可考虑UKF
  • 需考虑数值稳定性问题

历史背景


早期发展阶段(1980-1995年)

安时积分法(库仑计数)是最早的电池管理系统(BMS)方案。该方法通过测量电流并进行时间积分来计算SOC(电池荷电状态),硬件实现简单,仅需电流传感器和积分电路。然而,其存在以下关键缺陷:

  • 电流传感器的测量噪声和采样偏移会导致误差累积
  • 长期运行后SOC估算出现严重漂移(典型情况下每月误差超过10%)
  • 需依赖电池静置时的开路电压(OCV)定期校准
  • 无法适应持续动态工况(如电动汽车的频繁启停和加减速)

OCV查表法阶段

开路电压法(OCV)基于电池静置时极化完全消失的特性,此时开路电压与SOC呈一一对应关系。但该方法具有明显局限:

  • 需电池完全静置(通常要求2小时以上)
  • 无法实现在线实时SOC估算
  • 仅适合作为辅助校准手段
  • 受温度影响显著,需建立不同温度下的OCV-SOC曲线

典型应用:早期笔记本电脑电池的SOC校准

理论突破期(2000-2010年)

Gregory L. Plett教授发表的开创性论文将卡尔曼滤波引入SOC估算领域:

  • 2004年首次将扩展卡尔曼滤波(EKF)应用于锂电池SOC估计
  • 将SOC视为系统内部状态量
  • 利用端电压观测值反馈校正安时积分的漂移误差
  • 建立了基于等效电路模型的SOC估算理论框架

主要优势

  • 有效解决误差累积问题
  • 实现在线实时估算
  • 适应动态工况

算法多样化阶段(2010-2020年)

这一阶段SOC估算方法呈现多样化发展:

基于模型的方法

  • EKF(扩展卡尔曼滤波)成为工程实践主流
  • UKF(无迹卡尔曼滤波)解决非线性问题
  • 滑模观测器增强鲁棒性
  • 粒子滤波处理非高斯噪声

数据驱动方法兴起

  • BP神经网络
  • LSTM等深度学习模型
  • 需大量高质量训练数据
  • 受限于嵌入式系统算力(典型BMS MCU主频仅50-100MHz)

当前工程现状(2020年至今)

量产BMS主流技术方案

  • 采用二阶RC等效电路模型
  • 核心算法为EKF-SOC
  • 结合多种修正手段:
    • 安时积分作为基础
    • OCV定期校准
    • 温度补偿(-30℃~60℃)
    • SOH(电池健康状态)修正

深度学习应用现状

  • 主要用于仿真研究和算法开发
  • 车规级BMS直接应用较少
  • 面临AEC-Q100认证挑战

典型应用案例:特斯拉Model 3 BMS采用EKF+二阶RC模型方案

核心原理


非线性离散状态空间

在实际应用中,连续系统需进行离散化处理以适应数字控制系统的采样周期Δt。离散时间状态空间模型包含两个核心方程:

状态方程

  • :k时刻状态向量(含SOC、电容电压等)
  • :k-1时刻系统输入(充放电电流)
  • :过程噪声(零均值高斯白噪声)

观测方程

  • :k时刻观测值(电池端电压)
  • :观测噪声(高斯白噪声)
二阶RC模型离散化

锂电池二阶等效电路模型的离散状态转移函数具体展开为:

观测方程描述端电压与状态量的关系:

EKF两大步骤

扩展卡尔曼滤波器(EKF)通过线性化处理非线性系统,实现过程分为:

时间更新(预测步)
  • 状态先验预测

  • 雅可比矩阵计算 : 对状态转移函数f求偏导得状态转移雅可比矩阵 (二阶RC模型中为3×3矩阵)

  • 先验协方差预测

    • :过程噪声协方差矩阵(通常为对角矩阵)
量测更新(校正步)
  • 观测雅可比计算

    • 需通过OCV-SOC曲线求导获得
  • 卡尔曼增益计算

    • (R):观测噪声方差(反映电压传感器精度)
  • 状态更新

  • 协方差更新

工程强制约束

为保障算法物理合理性,需施加约束:

  • SOC限制在0%, 100%区间
  • 极化电压非负约束
  • 异常测量时采用限幅处理,防止滤波器发散

OCV-SOC多项式拟合

工程中通过实验数据拟合OCV曲线,典型三次多项式形式:

其导数为:

拟合流程

  • 在25℃环境温度下进行充放电测试
  • 采用低电流(如0.05C)获取准静态OCV数据
  • 使用最小二乘法进行多项式回归
  • 验证拟合优度(要求R²>0.99)

执行流程(10个模块)


参数初始化模块

  • 电池模型参数:
    • 欧姆内阻 (R₀)
    • 极化内阻 (R₁, R₂)
    • 极化电容 (C₁, C₂)
    • 额定容量 (Qₙ, 单位:Ah)
    • 采样周期 (dt, 单位:s)
  • EKF噪声矩阵:
    • 过程噪声协方差矩阵 Q(3×3,反映模型不确定性)
    • 量测噪声协方差矩阵 R(1×1,反映电压传感器误差)
  • 初始状态:
    • SOC (x₁)
    • 极化电压 (默认值:SOC=1,极化电压=0)
  • 初始协方差 P:3×3对角矩阵(通常设为小值如10⁻³I)

传感器输入模块

  • 读取当前时刻k的输入电流Iₖ(单位:A,放电为正,充电为负)
  • 读取电压传感器测量的端电压(单位:V)
  • 示例:当电流传感器采样值为2.5A,电压为3.7V时,Iₖ=2.5,

状态转移预测模块

基于离散二阶RC状态方程计算先验状态x̂ₖ⁻: (η为库仑效率,默认1.0)

状态转移雅可比F矩阵求解模块

计算3×3雅可比矩阵F:

协方差时间预测模块

更新先验协方差Pₖ⁻:物理意义:通过Q增加预测不确定性,避免滤波过度自信

观测方程与观测雅可比H矩阵模块

  • 预测端电压(OCV-SOC曲线通过查表或多项式拟合实现)
  • 计算1×3观测雅可比H:

卡尔曼增益计算模块

计算卡尔曼增益Kₖ(3×1矩阵):注意:需检查矩阵是否可逆

量测更新校正模块

  • 计算电压残差
  • 修正后验状态:

物理限幅约束模块

对状态量进行约束:

  • SOC限制在0,1范围内
  • 极化电压设为非负值(反映实际物理特性) 作用:防止滤波发散或非物理解出现

输出与缓存模块

  • 输出当前SOC估计值SOCₖ⁺
  • 更新后验协方差
  • 保存状态x̂ₖ⁺和Pₖ⁺供下一周期使用
  • 循环执行:每个采样周期dt重复步骤2-10,实现实时SOC估计

应用场景:适用于电动汽车BMS、储能系统等需要高精度电池状态估计的场合

算法性能分析


误差来源

模型误差
  • 二阶RC网络近似误差:实际电池动态特性较二阶RC等效电路模型更为复杂,该模型仅能近似模拟极化效应,无法反映更高阶动态响应。
  • 参数时变影响:电池内部参数(如电阻R、电容C)随温度变化(每10℃变化5-15%)及老化(循环100次后内阻可能增加20%)而发生改变。
  • 参数辨识误差:通过脉冲测试或最小二乘法辨识的参数与实际值存在偏差,这是导致SOC估计误差的主要来源之一。
传感器噪声
  • 电流采样误差:霍尔传感器存在零漂(典型±0.5%FS)和增益误差,采样电阻受温度影响(如50ppm/℃)。
  • 电压测量噪声:ADC量化误差及电路噪声(典型值1-5mV)在低SOC区域会显著降低OCV测量精度。
  • 噪声矩阵影响:过程噪声协方差矩阵Q和观测噪声协方差矩阵R的比值(Q/R)决定了卡尔曼增益分配,直接影响滤波器收敛速度和稳定性。
OCV-SOC曲线特性
  • 平台区问题 :在SOC 0.2-0.8范围内(磷酸铁锂尤为明显),OCV曲线平坦,导数,导致卡尔曼增益显著降低(平台区可能降至0.01以下),电压观测对SOC的校正作用减弱。
  • 温度影响:OCV曲线随温度漂移(约0.5mV/℃),进一步加剧平台区问题。
滤波发散风险
  • 初始状态误差:SOC初值偏差超过20%可能导致协方差矩阵P异常增长。
  • 噪声配置不当
    • Q过小:滤波器过度依赖模型,易发散;
    • R过小:对测量噪声过于敏感,导致估计振荡。
  • 数值计算问题:协方差矩阵可能失去正定性(可通过Joseph形式修正)。

调参影响分析

Q矩阵调整(过程噪声协方差)
  • Q增大10倍
    • 效果:模型预测权重增加60-70%;
    • 优点:提升电流噪声抑制能力(如容忍±2A噪声);
    • 缺点:收敛时间延长30-50%(从10分钟增至15分钟)。
  • Q减小10倍
    • 效果:观测校正权重增加;
    • 响应:1mV电压波动可能引起0.5%SOC跳动;
    • 适用场景:高精度电压测量环境。
R矩阵调整(观测噪声协方差)
  • R增大
    • 卡尔曼增益下降(K∝1/R),例如R从0.001增至0.01时增益下降约70%;
    • 适用情况:电压传感器精度较低(如±10mV)。
  • R减小
    • 风险:平台区可能引发SOC振荡(幅度1-3%);
    • 必要条件:电压测量精度需优于0.1%。
典型参数范围(动力电池应用)
  • Q:1e-6 ~ 1e-4(反映模型不确定性);
  • R:1e-4 ~ 1e-2(对应电压测量误差)。

收敛特性分析

初始误差收敛
  • 大误差收敛:初始误差30%时,在1C充放电工况下,前5分钟可校正约20%,完全收敛需15-20分钟;收敛速率与电流大小正相关。
  • 静置校准:需静置2小时以上(达到松弛标准),OCV法可将SOC误差降至1%以内。
工况特性
  • 动态工况优势:1C持续放电时,SOC估计误差可保持在2%以内,极化电压提供额外信息。
  • 平台区挑战:磷酸铁锂电池在40-60%SOC时,观测灵敏度下降80%,误差可能累积至3-5%;解决方案为结合库仑计数补偿。
温度影响
  • 低温(-10℃):内阻增加导致极化电压误差,SOC估计误差可能扩大2-3倍;解决方案为采用温度补偿参数表。

完整代码


.NET 控制台程序,直接复制运行;实现:简单矩阵运算、二阶 RC 模型、EKF‑SOC 迭代仿真;内置模拟电池真值用于对比误差。

cs 复制代码
using System;

namespace SocEstimateEKF
{
    /// <summary>
    /// 简单矩阵工具 3维矩阵,无第三方库
    /// </summary>
    public class Matrix3
    {
        public double[,] M = new double[3, 3];

        public static Matrix3 Identity()
        {
            var mat = new Matrix3();
            mat.M[0, 0] = 1; mat.M[1, 1] = 1; mat.M[2, 2] = 1;
            return mat;
        }

        public static Matrix3 Add(Matrix3 a, Matrix3 b)
        {
            var res = new Matrix3();
            for (int i = 0; i < 3; i++)
                for (int j = 0; j < 3; j++)
                    res.M[i, j] = a.M[i, j] + b.M[i, j];
            return res;
        }

        public static Matrix3 Multiply(Matrix3 a, Matrix3 b)
        {
            var res = new Matrix3();
            for (int i = 0; i < 3; i++)
                for (int k = 0; k < 3; k++)
                {
                    if (Math.Abs(a.M[i, k]) < 1e-12) continue;
                    for (int j = 0; j < 3; j++)
                        res.M[i, j] += a.M[i, k] * b.M[k, j];
                }
            return res;
        }

        public static Matrix3 Transpose(Matrix3 a)
        {
            var res = new Matrix3();
            for (int i = 0; i < 3; i++)
                for (int j = 0; j < 3; j++)
                    res.M[i, j] = a.M[j, i];
            return res;
        }

        //3阶矩阵求逆,伴随矩阵法
        public static Matrix3 Inverse(Matrix3 src)
        {
            double det = Det(src);
            if (Math.Abs(det) < 1e-14) throw new Exception("矩阵奇异无法求逆");
            Matrix3 adj = Adjoint(src);
            Matrix3 inv = new Matrix3();
            for (int i = 0; i < 3; i++)
                for (int j = 0; j < 3; j++)
                    inv.M[i, j] = adj.M[i, j] / det;
            return inv;
        }

        private static double Det(Matrix3 m)
        {
            double[,] a = m.M;
            return a[0, 0] * (a[1, 1] * a[2, 2] - a[1, 2] * a[2, 1])
                 - a[0, 1] * (a[1, 0] * a[2, 2] - a[1, 2] * a[2, 0])
                 + a[0, 2] * (a[1, 0] * a[2, 1] - a[1, 1] * a[2, 0]);
        }

        private static Matrix3 Adjoint(Matrix3 m)
        {
            double[,] a = m.M;
            Matrix3 adj = new Matrix3();
            adj.M[0, 0] = a[1, 1] * a[2, 2] - a[1, 2] * a[2, 1];
            adj.M[0, 1] = a[0, 2] * a[2, 1] - a[0, 1] * a[2, 2];
            adj.M[0, 2] = a[0, 1] * a[1, 2] - a[0, 2] * a[1, 1];

            adj.M[1, 0] = a[1, 2] * a[2, 0] - a[1, 0] * a[2, 2];
            adj.M[1, 1] = a[0, 0] * a[2, 2] - a[0, 2] * a[2, 0];
            adj.M[1, 2] = a[0, 2] * a[1, 0] - a[0, 0] * a[1, 2];

            adj.M[2, 0] = a[1, 0] * a[2, 1] - a[1, 1] * a[2, 0];
            adj.M[2, 1] = a[0, 1] * a[2, 0] - a[0, 0] * a[2, 1];
            adj.M[2, 2] = a[0, 0] * a[1, 1] - a[0, 1] * a[1, 0];
            return Transpose(adj);
        }
    }

    /// <summary>
    /// 三维状态向量 x=[SOC, Uc1, Uc2]
    /// </summary>
    public struct Vec3
    {
        public double Soc;
        public double Uc1;
        public double Uc2;
        public Vec3(double s, double u1, double u2) { Soc = s; Uc1 = u1; Uc2 = u2; }
    }

    /// <summary>
    /// EKF SOC估算器:二阶RC等效电路模型
    /// </summary>
    public class SocEKFEstimator
    {
        //=====电池参数=====
        public double R0 { get; set; }     //欧姆内阻 Ω
        public double R1 { get; set; }
        public double C1 { get; set; }
        public double R2 { get; set; }
        public double C2 { get; set; }
        public double Qn { get; set; }     //额定容量 Ah
        public double Dt { get; set; }     //采样周期 s

        //OCV‑SOC多项式拟合参数 Uocv = a0+a1*s+a2*s^2+a3*s^3
        private readonly double a0 = 3.25;
        private readonly double a1 = 1.80;
        private readonly double a2 = -2.10;
        private readonly double a3 = 1.20;

        //EKF 噪声
        public Matrix3 Q { get; set; }   //过程噪声协方差
        public double R { get; set; }    //观测噪声方差(电压)

        //EKF状态
        public Vec3 X;
        public Matrix3 P;

        //预计算系数
        private double expTau1, expTau2;
        private double k1, k2;

        public SocEKFEstimator(double r0, double r1, double c1, double r2, double c2, double qn, double dt)
        {
            R0 = r0; R1 = r1; C1 = c1;
            R2 = r2; C2 = c2;
            Qn = qn; Dt = dt;

            double tau1 = R1 * C1;
            double tau2 = R2 * C2;
            expTau1 = Math.Exp(-Dt / tau1);
            expTau2 = Math.Exp(-Dt / tau2);
            k1 = R1 * (1 - expTau1);
            k2 = R2 * (1 - expTau2);

            //初始化过程噪声Q
            Q = new Matrix3();
            Q.M[0, 0] = 1e-7;
            Q.M[1, 1] = 1e-6;
            Q.M[2, 2] = 1e-6;

            R = 0.002; //电压观测噪声方差

            //初始状态
            X = new Vec3(0.50, 0.0, 0.0);
            P = Matrix3.Identity();
            P.M[0, 0] = 0.04; //初始SOC误差方差很大
            P.M[1, 1] = 0.01;
            P.M[2, 2] = 0.01;
        }

        /// <summary>OCV开路电压</summary>
        private double GetOcv(double soc)
        {
            soc = Math.Clamp(soc, 0, 1);
            return a0 + a1 * soc + a2 * soc * soc + a3 * soc * soc * soc;
        }
        /// <summary>dUocv/dSoc 导数</summary>
        private double GetOcvDeriv(double soc)
        {
            soc = Math.Clamp(soc, 0, 1);
            return a1 + 2 * a2 * soc + 3 * a3 * soc * soc;
        }

        /// <summary>状态转移函数 f(x,u) u=电流I 放电>0</summary>
        private Vec3 StateTransfer(Vec3 xPrev, double I)
        {
            double socNew = xPrev.Soc - Dt * I / (3600.0 * Qn);
            double uc1New = xPrev.Uc1 * expTau1 + I * k1;
            double uc2New = xPrev.Uc2 * expTau2 + I * k2;
            return new Vec3(socNew, uc1New, uc2New);
        }

        /// <summary>计算状态转移雅可比F矩阵</summary>
        private Matrix3 CalcF()
        {
            Matrix3 F = Matrix3.Identity();
            F.M[1, 1] = expTau1;
            F.M[2, 2] = expTau2;
            return F;
        }

        /// <summary>观测输出h(x)预测端电压</summary>
        private double PredictVoltage(Vec3 xPred, double I)
        {
            double ocv = GetOcv(xPred.Soc);
            double ut = ocv - I * R0 - xPred.Uc1 - xPred.Uc2;
            return ut;
        }

        /// <summary>EKF单步迭代,输入电流I、测量电压Umeas;返回估算SOC</summary>
        public double Step(double I, double Umeas)
        {
            //====模块3:状态预测====
            Vec3 xMinus = StateTransfer(X, I);

            //====模块4:F雅可比====
            Matrix3 Fmat = CalcF();

            //====模块5:协方差时间更新 P- = F*P*F^T + Q ====
            Matrix3 Ft = Matrix3.Transpose(Fmat);
            Matrix3 fpf = Matrix3.Multiply(Matrix3.Multiply(Fmat, P), Ft);
            Matrix3 Pminus = Matrix3.Add(fpf, Q);

            //====模块6:观测雅可比H [dOCV/dSoc, -1, -1] ====
            double dhds = GetOcvDeriv(xMinus.Soc);
            double[] H = { dhds, -1.0, -1.0 };

            //预测电压
            double zPred = PredictVoltage(xMinus, I);
            double residual = Umeas - zPred;

            //====模块7:卡尔曼增益 K = P- * H^T * inv(H*P-*H^T + R) ====
            //H*Pminus*H^T 标量
            double hph = H[0] * (Pminus.M[0, 0] * H[0] + Pminus.M[0, 1] * H[1] + Pminus.M[0, 2] * H[2])
                        + H[1] * (Pminus.M[1, 0] * H[0] + Pminus.M[1, 1] * H[1] + Pminus.M[1, 2] * H[2])
                        + H[2] * (Pminus.M[2, 0] * H[0] + Pminus.M[2, 1] * H[1] + Pminus.M[2, 2] * H[2]);
            double S = hph + R;
            double K0 = (Pminus.M[0, 0] * H[0] + Pminus.M[0, 1] * H[1] + Pminus.M[0, 2] * H[2]) / S;
            double K1 = (Pminus.M[1, 0] * H[0] + Pminus.M[1, 1] * H[1] + Pminus.M[1, 2] * H[2]) / S;
            double K2 = (Pminus.M[2, 0] * H[0] + Pminus.M[2, 1] * H[1] + Pminus.M[2, 2] * H[2]) / S;

            //====模块8:量测更新校正状态 ====
            Vec3 xPlus = new Vec3();
            xPlus.Soc = xMinus.Soc + K0 * residual;
            xPlus.Uc1 = xMinus.Uc1 + K1 * residual;
            xPlus.Uc2 = xMinus.Uc2 + K2 * residual;

            //更新协方差 P+ = (I‑K*H)*Pminus
            Matrix3 IMat = Matrix3.Identity();
            Matrix3 KH = new Matrix3();
            KH.M[0, 0] = K0 * H[0]; KH.M[0, 1] = K0 * H[1]; KH.M[0, 2] = K0 * H[2];
            KH.M[1, 0] = K1 * H[0]; KH.M[1, 1] = K1 * H[1]; KH.M[1, 2] = K1 * H[2];
            KH.M[2, 0] = K2 * H[0]; KH.M[2, 1] = K2 * H[1]; KH.M[2, 2] = K2 * H[2];
            Matrix3 I_KH = new Matrix3();
            for (int i = 0; i < 3; i++)
                for (int j = 0; j < 3; j++)
                    I_KH.M[i, j] = IMat.M[i, j] - KH.M[i, j];
            Matrix3 Pplus = Matrix3.Multiply(I_KH, Pminus);

            //====模块9:物理限幅约束 ====
            xPlus.Soc = Math.Clamp(xPlus.Soc, 0.0, 1.0);
            xPlus.Uc1 = Math.Max(xPlus.Uc1, 0);
            xPlus.Uc2 = Math.Max(xPlus.Uc2, 0);

            //====模块10:保存状态给下一轮 ====
            X = xPlus;
            P = Pplus;

            return X.Soc;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("====二阶RC+EKF SOC估算仿真 C#====");
            //电池参数
            double r0 = 0.008;
            double r1 = 0.015; double c1 = 2500;
            double r2 = 0.025; double c2 = 12000;
            double qn = 50.0;    //50Ah电池
            double dt = 1.0;     //采样周期1s

            var ekf = new SocEKFEstimator(r0, r1, c1, r2, c2, qn, dt);
            ekf.X = new Vec3(0.2, 0, 0); //故意设置初始SOC错误0.2,真值0.8,看收敛

            //模拟电池真值模型
            double trueSoc = 0.80;
            double trueUc1 = 0;
            double trueUc2 = 0;
            Random rnd = new Random(1234);

            Console.WriteLine("时间(s)\tI(A)\t真值SOC\t估算SOC\t误差");
            for (int t = 0; t < 300; t++)
            {
                double I = 20; //持续放电20A
                //真值二阶RC迭代
                double tau1 = r1 * c1;
                double tau2 = r2 * c2;
                trueUc1 = trueUc1 * Math.Exp(-dt / tau1) + I * r1 * (1 - Math.Exp(-dt / tau1));
                trueUc2 = trueUc2 * Math.Exp(-dt / tau2) + I * r2 * (1 - Math.Exp(-dt / tau2));
                trueSoc -= dt * I / (3600.0 * qn);
                trueSoc = Math.Clamp(trueSoc, 0, 1);
                double trueUocv = ekf.GetOcv(trueSoc);
                double trueUt = trueUocv - I * r0 - trueUc1 - trueUc2;
                double measUt = trueUt + (rnd.NextDouble() - 0.5) * 0.02; //叠加电压噪声

                double estSoc = ekf.Step(I, measUt);
                double err = estSoc - trueSoc;
                if (t % 20 == 0)
                {
                    Console.WriteLine($"{t}\t{I:F1}\t{trueSoc:F4}\t{estSoc:F4}\t{err:F4}");
                }
            }
            Console.WriteLine("仿真结束,观察初始错误SOC逐步收敛到真值");
            Console.ReadKey();
        }
    }
}

代码说明

  • 手写 3 阶矩阵,不引入任何 MathNet/NMath 第三方库,适合嵌入式参考移植;
  • 10 个执行模块完整对应上文流程;
  • 仿真案例:故意设置 EKF 初始 SOC=0.2,电池真值 SOC=0.8,观察滤波收敛;叠加电压传感器噪声;
  • 可直接复制到 .NET Console 项目运行。
  • 嵌入式移植提示:浮点数可以改成 float,矩阵可以改为静态数组,节省内存。

优缺点详细分析


优点(EKF-SOC 方法)

安时积分校正能力
  • 采用卡尔曼滤波算法实时校正安时积分法的累积误差
  • 无需频繁静置电池进行OCV校准(传统方法通常需静置2小时以上)
  • 在动态工况(如电动汽车加速/减速循环)下仍能保持高精度
  • 示例:UDDS工况测试中,SOC误差可控制在±3%以内
模型特性
  • 基于二阶RC等效电路模型(含R0、R1、C1、R2、C2等参数)
  • 模型物理意义明确,参数对应实际电池特性(如R0代表欧姆内阻)
  • 工程应用成熟,已在多数车规级BMS中验证
计算效率
  • 相比UKF(无迹卡尔曼滤波)减少约60%计算量
  • 典型执行时间:STM32F4系列MCU上约0.8ms/次
  • 内存占用通常<5KB,适合资源受限的嵌入式系统
容错能力
  • 初始SOC误差在±20%范围内时,可在3-5个充放电循环内收敛
  • 通过过程噪声矩阵Q实现自适应调整

缺点(EKF-SOC 方法)

参数敏感性
  • 核心模型参数(R0/R1/C1/R2/C2)受温度影响显著
    • 示例:-20℃时R0可能比25℃增大300%
  • SOH衰减导致参数漂移(如循环500次后C1可能变化40%)
  • 参数失配5%可能导致SOC误差增加2-3%
观测性限制
  • 在LiFePO4电池的SOC 30-70%平台区,dUocv/dSOC可低至<1mV/%
    • 卡尔曼增益急剧减小,校正作用减弱
    • 解决方案:结合温度/电流变化率等多维信息辅助观测
线性化误差
  • 一阶泰勒展开在SOC变化率>5%/min时产生明显截断误差
    • 急加速工况下可能产生1-2%额外误差
    • 对比:UKF在强非线性场景表现更优
调参复杂度
  • 过程噪声Q和观测噪声R需根据电池类型调整
    • 典型初始值
    • 不当设置可能导致:
      • Q过大→滤波振荡
      • R过大→安时积分主导
      • Q/R比错误→发散风险
低温挑战
  • -10℃以下时:
    • 极化电阻R1可能增大5-8倍
    • 时间常数变化超过10倍
    • 解决方案:建立多温度点参数表补偿

方法对比

vs 安时积分法
  • 优点
    • 仅需电流采样和累加计算
    • 计算复杂度O(1),适合低端MCU
  • 缺点
    • 误差累积速率约1-3%/小时(依赖电流传感器精度)
    • 无自校正能力,需定期OCV复位
vs LSTM深度学习
  • 优点
    • 端到端建模,规避参数辨识问题
    • 在训练数据覆盖范围内精度可达±1%
  • 缺点
    • 需>10,000组充放电数据训练
    • 典型网络规模50-100层,需1+GFLOPS算力
    • 跨电池型号使用时精度下降可达5-8%
    • 目前主要应用于云端分析,罕见于车规BMS

适用场景详细说明


适用场景

车载动力电池 BMS 应用
  • 乘用车电池管理系统:适用于纯电动、混合动力乘用车的锂离子电池组状态估计
  • 商用车电池管理系统:满足电动巴士、重型卡车等大型商用车辆的大容量电池组需求
  • 储能电池系统:适用于电网储能、工商业储能等固定式储能系统的电池状态监控
嵌入式 MCU 资源受限环境
  • 可在 Cortex-M0/M3/M4 等低功耗微控制器上运行
  • 算法内存占用控制在 10KB 以内
  • 计算量优化,单次迭代周期<1ms @100MHz
电池参数标定能力
  • 支持实验室标定流程获取基础参数:
    • OCV-SOC 曲线(25℃标准温度下)
    • 电池二阶 RC 等效电路模型参数(R0、R1、R2、C1、C2)
    • 不同温度下的参数变化率
  • 标定数据格式支持.csv/.mat等通用格式导入
传感器配置要求
  • 电压采样:精度±5mV,采样率≥10Hz
  • 电流采样:精度±0.5%FS,采样率≥10Hz
  • 温度采样:至少3点采样(正极、负极、环境)

不适用场景及辅助补偿方案

无标定数据情况
  • 解决方案:需先进行标准充放电测试获取基础参数
  • 替代方案:使用同型号电池的默认参数(精度下降30%)
极端低温环境(-20℃以下)
  • 问题:电解液电导率下降导致模型参数漂移
  • 补偿方案:
    • 建立-30℃~60℃全温度参数表
    • 增加温度补偿系数(每℃参数变化率)
    • 低温下启用保守估计算法
电池长期静置情况
  • 问题:极化电压不完全消退导致SOC偏差
  • 解决方案:
    • 静置2小时后触发OCV校准流程
    • 采用OCV-SOC查表法校正
    • 静置期间自动切换至低功耗模式
高倍率充放电工况(>3C)
  • 问题:强非线性导致EKF线性化误差增大
  • 升级方案:
    • 改用UKF无迹卡尔曼滤波器
    • 增加电流变化率补偿项
    • 采用动态参数调整策略

工程量产BMS推荐策略

主从式SOC估算架构
  • 主算法:EKF-SOC(实时性、精度平衡)
  • 备份算法:安时积分(简单可靠)
  • 故障检测:双算法结果差异>5%触发报警
校准策略
  • OCV校准:静置条件满足后自动执行
  • 周期校准:每30天或容量衰减2%触发
参数在线更新
  • 温度补偿:实时根据温度查表调整参数
  • SOH辨识:基于容量衰减更新RC参数
  • 学习机制:记录历史数据优化参数
工程实施要点
  • 标定数据烧录至BMS非易失存储器
  • 建立参数版本管理系统
  • 预留10%计算余量应对算法升级

总结


SOC(State of Charge)无法通过传感器直接测量,属于典型的非线性系统状态估计问题。安时积分法虽然简单,但存在明显的累积误差;而基于二阶 RC 电池模型的扩展卡尔曼滤波(EKF)通过融合电流积分与电压观测反馈,能有效抑制积分漂移,已成为工业级 BMS 中主流的 SOC 估算方案。

EKF-SOC 的估算精度高度依赖于电池模型参数标定。在工程应用中,合理调整噪声矩阵 Q/R 参数并设置物理限幅约束是防止滤波发散的关键。本文提供的 C# 完整实现包含了所有矩阵运算过程,无需依赖第三方库,既适合 PC 端仿真学习,其核心算法也可移植至嵌入式 C 语言 BMS 开发。需要注意的是,实际产品开发不能直接套用仿真参数,必须针对具体电池进行参数标定,并引入温度补偿和参数在线辨识等机制以进一步提升系统鲁棒性

相关推荐
天空'之城9 天前
C 语言工业级通用组件手写 24:互补滤波
c语言·开发语言·互补滤波·嵌入式算法·工业级组件
天空'之城11 天前
C 语言工业级通用组件手写 23:卡尔曼滤波(简易版)
c语言·卡尔曼滤波·嵌入式算法·工业级组件
天空'之城11 天前
C 语言工业级通用组件手写 21:限幅消抖滤波
c语言·嵌入式算法·工业级组件·限幅消抖滤波
嘉仕新能15 天前
储能系统 SOC/SOH 估算测试:BMS 算法验证怎么在产线落地
soc估算·嘉仕新能·储能测试·bms测试·soh估算·充放电系统·电池内阻仪
想你依然心痛1 个月前
储能系统的BMS与EMS协同:电池簇管理与能量调度——均衡、热管理、SOC
热管理·主动均衡·电池管理系统(bms)·能量管理系统(ems)·电池簇管理·soc估算·能量调度
北域码匠1 个月前
嵌入式限幅滤波:工业信号降噪利器
c#·传感器采集·数据预处理·嵌入式算法·限幅滤波·数字滤波·数据降噪
北域码匠2 个月前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
可编程芯片开发4 个月前
基于EKF扩展卡尔曼滤波的四旋翼飞行器状态估计simulink建模与仿真
ekf·simulink·状态估计·扩展卡尔曼滤波·四旋翼飞行器
新能源BMS佬大7 个月前
【仿真到实战】STM32落地EKF算法实现锂电池SOC高精度估算(含硬件驱动与源码)
stm32·嵌入式硬件·算法·电池soc估计·bms电池管理系统·扩展卡尔曼滤波估计soc·野火开发板