电量计 -- SOC电量算法架构:库仑积分、开路电压与电量追赶机制

SOC电量算法架构:库仑积分、开路电压与电量追赶机制

电量计的核心任务是回答一个看似简单的问题:电池还剩多少电?但由于电池的非线性特性、温度影响、老化效应,这个问题远比想象中复杂。本文基于实际产品固件(Newton平台 lib_fg.c),详细拆解SOC算法的完整架构。

整体架构概览

SOC算法不是单一方法,而是多种方法的协同:

1
2
3
4
5
6
7
flowchart TD
A["开路电压法 OCV"] -->|"初始化/静置"| D["RSOC"]
B["库仑积分法 CC"] -->|"充放电过程"| D
C["电量追赶机制"] -->|"尾部修正"| D
E["DFCC动态容量"] -->|"修正FCC"| B
F["温度阻抗补偿"] -->|"修正电压"| A
F -->|"修正容量"| E
方法 使用场景 解决的问题
OCV查表 初始化、长时间静置 提供绝对SOC基准,消除累积误差
库仑积分 充放电过程中 连续追踪SOC变化,响应快
电量追赶 充电CV段、放电尾部 保证报满/报空的用户体验
DFCC 放电过程 补偿温度和电流对可用容量的影响
温度补偿 全程 消除温度对电压和容量的影响

库仑积分:连续追踪SOC变化

原理

库仑积分(Coulomb Counting)的思想极其朴素:累加流入/流出电池的电荷量,得到容量变化

$$SOC(t) = SOC(t_0) + \frac{1}{FCC} \int_{t_0}^{t} I(\tau) , d\tau$$

离散化后:

$$\Delta Q = \sum I_k \times \Delta t$$

$$RSOC = \frac{RCA}{FCC} \times 100%$$

其中 RCA(Remaining Capacity Available)是当前剩余容量,FCC(Full Charge Capacity)是满充容量。

固件实现

核心累加逻辑在 fg_update 函数中,每秒执行一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 输入验证:单次变化不超过0.1C,防止异常数据污染
if ((param->delta_mah * 10) > dsncap || (param->delta_mah * (-10)) > dsncap) {
return ERR_FG_PARAM; // 拒绝异常值
}

// 微安时精度累加
deltauah += (param->delta_mah * 1000); // mAh → μAh

// 整毫安时部分更新到cc_prv
cc_prv += deltauah / 1000;

// 保留不足1mAh的余数,避免截断误差
deltauah = fg_imod(deltauah, 1000); // 余数继续累加

如何避免误差累积

库仑积分最大的问题是误差会无限累积——电流传感器的偏置误差、ADC量化噪声、温度漂移都会随时间积分。算法通过以下机制控制累积误差:

1. 输入校验与异常拒绝

1
2
3
4
// 单次delta超过设计容量的10%视为异常,直接丢弃
if ((param->delta_mah * 10) > dsncap) {
return ERR_FG_PARAM;
}

2. 微安时精度的余数累加

库仑计硬件每秒输出的是整数mAh,但实际电荷可能是小数。算法用μAh(微安时)精度累加,只在凑够整毫安时后才更新容量:

1
2
3
deltauah += (param->delta_mah * 1000);  // 保持μAh精度
cc_prv += deltauah / 1000; // 整数部分进入容量
deltauah = fg_imod(deltauah, 1000); // 余数保留到下次

这避免了每秒截断1次带来的系统性误差。

3. FACC累积容量上限

FACC(Full Accumulated Capacity)记录从充满到当前的累计放电量,设有上限:

1
2
3
4
// FACC不超过设计容量的110%
if (facc > dsncap * FG_FACC_MAX / 100) {
facc = dsncap * FG_FACC_MAX / 100;
}

防止漏电流或传感器偏置导致FACC无限增长。

4. 满充/满放校准点

最有效的误差消除手段是在已知SOC点重置累积误差

  • 满充时:RSOC = 10000(100%),RCA = FCC
  • 满放时:RSOC = 0,标记校准完成

每次经过校准点,之前累积的所有误差一次性清零。

开路电压法:提供绝对SOC基准

原理

OCV(Open Circuit Voltage)与SOC存在确定的单调映射关系。当电池静置足够长时间(极化效应消失),测量端电压即可直接查表得到SOC。

$$SOC = f_{OCV \to SOC}(V_{terminal})$$

OCV法的优势是不累积误差——每次查表都是独立的绝对估计。劣势是需要电池处于静置状态,充放电过程中端电压受极化和IR压降影响,不能直接使用。

使用场景

场景一:系统初始化

上电时没有历史SOC信息,用OCV给出初始值:

1
2
3
4
// fg_init_soc: 系统启动时的SOC初始化
error = lut_soc_by_ocv(handle, tmp_volt, &tmp_soc_now);
rsoc_now = tmp_soc_now;
rca = fg_idiv(fcc * rsoc_now, 10000);

场景二:长时间静置后修正

电池静置时(|I| < 20mA),OCV法重新校准SOC,消除库仑积分的累积漂移:

