【纯技术】MTK Fuel Gauge算法分析

图片说明 作者:王进
目录
1. Battery 架构简析
2. MTK 电量算法简析
3. 25 平台 SW FG 算法分析
4. 误差和消除误差

Battery 架构简析

MTK 平台 Battery 软件架构基本如上图所示。

具体过程:

硬件 ADC 读取 Battery 的各路信息:包括温度,电压等。
MTK 开发的电量算法分析得到的数据。
Kernel 层将电量信息通过写文件节点的方式更新,并通过 UEVENT 通知上层。
上层 Service 开启 UEVENT LISTENER,监听到 UEVENT 后,读取 battery 相关文件节点, 获取电量信息。
Service 更新数据后,通过 Broadcast 通知所有开启了相关 listener 的 activities。

根据不同的电量读取和计算的策略,第一步的读取和第二步的算法部分会有比较大的差异, 而后面的数据更新和事件通知部分一致性较高。

本篇重点分析 72/82 平台 SW FG 算法实现,对比 SW_FG 和 HW_FG 在硬件及软件上的部 分差异,分析电量误差形成的一些原因和 MTK 已经采取的消除误差的措施。对于 Battery 数据更新和充电流程则粗略分析。

充电状态机,battery 充电的逻辑,就依赖于这张图,如果是用的 external charger ic,则应当 参考该 IC 的充电逻辑。

linear charging 下 cc 转 cv,是通过 ADC 读取电压后,软件切换。而使用 charger ic 则很 可能是硬件直接切换。

这部分的相关代码路径在:
alps/mediatek/kernel/drivers/power/linear_charging.c
alps/mediatek/kernel/drivers/power/switching_charging.c

kernel 层 battery 驱动工作的流程,Bat_thread 是工作的重点,通过单独的线程依赖 10s 定时 器,更新 battery 相关信息。电量算法分析后得到的数据也不会直接 update,Information Processing 还会针对一些特殊情况对显示电量做调整,比如 0%tracking&100%tracking。

除了 10s 一次的定时器更新,插拔充电器会触发中断,中断处理时同样会更新 battery 数据。

所有和 电池 充电相关的数据都存储在 power_supply 类型的结构体中,这是 linux 标准的电 源子系统体系。

MTK 电量算法简析

为了得到较为精确的电量数据,需要改善测量方式和计算方法,并针对已知误差采取优化手段。一下介绍 MTK 平台下采用的一些电量算法。

AUX ADC 算法:

事实上,所有算法都要依赖ADC读取电量信息,这边的AUX ADC算法指只依赖ADC读 值 然后查表读取电量的算法。

这种算法只重构了 ZCV table,误差会很大。

库仑积分法:

通过开路电压查表得到初始电量 D0,后续电量通过电流积分累积,通用性强,依赖初始电量的精确度。

混合型算法: SW FG 算法 HW FG 算法。事实上 MTK 平台项目通常采用的是混合型算法。

SW FG 的参考电路:

HW FG 的参考电路:

相同点:NTC 电阻用于测量温度 ADC 测量各路信号
不同点:HW FG 有单独的 ADC 和 20 毫欧的电阻 作电流的侦测。
HW FG 和 SW FG 最大差异就是电流的获取方式。

混合算法的流程,HW FG 通过 FGADC 读取 FG 电阻两端电压获得电流, 而 SW FG 则结 合库伦算法通过 SW 方式算得。这部分会详细介绍。

72/82 平台 SW FG 算法分析

主要分析上图黄色部分

大部分项目都采用混合算法,下面从算法初始化开始介绍下 SW FG 的算法实现。

battery_meter.c 这个 C 文件 主要负责电池电量算法的实现 向上主要承接 battery_common.c 向下调用
battery_meter_hal.c 中的接口,以读取电池的各路信号。

=>battery_meter_initial

首先看下调用这个 func 的 timing。

显然 在开机初始化阶段,就会进入该函数,且只会运行一次。

针对 AUXADC SW_FG HW_FG 三种不同的电池算法方案分别初始化,因为 82 平台采 用的 SW_FG, 所以接下去先主要分析 SW_FG 的流程。

SW_FG 的准备工作 分为两步: table_init oam_init

先看 table_init

首先要获取当前的温度信息

=> force_get_tbat

ADC 读值

