4. 开路电压法(OCV):OCV-SOC曲线标定、查表法实现、温度补偿及静置时间要求
开路电压法,说白了就是利用电池静置时的端电压来反推SOC。这个方法在BMS里属于基础中的基础,但也是最容易被忽视细节的地方。我刚开始做BMS那会儿,觉得这玩意儿太简单了——测个电压查个表不就完了?结果第一次做标定实验,数据乱得一塌糊涂,后来才发现坑全在细节里。
4.1 OCV-SOC曲线标定:这活儿看着简单,做起来全是坑
OCV-SOC曲线,就是电池开路电压与荷电状态之间的关系曲线。这条曲线是查表法的灵魂。你想想看,如果曲线不准,后面查表出来的SOC能准吗?
标定流程,我一般这么干:
- 满充校准:先把电池充到4.2V(或者厂家规定的满充电压),然后静置2小时以上。这一步是为了确定100% SOC点。
- 阶梯放电:以0.05C的小电流放电,每放5% SOC就停下来。为什么要用小电流?为了尽量消除极化影响。
- 静置记录:每次放电后静置1小时,记录开路电压。嗯,这里要注意,静置时间不够的话,测出来的电压是虚的。
- 重复直到放空:一直放到截止电压,记录下所有数据点。
- 曲线拟合:把数据点连起来,或者用多项式拟合。我个人习惯用分段线性插值,简单可靠。
关键点:标定时的温度一定要控制好。我建议在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法虽然简单,但做好了一样很可靠,关键是要把细节抠到位。
公众号:蓝海资料掘金营,微信deep3321