4. 开路电压(OCV)辨识:OCV-SOC关系曲线、OCV测试方法、数据拟合与查表

说到电池模型,OCV-SOC关系曲线是绕不开的核心。我个人习惯把OCV比作电池的“身份证”——每款电池的OCV曲线都不一样,辨识准了,模型就成功了一半。

开路电压,说白了就是电池静置足够久之后,正负极之间的电势差。它和SOC之间存在着单调的非线性关系。这个关系一旦确定,我们就可以通过测量端电压来反推SOC。嗯,这里要注意:OCV必须在电池完全静置、极化效应消失后才能测准。

4.1 OCV-SOC关系曲线长什么样?

不同材料的电池,OCV曲线差异很大。我在项目中遇到过一款磷酸铁锂电池,它的OCV曲线中间段几乎是一条平直线,SOC从30%到70%,电压变化不到50mV。你想想看,这种情况下用电压反推SOC,误差会非常大。

三元锂电池就好很多。它的OCV曲线比较陡,尤其是低SOC段和高SOC段,电压变化明显。一般来说,OCV-SOC曲线有以下几个特点:

  • 单调性:SOC越高,OCV越大。这是基本前提。
  • 非线性:中间段斜率小,两端斜率大。
  • 滞后性:充电和放电的OCV曲线不完全重合,存在迟滞效应。
  • 温度敏感性:低温下OCV会整体下移,高温下会上移。

重要提醒:OCV-SOC曲线是电池建模的基石。如果这条曲线不准,后面所有的参数辨识、状态估计都会跟着跑偏。我见过有人直接用厂家给的曲线,结果仿真误差超过5%。

4.2 OCV测试方法:两种主流方案

OCV怎么测?业界主要有两种方法。我两种都用过,各有优劣。

4.2.1 间歇放电法(也叫静置法)

这个方法比较传统,但很可靠。具体做法是:

  1. 把电池充满电,静置2小时以上。
  2. 以0.5C或1C的电流放电,放出一定电量(比如5%或10%SOC)。
  3. 立即停止放电,让电池静置。静置时间通常需要1-2小时。
  4. 记录静置结束时的端电压,这就是该SOC点对应的OCV。
  5. 重复步骤2-4,直到电池放空。

我曾经用这个方法测一款18650电池,整个流程跑了将近30个小时。为什么这么久?因为每个SOC点都要静置到电压稳定。你想想看,如果静置时间不够,极化电压没消除,测出来的OCV就是假的。

我的经验:静置时间怎么判断?看电压变化率。当电压在10分钟内变化小于1mV时,基本可以认为极化消除了。我一般会设置一个自动判断程序,省得一直盯着看。

4.2.2 低电流法(也叫小电流充放电法)

这个方法快很多,但精度稍差。做法是:

  1. 用非常小的电流(比如0.02C或0.05C)对电池进行恒流充放电。
  2. 记录整个过程中的电压和容量变化。
  3. 认为小电流下的端电压近似等于OCV。

为什么小电流可以近似?因为电流小,极化效应也小。但这里有个坑:电流到底多小才算“小”?我建议不要超过0.05C。电流再大,极化电压就不能忽略了。

对比项 间歇放电法 低电流法
测试时间 长(24-48小时) 短(4-8小时)
精度 高(误差<0.1%) 中等(误差0.3-0.5%)
设备要求 普通充放电设备 需要高精度小电流源
适用场景 实验室精确建模 快速标定、产线测试

避坑指南:我曾经用低电流法测一款大容量电池,电流设成了0.02C,结果测出来的曲线在低SOC段严重偏离。后来发现是电流太小,电池的自放电效应干扰了测量。所以,低电流法也要考虑电池的自放电率。

4.3 数据拟合:把离散点变成连续曲线

测完数据,我们得到的是几十个离散的OCV-SOC点。但实际仿真中,我们需要任意SOC下的OCV值。这就得做数据拟合。

常用的拟合方法有几种:

  • 多项式拟合:简单粗暴,但高阶多项式容易过拟合。我一般用5-7阶多项式。
  • 分段线性插值:把OCV曲线分成若干段,每段用直线连接。简单,但导数不连续。
  • 样条插值:平滑性好,导数连续。我个人比较推荐。
  • 查表法:直接存储离散点,中间值用线性插值。工程中最常用。

下面是一个简单的多项式拟合代码示例:

import numpy as np
from numpy.polynomial import Polynomial

# 假设我们测得了10个SOC点对应的OCV
SOC = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
OCV = np.array([3.0, 3.2, 3.35, 3.45, 3.52, 3.58, 3.63, 3.68, 3.75, 3.85, 4.2])

# 7阶多项式拟合
p = Polynomial.fit(SOC, OCV, 7)

# 查看拟合系数
print(p.coef)

# 预测SOC=0.55时的OCV
soc_query = 0.55
ocv_pred = p(soc_query)
print(f"SOC={soc_query} 时 OCV = {ocv_pred:.3f}V")

我的习惯:拟合完一定要做残差分析。把拟合值和实测值画在一起,看看偏差。如果某个SOC段偏差特别大,说明这个区域的数据点不够,或者拟合阶数不合适。我一般会要求拟合误差小于5mV。

4.4 查表法:工程中的最优解

说实话,在真正的BMS代码里,很少有人用多项式。为什么?因为多项式计算量大,而且边界处容易振荡。工程中最常用的还是查表法。

查表法的思路很简单:

  1. 把SOC从0到1分成N个等分点(比如100个点)。
  2. 每个点存一个OCV值。
  3. 运行时根据当前SOC,找到相邻的两个点,做线性插值。

查表法的好处是:计算快、不占内存、容易维护。如果换了电池,只需要更新表格数据,代码不用改。

下面是一个查表法的实现示例:

class OCVLookupTable:
    def __init__(self, soc_points, ocv_points):
        self.soc = soc_points
        self.ocv = ocv_points
    
    def get_ocv(self, soc):
        # 边界处理
        if soc <= self.soc[0]:
            return self.ocv[0]
        if soc >= self.soc[-1]:
            return self.ocv[-1]
        
        # 找到插值区间
        idx = 0
        while self.soc[idx] < soc:
            idx += 1
        
        # 线性插值
        ratio = (soc - self.soc[idx-1]) / (self.soc[idx] - self.soc[idx-1])
        return self.ocv[idx-1] + ratio * (self.ocv[idx] - self.ocv[idx-1])

# 使用示例
soc_table = np.linspace(0, 1, 101)
ocv_table = p(soc_table)  # 用之前拟合的多项式生成表格数据
lookup = OCVLookupTable(soc_table, ocv_table)

# 查询
print(lookup.get_ocv(0.55))

核心要点:OCV辨识不是一劳永逸的事。电池老化后,OCV曲线会发生变化。我建议每隔一段时间重新标定一次,或者在BMS中加入在线OCV修正功能。说白了,电池是会“变”的,我们的模型也得跟着变。

好了,关于OCV辨识就讲这么多。下一章我们会聊到电池的阻抗参数辨识,那又是另一番天地了。