1
2
3
4
5
6
7
if (param->current_ma >= -20 && param->current_ma <= 20) {
// 近零电流:直接用OCV查表
error = lut_soc_by_ocv(handle, tmp_volt, &tmp_soc_now);
} else if (param->current_ma > 20) {
// 有电流时:用RC表(考虑阻抗压降)
error = lut_rc_f(handle, tmp_volt, param->current_ma, tmp_cellthm, &tmp_soc_now);
}

场景三:充放电过程中的RC表查询

当电流 > 20mA时,端电压与OCV偏差较大。此时使用RC表(三维查找表:SOC × 电流 × 温度),本质是预先标定了不同工况下的电压-SOC关系,等价于”带负载的OCV表”。

OCV vs 库仑积分的协作

两种方法互补:

OCV法 库仑积分
精度类型 绝对精度(不累积) 相对精度(会漂移)
适用状态 静置/低电流 充放电过程
响应速度 需等待极化消失 实时响应
核心作用 校准基准 连续追踪

实际运行策略:库仑积分负责连续追踪,OCV在静置时校准,满充/满放时重置

电量追赶机制

问题背景

库仑积分从一个校准点出发持续累加,但由于误差累积和容量估计偏差,到达充满或放空时,显示的SOC往往不是精确的100%或0%。如果不做处理,用户会看到:

  • 充电结束但SOC显示98%(永远充不到100%)
  • 电池已到截止电压但SOC还显示3%(突然关机)

电量追赶机制就是为了解决这个”最后几公里”的问题。

充电CV段追赶(报满机制)

锂电池充电分CC(恒流)和CV(恒压)两个阶段。进入CV后,电流逐渐减小直到截止(EOC)。追赶机制利用CV段的电流下降速率来推算剩余容量:

1
2
3
4
5
6
7
8
9
10
11
12
// CV段:SOC从当前值线性追赶到100%
// 计算"每mA电流下降对应多少SOC上升"
unit_soc_upratio = fg_idiv(
(10000 - rsoc_now) * 1000, // 剩余SOC空间
tmp_tail_current - eocma // 剩余电流下降空间
);

// 根据电流实际下降量计算SOC增加量
tmp_soc_up = (save_tail_current - tmp_tail_current) * unit_soc_upratio / 1000;
tmp_soc_up += 5; // 最少0.05%/次,保证单调递增

rsoc_now += tmp_soc_up;

直觉:CV段电流从(比如)500mA降到100mA时,SOC应从95%升到100%。算法计算出”每降1mA → SOC升多少”的比例,按实际电流变化推进SOC。

满充判定条件

1
2
3
4
5
6
7
8
9
if (rsoc_now >= 9950) {  // SOC >= 99.5%
if (time_elapsed > parm_eocsec // 超过EOC等待时间
|| (status & FG_STAT_CHG_EOC)) // 或硬件报EOC
{
rsoc_now = 10000; // 报100%
rca = fcc; // 剩余容量 = 满充容量
status |= FG_STAT_CHG_FULL;
}
}

放电尾部追赶(报空机制)

放电末期(SOC < 5%),电压曲线陡峭,库仑积分的小误差也会导致提前或延迟报空。追赶机制切换为电压驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (rsoc_now <= DISCHARGE_TAIL_TH) {  // SOC < 5%
// 计算"每mV电压下降对应多少SOC下降"
unit_soc_dratio = fg_idiv(
rsoc_now * 1000,
tail_volt - parm_eodmv // 当前电压到截止电压的距离
);

// 根据电压实际下降量计算SOC减少量
tmp_soc_drop = fg_idiv(
unit_soc_dratio * (save_tail_voltage - tail_volt + 1),
1000
);

rsoc_now -= tmp_soc_drop;
}

直觉:SOC还剩3%,电压距截止还有50mV。那么每降1mV,SOC应降 3%/50 = 0.06%。这样SOC和电压同步到达终点,避免突然关机。

温度阻抗补偿

温度对容量的影响

锂电池的可用容量随温度显著变化:低温下内阻增大,电压提前跌到截止线,实际可放出的容量减少。算法在充电初始化时根据温度调整FCC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 极低温 (-10°C以下): 每降1°C容量减少约0.1%
if (tmp_thm <= -100) tmp_thm = -100;
fcc -= fg_idiv(fcc * (0 - tmp_thm), 1000);

// 低温 (0°C~10°C): 基础减5%,每降1°C额外减约0.13%
else if (tmp_thm <= 100) {
fcc -= fg_idiv(fcc * 100, 2000);
fcc -= fg_idiv(fcc * (100 - tmp_thm), 750);
}

// 高温 (>40°C): 适当增加容量(最多3.3%)
else if (tmp_thm >= 400) {
fcc += fg_idiv(fcc * (tmp_thm - 400), 4000);
}

系统阻抗补偿

电池到ADC采样点之间有PCB走线、保护MOSFET、连接器等系统阻抗。大电流时这些阻抗产生的压降会让采样电压偏低,导致SOC估计偏低。补偿方法:

1
2
// 从采样电压中去除系统阻抗压降
cellrv = param->volt_lo_mv - fg_idiv(param->current_ma * parm_sysim, 1000);

