Android电池管理系统

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_30624591/article/details/98879687

Android电池管理系统总体实现:
电池管理在Android系统中的主要作用是检测我们的电池状态,剩余电量实时更新,高温报警,低电量关机等。Android的电池管理模块,从Android的应用层到底层内核分为了4层来理解,从上到下依次为,应用层,framwork层,本地框架层,内核驱动层。
在这里插入图片描述

一、Android 电池服务

Android电池服务BatteryService,用来监听内核上报的电池事件,并将最新的电池数据上报给系统,系统收到新数据后会去更新电池显示状态、剩余电量等信息。如果收到过温报警和低电报警,系统会自动触发关机流程,保护电池和机器不受到危害。

Android电池服务的源码结构 :

    frameworks/base/services/java/com/android/server/ 
    ├── SystemServer.java 
             创建BatteryService、PowerManagerService、ActivityManagerService 
    frameworks/base/services/core/java/com/android/server/
    ├── BatteryService.java
             监听底层上报的battery事件,广播电池发生改变的消息 (广播Intent.ACTION_BATTERY_CHANGED)

    frameworks/base/services/core/java/com/android/server/am/
    ├── ActivityManagerService.java 
             创建BatteryStatsService 
    ├── BatteryStatsService.java 
             统计和记录电池参数的信息 

    frameworks/base/services/core/java/com/android/server/power/
    ├── PowerManagerService.java 
             监听电池发生变化的广播消息,并调节系统的电源状态,例如亮屏 

    frameworks/base/core/java/com/android/internal/os/
    ├── BatteryStatsImpl.java 
             统计和记录电池参数的信息,并通知其他模块 

二、Healthd

healthd是android4.4之后提出来的一种中介模型,安卓源码路径下system/core/healthd, 主要是通过binder机制去调用healthd向下监听来自底层的电池事件,向上传递电池数据信息给Framework层的BatteryService用来计算电池电量相关信息,BatteryService通过传递来的数据来计算电池电量等信息,因此healthd在电池管理系统中起着承上启下的作用。

主要是通过BatteryMonitor.cpp中的bool BatteryMonitor::update(void)函数上报信息,其中,内核首先会更新数据到/sys/class/power_supply/battery节点下各个属性。

Healthd的源码结构:

     System/core/healthd/
    ├── healthd.cpp 
             创建uevent socket,监听内核上报的内核事件 
    ├── BatteryMonitor.cpp 
             初始化本地电池数据结构,将power_supply路径下属性节点路径填充进去, 
    ├── BatteryMonitor.h 
    ├── BatteryPropertiesRegistrar.cpp 
             创建电池属性监听器,并将其注册到Android的系统服务中 
    ├── BatteryPropertiesRegistrar.h

health模块的代码位置位于/system/core/healthd/,其入口在Healthd.cpp中的main函数中:

healthd_mode_ops = &android_ops;  //开机充电时初始化结构体
healthd_mode_ops = &charger_ops;  //关机充电时初始化结构体

healthd_mode_ops是一个充电状态的结构体,正常开机情况下会 将android_ops结构体赋值给healthd_mode_ops ,如果在关机情况下会将 charget_ops结构体赋值给healthd_mode_opos,就是关机充电的使用。

然后在healthd_init中,主要做一些初始化工作,并且创建了一个epoll,主要用于将文件指针挂在工作队列中,用于轮询查看驱动层是否发来信息,然后调用uevent_init()对uevent进行注册,应为从上篇文章我们明白,电池驱动是通过uevent与health进行通信的,uevent通信本质上就是socket通信。

epollfd = epoll_create(MAX_EPOLL_EVENTS);

初始化之后,执行healthd_mainloop(),开启了一个无线循环,监听是否有uevent事件到来,如果在for循环中检测到uevent事件到来的时候,会直接调用 uevent_event函数

