背景:
应测试要求对电池进行保护测试,在NTC电阻开路或者短路时应自动停止对电池的充电,从而起到保护电池的作用,同时这也是做CE认证中需要过的两项测试项,也可以模拟的是电池的低温以及高温测试。开路意味着电阻阻值无穷大,模拟低温;短路意味着阻值无穷小,模拟高温。
本文档讲述的为当电池NTC短路,未能停止充电的处理。
测试环境:高温电池温度>60℃或者电池短路。
平台为:MT8766+MT6357+MT6371,MTK本身的平台对于此问题的处理。
硬件原理图:
电池NTC部分原理图:
如图所示,R3为电池NTC电阻,当其短路时,相当于电池处于高温,应该停止充电。
但是测试结果现象为:系统会不断重启,适配器端不断有电流充电,不断的开机重启。
抓取log分析,可以知道是短路导致获取到的电池温度大于等于60℃,系统跑关机流程,又因为是插着适配器的情况,所以系统又会跑到关机充电流程,进入kernel关机充电,但由于温度过高,又跑关机,如此反复,是的适配器端的电流在不断变化,系统没有停止充电。
跟踪代码分析系统会跑到
kernel-4.9\drivers\power\supply\mediatek\battery\mtk_power_misc.c中的mtk_power_misc_psy_event函数触发高温关机,代码如下所示:
该函数在电池数据更新时会通过通知链的方式被调用到,所做的操作为:
- 通过power_supply注册的battery调用get_property的POWER_SUPPLY_PROP_TEMP获取电池温度。
- 判断电池温度是否≥60℃,是跑步骤3,否则退出返回。
- 设置标志位,并唤醒wait_que等待队列。
步骤3的函数如下:
唤醒wait_que等待队列,wait_que控制着一个线程函数的运行,当该队列被唤醒,并且标志位满足条件后,跑线程循环,做相关处理操作,这里我们使用做的是关机流程。
线程的函数如下所示:
- 判断是否满足条件继续向下执行,不满足的话,阻塞在此处。
- 判断sdd->overheat高温标志位,true表示过温,进入清零,调用关机函数。
- 调用关机函数,系统掉电。
上述就是充电电池高温时,系统跑关机的代码流程。
抓取到的log如下所示:
Log中的count数值表示的为多次获取电池温度,确认是超过60℃。
红框中的为刚才分析的关机流程处理log。
解决对策:
原本是想通过在kernel中进行处理的,但是发现在kernel中处理的话,需要修改的地方很多,需要,还需要考虑到系统跑起来后实际充电电流控制不单单只有电池NTC决定,还有其他一些因素,如主板温度,CPU等。
因此最后决定在lk或者pl阶段对电池充电进行处理使之不充电,处理方式就像判断电池是否存在一样,由于lk阶段有现成的电池温度调用函数,因此可在LK阶段的电池初始化部分就对电池状态进行判断,从未实现高温(NTC短路)停止电池充电。
通过原理图以及数据手册可知:IC的充电使能寄存器默认是enable
而且充电IC的使能脚有主芯片控制,查看GPIO上电默认电平为低电平(PD),如下图所示:
DVDD18_IOBL | AE22 | 2/4/6/8/10/12/14/16 | PD | OL | PERIPHERAL_EN5 | 0 | GPIO_CHG_EN_0 |
因此充电IC一上电就是出于充电使能状态,充电配置均是默认状态,充电使能。
因为充电控制GPIO上电默认的状态(GPIO_CHG_EN_0)为低电平,充电IC(MT6371)默认也为充电使能,所以我们可以通过控制这两部分来实现电池不充电,参考之前电池开路的方式采用的是通过控制充电使能脚(GPIO_CHG_EN_0)来不使能电池充电。
使用到的dts文件:
kernel-4.9\arch\arm64\boot\dts\mediatek\bat_setting\mt6761_xxx_battery_prop_ext.dtsi
部分参数如下:
bat_gm30: battery{
compatible = "mediatek,bat_gm30";
DIFFERENCE_FULLOCV_ITH = <(200)>;
/* Charging termination current*/
SHUTDOWN_1_TIME = <(60)>;
/* If ui_soc shows 1% more than X minites, system will shutdown*/
KEEP_100_PERCENT = <(1)>;
/* The ui_soc will keep 100% until SOC drop X percents after unplugged*/
UIFULLLIMIT_EN = <(0)>;
/* using current to limit uisoc in 100% case*/
UILOWLIMIT_EN = <(0)>;
/* using voltage to limit uisoc in 1% case*/
R_FG_VALUE = <(10)>;
/* R_sense resistance*/
EMBEDDED_SEL = <(0)>;
/* Configures whether using embedded battery or not*/
PMIC_SHUTDOWN_CURRENT = <(20)>;
/* System shutdown current*/
FG_METER_RESISTANCE = <(50)>;
/* The resistance of PCB*/
UISOC_UPDATE_T = <(0)>;
/*
* uisoc_update_type:
* 0: only ui_soc interrupt update ui_soc
* 1: coulomb/nafg will update ui_soc if delta car > ht/lt_gap /2
* 2: coulomb/nafg will update ui_soc
*/
CAR_TUNE_VALUE = <(120)>;
/* Tune value for current measurement*/
PMIC_MIN_VOL = <(34000)>;
/* vboot voltage for gauge 0%*/
POWERON_SYSTEM_IBOOT = <(500)>;
...
};
温度转换表以及部分默认配置参数:
实现代码如下:
在lk的platform.c函数platform_init中调用新增的判断函数check_tbat_init,进行温度判断处理。
platform_init中的调用位置如下:
void platform_init(void)
{
。。。//电池初始化位置使能寄存器充电前调用判断
#if !defined(NO_BAT_INIT)
#ifdef MTK_CHARGER_NEW_ARCH
dprintf(CRITICAL, "MTK_CHARGER_NEW_ARCH\n");
mtk_charger_init();
pmic_dlpt_init();
check_sw_ocv();
#if defined(MTK_TBAT_CHECK_SUPPORT)
check_tbat_init();
#endif
mtk_charger_start();
get_dlpt_imix_r();
#else
dprintf(CRITICAL, "mt65xx_bat_init\n");
mt65xx_bat_init();
#endif // MTK_CHARGER_NEW_ARCH
#endif // !defined(NO_BAT_INIT)
#endif // MACH_FPGA
PROFILING_END(); /* battery init */
。。。
}
判断温度函数如下:
该函数实现的功能为在while循环中不断的获取电池温度,直到满足(温度<60℃)要求才退出循环,继续正常的开机流程,否则的不断循环,类似电池开路的判断流程。
函数中具体实现功能为:
- 判断电池是否存在,通过读取DTS配置参数。
默认未配置的话fg_cust_data.disable_mtkbattery = 0
2.判断是否充电,是的话适当延时一下。通过获取寄存器值判断
3.事先不使能充电IC的充电引脚,使得电池不充电,该步骤为被函数中最重要一步。实现电池不充电。
4.在while循环中不断的获取通过force_get_tbat(true)电池温度,参数true表示不断获取新的电池温度,否则获取之前就是电池温度,不更新。
5.判断获取的温度是否满足要求,是的话跳出循环,否则继续循环获取温度。
6.不断的重新喂狗功能,否则的话,一段时间后系统会重启。
7.若满足条件退出循环,重新使能充电控制引脚。
步骤3中引脚的控制函数如下:flg为true(1),使能充电IC的充电控制引脚GPIO_CHG_EN_0
步骤4:获取电池温度force_get_tbat(true)是通过ADC获取引脚电压,在通过温度_电池对照表比对获取电池温度
该函数为获取当前电池温度函数,是该问题解决的主要调用函数,在函数中通过获取ADC量化值,算法计算电流,以及补偿和电压转换温度转换表计算最终的电池温度,用于判断是否满足要求。
该函数的具体分析为:
1、判断执行的条件:是否电池存在,是否初始化ok。
2、获取温度对应引脚AUXADC_LIST_BATTEMP的电压值,该步骤最终调用的是int pmic_get_auxadc_value(PMIC_AUXADC_LIST list)函数,获取当前的引脚电压值。
3、通过gauge_get_current函数获取电流状态,以及电流值。该函数最终调用的是fgauge_get_current函数,通过配置寄存器的值去取寄存器PMIC_FG_CURRENT_OUT对应的值,在通过计算方式获取电流值
int fgauge_get_current(bool *fg_is_charging, int *data)
{
unsigned short uvalue16 = 0;
signed int dvalue = 0;
signed int curr_rfg = 0;
signed int ori_curr = 0;
int m = 0;
long long Temp_Value = 0;
int ret;
dprintf(CRITICAL, "[fgauge_read_current]g_isbat_init:%d,cartune:%d,%d,%d\n",
g_isbat_init, fg_cust_data.car_tune_value, fg_cust_data.r_fg_value, UNIT_FGCURRENT);
ret = pmic_config_interface(MT6357_FGADC_CON1, 0x0001, 0x000F, 0x0);
m = 0;
while (fg_get_data_ready_status() == 0) {
m++;
if (m > 1000) {
dprintf(CRITICAL, "[fgauge_read_current] fg_get_data_ready_status timeout 1 !%d\n", ret);
break;
}
}
uvalue16 = pmic_get_register_value(PMIC_FG_CURRENT_OUT);
ret = pmic_config_interface(MT6357_FGADC_CON1, 0x0008, 0x000F, 0x0);
m = 0;
while (fg_get_data_ready_status() != 0) {
m++;
if (m > 1000) {
dprintf(CRITICAL, "[fgauge_read_current] fg_get_data_ready_status timeout 2!%d\n", ret);
break;
}
}
ret = pmic_config_interface(MT6357_FGADC_CON1, 0x0000, 0x000F, 0x0);
/*calculate the real world data */
dvalue = (unsigned int) uvalue16;
if (dvalue == 0) {
Temp_Value = (long long) dvalue;
*fg_is_charging = false;
} else if (dvalue > 32767) {
/* > 0x8000 */
Temp_Value = (long long) (dvalue - 65535);
Temp_Value = Temp_Value - (Temp_Value * 2);
*fg_is_charging = false;
} else {
Temp_Value = (long long) dvalue;
*fg_is_charging = true;
}
Temp_Value = Temp_Value * UNIT_FGCURRENT;
Temp_Value = Temp_Value / 100000;
dvalue = (unsigned int) Temp_Value;
ori_curr = dvalue;
curr_rfg = dvalue;
/* Auto adjust value */
if (fg_cust_data.r_fg_value != 100) {
dvalue = (dvalue * 100) / fg_cust_data.r_fg_value;
curr_rfg = dvalue;
}
dvalue = ((dvalue * fg_cust_data.car_tune_value) / 1000);
*data = dvalue;
dprintf(CRITICAL, "[fgauge_read_current]is_charge:%d Ori_curr:0x%x,curr:[%d,%d,Final:%d] Rfg:%d ratio:%d\n",
*fg_is_charging, uvalue16, ori_curr, curr_rfg, dvalue,
fg_cust_data.r_fg_value, fg_cust_data.car_tune_value);
return 0;
}
先配置MT6357_FGADC_CON1寄存器的值,准备获取ADC(uvalue16 )值,获取之后重新配置MT6357_FGADC_CON1,该寄存器值配置MTK未给出相关的信息。
获取uvalue16 之后,通过计算补偿获取到真实的电流值*data,并返回。
- 通过获取到的电压以及电流值以及补偿数值进行计算获取电池温度电压值,用于BattVoltToTemp获取实际的温度,而fg_current_state值用于确保vol_cali为正值。
- 调用BattVoltToTemp函数获取实际的温度值,该函数中最终通过温度数值对照表计算出实际温度。
int BattVoltToTemp(int dwVolt, int volt_cali)
{
long long TRes_temp;
long long TRes;
int sBaTTMP = -100;
int vbif28 = RBAT_PULL_UP_VOLT; /* 2 side: BattVoltToTemp, TempToBattVolt */
int delta_v;
TRes_temp = (RBAT_PULL_UP_R * (long long) dwVolt);
#ifdef RBAT_PULL_UP_VOLT_BY_BIF
vbif28 = pmic_get_auxadc_value(AUXADC_LIST_VBIF) + volt_cali;
/* delta_v = abs(vbif28 - dwVolt); */
delta_v = vbif28 - dwVolt;
if(delta_v < 0)
delta_v = delta_v * -1;
/* avoid divide 0 case */
if (delta_v == 0)
delta_v = 1;
/*do_div(TRes_temp, delta_v); */
TRes_temp = TRes_temp / delta_v;
if (vbif28 > 3000 || vbif28 < 2500)
dprintf(CRITICAL, "[RBAT_PULL_UP_VOLT_BY_BIF] vbif28:%d\n", pmic_get_auxadc_value(AUXADC_LIST_VBIF));
#else
/* delta_v = abs(RBAT_PULL_UP_VOLT - dwVolt); */
delta_v = RBAT_PULL_UP_VOLT - dwVolt;
if(delta_v < 0)
delta_v = delta_v * -1;
if (delta_v == 0)
delta_v = 1;
/* do_div(TRes_temp, delta_v); */
TRes_temp = TRes_temp / delta_v;
#endif
#ifdef RBAT_PULL_DOWN_R
TRes = (TRes_temp * RBAT_PULL_DOWN_R);
/* do_div(TRes, abs(RBAT_PULL_DOWN_R - TRes_temp)); */
if ( (RBAT_PULL_DOWN_R - TRes_temp) > 0 )
TRes = TRes / (RBAT_PULL_DOWN_R - TRes_temp);
else if( (RBAT_PULL_DOWN_R - TRes_temp) < 0)
TRes = TRes / (RBAT_PULL_DOWN_R - TRes_temp) * -1;
else if ((RBAT_PULL_DOWN_R - TRes_temp) == 0)
TRes = TRes;
#else
TRes = TRes_temp;
#endif
/* convert register to temperature */
if (!pmic_is_bif_exist())
sBaTTMP = BattThermistorConverTemp((int)TRes);
else
sBaTTMP = BattThermistorConverTemp((int)TRes - BIF_NTC_R);
dprintf(INFO, "[BattVoltToTemp] %d %d %d %d\n", dwVolt, RBAT_PULL_UP_R, vbif28, volt_cali);
return sBaTTMP;
}
该函数在获取温度前还需要进行一些预算,获取Tres_temp。通过配置的上拉电阻RBAT_PULL_UP_R和上拉电压RBAT_PULL_UP_VOLT计算得到Tres_temp的值。
将该值带入BattThermistorConverTemp函数中计算(两点法)出实际的温度值sBaTTMP。
至此获取到实际的电池温度值后,就可以用于判断是否正常进入开机还是不断的循环继续进行温度判断直到符合条件才开机。