这边就是 MTK 为了结合实际温度 获取较为精确的电池信息 而采取的线性平均值法。原理是利用预先测得的分布在-10 0 25 50 摄氏度下的 ZCV 表,结合真实温度,动态重构一张当 前温度下的 ZCV 表格。

TEMPERATURE 对应预留的空 ZCV 表格,如下

构造新表的函数如下

采用线性平均法 填补了有效温度内所有的 ZCV 对应值 但与真实曲线必然存在一定的误差。

=>oam_init

常见的指针函数传参比较有趣,vol_bat 这个参数下传给底下 pmic 做 count,然后被重新赋值成读取的v_bat值 之所以能这样做是因为这两块代码同处在kernel层并地址传参

battery_meter_hal.c 虽然顶着 hal 的名头,其实是驱动程序,工作在内核层,主要实现上表 各结构体 针对 MTK 不同种的充电方案 读取各项参数,包括 v_bat temperature v_i_sense 等

这边走 pmic

这个函数也是起分流作用的 通过dwchannel,分到不同的处理函数去。硬件上,ADC通过 一个 mux 数据选择器 对各路模拟信号进行切换 有点类似 cpu 的时间片和移动通信的时 隙切换。

vbat 是 channel 5, 要等到 adc 数据 ready才能去读寄存器,看一下 pmic 的手册

精度 15bit 的 ADC 其中 14bit 用来存储数据 1 个 bit 做 ready 信号 ,似乎 ADC3 和我们之前的 dwchannel number 有点对不上?

可以看到dwchannel 5 最终访问的仍是 ADC3,另外可以直接比较下寄存器地址。

和 datasheet 左上角的寄存器地址一致

最后还要做次数值转换,公式如下:

分辨率计算:测量电压范围/(2^AD 位数-1)

另外,对于同为电压值的 v_bat 和 v_i_sense,可能会出现 adc 量程不够的问题 这时候需 要通过电阻分压。 所以 case 6 和 7 的 r_val_temp 为分压比

和前面一样 ADC 读值 但是 6320 的 spec 上对于 PCHR 没有说明 从函数定义的名称上看 是开路电压 但是如何在一个闭路的环境中通过 ADC 读取 ocv,有些不解。 查了下读这 个值的 timing,只有在 init suspend 和 resume 的时候才去获取.猜想 一是可能 利用 linear charging 这种充 9 停 1 的方式,在第 10s 读取电压 作为 OCV 也可能是因为刚开机时 电流 还不大 读到的电压值 可用作 OCV 总之 这个值应该是真实的开路电压的一个近似值。

=> oam_init

根据电压读表获取电量

可以看到用的是table_init时重构的新表

Ok 这边再一次 利用线性平均法 这一次是针对 ADC 读到测到的电压 线性平均后 得 到一个较精准的电量

=> oam_init

首先判断是否插着充电器,读 PMU 寄存器实现

为什么 需要判断有没有插着充电器呢?

我是这么理解的,之前通过 ADC 读取了两个电压值 其中一个是 V_BAT,另一个是 hw_ocv 是在闭路环境下读取的开路电压近似值, 如果此时插着充电器,会有充电电流通过 这个 hw_ocv 的值和开路电压的误差会增大,因此需要做进一步的处理。

插入充电器时,电量误差不大于 30,满电误差不大于 10,否则 hw_ocv 无效

=>oam_init

Dod 是指用电深度,100-dod = 电池剩余容量

看一下 dod_init 的实现

dod init

首先看 g_rtc_fg_soc 这个变量,MTK 的策略是每隔一段时间将当前电量存储到 RTC 寄存器中,在开机时读取该电量。主要目的是改善用户体验。

看下这个值是何时被写入的:

电池信息 10s update 1 次同样 UI 电量 10s 存入 RTC 寄存器一次。

用7个bit 存储电量信息。

开机时直接显示关机时保存的电量,会增强用户体验性,但是如果是更换电池或其他情况造成关机电量和开机电量相差过大,显然应该采用开机电量,否则后续电池电量跳变反而会影 响用户体验。

Normal boot 下忽略||后面的条件主要就是要求没有插入充电器不处于低电量误差不超过 40%

经过前面所有的判断最终得到了 gFG_capacity 这个电量,也就是开机电量,因为开机电量在整个电量计算中相当重要并且又要结合用户体验 所以之前会有很多的条件分支。

