第二讲:安时积分法实现——电流采样与积分、初始SOC标定、时间离散化处理

好,咱们接着往下聊。上一讲我们把安时积分法的原理讲清楚了,说白了就是「电荷进出计数器」。但原理归原理,真要把它跑在单片机上,你会发现一堆坑等着你。今天这一讲,我就带你把这些坑一个一个填上。

2.1 电流采样:精度与噪声的博弈

先说说电流采样。这是安时积分法的「眼睛」,眼睛要是花了,后面算出来的SOC全是扯淡。

我在项目中遇到过最典型的情况:采样电阻选大了,发热严重;选小了,小电流工况下信噪比惨不忍睹。你想想看,电池在休眠状态下的自放电电流可能只有几毫安,如果采样分辨率不够,积分出来的SOC每天都在「自动增长」——这显然不合理。

2.1.1 采样电阻与ADC选型

我个人习惯的做法是这样的:

  • 采样电阻:选1mΩ~5mΩ的合金电阻,温漂要控制在50ppm/℃以内
  • ADC分辨率:至少16位,推荐24位Σ-Δ型ADC
  • 采样率:100Hz~1kHz,别太低也别太高

为什么采样率不能太高?嗯,这里要注意:ADC的噪声是随带宽增加的。你采样率提到10kHz,噪声功率直接翻10倍,积分出来的SOC抖得像心电图。我一般用100Hz,够用。

关键公式:最小可检测电流

I_min = V_ref / (2^N * R_sense)

举例:V_ref=2.5V, N=16, R_sense=2mΩ
I_min = 2.5 / (65536 * 0.002) ≈ 19mA

看到没?16位ADC配2mΩ电阻,只能分辨到19mA。如果你的电池休眠电流只有5mA,那这19mA以下的电流你根本测不到。怎么办?要么换24位ADC,要么加一个低功耗的电流检测芯片。

2.1.2 采样数据的预处理

ADC采回来的原始数据,千万别直接拿去积分。我吃过这个亏——有一次项目急着交付,我偷懒没做滤波,结果SOC在静置状态下跳了3%。

我的标准流程是:

  1. 中值滤波:取连续5个采样点的中值,剔除毛刺
  2. 滑动平均:对中值滤波后的数据做16点滑动平均
  3. 零点校准:系统上电时,测量零电流时的ADC值,作为偏移量扣除

避坑指南:我曾经在零点校准时犯过一个低级错误——只校准了一次。后来发现温度变化会导致ADC偏移漂移。现在我每10分钟做一次零点校准,前提是检测到电流确实为零(比如继电器断开时)。

2.2 初始SOC标定:别让起点就错了

安时积分法有个致命弱点:它是个相对测量。你只知道从起点开始充了多少、放了多少,但起点本身是多少?如果起点错了,后面全错。

初始SOC标定,说白了就是回答一个问题:「电池现在有多少电?」

2.2.1 开路电压法标定

最常用的方法是开路电压法。锂电池的OCV与SOC有对应关系,查表就能得到初始值。

但这里有个坑:电池需要静置足够长时间,让极化电压消失。我一般要求静置2小时以上,磷酸铁锂电池甚至要4小时。

电池类型 静置时间 OCV-SOC曲线特点
三元锂 ≥1小时 中间段较陡,精度高
磷酸铁锂 ≥4小时 中间段平坦,精度差
钛酸锂 ≥30分钟 整体线性度好

特别注意:磷酸铁锂电池的OCV-SOC曲线在20%~80%区间几乎是一条直线,电压变化不到50mV。这时候用开路电压法标定,误差可能达到10%以上。我的做法是:如果检测到电池处于这个区间,就结合上次下电时的SOC做加权平均。

2.2.2 满充/满放标定

最可靠的标定方式,其实是让电池跑到满充或满放状态。满充时SOC=100%,满放时SOC=0%(当然要留保护余量)。