在uevent_event函数中,会判断SSUBSYSTEM是否等于POWER_SUPPLY_SUBSYSTEM,如果是的话,说明是电池驱动传过来的信息,会执行healthd_battery_update()函数。在该函数中会直接调用BatteryMonitor中的update()函数,对电池的各个属性进行更新,并在最后调用在前面注册的监听进行上报。

在update()函数中调用 healthd_mode_ops->battery_update(&props) 进行,其中battery_update(&props)为在health.cpp中初始化的结构体,最后返回当前的充电状态。

battery_update()调用的函数如下,调用注册的监听进行上报:

void healthd_mode_android_battery_update(
struct android::BatteryProperties *props) {
if (gBatteryPropertiesRegistrar != NULL)
    gBatteryPropertiesRegistrar->notifyListeners(*props); //调用监听并传入属性值
return;
}

接着gBatteryPropertiesRegistrar 这个监听就会回调在framwork层实现的函数,然后通过Binder机制向上层通信

ps:这篇文章介绍Healthd说的比较详细:https://blog.csdn.net/zhou12314/article/details/79404348

三、驱动

驱动部分大概流程是这样的:
Android内核中的电池驱动采取的是linux 内核驱动中的 power_supply子系统框架进行上报电池状态。power_supply主要通过sys文件系统向用户层提供读取电池状态的接口,路径为 /sys/class/power_supply/ , 该目录下通常会有 ac , battery, usb 三个目录,代表给Android系统供电的三种能源类型,其中电池的状态就在battery的目录下,当电池状态变化的时候会通过uevent机制通知上层,然后上层通过读取该目录下相应的值来动态的显示电池状态。

驱动的源码结构 :

kernel/drivers/power

1、对电源(ac , battery, usb)进行初始化(这里说的是rk818):

对应驱动文件:drivers/power/rk818_charger.c

 469 static const struct power_supply_desc rk818_ac_desc = {
 470         .name           = "ac",//设备名称
 471         .type           = POWER_SUPPLY_TYPE_MAINS,//类型
 472         .properties     = rk818_ac_props,//属性
 473         .num_properties = ARRAY_SIZE(rk818_ac_props),//属性数目
474         .get_property   = rk818_cg_ac_get_property,//得到属性的函数
 475 };
 476
 477 static const struct power_supply_desc rk818_usb_desc = {
 478         .name           = "usb",
 479         .type           = POWER_SUPPLY_TYPE_USB,
 480         .properties     = rk818_usb_props,
 481         .num_properties = ARRAY_SIZE(rk818_usb_props),
482         .get_property   = rk818_cg_usb_get_property,
483 };

对应驱动文件:drivers/power/rk818_battery.c

1017 static const struct power_supply_desc rk818_bat_desc = {
1018         .name           = "battery",
1019         .type           = POWER_SUPPLY_TYPE_BATTERY,
1020         .properties     = rk818_bat_props,
1021         .num_properties = ARRAY_SIZE(rk818_bat_props),
1022         .get_property   = rk818_battery_get_property,
1023 };

这里主要是实现给电源名字类型等赋初值,最主要是将get_property函数指向我们写好的可以得到电源的属性的函数的起始地址,以便当内核需要用到驱动的信息的时候进行回调。

2、通过power_supply_register(devm_power_supply_register最终也是调用power_supply_register)将所提供的电源进行注册,即把他们的属性写到sys文件系统里,使用户空间可以得到有关电源的信息。

489         cg->usb_psy = devm_power_supply_register(cg->dev, &rk818_usb_desc,
490                                                  &psy_cfg);

power_supply_register调用内核提供的函数device_create()和power_supply_create_attrs来实现电源的注册,这里电源类型是usb。

3、Power Supply驱动程序头文件是kernel/include/linux/power_supply.h,注册和注销驱动程序的函数如下:

int power_supply_register(struct device *parent,struct power_supply *psy);

void power_supply_unregister(struct power_supply *psy);

