4. 开路电压法(OCV):OCV-SOC曲线标定、查表法实现、温度补偿及静置时间要求

开路电压法,说白了就是利用电池静置时的端电压来反推SOC。这个方法在BMS里属于基础中的基础,但也是最容易被忽视细节的地方。我刚开始做BMS那会儿,觉得这玩意儿太简单了——测个电压查个表不就完了?结果第一次做标定实验,数据乱得一塌糊涂,后来才发现坑全在细节里。

4.1 OCV-SOC曲线标定:这活儿看着简单,做起来全是坑

OCV-SOC曲线,就是电池开路电压与荷电状态之间的关系曲线。这条曲线是查表法的灵魂。你想想看,如果曲线不准,后面查表出来的SOC能准吗?

标定流程,我一般这么干:

  1. 满充校准:先把电池充到4.2V(或者厂家规定的满充电压),然后静置2小时以上。这一步是为了确定100% SOC点。
  2. 阶梯放电:以0.05C的小电流放电,每放5% SOC就停下来。为什么要用小电流?为了尽量消除极化影响。
  3. 静置记录:每次放电后静置1小时,记录开路电压。嗯,这里要注意,静置时间不够的话,测出来的电压是虚的。
  4. 重复直到放空:一直放到截止电压,记录下所有数据点。
  5. 曲线拟合:把数据点连起来,或者用多项式拟合。我个人习惯用分段线性插值,简单可靠。

关键点:标定时的温度一定要控制好。我建议在25°C恒温箱里做,否则数据没法用。

这里有个典型的OCV-SOC曲线数据,以三元锂电池为例:

SOC (%) OCV (V) SOC (%) OCV (V)
100 4.20 50 3.70
95 4.15 40 3.65
90 4.10 30 3.60
80 4.00 20 3.55
70 3.90 10 3.45
60 3.80 0 3.20

你看,中间段(20%-80%)曲线比较平缓,两端变化剧烈。这意味着什么?意味着中间段查表时,电压稍微波动一点,SOC误差就会很大。我在项目中遇到过这种情况,后来加了滤波才稳住。

4.2 查表法实现:代码写起来不难,但效率要讲究

查表法,说白了就是把标定好的数据存成数组,然后根据实测电压去查对应的SOC。实现方式有两种:

  • 线性插值:找到电压落在哪两个标定点之间,然后线性算出SOC。精度一般,但计算量小。
  • 分段多项式插值:精度高,但计算量大,MCU扛不住的话就别用了。

我个人习惯用线性插值,配合足够密的标定点(比如每2% SOC一个点),精度完全够用。下面是我常用的代码模板:

// OCV-SOC 查表函数
// 输入:实测开路电压 (mV)
// 输出:SOC (0-100%)
// 表格数据:ocv_table[] 存储电压值,soc_table[] 存储对应SOC

uint8_t get_soc_from_ocv(uint16_t voltage_mv)
{
    uint8_t i;
    uint8_t soc;
    uint16_t v_low, v_high;
    uint8_t soc_low, soc_high;

    // 边界检查:电压超出范围直接返回极限值
    if (voltage_mv <= ocv_table[0]) {
        return 0;
    }
    if (voltage_mv >= ocv_table[TABLE_SIZE-1]) {
        return 100;
    }

    // 二分查找,找到电压所在的区间
    uint8_t left = 0;
    uint8_t right = TABLE_SIZE - 1;
    uint8_t mid;

    while (right - left > 1) {
        mid = (left + right) >> 1;
        if (voltage_mv < ocv_table[mid]) {
            right = mid;
        } else {
            left = mid;
        }
    }

    // 线性插值计算
    v_low = ocv_table[left];
    v_high = ocv_table[right];
    soc_low = soc_table[left];
    soc_high = soc_table[right];

    // soc = soc_low + (voltage - v_low) * (soc_high - soc_low) / (v_high - v_low)
    uint32_t temp = (uint32_t)(voltage_mv - v_low) * (soc_high - soc_low);
    soc = soc_low + (uint8_t)(temp / (v_high - v_low));

    return soc;
}

