第三讲:开路电压法(OCV)——OCV-SOC曲线标定、查表法实现、温度补偿
各位同行,今天我们来聊聊开路电压法。说白了,就是利用电池静置时的端电压来反推SOC。这个方法在BMS里属于“压舱石”级别的存在——它不依赖历史积分,没有累积误差,只要电池静置时间够长,精度就非常可靠。
但这里有个坑:OCV-SOC曲线不是一条“万能曲线”。不同化学体系、不同老化程度、不同温度下,曲线都会漂移。我最早做项目时,直接拿厂家给的25℃曲线去算SOC,结果冬天现场数据一塌糊涂。嗯,从那以后我就明白了——OCV法看着简单,但要做好,得下功夫。
一、OCV-SOC曲线标定:从实验室到量产
标定曲线,说白了就是给电池拍一张“电压-电量”的X光片。怎么做?我习惯分三步走:
- 准备电池:选全新电芯,先做几个充放循环活化。我个人习惯至少循环3次,让电化学状态稳定下来。
- 分段静置:以5% SOC为步长,从100%开始放电到0%。每放5%电量,就静置2小时以上。为什么要这么久?因为极化电压需要时间消散。我在项目中遇到过,有些磷酸铁锂电池静置1小时电压还在缓慢下降,必须等它稳定。
- 记录数据:记录每个静置点结束时的端电压,然后做插值或拟合,得到完整的OCV-SOC曲线。
你想想看,如果只标定25℃一条曲线,到了零下10℃会怎样?电压整体下移,查表查出来的SOC可能偏大10%以上。所以,我建议至少标定三个温度点:-20℃、25℃、55℃。中间温度用线性插值就行。
核心要点:标定曲线时,静置时间必须足够。我曾经用1小时静置数据做曲线,结果查表误差达到3%。后来改成2小时,误差降到0.5%以内。这个时间成本,值得花。
二、查表法实现:代码怎么写才高效?
曲线标定好了,接下来就是查表。嵌入式系统里,查表法比实时计算快得多。我常用的方法是二分查找加线性插值。直接上代码:
/* OCV-SOC 查表结构体 */
typedef struct {
float ocv; /* 开路电压,单位V */
float soc; /* 对应的SOC,单位% */
} OCV_Table_t;
/* 示例:25℃下的OCV-SOC表,共21个点 */
const OCV_Table_t ocv_table_25C[21] = {
{3.000, 0.0}, {3.100, 5.0}, {3.200, 10.0}, /* ... 中间省略 ... */
{4.200, 100.0}
};
/* 二分查找 + 线性插值 */
float OCV_To_SOC(float voltage, const OCV_Table_t *table, uint8_t size)
{
uint8_t low = 0, high = size - 1, mid;
float soc;
/* 边界检查 */
if (voltage <= table[low].ocv) return table[low].soc;
if (voltage >= table[high].ocv) return table[high].soc;
/* 二分查找 */
while (high - low > 1) {
mid = (low + high) >> 1;
if (voltage >= table[mid].ocv)
low = mid;
else
high = mid;
}
/* 线性插值 */
soc = table[low].soc +
(voltage - table[low].ocv) *
(table[high].soc - table[low].soc) /
(table[high].ocv - table[low].ocv);
return soc;
}
这段代码我用了很多年,基本没出过问题。注意几点:
- 表格点要按电压升序排列,否则二分查找会乱套。
- 边界检查不能省。万一ADC采到4.3V(过压),直接返回100% SOC,别让它插值插到天上去。
- 二分查找的循环条件用
high - low > 1,这样退出时low和high就是相邻的两个点,直接插值。
我的小技巧:如果MCU资源紧张,可以把表格点压缩到16个以内。我试过,只要曲线形状不是太陡,16个点插值误差能控制在1%以内。省下来的Flash空间,可以放别的算法。
三、温度补偿:别让冬天“冻住”你的SOC
温度对OCV的影响,说白了就是电池内阻和电化学活性的变化。低温下,电压整体偏低;高温下,电压偏高。如果不补偿,查表结果会严重偏离真实值。
我常用的补偿方法是:建立多温度OCV表,运行时根据当前温度选择或插值。
举个例子,假设我们标定了-20℃、25℃、55℃三张表:
| 温度区间 | 使用策略 |
|---|---|
| T < -10℃ | 直接使用-20℃表 |
| -10℃ ≤ T ≤ 40℃ | 在-20℃表和25℃表之间线性插值 |
| 40℃ < T ≤ 70℃ | 在25℃表和55℃表之间线性插值 |
| T > 70℃ | 直接使用55℃表 |
代码实现也不复杂:
float OCV_To_SOC_With_Temp(float voltage, float temperature)
{
float soc_low, soc_high, soc;
float ratio;
if (temperature < -10.0f) {
/* 直接用低温表 */
return OCV_To_SOC(voltage, ocv_table_N20C, TABLE_SIZE);
}
else if (temperature <= 40.0f) {
/* 在-20℃和25℃之间插值 */
soc_low = OCV_To_SOC(voltage, ocv_table_N20C, TABLE_SIZE);
soc_high = OCV_To_SOC(voltage, ocv_table_25C, TABLE_SIZE);
ratio = (temperature + 20.0f) / 45.0f; /* 归一化到[0,1] */
soc = soc_low + ratio * (soc_high - soc_low);
}
else if (temperature <= 70.0f) {
/* 在25℃和55℃之间插值 */
soc_low = OCV_To_SOC(voltage, ocv_table_25C, TABLE_SIZE);
soc_high = OCV_To_SOC(voltage, ocv_table_55C, TABLE_SIZE);
ratio = (temperature - 25.0f) / 30.0f;
soc = soc_low + ratio * (soc_high - soc_low);
}
else {
/* 直接用高温表 */
return OCV_To_SOC(voltage, ocv_table_55C, TABLE_SIZE);
}
return soc;
}
注意:温度补偿不是万能的。如果电池温度低于-30℃或高于60℃,OCV法的可靠性会大幅下降。我建议在这种极端温度下,改用安时积分法为主,OCV法只做辅助校准。另外,温度传感器必须紧贴电芯表面,我曾经见过把传感器贴在模组外壳上的设计,测出来的温度偏差5℃以上,补偿全白做。
四、实战中的几个坑
最后,分享几个我踩过的坑,希望能帮你少走弯路:
- 静置时间不够:电池刚充完电,极化电压可能高达几十毫伏。如果不等它消散就测OCV,查出来的SOC可能偏大5%。我建议至少静置30分钟,磷酸铁锂要更久。
- 曲线老化漂移:电池用了两年后,OCV-SOC曲线会整体下移。我习惯每半年或每100次满充后,重新标定一次曲线。或者用算法在线修正,这个后面章节会讲。
- ADC精度不够:OCV法对电压测量精度要求很高。1mV的误差,可能带来0.5%的SOC偏差。我建议用16位以上的ADC,并且定期校准。
好了,这一讲就到这里。OCV法看着简单,但要做好,需要细心和耐心。下一讲我们会聊安时积分法,那个又是另一番天地了。
课后思考:如果你的电池在低温下OCV曲线变得非常平坦(比如磷酸铁锂在20%-80%区间),查表法会有什么问题?该怎么解决?欢迎在评论区讨论。