parm_sysim 是系统阻抗参数(单位mΩ),在产线校准时标定。

DFCC动态容量补偿

问题

FCC在充满时标定,但放电过程中实际可用容量受电流大小和温度影响。大电流放电时,内阻压降更大,电压更早到达截止线,可用容量比标称值少。

三维查表补偿

DFCC(Dynamic Full Charge Capacity)使用三维查找表 DFCC_table[SOC][电流][温度] 给出容量补偿系数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取当前工况的DFCC补偿系数 (基准10000 = 1.0)
error = lut_dfcc_f(handle, rsoc_now, -curr_now, cellthm, &dfcc_comp);

// 计算补偿后的FCC
rca_prv = (fg_idiv(tmp_soh_mah, rsoc_now) * dfcc_comp) / 10000;

// 平滑更新: 30%新值 + 70%旧值,防止跳变
rca_prv = fg_idiv(rca_prv * 3, 10) + fg_idiv(fcc * 7, 10);

// 限幅: 20%~200% 的FACC范围内
if (rca_prv > facc * 2) rca_prv = facc * 2;
if (rca_prv < facc / 5) rca_prv = facc / 5;

fcc = rca_prv;

这样在大电流低温放电时,FCC被下调,SOC下降更快,避免到截止电压时SOC还显示很高。

系统校准

校准时机

算法在特定条件下标记校准点,用于修正长期漂移:

满充校准:充电到100%且满足条件时标记

1
2
3
4
5
6
7
if ((timesec > FG_CALI_TIME) &&           // 运行超过5分钟
(!(status & FG_STAT_CHG_FST)) && // 非首次充电
(cellthm <= FG_DC_THM_HI) && // 温度在15~40°C之间
(cellthm >= FG_DC_THM_LO))
{
status |= FG_STAT_CALI_FC; // 标记满充校准完成
}

满放校准:放电到截止电压时标记

满充校准点将RCA重置为FCC,满放校准点将RCA重置为0。两次校准之间的库仑积分误差被一次性消除。

校准对FCC的更新

经过一个完整的充放电循环后,实际放出的容量可以用来更新FCC(电池健康状态SOH的体现):

$$FCC_{new} = \alpha \times FCC_{measured} + (1 - \alpha) \times FCC_{old}$$

平滑因子 $\alpha$ 通常取0.3,避免单次测量误差过大影响FCC。

状态机与算法流程

整体算法由状态机驱动,根据电池工作状态选择不同的处理分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
flowchart TD
A["fg_update - 每秒调用"] --> B["电压/电流平均值计算"]
B --> C["库仑计数累加"]
C --> D{"当前工作模式?"}
D -->|"静置 imode=0"| E["fg_idle_process"]
D -->|"充电 imode>0"| F["fg_mobile_charge_process"]
D -->|"放电 imode<0"| G["fg_mobile_discharge_process"]
E --> H["OCV校准 / 伪充电"]
F --> I["CC段比例计算 → CV追赶 → 报满"]
G --> J["DFCC补偿 → 尾部追赶 → 报空"]
H & I & J --> K["更新FACC/CC_PRV"]
K --> L["计算最终RSOC和RCA"]
L --> M["输出结果"]

静置处理 (fg_idle_process)

  • 电压稳定性检测(50mV窗口内)
  • 温度稳定性检测(5°C窗口内)
  • 满足条件后用OCV重新估算SOC
  • 如果检测到充电器接入但未进入充电模式,执行”伪充电”逻辑

充电处理 (fg_mobile_charge_process)

  1. 初始化:计算温度补偿后的FCC,确定充电SOC起点
  2. CC段:库仑积分正常累加,SOC按比例上升
  3. CV检测:电压达到充电截止且电流开始下降 → 进入CV
  4. CV追赶:SOC从当前值线性追赶到100%
  5. 报满:SOC >= 99.5% 且满足时间/EOC条件 → 报100%

放电处理 (fg_mobile_discharge_process)

  1. 初始化:加载FCC,考虑老化和温度
  2. 正常段:库仑积分 + DFCC动态补偿
  3. RC表辅助:用电压/电流/温度三维表验证SOC合理性
  4. 尾部追赶:SOC < 5% 时切换为电压驱动
  5. 报空:电压到截止线 → SOC = 0%

关键设计思想总结

  1. 库仑积分是主线:充放电过程中SOC的变化完全由库仑积分驱动,保证连续性和实时性

  2. OCV是校准锚点:在静置和满充/满放时用OCV重置SOC,定期消除累积误差

  3. 追赶机制保证用户体验:充电必须报满、放电不能突然关机,这是产品层面的硬性要求

  4. DFCC补偿物理现实:大电流和低温下可用容量减少是物理事实,必须在算法中体现

  5. 多级防护:输入校验、范围限幅、平滑滤波、异常回退,层层保护避免SOC跳变

系列文章

  • 本文:SOC电量算法架构 - 库仑积分、开路电压与电量追赶机制
  • 相关:阻抗追踪算法原理 - 电池模型与动态阻抗估计
  • 相关:DFCC动态FCC自适应补偿算法