Q_MAX_POS_25 是常温下电池容量因为 FG 算法计算电池容量会用到库伦积分所以需要关注电池容量的问题。这个值需要根据实际电池容量客制化。

即 25 度用标准容量其他温度下需要乘上一个比例值。

oam_v_ocv_1 和oam_v_ocv_2 现在是根据 dod_init的结果取得的 大部分情况下就是关机电量查表得到的ocv电压值 而注释掉的原方案直接采用hw_ocv的值

这边是算 ocv1 和 ocv2 对应的电池内阻 r,通过查表的方式获取,因为 r 和 v 的对应表也是开路条件下测得所以用 hw_ocv 查表获取的值比原先通过 vbat 取平均要精准些。

其他一些 oam 电量算法 需要的参数初始化

oam_init 后, oam_run 这个 func 负责 电量的计算,看一下调用的时机。

=>mt_battery_GetBatteryData


显然也是 10s 轮询一次,get_percentage 这个 func 多个分支对应不同的电量算法

=>oam_run

先看下 MTK SW FG 算法的原理图

SW FG 的核心 在于 通过两种方式更新电压,去逼近真实开路电压 最终查表获取近似真 实的电量值。

ocv1 被假定为开路电压 ocv2 则是闭路电压,以下结合实际代码和上述流程图分析下 SW_FG 算法流程


D0 D1 D2 D3 D4 D5 代表不同的放电深度

这个算法的思路是这样的: 最终通过开路电压 oam_v_ocv_1 查 ZCV 表得到当前的电量值
-> 开路电压需要通过闭路电压 v_bat 和 闭路电流 oam_i_2 去回溯电池内阻 逐次逼近 –> oam_i_2 通过 另一种方式 电量积分更新的电压 oam_v_ocv_2

总的来说:电压通过两种方式更新 电流积分求电量后查表 /电池内阻回溯 IR drop 求得
电池内阻 更新方式只有一种 根据电压查表

具体分析部分代码:

闭路电压的更新不需要算法支持直接通过读寄存器实现,注意vol_bat这个参数被复用, 下传表平均次数 返回时为最终的v_bat电压值

ocv_1 和 ocv_2 分别是两种方式更新的电压,这边通过内阻的 IR drop 求电流.

上图 R 可以是电池内阻

关键是 oam_i_2 这边的 I2 有几个作用:

<1>因为电流是通过上图的内阻IR drop得到的,而方式一内阻回溯逼近开路电压本质也是 IR drop,如果使用 oam_i_1 则没有意义,只能使用不同体系的 I2.
<2>方式二 电流积分求电量查表 同样依赖 oam_i_2 这个体系是累积积分 不需要引用其 他体系的参数
<3>I2 的方向作为充电还是放电的依据

而 oam_i_1 只有作用 3

oam_car_1/oam_car_2 是累积电量 显然 oam_car_2 是算法的有效参数

gFG_BATT_CAPACITY_aging 是电池总容量,之前分析过了,根据开机时读取的电池温度会有所不同。

d2 为积分法算出的电池当前容量;d0 为开机电量,不会更新;d1 不重要

内阻回溯 IR drop 逼近开路电压,具体分析下:

主要是这个 for 循环,首先通过 v_bat 去查表得到电池内阻 r 然后用另一种算法求得的电流 I 和内阻 r 算出内阻分去的电压 v,推算 ocv_1.

几次循环 反复这个过程 逼近真实的开路电压。

有几个注意点:
1.

这边的电压单位到底是 v 还是 mv? 实际上是 0.1mv

2.gFG_resistance_bat 和 R_FG_VALUE 分别是指代 电池内阻和硬件 FG 使用的 FG 电阻(一般是 20 毫欧) 这边是 SW_FG 所以后一项为 0

3.

因为 ret_compensate 是 int 型变量 做除法时取整处理 会引入较大误差, 加上这个值使结果四舍五入

实际做 6 次内阻回溯,这时的电压值已经和开路电压比较接近,查表得到 D3,D3 基本就能反映当前电量了。

MTK 在 D3 的问题上针对电量跳变的情况 又做了步优化得到 D5,看下代码

这部分代码比较简单,1 分钟内电量值不会改变,且每分钟电量的变化不会大于 1%,这样 用户体验会比较好。防止因为低电压时陡峭的电量曲线,以及比较耗电的应用,或突然的大 电流引起的电量跳变。当然特殊应用下应该取消这个功能,否则会带来电量变化延时等问题。