struct power_supply {

const char *name; /*设备名称*/

enum power_supply_type type; /* 类型 */

enum power_supply_property *properties; /* 属性指针 */

size_t num_properties; /*属性的数目*/

char* *supplied_to;

size_t num_supplicants;

int (*get_property)(struct power_supply *psy, /*获得属性*/

enum power_supply_property psp,

union power_supply_propval *val);

void (*external_power_changed)(struct power_supply *psy);

/* ...... 省略部分内容 */

内核主要通过get_property这个函数指针来获得驱动中的有关电池的信息,而这个函数在内核中只给出了声明,我们在写驱动的时候要自己实现这个函数,即将自己写的函数赋值给这个函数指针,当内核需要驱动中电源信息的时候就回调这个get_property函数。另外,我们写驱动程序的时候又要给用户提供接口,内核中提供给用户的接口就是sysfs,通过读取sysfs文件系统中文件内容,就可以得到电源的信息。内核主要通过两个文件power_supply_class.c 和power_supply_core.c,我们调用其中的函数就可以把电源(BATTERY,USB或AC)的信息展现给用户,有关电源的属性写在/sys/class/powersupply文件夹下(此文件夹为程序运行后所生成的)。

ac和usb只创建了一个online属性,上层通过判断ac和usb的online状态(1表示设备接入,0表示设备拔出)便可知道当前系统是由什么设备在充电了;而battery则创建了如status、health、present、capacity、batt_vol等等和电池相关的诸多属性,上层通过这些电池属性uevent便可监控电池的当前工作状态了。下面举例是battery,ac和usb同理。

955 static int rk818_battery_get_property(struct power_supply *psy,
956                                       enum power_supply_property psp,
957                                       union power_supply_propval *val)
958 {
959         struct rk818_battery *di = power_supply_get_drvdata(psy);
960
961         switch (psp) {
962         case POWER_SUPPLY_PROP_CURRENT_NOW:
963                 val->intval = di->current_avg * 1000;/*uA*/ //获取电池电流
964                 if (di->pdata->bat_mode == MODE_VIRTUAL)
965                         val->intval = VIRTUAL_CURRENT * 1000;
966                 break;
967         case POWER_SUPPLY_PROP_VOLTAGE_NOW:
968                 val->intval = di->voltage_avg * 1000;/*uV*/ //获取电池电压
 969                 if (di->pdata->bat_mode == MODE_VIRTUAL)
970                         val->intval = VIRTUAL_VOLTAGE * 1000;
971                 break;
972         case POWER_SUPPLY_PROP_PRESENT:
973                 val->intval = is_rk818_bat_exist(di);
974                 if (di->pdata->bat_mode == MODE_VIRTUAL)
975                         val->intval = VIRTUAL_PRESET;
976                 break;
977         case POWER_SUPPLY_PROP_CAPACITY:
978                 val->intval = di->dsoc; //获取电池电量
979                 if (di->pdata->bat_mode == MODE_VIRTUAL)
980                         val->intval = VIRTUAL_SOC;
981                 DBG("<%s>. report dsoc: %d\n", __func__, val->intval);
982                 break;
983         case POWER_SUPPLY_PROP_HEALTH:
984                 val->intval = POWER_SUPPLY_HEALTH_GOOD;
985                 break;
986         case POWER_SUPPLY_PROP_TEMP:
987                 val->intval = di->temperature;
988                 if (di->pdata->bat_mode == MODE_VIRTUAL)
989                         val->intval = VIRTUAL_TEMPERATURE;
990                 break;

各能源设备属性概况如下(adb工具cat可以查看):

/sys/class/power_supply/ac/online AC 电源连接状态

/sys/class/power_supply/usb/online USB电源连接状态

/sys/class/power_supply/battery/status 充电状态

/sys/class/power_supply/battery/health 电池状态

/sys/class/power_supply/battery/present 使用状态

/sys/class/power_supply/battery/capacity 电池 level

/sys/class/power_supply/battery/batt_vol 电池电压

/sys/class/power_supply/battery/batt_temp 电池温度

/sys/class/power_supply/battery/technology 电池技术

当供电设备的状态或者电量发生变化后,会调用power_supply_changed(&battery_data->battery)更新这些文件;

//Send uevent.
power_supply_changed(&battery_data->battery);

备注:
1、Uevent机制
Uevent是内核通知android有状态变化的一种方法,比如USB线插入、拔出,电池电量变化等等。其本质是内核发送(可以通过socket)一个字符串,应用层(android)接收并解释该字符串,获取相应信息。
使用示例:

下面说说我之前在wm105墨西哥项目上实现的电池驱动,Android需要显示电池电量和充放电状态
所以按照电池驱动框架做了一个伪电池驱动, 主要是使用它的AC充电状态和电池电量这两个property。

1、充放电状态检测和电池电量更新:主要是通过检测一个GPIO的电平来实现,高电平则认为AC充电器拔掉,电池处于放电状态;低电平则认为充电器插入,电池进入充电状态。

87 static int gt_ac_set_property(struct power_supply *psy,
 88                         enum power_supply_property psp,
 89                         const union power_supply_propval *val)
 90 {
 91
 92         int ret = 0;
 93
 94         switch (psp) {
 95         case POWER_SUPPLY_PROP_ONLINE:
 96                 data->bat_status = data->ac_online = val->intval;
 97                 power_supply_changed(data->ac);
 98                 break;
 99         default:
100                 ret = -EINVAL;
101                 break;
102         }
103         return ret;
104 }
105 static int gt_battery_get_property(struct power_supply *psy,
106                                  enum power_supply_property psp,
107                                  union power_supply_propval *val)
108 {
109
110         int ret = 0;
111         switch (psp) {
112         case POWER_SUPPLY_PROP_STATUS:
113                 val->intval = data->bat_status;
114                 break;
115         case POWER_SUPPLY_PROP_HEALTH:
116                 val->intval = POWER_SUPPLY_HEALTH_GOOD;
117                 break;
118         case POWER_SUPPLY_PROP_PRESENT:
119                 val->intval = data->bat_present;
120                 break;
121         case POWER_SUPPLY_PROP_TECHNOLOGY:
122                 val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
123                 break;
124         case POWER_SUPPLY_PROP_CAPACITY:
125                 val->intval = data->bat_capacity;
126                 break;
127         default:
128                 ret = -EINVAL;
129                 break;
130         }
131
132            return ret;
133     }

2、通过ADC来读取对应电池电压(这里由于adc的量程最大只能到1.8v,所以硬件做了分压处理,大概分了1/3,即读到的电压值应该乘以3就是对应电池电压值)对应的ADC值,然后通过ADC值换算出对应的电池电压,rk3288的adc采样的位数是10,所以adc值对应2的10次方,也就是1024。
具体换算:ADC值 = 1024 x ADC电压值/1800, //电压单位是mV
然后算出对应电压值,在根据电压值大概计算当前电量,3.5V左右对应0%,4.2V左右对应100%电量。

3、对应的节点:
/sys/class/power_supply/gt-ac
/sys/class/power_supply/gt-battery

手动改变电池上报的电量:echo 90 > /sys/class/power_supply/gt-battery //上报90%电量值给上层
手动改变电池充放电状态:echo 1 > /sys/class/power_supply/gt-ac //设置为充电状态

4.调试手段:

获取电池信息
adb命令:adb shell dumpsys battery
得到信息如下:
AC powered: false
USB powered: true
Wireless powered: false
status: 1 #电池状态:2:充电状态 ,其他数字为非充电状态
health: 2
present: true
level: 55 #电量: 百分比
scale: 100
voltage: 3977
current now: -335232
temperature: 335 #电池状态
technology: Li-poly

展开阅读全文

没有更多推荐了,返回首页