小技巧:二分查找比顺序查找快得多,尤其表格有几十个点的时候。我曾经见过有人用顺序查找,每次查表要循环几十次,MCU累得够呛。

4.3 温度补偿:不做这一步,你的OCV法就是废的

电池的OCV会随温度变化,这是物理规律。你想想看,冬天零下10°C和夏天40°C,同一块电池的端电压能一样吗?

温度补偿的做法,一般是在不同温度下标定多条OCV-SOC曲线。比如:

  • -20°C 一条曲线
  • 0°C 一条曲线
  • 25°C 一条曲线
  • 45°C 一条曲线

实际使用时,根据当前温度选择最近的曲线,或者用插值法在两条曲线之间过渡。我建议至少标定3个温度点(低温、常温、高温),否则补偿效果有限。

这里有个经验公式,可以粗略估算温度对OCV的影响:

// 温度补偿系数:每°C的电压变化量 (mV/°C)
// 不同SOC段系数不同,这里以三元锂为例
// SOC > 50% 时,系数约 -0.3 mV/°C
// SOC < 50% 时,系数约 -0.5 mV/°C

int16_t temp_compensation(uint16_t voltage_mv, int8_t temperature, uint8_t soc_est)
{
    int16_t compensated_voltage;
    int16_t delta_v;

    // 以25°C为基准
    int8_t delta_t = temperature - 25;

    if (soc_est > 50) {
        delta_v = (int16_t)delta_t * (-3) / 10;  // -0.3 mV/°C
    } else {
        delta_v = (int16_t)delta_t * (-5) / 10;  // -0.5 mV/°C
    }

    compensated_voltage = (int16_t)voltage_mv + delta_v;
    return compensated_voltage;
}

警告:温度补偿系数不是固定的!不同电芯、不同老化程度,系数都会变。我曾经吃过这个亏,用了一组通用系数,结果低温下SOC误差飙到15%。后来老老实实重新标定,才把误差压下来。

4.4 静置时间要求:别急着查表,让电池喘口气

开路电压法有个前提:电池必须处于电化学平衡状态。说白了,就是刚充完电或放完电,内部离子还在乱跑,这时候测出来的电压是虚的,不能用来查表。

静置时间多长才够?这取决于电池 chemistry 和温度:

  • 三元锂电池:25°C下建议静置30分钟以上,低温下要1-2小时。
  • 磷酸铁锂电池:这玩意儿极化恢复慢,我建议至少静置1小时,低温下2小时起步。
  • 钛酸锂电池:极化小,15-20分钟基本就够了。

怎么判断静置够了没?看电压变化率。我一般用这个判据:

// 判断静置是否完成
// 连续3次采样(间隔10秒),电压变化小于1mV,认为静置完成

bool is_rest_complete(uint16_t voltage_now)
{
    static uint16_t last_voltage = 0;
    static uint8_t stable_count = 0;

    if (abs(voltage_now - last_voltage) <= 1) {
        stable_count++;
    } else {
        stable_count = 0;
    }

    last_voltage = voltage_now;

    if (stable_count >= 3) {
        return true;
    }
    return false;
}

核心原则:宁等勿急。静置时间不够,查出来的SOC就是错的。这个错会一直累积下去,直到下一次静置校准才能纠正。

好了,关于开路电压法,核心就是这四块:曲线标定、查表实现、温度补偿、静置时间。每个环节都有坑,但踩过一次就记住了。我个人觉得,OCV法虽然简单,但做好了一样很可靠,关键是要把细节抠到位。

开路电压法(OCV)知识体系 OCV-SOC 核心方法 OCV-SOC曲线标定 满充→阶梯放电→静置→拟合 查表法实现 二分查找 + 线性插值 温度补偿 多温度标定 + 插值补偿 静置时间要求 电压变化率判据 四个环节缺一不可,细节决定精度

公众号:蓝海资料掘金营,微信deep3321