1、锂电池介绍
锂离子电池由日本索尼公司于1990年最先开发成功。它是把锂离子嵌入碳(石油焦炭和石墨)中形成负极(传统锂电池用锂或锂合金作负极)。正极材料常用LixCoO2 ,也用 LixNiO2 ,和LixMnO4 ,电解液用LiPF6+二乙烯碳酸酯(EC)+二甲基碳酸酯(DMC)。
石油焦炭和石墨作负极材料无毒,且资源充足,锂离子嵌入碳中,克服了锂的高活性,解决了传统锂电池存在的安全问题,正极LixCoO2在充、放电性能和寿命上均能达到较高水平,使成本降低,总之锂离子电池的综合性能提高了。预计21世纪锂离子电池将会占有很大的市场。
锂离子二次电池充、放电时的反应式为LiCoO2+C=Li1-xCoO2+LixC
上图是锂电池容量和电压的对比图,可以看到当容量为0%时,电压最大,容量为100%时,电压最小,容量指的是电池还可以装下多少电量的意思。
过放:锂电池如果电压低于一定的门限,是不能够还原的,就假设,你有一个手机,放着几个月一直没有充电,如果电池电芯没有过放保护,那你的手机想再次充电就不行了。
过充:电池爆炸,大部分因为过充引起的,电芯做的不好,锂电池已经充满电了,没有做好门限保护,导致爆炸,但是爆炸的原因不只是这个,比如充电器短路,手机内部短路。
2、Android电池管理框架
问题:
有时候我们发现我电量50%掉到30%用了一个小时,但是同样的使用方法20%到关机,可能只用了半个小时,这就是涉及电池曲线或者电量器的问题
电池曲线:
有点低成本手机,或者平板电脑,没有电量器,就只能用ADC的值通过自己的算法来调整电池百分比,如果这个电池曲线调整的不好,就会出现上述问题。
电量器:
电量器也是用来计算电量的,但是有个芯片专门做这个事情,理论肯定比上面没有电量器的效果好
Android电池整体框架
本层属于电池的驱动部分,负责与硬件进行交互,当电池电量信息发生变化时,生成相应的uevent,上报给用户层。
主要相关代码路径:
本层在Android中属于Native层,healthd中运行一个系统服务batteryproperties,负责监听Kernel中上报的uevent,对电池电量进行实时监控。
主要相关代码路径:
本层提供了C++/Java两套接口来访问batteryproperties系统服务。
本层的系统服务battery使用Java代码写成,运行在fwk的中SystemServer进程。
该系统服务的主要作用是:监听batteryproperties服务中的电池信息变化消息,并将该消息以系统广播的形式转发至Android系统中各处。
主要相关代码路径:
\frameworks\native\services\batteryservice\IBatteryPropertiesRegistrar.cpp
\frameworks\native\services\batteryservice\IBatteryPropertiesListener.cpp
\frameworks\native\services\batteryservice\BatteryProperties.cpp
\frameworks\base\core\java\android\os\IBatteryPropertiesRegistrar.aidl
\frameworks\base\core\java\android\os\IBatteryPropertiesListener.aidl
\frameworks\base\core\java\android\os\BatteryProperties.java
\frameworks\base\services\core\java\com\android\server\BatteryService.java
该部分属于电量上报的最后的环节。其主要工作是:监听系统广播Intent.ACTION_BATTERY_CHANGED,并对UI作出相应更新。
主要相关代码路径
\frameworks\base\packages\SystemUI\src\com\android\systemui\power\PowerUI.java
3、u-boot到kernel关机充电流程
Android充电有很多场景,关机充电是比较重要的一个需要了解的。
开机流程:
充电检测开机流程:
u-boot代码:
在u-boot里面,我们很多时候需要把一些信息传给kernel,目前用到的方法是command_line,
开机方式也是这样的。u-boot代码如下代码如下,kernel解析部分代码请到init/main.c下面去找
以后抽个文章专门说明下
510 #ifdef CONFIG_RK_SDCARD_BOOT_EN
511 if (StorageSDCardUpdateMode()) { /* sdcard undate */
512 snprintf(command_line, sizeof(command_line),
513 "%s %s", command_line, "sdfwupdate");
514 }
515 #endif
516
517 #ifdef CONFIG_RK_UMS_BOOT_EN
518 if (StorageUMSUpdateMode()) { /* ums update */
519 snprintf(command_line, sizeof(command_line),
520 "%s %s", command_line, "usbfwupdate");
521 }
522 #endif
523
524 #ifdef CONFIG_POWER_RK818
525 if (is_rk81x_fg_init() != 0) {
526 snprintf(command_line, sizeof(command_line),
527 "%s %s", command_line, "loader_charged");
528 }
529 #endif
530 if (charge) {
531 snprintf(command_line, sizeof(command_line),
532 "%s %s", command_line, "androidboot.mode=charger");
533 }
534
535 #if defined(CONFIG_LCD) && defined(CONFIG_RK_FB_DDREND)
536 /*
537 * uboot fb commandline: uboot_logo=@[:]
538 * size - fb size, address - fb address, offset - kernel bmp logo offset.
539 * offset is optional, depend on resource image has kernel_logo.bmp.
540 */
541 if (g_logo_on_state != 0) {
542 snprintf(command_line, sizeof(command_line),
543 "%s uboot_logo=0x%08x@0x%08lx", command_line, CONFIG_RK_LCD_SIZE, gd->fb_base);
544 #if defined(CONFIG_KERNEL_LOGO)
545 if (g_rk_fb_size != -1)
546 snprintf(command_line, sizeof(command_line),
547 "%s:0x%08x", command_line, g_rk_fb_size);
548 #endif /* CONFIG_KERNEL_LOGO */
549 }
550 #endif /* CONFIG_RK_FB_DDREND */
551
552 #if defined(CONFIG_RK_DEVICEINFO)
553 if (g_is_devinfo_load)
554 snprintf(command_line, sizeof(command_line),
555 "%s stb_devinfo=0x%08x@0x%08x",
556 command_line, SZ_8K, CONFIG_RKHDMI_PARAM_ADDR);
557 #endif /* CONFIG_RK_DEVICEINFO*/
558
559 snprintf(command_line, sizeof(command_line),
"./common/cmd_bootrk.c" 709L, 19309C
4、充电电流
电池充电有几个阶段
在软件上需要根据电池厂家的的不同阶段来给设置充电电流大小。
举个栗子:
我们用USB先连接PC机给手机充电,这时候适配器不是DC模式,充电电流如果设置过大,就会导致PC蓝屏。
而不同的电源适配器,D+ D- 的状态不同,被识别的状态也不一样,流程也会不同。
之前做的一个功能是,在恒压充电下,为了提高充电速度,我每间隔50ma提高充电电流,同时去检查电池两端的电压大小,如果电压降低到一定程度,就不会再增加充电电流。
5、kernel充电曲线代码
上面提到的问题,如果没有电量器的情况下,我们需要用数组来计算电池百分比,贴上这部分代码给大家看看,这部分代码可以适用于很多地方。
static struct batt_vol_cal batt_table[BATT_NUM] = {
{3400,3520},
{3610,3715},
{3672,3790},
{3705,3825},
{3734,3841},
{3764,3864},
{3808,3930},
{3845,3997},
{3964,4047},
{4034,4144},
{4120,4200},
};
static int rk29_adc_battery_voltage_to_capacity(struct rk29_adc_battery_data *bat, int BatVoltage)
{
int i = 0;
int capacity = 0;
struct batt_vol_cal *p;
p = batt_table;
if (rk29_adc_battery_get_charge_level(bat)){ //charge
if(BatVoltage >= (p[BATT_NUM - 1].charge_vol)){
capacity = 100;
}
else{
if(BatVoltage <= (p[0].charge_vol)){
capacity = 0;
}
else{
for(i = 0; i < BATT_NUM - 1; i++){
if(((p[i].charge_vol) <= BatVoltage) && (BatVoltage < (p[i+1].charge_vol))){
capacity = i * 10 + ((BatVoltage - p[i].charge_vol) * 10) / (p[i+1].charge_vol- p[i].charge_vol);
break;
}
}
}
}
}
else{ //discharge
if(BatVoltage >= (p[BATT_NUM - 1].dis_charge_vol)){
capacity = 100;
}
else{
if(BatVoltage <= (p[0].dis_charge_vol)){
capacity = 0;
}
else{
for(i = 0; i < BATT_NUM - 1; i++){
if(((p[i].dis_charge_vol) <= BatVoltage) && (BatVoltage < (p[i+1].dis_charge_vol))){
capacity = i * 10 + ((BatVoltage - p[i].dis_charge_vol) * 10) / (p[i+1].dis_charge_vol- p[i].dis_charge_vol); ;
break;
}
}
}
}
}
return capacity;
}
static void rk29_adc_battery_capacity_samples(struct rk29_adc_battery_data *bat)
{
int capacity = 0;
struct rk29_adc_battery_platform_data *pdata = bat->pdata;
//充放电状态变化后,Buffer填满之前,不更新
if (bat->bat_status_cnt < NUM_VOLTAGE_SAMPLE) {
bat->gBatCapacityDisChargeCnt = 0;
bat->gBatCapacityChargeCnt = 0;
return;
}
capacity = rk29_adc_battery_voltage_to_capacity(bat, bat->bat_voltage);
if (rk29_adc_battery_get_charge_level(bat)){
if (capacity > bat->bat_capacity){
//实际采样到的容量比显示的容量大,逐级上升
if (++(bat->gBatCapacityDisChargeCnt) >= NUM_CHARGE_MIN_SAMPLE){
bat->gBatCapacityDisChargeCnt = 0;
if (bat->bat_capacity < 99){
bat->bat_capacity++;
bat->bat_change = 1;
}
}
bat->gBatCapacityChargeCnt = 0;
}
else{ // 实际的容量比采样比 显示的容量小
bat->gBatCapacityDisChargeCnt = 0;
(bat->gBatCapacityChargeCnt)++;
if (pdata->charge_ok_pin != INVALID_GPIO){
if (gpio_get_value(pdata->charge_ok_pin) == pdata->charge_ok_level){
//检测到电池充满标志,同时长时间内充电电压无变化,开始启动计时充电,快速上升容量
if (bat->gBatCapacityChargeCnt >= NUM_CHARGE_MIN_SAMPLE){
bat->gBatCapacityChargeCnt = 0;
if (bat->bat_capacity < 99){
bat->bat_capacity++;
bat->bat_change = 1;
}
}
}
else{
#if 0
if (capacity > capacitytmp){
//过程中如果电压有增长,定时器复位,防止定时器模拟充电比实际充电快
gBatCapacityChargeCnt = 0;
}
else if (/*bat->bat_capacity >= 85) &&*/ (gBatCapacityChargeCnt > NUM_CHARGE_MAX_SAMPLE)){
gBatCapacityChargeCnt = (NUM_CHARGE_MAX_SAMPLE - NUM_CHARGE_MID_SAMPLE);
if (bat->bat_capacity < 99){
bat->bat_capacity++;
bat->bat_change = 1;
}
}
}
#else // 防止电池老化后出现冲不满的情况,
if (capacity > bat->capacitytmp){
//过程中如果电压有增长,定时器复位,防止定时器模拟充电比实际充电快
bat->gBatCapacityChargeCnt = 0;
}
else{
if ((bat->bat_capacity >= 85) &&((bat->gBatCapacityChargeCnt) > NUM_CHARGE_MAX_SAMPLE)){
bat->gBatCapacityChargeCnt = (NUM_CHARGE_MAX_SAMPLE - NUM_CHARGE_MID_SAMPLE);
if (bat->bat_capacity < 99){
bat->bat_capacity++;
bat->bat_change = 1;
}
}
}
}
#endif
}
else{
//没有充电满检测脚,长时间内电压无变化,定时器模拟充电
if (capacity > bat->capacitytmp){
//过程中如果电压有增长,定时器复位,防止定时器模拟充电比实际充电快
bat->gBatCapacityChargeCnt = 0;
}
else{
if ((bat->bat_capacity >= 85) &&(bat->gBatCapacityChargeCnt > NUM_CHARGE_MAX_SAMPLE)){
bat->gBatCapacityChargeCnt = (NUM_CHARGE_MAX_SAMPLE - NUM_CHARGE_MID_SAMPLE);
if (bat->bat_capacity < 99){
bat->bat_capacity++;
bat->bat_change = 1;
}
}
}
}
}
}
else{
//放电时,只允许电压下降
if (capacity < bat->bat_capacity){
if (++(bat->gBatCapacityDisChargeCnt) >= NUM_DISCHARGE_MIN_SAMPLE){
bat->gBatCapacityDisChargeCnt = 0;
if (bat->bat_capacity > 0){
bat->bat_capacity-- ;
bat->bat_change = 1;
}
}
}
else{
bat->gBatCapacityDisChargeCnt = 0;
}
bat->gBatCapacityChargeCnt = 0;
}
bat->capacitytmp = capacity;
}
好了今天就这么多,具体问题还是要具体分析去看代码
推荐阅读:
专辑|Linux文章汇总
专辑|程序人生
嵌入式Linux
微信扫描二维码,关注我的公众号