1.概念
ZCV:开路电压
OCV: 开路电压
VC:闭路电压
CAR:库伦计
DOD: 放电深度,100-DOD 即电容容量
Cmax/Qmax: 电池容量
2.相关文件关系
Battery_common.c (s:\i841\mediatek\kernel\drivers\power) // 充电逻辑文件
Charging_hw_pmic.c (s:\i841\mediatek\platform\mt6582\kernel\drivers\power) // 具体的充电芯片,相关电池参数检测
Linear_charging.c (s:\i841\mediatek\kernel\drivers\power) // 充电状态控制,内部 PMIC
Switch_charging.c (s:\i841\mediatek\kernel\drivers\power) // 充电状态控制,外部 Charge IC
3.硬件原理图
NTC 检测温度电路原理如下:
Vu Ru:上拉电阻值
--- Rd: 下拉电阻值
| Rntc: NTC 温度电阻 阻值
||| Ru Vu: 上拉电压值
| Gnd: 地
---------- -----Vntc Vntc: NTC 电压
| |
Rntc ||| ||| Rd
| |
---------- Rntc = (Ru*Rd*Vntc) / (Vru * Rd - Vntc * Ru)
|
-----
---
-
Gnd
4.充电框架总结
1.0 battery_common.c ----->充电控制主线程
kernel-3.18/drivers/power/mediatek/battery_common.c
late_initcall(battery_init);
static int __init battery_init(void)
ret = platform_device_register(&battery_device);
ret = platform_driver_register(&battery_driver);
struct platform_device battery_device = {
.name = "battery",
.id = -1,
};
static struct platform_driver battery_driver = {
.probe = battery_probe,
.remove = battery_remove,
.shutdown = battery_shutdown,
.driver = {
.name = "battery",
.pm = &battery_pm_ops,
},
};
/*在 Battery 驱动模块中, battery_probe 函数中会创建一些设备节点,并且运行一个线程 bat_thread_kthread 获取电池相关的数据信息*/
static int battery_probe(struct platform_device *dev)
//注册字符设备
/* Integrate with NVRAM */
ret = alloc_chrdev_region(&adc_cali_devno, 0, 1, ADC_CALI_DEVNAME);//动态申请一个主设备号
adc_cali_cdev = cdev_alloc();//分配一个字符设备
adc_cali_cdev->owner = THIS_MODULE;//填充该字符设备owner成员
adc_cali_cdev->ops = &adc_cali_fops;//填充该字符设备的ops成员
ret = cdev_add(adc_cali_cdev, adc_cali_devno, 1);//注册字符设备,把 adc_cali_cdev 字符设备注册到内核中去
//创建相关的sys系统节点,根据udev机制,在 class_create 创建sys相关联的类文件下调用 device_create 就可以创建对应的设备节点
adc_cali_major = MAJOR(adc_cali_devno);
adc_cali_class = class_create(THIS_MODULE, ADC_CALI_DEVNAME);
class_dev = (struct class_device *)device_create(adc_cali_class,
NULL,
adc_cali_devno, NULL, ADC_CALI_DEVNAME);
//与特定硬件的入口操作函数关联
get_charging_control();
static void get_charging_control(void)
{
battery_charging_control = chr_control_interface;//用来绑定内部的PMIC 或者外部充电芯片的操作函数入口
}
signed int chr_control_interface(CHARGING_CTRL_CMD cmd, void *data)
{ signed int status;
if (cmd < CHARGING_CMD_NUMBER) {
if (charging_func[cmd] != NULL)
status = charging_func[cmd](data);
else {
battery_log(BAT_LOG_CRTI, "[chr_control_interface]cmd:%d not supported\n", cmd);
status = STATUS_UNSUPPORTED;
}
} else
status = STATUS_UNSUPPORTED;
return status;
}
batt_init_cust_data();//初始化一些客制化的信息
//获得启动模式
battery_charging_control(CHARGING_CMD_GET_PLATFORM_BOOT_MODE, &g_platform_boot_mode);
/* Integrate with Android Battery Service
当probe函数注册完了字符设备后,函数进行的随后的进行的操作是在sys下面建立设备节点,
总共建立了四个设备节点,分别为ac_main、usb_main、wireless_main和battery_main,
这四个节点分别为使用适配器、USB、无线充电以及使用电池供电。电池电量发生变化的时候,
会通过这些节点将数据上报给上层,也就是说上层是通过这些节点来读取底层电池电量变化的数据的。
通过 power_supply_register() 函数在 /sys/class/power_supply 下分别注册创建 ac battery usb wireless 四个文件节点
*/
ret = power_supply_register(&(dev->dev), &ac_main.psy);
ret = power_supply_register(&(dev->dev), &usb_main.psy);
ret = power_supply_register(&(dev->dev), &wireless_main.psy);
ret = power_supply_register(&(dev->dev), &battery_main.psy);
//初始化电池状态结构体
/* Initialization BMT Struct */
BMT_status.bat_exist = KAL_TRUE; /* phone must have battery */
BMT_status.charger_exist = KAL_FALSE; /* for default, no charger */
BMT_status.bat_vol = 0;
BMT_status.ICharging = 0;
BMT_status.temperature = 0;
BMT_status.charger_vol = 0;
BMT_status.total_charging_time = 0;
BMT_status.PRE_charging_time = 0;
BMT_status.CC_charging_time = 0;
BMT_status.TOPOFF_charging_time = 0;
BMT_status.POSTFULL_charging_time = 0;
BMT_status.SOC = 0;
BMT_status.UI_SOC = 0;
BMT_status.bat_charging_state = CHR_PRE;
BMT_status.bat_in_recharging_state = KAL_FALSE;
BMT_status.bat_full = KAL_FALSE;
BMT_status.nPercent_ZCV = 0;
BMT_status.nPrecent_UI_SOC_check_point = battery_meter_get_battery_nPercent_UI_SOC();/N%同步点对应的开路电压以及UI电量
/* battery kernel thread for 10s check and charger in/out event */
/* Replace GPT timer by hrtime */
battery_kthread_hrtimer_init(); //创建一个定时器,定时器启动 bat_thread_kthread 函数,用于定时唤醒下面的内核线程更新电量
kthread_run(bat_thread_kthread, NULL, "bat_thread_kthread"); //核心充电线程
/*在这个线程中,每隔10s会去调用函数 BAT_Thread 去获取电池数据*/
int bat_thread_kthread(void *x)
/* Run on a process content */
while (1) {
if (((chargin_hw_init_done == KAL_TRUE) && (battery_suspended == KAL_FALSE))
|| ((chargin_hw_init_done == KAL_TRUE) && (fg_wake_up_bat == KAL_TRUE)))
BAT_thread();
wait_event(bat_thread_wq, (bat_thread_timeout == KAL_TRUE));// 睡眠等待唤醒
// 每 10s 启动一次
hrtimer_start(&battery_kthread_timer, ktime, HRTIMER_MODE_REL);
ktime = ktime_set(BAT_TASK_PERIOD, 0); /* 10s, 10* 1000 ms */ /*#define BAT_TASK_PERIOD 10 /* 10sec */
}
void BAT_thread(void)
{
static kal_bool battery_meter_initilized = KAL_FALSE;
//当系统第一次启动的时候 battery_meter_initilized 为 KAL_FALSE ,
所以 BAT_thread 会调用 battery_meter_initial 函数。
battery_meter_initial 函数为系统启动时运行的,为电池充电做一些初始化操作。
if (battery_meter_initilized == KAL_FALSE) {
//第一次执行时运行,获得开机显示电量,初始化电池算法(oam)参数,
//开机时就运行,只会运行一次,对电池算法(oam)方案进行初始化,并获得开机显示电量百分比
/*第一次进该函数会进行一些初始化,如设置D0的值(初始电量,构建电池曲线等) table_init, oam_init*/
//进行一系列的电池参数与温度对应关系的表格的初始化,并根据电池当前电压,hw ocv 取一个较合适值
//取和合适值对应容量,再与RTC保持容量比较,选择一个合适量为开机电池容量,最后初始化oam算法参数
battery_meter_initial(); /* move from battery_probe() to decrease booting time */
//MTK有 AUXADC、 SW_FG 、 HW_FG 三种不同的电池算法方案,这三种方案分别初始化
//接下去先主要分析 SW_FG 的流程。而 SW_FG 中主要是利用线性插值算法来重构zcv表格,利用积分算法求电池的当前电量。
signed int battery_meter_initial(void)
{
#if defined(SOC_BY_SW_FG)
g_auxadc_solution = 1;
table_init();
void table_init(void)
{
BATTERY_PROFILE_STRUCT_P profile_p;
R_PROFILE_STRUCT_P profile_p_r_table;
int temperature = force_get_tbat(KAL_FALSE);//求电池的温度,或者获取电池的温度
int force_get_tbat(kal_bool update)
{
static int pre_bat_temperature_val = -1;
if (update == KAL_TRUE || pre_bat_temperature_val == -1) {
/* Get V_BAT_Temperature */
bat_temperature_volt = 2;
//求的NTC电阻的电压,也就是原理图中的 BAT_ON
ret =
battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_TEMP, &bat_temperature_volt);
if (bat_temperature_volt != 0) {
/* BattVoltToTemp 函数就是任何将ADC读出的电压值转换为温度值*/
bat_temperature_val = BattVoltToTemp(bat_temperature_volt);
int BattVoltToTemp(int dwVolt)
{
long long TRes_temp;
long long TRes;
int sBaTTMP = -100;
/* TRes_temp = ((long long)RBAT_PULL_UP_R*(long long)dwVolt) / (RBAT_PULL_UP_VOLT-dwVolt); */
/* TRes = (TRes_temp * (long long)RBAT_PULL_DOWN_R)/((long long)RBAT_PULL_DOWN_R - TRes_temp); */
TRes_temp = (batt_meter_cust_data.rbat_pull_up_r * (long long) dwVolt);
do_div(TRes_temp, (batt_meter_cust_data.rbat_pull_up_volt - dwVolt));
/* convert register to temperature */
/*NTC 电阻就是通过与电阻的串联跟并联并且通过电压值来得到的。计算出系统当前NTC电阻的电阻值后,
然后就调用 BattThermistorConverTemp 函数进行查表,对比出当前系统的温度。而
BattThermistorConverTemp 函数是通过 alps/kernel-3.18/drivers/misc/mediatek/include/mt-plat/mt6580/include/mach/mt_battery_meter_table.h中的
Batt_Temperature_Table 结构体,然后根据电阻值落在哪个区间,根据线性插值的方法求出当前电池的温度*/
sBaTTMP = BattThermistorConverTemp((int)TRes);
return sBaTTMP;
}
}
return bat_temperature_val;
#endif
}
/*MTK的 zcv电池参数表格会预先测得的在-10 0 25 50 摄氏度开路电压跟放电深度之间的关系。
结合真实的温度值,系统会自己构建一张当前温度值的ZCV电池曲线表格*/
/* Re-constructure r-table profile according to current temperature */
profile_p_r_table = fgauge_get_profile_r_table(batt_meter_cust_data.temperature_t);//返回NTC电阻跟电压表格
R_PROFILE_STRUCT_P fgauge_get_profile_r_table(unsigned int temperature)
{
switch (temperature) {
case batt_meter_cust_data.temperature_t0:
return &r_profile_t0[g_fg_battery_id][0];
/*break;*/
case batt_meter_cust_data.temperature_t1:
return &r_profile_t1[g_fg_battery_id][0];
/*break;*/
case batt_meter_cust_data.temperature_t2:
return &r_profile_t2[g_fg_battery_id][0];
/*break;*/
case batt_meter_cust_data.temperature_t3:
return &r_profile_t3[g_fg_battery_id][0];
/*break;*/
case batt_meter_cust_data.temperature_t:
return &r_profile_temperature[0];
/*break;*/
default:
return NULL;
/*break;*/
}
}
/*调用 fgauge_get_profile_r_table 函数会根据上面读取到的温度来返回相对应的 r_profile_t 数组,
r_profile_t 数组在 alps/kernel-3.18/drivers/misc/mediatek/include/mt-plat/mt6580/include/mach/mt_battery_meter_table.h
中,而随后调用的 fgauge_construct_r_table_profile 函数也是先根据当前电池的温度确定是落入哪个温度范围,
假如当前电池温度是落入打0到25度之间,然后根据开路电压值和NTC电阻值的不同采用线性平均法分别构建出两个数值,
并以这两个值在构造出一个新的开路电压跟NTC电阻的动态的结构体。*/
//动态构建一个NTC电阻跟电压关系的表格
fgauge_construct_r_table_profile(temperature, profile_p_r_table);//重构ZCV表格的方法是通过线性插值的方法重构的
void fgauge_construct_r_table_profile(signed int temperature, R_PROFILE_STRUCT_P temp_profile_p)
{
/* Interpolation for V_BAT */ //构建表格中的电压值
for (i = 0; i < saddles; i++) {
if (((high_profile_p + i)->voltage) > ((low_profile_p + i)->voltage)) {
temp_v_1 = (high_profile_p + i)->voltage;
temp_v_2 = (low_profile_p + i)->voltage;
(temp_profile_p + i)->voltage = temp_v_2 + //采用线性平均法求电压
(((temperature - low_temperature) * (temp_v_1 - temp_v_2)
) / (high_temperature - low_temperature)
);
} else {
temp_v_1 = (low_profile_p + i)->voltage;
temp_v_2 = (high_profile_p + i)->voltage;
(temp_profile_p + i)->voltage = temp_v_2 +
(((high_temperature - temperature) * (temp_v_1 - temp_v_2)
) / (high_temperature - low_temperature)
);
}
}
/* Interpolation for R_BAT */ // 构建表格中的NTC电阻值
for (i = 0; i < saddles; i++) {
if (((high_profile_p + i)->resistance) > ((low_profile_p + i)->resistance)) {
temp_r_1 = (high_profile_p + i)->resistance;
temp_r_2 = (low_profile_p + i)->resistance;
(temp_profile_p + i)->resistance = temp_r_2 + //采用线性平均法求电阻
(((temperature - low_temperature) * (temp_r_1 - temp_r_2)
) / (high_temperature - low_temperature)
);
} else {
temp_r_1 = (low_profile_p + i)->resistance;
temp_r_2 = (high_profile_p + i)->resistance;
(temp_profile_p + i)->resistance = temp_r_2 +
(((high_temperature - temperature) * (temp_r_1 - temp_r_2)
) / (high_temperature - low_temperature)
);
}
}
}
/* Re-constructure battery profile according to current temperature */
profile_p = fgauge_get_profile(batt_meter_cust_data.temperature_t);
fgauge_construct_battery_profile(temperature, profile_p);利用同样的原理(线性插值法),动态生成了一个开路电压与放电深度关系的结构体
}
/*当 table_init 函数后执行 oam_init() 函数, 系统会对一些变量进行初始化操作,
包括在 dod_init 函数中对 oam_v_ocv_1 和 oam_v_ocv_2 进行初始化赋值,读取RTC实时时钟芯片的电量值等等,
经过这一系列操作后,就会进入 battery 系统一个最重要的部分,利用积分的方式来求电池的当前电量*/
oam_init();//会调用 dod_init
bm_print(BM_LOG_CRTI, "[battery_meter_initial] SOC_BY_SW_FG done\n");
#endif
meter_initilized = KAL_TRUE;
}
/*MTK系统是通过 battery_meter_get_battery_percentage 函数来读取当前电池的电量的,然后在通过设备结点,通过给上层调用。
battery_meter_get_battery_percentage 函数主要就是调用 oam_run 函数来实现电流的库伦算法。积分法是利用电流计算公式
I = Q/t来求的,它的优点时适用于各种电池,但缺点是初始电量无法获取。*/
BMT_status.nPercent_ZCV = battery_meter_get_battery_nPercent_zcv();
battery_meter_initilized = KAL_TRUE;
}
/**/
mt_battery_charger_detect_check();//充电器型号,是否插入等方面的检测,
mt_battery_GetBatteryData();//核心函数获取电池数据 //通过具体的充电芯片来电池信息、充电信息、获得电池电量百分比,//通过 oam 算法获得电量百分比。
if (BMT_status.charger_exist == KAL_TRUE)
check_battery_exist();
mt_battery_thermal_check();//电池温度检测,如果温度超过60度,关机重启。
mt_battery_notify_check();//检测电池电压,电流等 //电池状态检测,如果有问题,则会调用print()进行打印。
if (BMT_status.charger_exist == KAL_TRUE) {
// 检查电池状态,设置到 BMT_status.bat_charging_state 中
mt_battery_CheckBatteryStatus(); //充电异常检测
// 充电策略,这里有两个文件: switch_charging.c 和 linear_charging.c
// 他们的关系是,如果定义了任一外部充电 IC,则选择 switch_charging.c 的函数,否则就是 linear_charging.c 的函数
// 这里就是调用具体的芯片的充电相关函数进行充电
mt_battery_charging_algorithm();//切换充电模式 Pre_CC->CC->CV->Full
void mt_battery_charging_algorithm()
{
switch(BMT_status.bat_charging_state)
{
case CHR_PRE :
BAT_PreChargeModeAction();
break;
case CHR_CC :
BAT_ConstantCurrentModeAction();
/
// MTK 充电是充 9s 停 1s
// Charging 9s and discharging 1s : start
battery_charging_control(CHARGING_CMD_ENABLE,&charging_enable);
break;
case CHR_TOP_OFF :
BAT_TopOffModeAction();
break;
case CHR_BATFULL:
BAT_BatteryFullAction();
break;
case CHR_HOLD:
BAT_BatteryHoldAction();
break;
case CHR_ERROR:
BAT_BatteryStatusFailAction();
break;
}
}
}
mt_battery_update_status();//上报电池数据 //更新电池显示状态,更新设置节点的内容:sys/class/power_supply/文件节点(wireless_main/battery_main/ac_main/usb_main)
mt_kpoc_power_off_check();
}
void mt_battery_GetBatteryData(void)
SOC = battery_meter_get_battery_percentage();
signed int battery_meter_get_battery_percentage(void)
oam_run();
void oam_run(void)
首先通过adc读取电池 Vbat脚的电压值,
然后通过这个闭路电压值查找 R—Table (mt_battery_meter_table.h )
获得当前电压和温度下的电池内阻值,然后通过递归回溯的方法得到开路电压 OCV,
然后通过这个OCV电压值查找放电深度表获取当前的放电深度,从而算出剩余的电池电量
调试驱动时应注意的一些关键点
1、电池曲线
2、充电电流的一些设置( AC_CHARGER_CURRENT , NON_STD_AC_CHARGER_CURRENT , USB_CHARGER_CURRENT 等),是否是高压电池 HIGH_BATTERY_VOLTAGE_SUPPORT
最高温度: MAX_CHARGE_TEMPERATURE
最高电压: V_CHARGER_MAX
截止满充电流大小: CHARGING_FULL_CURRENT
充关机最大电池差异: CUST_POWERON_DELTA_CAPACITY_TOLRANCE (若小于这个值,则下一次开机用RTC里面存储的剩余电池电量值)
关机电压: SYSTEM_OFF_VOLTAGE
UI电量同步时间: SYNC_TO_REAL_TRACKING_TIME
MTK电池显示的具体过程为:硬件ADC读取Battery的各路信息:包括温度,电压等。
然后利用MTK开发的电量算法分析得到的数据。Kernel层将电量信息通过写文件节点的方式更新,
并通过UEVENT通知上层。上层Service开启UEVENT LISTENER,监听到UEVENT后,读取battery相关文件节点,
获取电量信息。Service更新数据后,通过Broadcast通知所有开启了相关 listener 的 activities 。