我在项目中是这样设计的:

  • 充电到截止电压且电流小于0.05C时,强制标定SOC=100%
  • 放电到保护电压且负载断开时,强制标定SOC=0%

每次标定完,我都会把当前的积分值清零,重新开始。这就像你跑步时每公里重置一下计步器,误差不会累积。

2.3 时间离散化处理:从连续到离散的跨越

理论上的安时积分是连续的:

SOC(t) = SOC(0) - (1/Q_n) * ∫I(t)dt

但单片机是数字系统,只能做离散计算。说白了,就是把积分变成累加:

SOC[k] = SOC[k-1] - (I[k] * Δt) / Q_n

这里Δt就是采样周期。我一般用100ms,也就是0.1秒。

2.3.1 离散化带来的误差

你想想看,电流是连续变化的,但我们每100ms才采一个点。这中间的变化被忽略了,这就是离散化误差。

举个例子:一个脉冲电流,峰值100A,持续50ms。如果采样周期是100ms,你可能刚好错过这个脉冲,或者只采到一半。积分出来的结果跟实际差了50%。

我的解决方案

  • 对于高频脉冲工况(如电机驱动),把采样率提高到1kHz
  • 在硬件上加一个模拟积分器,用运放对电流做实时积分,MCU只读取积分结果
  • 或者用电流传感器的Vout引脚直接接ADC,同时用其频率输出引脚做硬件计数

2.3.2 代码实现示例

下面是我常用的安时积分代码框架,你可以直接拿去用:

// 安时积分主函数,每10ms调用一次
void SOC_Update(void)
{
    static float soc_integral = 0.0f;  // 积分累积量,单位:Ah
    static uint32_t last_tick = 0;
    
    float current_A = Get_Current();   // 获取当前电流,单位:A
    uint32_t now_tick = Get_Tick();    // 获取当前系统滴答,单位:ms
    
    float dt = (now_tick - last_tick) / 1000.0f;  // 时间差,单位:s
    last_tick = now_tick;
    
    // 安时积分:ΔAh = I * Δt / 3600
    float delta_ah = current_A * dt / 3600.0f;
    soc_integral += delta_ah;
    
    // 计算SOC百分比
    // 注意:Q_n是电池额定容量,单位:Ah
    float soc_percent = (soc_integral / Q_n) * 100.0f;
    
    // 加上初始SOC
    float soc_final = soc_initial - soc_percent;
    
    // 限幅处理
    if(soc_final > 100.0f) soc_final = 100.0f;
    if(soc_final < 0.0f)   soc_final = 0.0f;
    
    // 更新全局变量
    g_soc = soc_final;
}

经验之谈:注意代码中的dt计算。我见过有人直接用固定值Δt,但实际系统中定时器可能有抖动。用系统滴答实时计算dt,虽然多花几个CPU周期,但精度提升明显。

2.3.3 累积误差的初步抑制

离散化带来的误差会随着时间累积。我常用的几个小技巧:

  • 定期清零:每次检测到满充或满放,强制重置积分值
  • 死区处理:电流绝对值小于10mA时,认为电流为零,不积分
  • 温度补偿:容量Q_n随温度变化,查表修正

这些方法虽然不能完全消除误差,但能把误差控制在5%以内。下一讲我们会专门讲误差补偿,到时候再深入展开。

小结

这一讲我们聊了三个核心问题:

  1. 电流采样:选好ADC和采样电阻,做好滤波和零点校准
  2. 初始SOC标定:开路电压法+满充满放标定,双保险
  3. 时间离散化:把连续积分变成离散累加,注意采样率和dt的实时计算

说实话,安时积分法本身不复杂,难的是把每个细节都做到位。我刚开始做BMS时,觉得这玩意儿太简单了,结果被现场问题打得鼻青脸肿。后来才明白,嵌入式开发就是这样——细节决定成败。

下一讲我们聊误差补偿,我会把压箱底的卡尔曼滤波和自适应算法拿出来。到时候见。