android电池充电以及电量检测驱动分析
前段时间比较烦躁,各种不想学习不想工作,于是休息了几天。这几天又下来任务了--调试充电电路和电池电量检测电路,于是又开始工作,顺便把调试过程记录下来。
平台: cpu 飞思卡尔imx6q 4核
充电芯片 MAX8903
电量检测芯片 MAX11801
android版本 android4.0
一、电量检测
我们用的电池电量检测芯片MAX11801其实是一款电阻触摸屏的驱动芯片,它外带一个AD采集引脚,因此我们用这个引脚来检测电池电压。MAX11801电源为3.3V而电池电压范围可能是0~4.2V,因此我们需要给电池电压分压。我们所用的电路如下
知道了硬件电路下面来 添加这个芯片的驱动,这是一个i2c的芯片,因此首先在board文件中添加i2c设备
I2C_BOARD_INFO("max11801", 0x48),.platform_data = (void *)&max11801_mode,.irq = gpio_to_irq(SABRESD_TS_INT),},然后添加这个芯片的驱动文件放在/drivers/input/touchiscreen/max11801_ts.c
对于这个驱动文件我们只要读取出AD的值就可以了,对于触摸屏部分我们并不需要,因此主要是下面几个函数
static u32 max11801_dcm_sample_aux(struct i2c_client *client){u8 temp_buf;int ret;int aux = 0;u32 sample_data = 0;/* AUX_measurement*/max11801_dcm_write_command(client, AUX_measurement);//发送AD采集命令mdelay(5);ret = i2c_smbus_read_i2c_block_data(client, FIFO_RD_AUX_MSB, //读取高字节数据1, &temp_buf);if (ret < 1)printk(KERN_DEBUG "FIFO_RD_AUX_MSB read fails\n");elseaux_buf[0] = temp_buf;mdelay(5);ret = i2c_smbus_read_i2c_block_data(client, FIFO_RD_AUX_LSB, //读取低字节数据1, &temp_buf);if (ret < 1)printk(KERN_DEBUG "FIFO_RD_AUX_LSB read fails\n");elseaux_buf[1] = temp_buf;aux = (aux_buf[0] << 4) + //视最低4位无效并去掉(aux_buf[1] >> 4);/*10k和18.7k并联后电阻 R=18.7*10/(18.7+10)=6.516V(aux) = V(bat)*6.516/(6.516+18.7)V(aux) = aux*3300/0xfffV(bat) = aux*1386880/444717*/sample_data = (aux*1386880)/444717;//计算出电池电压return sample_data;}u32 max11801_read_adc(void){u32 adc_data;adc_data = max11801_dcm_sample_aux(max11801_client);//printk("----%s %d\n",__func__,adc_data);//lijianzhangreturn adc_data;}EXPORT_SYMBOL_GPL(max11801_read_adc);
由于电池电量检测的驱动非常简单,而且和充电驱动关系非常密切,因此一般都卸载充电驱动里面,我们也是这么做的。下面的代码都是从充电驱动中摘出来的,因此当大家看到,一些设备文件和函数参数类型 都是充电驱动中的 时候不要太奇怪。
通过上面的max11801_read_adc函数我们已经得到了理论计算的电池的电压,但实际应用中由于分压电阻误差,焊接问题等,这个电压会有一定的误差因此需要一个校正函数
u32 calibration_voltage(struct max8903_data *data){int volt[ADC_SAMPLE_COUNT];u32 voltage_data;int i;for (i = 0; i < ADC_SAMPLE_COUNT; i++) { //多次采样,防止AD误差if (data->charger_online == 0 && data->usb_charger_online == 0) {/* ADC offset when battery is discharger*/volt[i] = max11801_read_adc()-offset_discharger; //没有充电情况下 电压误差} else {if (data->charger_online == 1)volt[i] = max11801_read_adc()-offset_charger;//DC充电式 电压误差else if (data->usb_charger_online == 1)volt[i] = max11801_read_adc()-offset_usb_charger;//usb充电 电压误差else if (data->charger_online == 1 && data->usb_charger_online == 1)volt[i] = max11801_read_adc()-offset_charger;}}sort(volt, i, 4, cmp_func, NULL);//对电压排序for (i = 0; i < ADC_SAMPLE_COUNT; i++)pr_debug("volt_sorted[%2d]: %d\n", i, volt[i]);/* get the average of second max/min of remained. */voltage_data = (volt[2] + volt[ADC_SAMPLE_COUNT - 3]) / 2;//去掉最大值最小值 并对剩余数据求平均return voltage_data;}从上面函数我们读取到了正确的电压值。电池电压是随时变化的,我们要检测电池电量,必须随时采集,因此用一个定时器来做这件事情,代码如下:
INIT_DELAYED_WORK(&data->work, max8903_battery_work);schedule_delayed_work(&data->work, data->interval);
电压采集完成后就是将电压上报出去,上报的过程是:我们读取到电压变化->告诉android端电池电压变化了->android会通过power_supply设备文件来读取具体的电压值。
我们来看定时器回调函数
static void max8903_battery_work(struct work_struct *work){struct max8903_data *data;data = container_of(work, struct max8903_data, work.work);data->interval = HZ * BATTERY_UPDATE_INTERVAL;max8903_charger_update_status(data); //检测充电状态max8903_battery_update_status(data); //检测电池状态/* reschedule for the next time */schedule_delayed_work(&data->work, data->interval);//定时器继续}检测电池状态函数
static void max8903_battery_update_status(struct max8903_data *data){int temp;static int temp_last;bool changed_flag;changed_flag = false;mutex_lock(&data->work_lock);temp = calibration_voltage(data);if (temp_last == 0) {data->voltage_uV = temp;temp_last = temp;}if (data->charger_online == 0 && temp_last != 0) {//DC充电状态if (temp < temp_last) {temp_last = temp;data->voltage_uV = temp;} else {data->voltage_uV = temp_last;}}if (data->charger_online == 1 || data->usb_charger_online == 1) {//USB充电状态和DC充电状态data->voltage_uV = temp;temp_last = temp;}data->percent = calibrate_battery_capability_percent(data);//计算电量的百分比if (data->percent != data->old_percent) { //电池电压有变化data->old_percent = data->percent;changed_flag = true;}if (changed_flag) { //如果有变化changed_flag = false;power_supply_changed(&data->bat);//告诉android端 电池电量改变了}/* because boot time gap between led framwork and charger framwork,when system boots with charger attatched, charger led framwork loses the first charger online event,add once extra power_supply_changed can fix this issure*/if (data->first_delay_count < 200) {data->first_delay_count = data->first_delay_count + 1 ;power_supply_changed(&data->bat);}mutex_unlock(&data->work_lock);}这里我们看到了 power_supply_changed(&data->bat);告诉android端 电池电量改变了,那么下一步android来读取具体电压,就涉及到了power_supply设备文件。
来看设备文件的建立过程
data->bat.name = "max8903-charger";data->bat.type = POWER_SUPPLY_TYPE_BATTERY;data->bat.properties = max8903_battery_props;data->bat.num_properties = ARRAY_SIZE(max8903_battery_props);data->bat.get_property = max8903_battery_get_property;data->bat.use_for_apm = 1;retval = power_supply_register(&pdev->dev, &data->bat);//注册设备文件if (retval) {dev_err(data->dev, "failed to register battery\n");goto battery_failed;}这里注册了一个名为max8903-charger的 power_supply设备文件,这个设备文件包含了ARRAY_SIZE(max8903_battery_props)个操作分别为
static enum power_supply_property max8903_battery_props[] = {POWER_SUPPLY_PROP_VOLTAGE_NOW,//当前电压POWER_SUPPLY_PROP_STATUS, //当前充电状态POWER_SUPPLY_PROP_PRESENT, //不太清除POWER_SUPPLY_PROP_CAPACITY, //电量百分比POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,//电池极限电压 最大值POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,//电池极限电压 最小值POWER_SUPPLY_PROP_HEALTH, //电池健康状态POWER_SUPPLY_PROP_CAPACITY_LEVEL,//电量水平,low或者normal};这些状态是通过max8903_battery_get_property()这个函数来读取的
static int max8903_battery_get_property(struct power_supply *bat, enum power_supply_property psp, union power_supply_propval *val){struct max8903_data *di = container_of(bat,struct max8903_data, bat);switch (psp) {case POWER_SUPPLY_PROP_STATUS:val->intval = POWER_SUPPLY_STATUS_UNKNOWN;if (gpio_get_value(di->pdata->chg) == 0) {di->battery_status = POWER_SUPPLY_STATUS_CHARGING; //正在充电} else if (di->ta_in &&gpio_get_value(di->pdata->chg) == 1) {if (di->percent >= 99)di->battery_status = POWER_SUPPLY_STATUS_FULL;//电量大于99就充满了elsedi->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING;} else if (di->usb_in &&gpio_get_value(di->pdata->chg) == 1) {if (di->percent >= 99) di->battery_status = POWER_SUPPLY_STATUS_FULL;else di->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING;}val->intval = di->battery_status;return 0;default:break;}switch (psp) {case POWER_SUPPLY_PROP_VOLTAGE_NOW:val->intval = di->voltage_uV;break;case POWER_SUPPLY_PROP_CHARGE_NOW:val->intval = 0;break;case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:val->intval = HIGH_VOLT_THRESHOLD;break;case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:val->intval = LOW_VOLT_THRESHOLD;break;case POWER_SUPPLY_PROP_PRESENT:val->intval = 1;break;case POWER_SUPPLY_PROP_CAPACITY:val->intval = di->percent < 0 ? 0 :(di->percent > 100 ? 100 : di->percent);break;case POWER_SUPPLY_PROP_HEALTH:val->intval = POWER_SUPPLY_HEALTH_GOOD;if (di->fault)val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;break;case POWER_SUPPLY_PROP_CAPACITY_LEVEL:if (di->battery_status == POWER_SUPPLY_STATUS_FULL)val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;else if (di->percent <= 15)val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;//电量小于15%就报低电量elseval->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;//否则就报正常break;default:return -EINVAL;}return 0;}当我们注册设备文件以后,可以在/sys/devices/platform/max8903-charger.1/power_supply/max8903-charger目录下找到其设备文件如下
我们通过cat命令就可以随时查看电池状态。
二、电池电压校正参数
上面我们知道根据硬件实际情况不同,AD采集出来的电池电压需要校正参数。也就是
static int offset_discharger;
static int offset_charger;
static int offset_usb_charger;
对于这三个参数,当然我们可以在驱动力写死,但是为了以后的兼容性我们可以通过android上层来设置,当我们设备出厂时候,通过一配置文件方便的来修改这三个参数,下面我们就来介绍一下,怎么用设备文件和脚本,来修改者三个参数:
我们用的是sys文件系统的设备文件,创建代码为
ret = device_create_file(&pdev->dev, &max8903_discharger_dev_attr);if (ret)dev_err(&pdev->dev, "create device file failed!\n");ret = device_create_file(&pdev->dev, &max8903_charger_dev_attr);if (ret)dev_err(&pdev->dev, "create device file failed!\n");ret = device_create_file(&pdev->dev, &max8903_usb_charger_dev_attr);if (ret)dev_err(&pdev->dev, "create device file failed!\n");设备文件的实现代码为
static ssize_t max8903_voltage_offset_discharger_show(struct device *dev, struct device_attribute *attr, char *buf){return sprintf(buf, "read offset_discharger:%04d\n",offset_discharger);}static ssize_t max8903_voltage_offset_discharger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){offset_discharger = simple_strtoul(buf, NULL, 10);pr_info("read offset_discharger:%04d\n", offset_discharger);return count;}static ssize_t max8903_voltage_offset_charger_show(struct device *dev, struct device_attribute *attr, char *buf){return sprintf(buf, "read offset_charger:%04d\n",offset_charger);}static ssize_t max8903_voltage_offset_charger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){offset_charger = simple_strtoul(buf, NULL, 10);pr_info("read offset_charger:%04d\n", offset_charger);return count;}static ssize_t max8903_voltage_offset_usb_charger_show(struct device *dev, struct device_attribute *attr, char *buf){return sprintf(buf, "read offset_usb_charger:%04d\n",offset_usb_charger);}static ssize_t max8903_voltage_offset_usb_charger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){offset_usb_charger = simple_strtoul(buf, NULL, 10);pr_info("read offset_charger:%04d\n", offset_usb_charger);return count;}static struct device_attribute max8903_discharger_dev_attr = {.attr = { .name = "max8903_ctl_offset_discharger", .mode = S_IRUSR | S_IWUSR, },.show = max8903_voltage_offset_discharger_show,.store = max8903_voltage_offset_discharger_store,};static struct device_attribute max8903_charger_dev_attr = {.attr = { .name = "max8903_ctl_offset_charger", .mode = S_IRUSR | S_IWUSR, },.show = max8903_voltage_offset_charger_show,.store = max8903_voltage_offset_charger_store,};static struct device_attribute max8903_usb_charger_dev_attr = {.attr = { .name = "max8903_ctl_offset_usb_charger", .mode = S_IRUSR | S_IWUSR, },.show = max8903_voltage_offset_usb_charger_show,.store = max8903_voltage_offset_usb_charger_store,};这样,我们就可以在/sys/devices/platform/max8903-charger.1目录下看到这样三个设备文件
我们用cat命令可以读出当前值,用echo "500">>max8903_ctl_offset_charger 可以修改当前值
这样我们就可以在系统启动的时候,用脚本来自动修改者三个值,我用的办法是在init.rc的on boot阶段增加这么三行
#battery charge write /sys/devices/platform/max8903-charger.1/max8903_ctl_offset_charger 150 write /sys/devices/platform/max8903-charger.1/max8903_ctl_offset_discharger 200 write /sys/devices/platform/max8903-charger.1/max8903_ctl_offset_usb_charger 250当然大家也可以把这三行命令写在另外一个脚本里,然后init.rc中调用
三、电池充电
电池充电的电路
一共有4个引脚输出到cpu中:
CHG_FLT1_B 电池检测错误
UOK_B usb插入
DOK_B DC插入
CHG_STATUS1_B 充电状态
对于充电状态的检测过程,和电量检测基本相同, 检测到状态变化->告诉android层发生变化->android层通过设备文件来读取变化值
知道了这些我们来看驱动,首先在board文件中添加max8903设备
static struct max8903_pdata charger1_data = {.dok = SABRESD_CHARGE_DOK_B,.uok = SABRESD_CHARGE_UOK_B,.chg = CHARGE_STATE2,.flt = CHARGE_STATE1,.dcm_always_high = true,.dc_valid = true,.usb_valid = true, };static struct platform_device sabresd_max8903_charger_1 = {.name= "max8903-charger",.id= 1,.dev= {.platform_data = &charger1_data,},};
platform_device_register(&sabresd_max8903_charger_1);然后在/derivers/power/目录下添加驱动文件。充电状态的变化都是IO电平的变化,我们来看驱动是怎么处理这4个io的,首先在probe函数中
申请IO
if (pdata->dc_valid) {if (pdata->dok && gpio_is_valid(pdata->dok)) {gpio = pdata->dok; /* PULL_UPed Interrupt *//* set DOK gpio input */ret = gpio_request(gpio, "max8903-DOK");if (ret) {printk(KERN_ERR"request max8903-DOK error!!\n");goto err;} else {gpio_direction_input(gpio);}ta_in = gpio_get_value(gpio) ? 0 : 1;} else if (pdata->dok && gpio_is_valid(pdata->dok) && pdata->dcm_always_high) {ta_in = pdata->dok; /* PULL_UPed Interrupt */ta_in = gpio_get_value(gpio) ? 0 : 1;} else {dev_err(dev, "When DC is wired, DOK and DCM should"" be wired as well."" or set dcm always high\n");ret = -EINVAL;goto err;}}if (pdata->usb_valid) {if (pdata->uok && gpio_is_valid(pdata->uok)) {gpio = pdata->uok;/* set UOK gpio input */ret = gpio_request(gpio, "max8903-UOK");if (ret) {printk(KERN_ERR"request max8903-UOK error!!\n");goto err;} else {gpio_direction_input(gpio);}usb_in = gpio_get_value(gpio) ? 0 : 1;} else {dev_err(dev, "When USB is wired, UOK should be wired.""as well.\n");ret = -EINVAL;goto err;}}if (pdata->chg) {if (!gpio_is_valid(pdata->chg)) {dev_err(dev, "Invalid pin: chg.\n");ret = -EINVAL;goto err;}/* set CHG gpio input */ret = gpio_request(pdata->chg, "max8903-CHG");if (ret) {printk(KERN_ERR"request max8903-CHG error!!\n");goto err;} else {gpio_direction_input(pdata->chg);}}if (pdata->flt) {if (!gpio_is_valid(pdata->flt)) {dev_err(dev, "Invalid pin: flt.\n");ret = -EINVAL;goto err;}/* set FLT gpio input */ret = gpio_request(pdata->flt, "max8903-FLT");if (ret) {printk(KERN_ERR"request max8903-FLT error!!\n");goto err;} else {gpio_direction_input(pdata->flt);}}if (pdata->usus) {if (!gpio_is_valid(pdata->usus)) {dev_err(dev, "Invalid pin: usus.\n");ret = -EINVAL;goto err;}}注册DC充电的设备文件
mutex_init(&data->work_lock);data->fault = false;data->ta_in = ta_in;data->usb_in = usb_in;data->psy.name = "max8903-ac";data->psy.type = POWER_SUPPLY_TYPE_MAINS;data->psy.get_property = max8903_get_property;data->psy.properties = max8903_charger_props;data->psy.num_properties = ARRAY_SIZE(max8903_charger_props);ret = power_supply_register(dev, &data->psy);if (ret) {dev_err(dev, "failed: power supply register.\n");goto err_psy;}注册USB充电的设备文件
data->usb.name = "max8903-usb";data->usb.type = POWER_SUPPLY_TYPE_USB;data->usb.get_property = max8903_get_usb_property;data->usb.properties = max8903_charger_props;data->usb.num_properties = ARRAY_SIZE(max8903_charger_props);ret = power_supply_register(dev, &data->usb);if (ret) {dev_err(dev, "failed: power supply register.\n");goto err_psy;}这两个设备文件都只有一个操作:检测充电器是否在线
static enum power_supply_property max8903_charger_props[] = {POWER_SUPPLY_PROP_ONLINE,};操作函数也很简单
static int max8903_get_property(struct power_supply *psy,enum power_supply_property psp,union power_supply_propval *val){struct max8903_data *data = container_of(psy,struct max8903_data, psy);switch (psp) {case POWER_SUPPLY_PROP_ONLINE:val->intval = 0;if (data->ta_in)val->intval = 1;data->charger_online = val->intval;break;default:return -EINVAL;}return 0;}static int max8903_get_usb_property(struct power_supply *usb,enum power_supply_property psp,union power_supply_propval *val){struct max8903_data *data = container_of(usb,struct max8903_data, usb);switch (psp) {case POWER_SUPPLY_PROP_ONLINE:val->intval = 0;if (data->usb_in)val->intval = 1;data->usb_charger_online = val->intval;break;default:return -EINVAL;}return 0;}我们可以通过/sys/devices/platform/max8903-charger.1/power_supply/max8903-ac 目录和/sys/devices/platform/max8903-charger.1/power_supply/max8903-usb目录下的设备文件来访问充电器的状态
接下来是IO中断
if (pdata->dc_valid) {ret = request_threaded_irq(gpio_to_irq(pdata->dok),NULL, max8903_dcin,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"MAX8903 DC IN", data);if (ret) {dev_err(dev, "Cannot request irq %d for DC (%d)\n",gpio_to_irq(pdata->dok), ret);goto err_usb_irq;}}if (pdata->usb_valid) {ret = request_threaded_irq(gpio_to_irq(pdata->uok),NULL, max8903_usbin,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"MAX8903 USB IN", data);if (ret) {dev_err(dev, "Cannot request irq %d for USB (%d)\n",gpio_to_irq(pdata->uok), ret);goto err_dc_irq;}}if (pdata->flt) {ret = request_threaded_irq(gpio_to_irq(pdata->flt),NULL, max8903_fault,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"MAX8903 Fault", data);if (ret) {dev_err(dev, "Cannot request irq %d for Fault (%d)\n",gpio_to_irq(pdata->flt), ret);goto err_flt_irq;}}if (pdata->chg) {ret = request_threaded_irq(gpio_to_irq(pdata->chg),NULL, max8903_chg,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"MAX8903 Fault", data);if (ret) {dev_err(dev, "Cannot request irq %d for Fault (%d)\n",gpio_to_irq(pdata->flt), ret);goto err_chg_irq;}}这4个IO的中断处理函数很类似
static irqreturn_t max8903_dcin(int irq, void *_data){struct max8903_data *data = _data;struct max8903_pdata *pdata = data->pdata;bool ta_in;ta_in = gpio_get_value(pdata->dok) ? false : true; //保存当前dok值if (ta_in == data->ta_in)return IRQ_HANDLED;data->ta_in = ta_in;pr_info("TA(DC-IN) Charger %s.\n", ta_in ?"Connected" : "Disconnected");max8903_charger_update_status(data); max8903_battery_update_status(data); power_supply_changed(&data->psy); //报告状态改变power_supply_changed(&data->bat);return IRQ_HANDLED;}static irqreturn_t max8903_usbin(int irq, void *_data){struct max8903_data *data = _data;struct max8903_pdata *pdata = data->pdata;bool usb_in;usb_in = gpio_get_value(pdata->uok) ? false : true; //保存当前uok值if (usb_in == data->usb_in)return IRQ_HANDLED;data->usb_in = usb_in;max8903_charger_update_status(data);max8903_battery_update_status(data);pr_info("USB Charger %s.\n", usb_in ?"Connected" : "Disconnected");power_supply_changed(&data->bat);power_supply_changed(&data->usb); //报告状态改变return IRQ_HANDLED;}static irqreturn_t max8903_fault(int irq, void *_data){struct max8903_data *data = _data;struct max8903_pdata *pdata = data->pdata;bool fault;fault = gpio_get_value(pdata->flt) ? false : true; //保存当前电池错误值if (fault == data->fault)return IRQ_HANDLED;data->fault = fault;if (fault)dev_err(data->dev, "Charger suffers a fault and stops.\n");elsedev_err(data->dev, "Charger recovered from a fault.\n");max8903_charger_update_status(data);max8903_battery_update_status(data);power_supply_changed(&data->psy);power_supply_changed(&data->bat);power_supply_changed(&data->usb); //报告状态改变return IRQ_HANDLED;}static irqreturn_t max8903_chg(int irq, void *_data){struct max8903_data *data = _data;struct max8903_pdata *pdata = data->pdata;int chg_state;chg_state = gpio_get_value(pdata->chg) ? false : true;//保存电池充电状态if (chg_state == data->chg_state)return IRQ_HANDLED;data->chg_state = chg_state;max8903_charger_update_status(data);max8903_battery_update_status(data);power_supply_changed(&data->psy);power_supply_changed(&data->bat);power_supply_changed(&data->usb);//报告状态改变return IRQ_HANDLED;}到了这里电池充电的流程就走完了。
更多相关文章
- Cocos2d-x学习之创建Android工程和编译
- Android用户界面开发(11):Menu
- android解析xml文件的方式(其三)
- 纯ant命令行打包android apk之图文从原理角度完全详解android打
- mac Android(安卓)Studio 真机测试 配置
- android 写入文件到SD卡中去
- 如何将打开res raw目录中的数据库文件?
- 将Android移植到FS2410开发板上
- Android(安卓)Bluetooth HID实现详解