本文从个人公众号转载而来,欢迎交流,欢迎关注个人公众号“FAE老冰”
目录
1.MTK Battery架构简析
2.MTK电量算法简析
3.MT65MT6572/MT6582平台SW FG算法分析
4.误差和消除误差
1.MTK Battery架构简析
MTK平台Battery软件架构基本如上图所示。
具体过程:
硬件ADC读取Battery的各路信息:包括温度,电压等。
MTK开发的电量算法分析得到的数据。
Kernel层将电量信息通过写文件节点的方式更新,并通过UEVENT通知上层。
上层Service开启UEVENT LISTENER,监听到UEVENT后,读取battery相关文件节
点,获取电量信息。
Service更新数据后,通过Broadcast通知所有开启了相关listener的activities。
根据不同的电量读取和计算的策略,第一步的读取和第二步的算法部分会有比较大的差
异,而后面的数据更新和事件通知部分一致性较高。
本篇重点分析MT65MT6572/MT6582平台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标准的电源子系统体系。
2.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方式算得。这部分会详细介绍。
3.MT65MT6572/MT6582平台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三种不同的电池算法方案 分别初始化,因为MT6582平台采用的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的实现
首先看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就是菜单栏看到的电池电量了。
4.误差和消除误差
因为电量值本身不容易通过仪器直接测量,只能依赖开路电压与电量的关系即电量曲线或者电流积分公式演算,这样一个过程会有很多产生误差的点,MTK针对其中一些给出了优化方式。
另外 由于电池特性 有些时候真实的电量数据反而使得用户体验下降,这时候还要针对电量数据做一些特殊处理,以满足用户的预期。
以下是MT6582平台 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的所有温度点。
MT6582平台 MTK电量算法只会在初始化的时候去通过读取的温度重构zcv table,但假设使用者周围的温度在手机使用过程中变化比较大,或者电池本身发热很厉害,电池内阻值有了较大改变,则测出的电量偏差也会比较大。
假设从高温环境到低温环境,根据电池特性,电池内阻会大幅增大,而电池可使用容量将会下降。实际耗流假设变化不大,由于还是用之前的zcv表格,用于计算的内阻比实际内阻小很多,则算出来的电流会偏大。这样显示电量会快速下降。
开机电压的误差:
由于开机过程中,外设逐渐进入工作状态,电流逐渐增大,这时候ADC读到的电压偏差比较大。
MTK方案:89的HW FG会在PMU未开放restb时 获取电压 取代MT6582的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等,启到修正误差的作用。
联系客服