第四节:开路电压法修正——OCV-SOC曲线标定、查表法实现、静置时间判断
各位同学,咱们接着聊。
上一节讲了安时积分怎么被误差慢慢"吃掉"精度。那怎么给它"喂解药"呢?最经典的一招,就是开路电压法修正。
说白了,就是利用电池静置后的开路电压(OCV),反推出当前的真实SOC。然后拿这个真实值,去修正安时积分累积的偏差。
这个方法,我在做第一块BMS样机时就用了。简单、可靠,直到今天,几乎所有量产BMS都离不开它。
4.1 OCV-SOC曲线标定——你得先有把"尺子"
开路电压法修正的前提,是你手里有一张准确的OCV-SOC关系表。这张表,就是你的"尺子"。
没有这把尺子,你测出开路电压也没用,因为不知道它对应多少SOC。
4.1.1 标定流程
我个人习惯,标定OCV曲线时,会按以下步骤来:
- 准备电池:充满电(SOC=100%),静置2小时以上。
- 放电:以0.05C小电流放电,放出5%的容量。然后静置1小时。
- 记录:静置结束前10秒,记录端电压。这个电压就是该SOC点下的OCV。
- 重复:继续放电5%,静置,记录。直到SOC=0%。
- 充电方向:同样步骤,从0%充到100%,再记录一组数据。
- 取平均:充放电两组数据取平均,作为最终标定值。
4.1.2 典型数据长什么样?
以三元锂电池为例,标定出来的数据大概是这样:
| SOC (%) | OCV (V) - 25℃ | OCV (V) - 0℃ | OCV (V) - 45℃ |
|---|---|---|---|
| 100 | 4.200 | 4.180 | 4.210 |
| 90 | 4.080 | 4.050 | 4.095 |
| 80 | 3.980 | 3.940 | 3.995 |
| 70 | 3.900 | 3.860 | 3.915 |
| 60 | 3.820 | 3.780 | 3.835 |
| 50 | 3.750 | 3.710 | 3.765 |
| 40 | 3.680 | 3.640 | 3.695 |
| 30 | 3.600 | 3.560 | 3.615 |
| 20 | 3.480 | 3.440 | 3.495 |
| 10 | 3.300 | 3.260 | 3.315 |
| 0 | 3.000 | 2.960 | 3.015 |
你看,SOC在中间段(30%-70%)时,OCV变化比较平缓。两头(0%-10%和90%-100%)变化很陡。这就是为什么开路电压法在中间段精度一般,但在两端很准。
4.2 查表法实现——别用公式,用查表
拿到标定数据后,怎么用?
有些同学喜欢拟合一个多项式公式。我劝你别这么做。为什么?
因为OCV-SOC曲线不是简单的多项式能描述的。你硬要拟合,要么阶数太高导致计算量大,要么精度不够。我在项目里吃过这个亏,后来老老实实改用查表法了。
4.2.1 线性插值查表
查表法的核心就两步:
- 找到当前OCV在表中的位置(两个相邻点之间)。
- 用线性插值算出对应的SOC。
代码实现很简单,我直接贴出来:
/**
* @brief 通过OCV查表获取SOC(线性插值)
* @param ocv_mv 当前开路电压,单位mV
* @param table OCV-SOC标定表,按OCV升序排列
* @param size 表的大小
* @return SOC值,单位0.1%
*/
int16_t ocv_to_soc(int32_t ocv_mv, const OCV_Table_Type *table, uint16_t size)
{
uint16_t i;
int32_t soc;
// 边界检查:低于最低OCV
if (ocv_mv <= table[0].ocv_mv) {
return table[0].soc_x10;
}
// 边界检查:高于最高OCV
if (ocv_mv >= table[size-1].ocv_mv) {
return table[size-1].soc_x10;
}
// 查找区间
for (i = 0; i < size-1; i++) {
if (ocv_mv >= table[i].ocv_mv && ocv_mv < table[i+1].ocv_mv) {
// 线性插值
// SOC = SOC_low + (OCV - OCV_low) * (SOC_high - SOC_low) / (OCV_high - OCV_low)
int32_t delta_ocv = table[i+1].ocv_mv - table[i].ocv_mv;
int32_t delta_soc = table[i+1].soc_x10 - table[i].soc_x10;
// 防止除零
if (delta_ocv == 0) {
return table[i].soc_x10;
}
soc = table[i].soc_x10 + (ocv_mv - table[i].ocv_mv) * delta_soc / delta_ocv;
return (int16_t)soc;
}
}
// 理论上不会走到这里
return table[size-1].soc_x10;
}
4.2.2 二分查找优化
上面的代码是顺序查找。如果表很大(比如100个点),每次查表都要遍历,太慢了。
我建议用二分查找。代码稍微复杂一点,但速度快得多:
int16_t ocv_to_soc_binary(int32_t ocv_mv, const OCV_Table_Type *table, uint16_t size)
{
uint16_t low = 0;
uint16_t high = size - 1;
uint16_t mid;
// 边界检查
if (ocv_mv <= table[low].ocv_mv) return table[low].soc_x10;
if (ocv_mv >= table[high].ocv_mv) return table[high].soc_x10;
while (high - low > 1) {
mid = (low + high) / 2;
if (ocv_mv < table[mid].ocv_mv) {
high = mid;
} else {
low = mid;
}
}
// 此时low和high是相邻的两个点
int32_t delta_ocv = table[high].ocv_mv - table[low].ocv_mv;
int32_t delta_soc = table[high].soc_x10 - table[low].soc_x10;
if (delta_ocv == 0) return table[low].soc_x10;
return table[low].soc_x10 + (ocv_mv - table[low].ocv_mv) * delta_soc / delta_ocv;
}
你看,二分查找最多查log2(N)次。100个点的表,最多查7次。比顺序查找快了一个数量级。
4.3 静置时间判断——别急着修正
有了OCV-SOC表,是不是随时都能用?
不是的。你得等电池"静下来"。
电池刚停止充放电时,端电压不是真正的开路电压。因为内部还有极化效应,电压会慢慢回升(或下降)。这个过程叫"静置"或"弛豫"。
4.3.1 静置多久才够?
这个时间取决于电池化学体系和温度。我总结了一个经验值:
| 电池类型 | 25℃静置时间 | 0℃静置时间 | -10℃静置时间 |
|---|---|---|---|
| 三元锂 | 30分钟 | 60分钟 | 120分钟 |
| 磷酸铁锂 | 60分钟 | 120分钟 | 180分钟 |
| 钛酸锂 | 20分钟 | 40分钟 | 80分钟 |
4.3.2 如何判断静置完成?
不能光靠定时器。因为电池状态不同,需要的静置时间也不同。
我推荐用"电压变化率"来判断:
/**
* @brief 判断电池是否已静置完成
* @param voltage_now 当前电压
* @param voltage_1min 1分钟前的电压
* @param voltage_5min 5分钟前的电压
* @return 1: 静置完成, 0: 未完成
*/
uint8_t is_rest_complete(int32_t voltage_now, int32_t voltage_1min, int32_t voltage_5min)
{
// 条件1:1分钟内电压变化小于2mV
if (abs(voltage_now - voltage_1min) > 2) {
return 0;
}
// 条件2:5分钟内电压变化小于5mV
if (abs(voltage_now - voltage_5min) > 5) {
return 0;
}
// 条件3:电流为0(或小于静置阈值)
// 这个由外部判断,这里假设已经满足
return 1;
}
这个判断逻辑,我一般在BMS的主循环里每1秒调用一次。当返回1时,就认为电池静置完成了,可以触发OCV修正。
4.3.3 修正时机选择
静置完成后,是不是立刻修正?
我建议再等一等。等什么?等安时积分和OCV查表的结果差异稳定下来。
具体做法:
- 静置完成后,连续读取3次OCV(间隔10秒)。
- 3次查表得到的SOC取平均。
- 如果这个平均值与安时积分结果的偏差超过5%,才进行修正。
- 修正时,直接把安时积分的SOC替换为OCV查表结果。
4.4 实战中的坑
最后,分享几个我踩过的坑:
- 坑一:磷酸铁锂的OCV平台。磷酸铁锂的OCV曲线在30%-80%区间几乎是一条平线。电压变化只有几毫伏。这时候查表法基本失效。我的解决方案是:在平台区,降低OCV修正的权重,主要依赖安时积分。
- 坑二:温度补偿。前面说了,温度会影响OCV。我建议至少标定25℃和0℃两组数据,中间温度用线性插值。更精确的做法是标定-10℃、0℃、25℃、45℃四组。
- 坑三:老化影响。电池用久了,OCV曲线会变化。我一般建议每半年或每100次循环,重新标定一次。或者在BMS里预留一个"在线自学习"功能,根据实际使用数据微调OCV表。
好了,这一节的内容就到这里。下一节,我们会讲卡尔曼滤波——一种更高级的SOC估算方法。它能自动融合安时积分和OCV修正,不需要你手动判断静置时间。但前提是,你得先把这一节的基础打牢。
有什么问题,欢迎在课程群里交流。咱们下节见。