这边的返回值将填充 BMT_status.SOC ,这个参数再经过优化得到 BMT_status.UI_SOC 就是菜单栏看到的电池电量了。

误差和消除误差

因为电量值本身不容易通过仪器直接测量,只能依赖开路电压与电量的关系即电量曲线或者 电流积分公式演算,这样一个过程会有很多产生误差的点,MTK 针对其中一些给出了优化 方式。

另外,由于电池特性,有些时候真实的电量数据反而使得用户体验下降,这时候还要针对电量 数据做一些特殊处理,以满足用户的预期。

以下是 82 平台 SW FG 中部分电量误差产生的原因 以及 MTK 用于消除/减小误差的方法。

库伦积分时的电流:

即使是 cc 状态,电流也是有波动的,而进入 cv 状态后,电流的变化会更大。因为这样一个 电流变化并无规律,所以不可能导出电流公式用于电量积分。目前代码会把每 10s 算一次 电流作为这 10s 的平均电流。

这样会形成一个电量累积误差。

MTK 的观点: MTK 认为电流误差既有正误差,也有负误差,在较长的一段时间内认为误差 相互抵消。但实际进入恒压后,误差应该会稍大。

电池内阻不稳定造成的误差:

电池的内阻会随温度变化,且幅度很大,而在 SW FG 的算法中依赖电池内阻计算电流大小, 如果使用固定值的电池内阻会严重影响电流的计算。

MTK 应对方案:

1.建立 zcv table

MTK 的 zcv table,建立了几个特定温度(-10,0,25,50)下的内阻 r 和开路电压 ocv 的关 系,这样可以根据可量测的电压信号查表推算内阻

2.线性平均值

MTK 在算法初始化时,读取温度信息,通过线性平均重构 zcv table,这样可以覆盖从-10 到 50 的所有温度点。

82 平台 MTK 电量算法只会在初始化的时候去通过读取的温度重构 zcv table,但假设使用 者周围的温度在手机使用过程中变化比较大,或者电池本身发热很厉害,电池内阻值有了较 大改变,则测出的电量偏差也会比较大。

假设从高温环境到低温环境,根据电池特性,电池内阻会大幅增大,而电池可使用容量将会下降。实际耗流假设变化不大,由于还是用之前的 zcv 表格,用于计算的内阻比实际内阻小很多,则算出来的电流会偏大。这样显示电量会快速下降。

开机电压的误差:

由于开机过程中,外设逐渐进入工作状态,电流逐渐增大,这时候 ADC 读到的电压偏差比较大。

MTK方案:89的HW FG会在PMU未开放restb时 获取电压 取代82的sw读开机ocv的方案

ADC 精度问题

这是硬件设施的问题,有一点,精度和分辨率是两码事。精度指转换后所得结果相对与实际值的准确度 、分辨率是指转换器所能分辨的模拟信号的最小变化值。

插拔和重新开机的显示误差

开机过程中有累积误差,开机读取 ocv 查表同样有误差,这样的结果是用户可能看到开关机的电量出现很大误差。

MTK 方案:使用 rtc 寄存器每隔一段时间存储当前电量,开机时若开机电量和关机差别不大, 则直接使用关机前电量,以改善用户体验。

电池老化的误差

电池在使用较长时间后,容量会减小。

MTK 方案: HW FG 可以通过充电到 100%的过程重新算得电池最大容量,SW FG 则并没有采取这种方 案,可能是 SW FG 的库伦积分误差较大。

100%tracking & 0%tracking

充满电和关机实际上有两个判断标准:

软件关机电压:system_off_voltage 电量显示 0%
截止电流:top_off_current 电量显示 100%

由于误差,这两套标准在实际使用中肯定不一致,MTK 通过 UI_SOC 这个变量对算法得到 SOC 进行处理。让电量显示 follow 电压和电流的判断。

UI_SOC 如先到 0%,需要等待电压和电流的判断;
UI_SOC 如后到,则需加快步伐 每次-1

代码如下:

100% tracking 原理和上面的差不多,不一一列举。

充满电后,还回去 reset 之前 FG 算法的一些参数 比如 DOD CAR 等,启到修正误差的作用。