瑞芯微Rockchip开发者社区
登录
直播中
杨春林
7年用户
932经验值
私信
关注
[问答]
请问RK3288_Android7.1如何通过ADC实现电池电量粗略计算上报?
开启该帖子的消息推送
RK3288
请问RK3288_Android7.1如何通过ADC实现电池电量粗略计算上报?
回帖
(1)
张华仁
2022-3-3 14:19:26
< >
一. 背景及问题:
由于项目需要,板子硬件接的PMU【rk808】是不带电池功能的,不支持库仑计计算电量,而项目又需要接电池使用,所以硬件把电池端接到一个ADC口,做了个简单的电池威廉希尔官方网站 ,通过ADC读取数值来确定电池电压,然后换算电池电量。
下面是原理图方面:可以看到,原理图通过把电池电压分压之后,接到了主控端的ADC0口,现在就需要写个驱动通过读取ADC的数值来粗略计算电池的电量,然后上报上层显示状态信息。
二.思路和方法:
电池驱动部分大概流程是这样的:
Android内核中的电池驱动采取的是linux 内核驱动中的 power_supply子系统框架进行上报电池状态。power_supply主要通过sys文件系统向用户层提供读取电池状态的接口,路径为 /sys/class/power_supply/ , 该目录下通常会有 ac , battery, u*** 三个目录,代表给Android系统供电的三种能源类型,其中电池的状态就在battery的目录下,当电池状态变化的时候会通过uevent机制通知上层,然后上层通过读取该目录下相应的值来动态的显示电池状态。
电池驱动的源码目录:kernel/drivers/power
三. 技术总结
我这里参考rk818电池驱动的实现方式,把电池电量和状态上报到上层,实现动态的显示电池状态信息。
1、对电源(ac , battery, u***)进行初始化(这里说的是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_u***_desc = {
478 .name = "u***",
479 .type = POWER_SUPPLY_TYPE_USB,
480 .properties = rk818_u***_props,
481 .num_properties = ARRAY_SIZE(rk818_u***_props),
482 .get_property = rk818_cg_u***_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->u***_psy = devm_power_supply_register(cg->dev, &rk818_u***_desc,
490 &psy_cfg);
power_supply_register调用内核提供的函数device_create()和power_supply_create_attrs来实现电源的注册,这里电源类型是u***。
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和u***只创建了一个online属性,上层通过判断ac和u***的online状态(1表示设备接入,0表示设备拔出)便可知道当前系统是由什么设备在充电了;而battery则创建了如status、health、present、capacity、batt_vol等等和电池相关的诸多属性,上层通过这些电池属性uevent便可监控电池的当前工作状态了。下面举例是battery,ac和u***同理。
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: %dn", __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/u***/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)接收并解释该字符串,获取相应信息。
使用示例:
下面说说我之前在项目上实现的电池驱动,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
5、驱动调试过程问题点【后续需注意的问题】
5.1驱动申请ADC通道时,会出现申请报错的情况?
驱动需要获取ADC通道来使用时,需要对驱动的加载时间进行控制,必须要在saradc初始化之后。saradc是使用module_platform_driver()进行平台设备驱动注册,最终调用的是module_init()。所以用户的驱动加载函数只需使用比module_init()优先级低的,例如:late_initcall(),就能保证驱动的加载的时间比saradc初始化时间晚,可避免出错。
5.2驱动的ADC通道获取的电压值采用了分压,大概分到三分之一,所以在计算电池电压的时候,需要乘以3后才得到实际电池电压。
< >
一. 背景及问题:
由于项目需要,板子硬件接的PMU【rk808】是不带电池功能的,不支持库仑计计算电量,而项目又需要接电池使用,所以硬件把电池端接到一个ADC口,做了个简单的电池威廉希尔官方网站 ,通过ADC读取数值来确定电池电压,然后换算电池电量。
下面是原理图方面:可以看到,原理图通过把电池电压分压之后,接到了主控端的ADC0口,现在就需要写个驱动通过读取ADC的数值来粗略计算电池的电量,然后上报上层显示状态信息。
二.思路和方法:
电池驱动部分大概流程是这样的:
Android内核中的电池驱动采取的是linux 内核驱动中的 power_supply子系统框架进行上报电池状态。power_supply主要通过sys文件系统向用户层提供读取电池状态的接口,路径为 /sys/class/power_supply/ , 该目录下通常会有 ac , battery, u*** 三个目录,代表给Android系统供电的三种能源类型,其中电池的状态就在battery的目录下,当电池状态变化的时候会通过uevent机制通知上层,然后上层通过读取该目录下相应的值来动态的显示电池状态。
电池驱动的源码目录:kernel/drivers/power
三. 技术总结
我这里参考rk818电池驱动的实现方式,把电池电量和状态上报到上层,实现动态的显示电池状态信息。
1、对电源(ac , battery, u***)进行初始化(这里说的是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_u***_desc = {
478 .name = "u***",
479 .type = POWER_SUPPLY_TYPE_USB,
480 .properties = rk818_u***_props,
481 .num_properties = ARRAY_SIZE(rk818_u***_props),
482 .get_property = rk818_cg_u***_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->u***_psy = devm_power_supply_register(cg->dev, &rk818_u***_desc,
490 &psy_cfg);
power_supply_register调用内核提供的函数device_create()和power_supply_create_attrs来实现电源的注册,这里电源类型是u***。
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和u***只创建了一个online属性,上层通过判断ac和u***的online状态(1表示设备接入,0表示设备拔出)便可知道当前系统是由什么设备在充电了;而battery则创建了如status、health、present、capacity、batt_vol等等和电池相关的诸多属性,上层通过这些电池属性uevent便可监控电池的当前工作状态了。下面举例是battery,ac和u***同理。
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: %dn", __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/u***/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)接收并解释该字符串,获取相应信息。
使用示例:
下面说说我之前在项目上实现的电池驱动,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
5、驱动调试过程问题点【后续需注意的问题】
5.1驱动申请ADC通道时,会出现申请报错的情况?
驱动需要获取ADC通道来使用时,需要对驱动的加载时间进行控制,必须要在saradc初始化之后。saradc是使用module_platform_driver()进行平台设备驱动注册,最终调用的是module_init()。所以用户的驱动加载函数只需使用比module_init()优先级低的,例如:late_initcall(),就能保证驱动的加载的时间比saradc初始化时间晚,可避免出错。
5.2驱动的ADC通道获取的电压值采用了分压,大概分到三分之一,所以在计算电池电压的时候,需要乘以3后才得到实际电池电压。
举报
更多回帖
rotate(-90deg);
回复
相关问答
RK3288
请问
RK3288_Android7.1
如何调试红外遥控IR?
2022-03-03
947
asrc如何调试_
RK3288_Android7.1
?
2022-03-03
756
请问
RK3288_Android7.1
如何调试uart串口屏?
2022-03-03
828
请问
一下
rk3288
应用层如何获取
电池电量
呢
2022-06-10
1919
RK3288_Android7.1
接eDP屏休眠led状态灯没有亮红色是怎么回事
2022-03-03
691
如何解决
RK3288_Android7.1
色温固化的部分移植问题?
2022-03-03
1207
怎样在
RK3288_Android7.1
上增加自定义的远程遥控按键呢
2022-03-04
1175
Android
手机
电池电量
的应用,不看肯定后悔
2021-04-19
2199
用N76E616AD怎么测量
电池电量
?
2023-06-28
436
请问
一下
RK3288
Android
7.1
是怎样调试USB MIDI的
2022-03-03
1369
发帖
登录/注册
20万+
工程师都在用,
免费
PCB检查工具
无需安装、支持浏览器和手机在线查看、实时共享
查看
点击登录
登录更多精彩功能!
英国威廉希尔公司网站
william hill官网 版块
小组
免费开发板试用
ebook
直播
搜索
登录
×
20
完善资料,
赚取积分