本文转载自:http://blog.csdn.net/xubin341719/article/details/8969369

电池电量计,库仑计,用max17040这颗电量IC去计量电池电量,这种方法比较合理。想起比较遥远的年代,做samsung s5pc110/sp5v210的时候,计量电量用一个AD口加两个分压电阻就做了,低电量的时候系统一直判断不准确,“低电关机”提示一会有,一会没有,客户那个郁闷呀,“到底是有电还是没电?”。

如下图,通过两个分压电阻,和一个AD脚去侦测VCC(电池)电压。

一、MAX17040的工作原理

电量计MAX17040,他通过芯片去测量电池电量,芯片本身集成的电路比较复杂,同时可以通过软件上的一些算法去实现一些处理,是测量出的电量更加准确。还有一个好处,就是他之接输出数字量,通过IIC直接读取,我们在电路设计、程序处理上更加的统一化。

如下图所示,MAX17040和电池盒主控的关系,一个AD脚接到电池VBAT+,检测到的电量信息,通过IIC传到主控。

下面是电路图,电路接口比较简单,VBAT+,接到max17040的CELL,IIC接到主控的IIC2接口,这个我们在程序中要配置。看这个器件比较简单吧。

看下max17040的内部结构,其实这也是一个AD转换的过程,单独一颗芯片去实现,这样看起来比较专业些。CELL接口,其实就是一个ADC转换的引脚,我们可以看到芯片内部有自己的时钟(time base),IIC控制器之类的,通过CELL采集到的模拟量,转换成数字量,传输给主控。

通过上面的介绍Max17040的硬件、原理我们基本上都了解了,比较简单,下面我们就重点去分析下驱动程序。

二、MAX17040 总体流程

电量计的工作流程比较简单,max17040通过CELL ADC转换引脚,把电池的相关信息,实时读取,存入max17040相应的寄存器,驱动申请一个定时器,记时结束,通过IIC去读取电池状态信息,和老的电池信息对比,如果用变化上报,然后重新计时;这样循环操作,流程如下所示:

三、MAX17040这个电量计驱动,我们主要用到以下知识点

1、IIC的注册(这个在TP、CAMERA中都有分析);

2、Linux 中定时器的使用;

3、任务初始化宏;

4、linux定时器调度队列;

5、max17040测到电量后如何上传到系统(这个电池系统中有简要的分析);

6、AC、USB充电状态的上报,这个和电池电量是一种方法。

7、电池曲线的测量与加入;

1、IIC的注册

IIC这个总线,在工作中用的比较多,TP、CAMERA、电量计、充电IC、音频芯片、电源管理芯片、基本所有的传感器,所以这大家要仔细看下,后面有时间的话单独列一片介绍下IIC,从单片机时代都用的比较多,看来条总线的生命力很强,像C语言一样,很难被同类的东西替代到,至少现在应该是这样的。

看下他结构体的初始化与驱动的申请,这个比较统一,这里就不想想解释了。

(1)、IIC驱动的注册:

 static const struct i2c_device_id max17040_id[] = {
{ "max17040", },
{ }
};
MODULE_DEVICE_TABLE(i2c, max17040_id); static struct i2c_driver max17040_i2c_driver = {
.driver = {
.name = "max17040",
},
.probe = max17040_probe,
.remove = __devexit_p(max17040_remove),
.suspend = max17040_suspend,
.resume = max17040_resume,
.id_table = max17040_id,
}; static int __init max17040_init(void)
{
printk("MAX17040 max17040_init !!\n");
wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present");
return i2c_add_driver(&max17040_i2c_driver);
}
module_init(max17040_init);
 

(2)在arch/arm/mach-exynos/mach-smdk4x12.c中,IC平台驱动的注册:

 static struct i2c_board_info i2c_devs2[] __initdata = {
#if defined(CONFIG_BATTERY_MAX17040)
{
I2C_BOARD_INFO("max17040", 0x36),//IIC地址;
.platform_data = &max17040_platform_data,
},
#endif
……………………
};

下图就是我们IIC驱动注册生成的文件;

/sys/bus/i2c/drivers/max17040

2、linux 中定时器的使用

定时器,就是定一个时间, 比如:申请一个10秒定时器,linux系统开始计时,到10秒,请示器清零重新计时并发出信号告知系统计时完成,系统接到这个信号,做相应的处理;

3、任务初始化宏

任务结构体的初始化完成后,接下来要将任务安排进工作队列。 可采用多种方法来完成这一操作。 首先,利用 queue_work 简单地将任务安排进工作队列(这将任务绑定到当前的 CPU)。 或者,可以通过 queue_work_on 来指定处理程序在哪个 CPU 上运行。 两个附加的函数为延迟任务提供相同的功能(其结构体装入结构体 work_struct 之中,并有一个 计时器用于任务延迟 )。

4、linux定时器调度队列

 INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);
schedule_delayed_work(&chip->work, MAX17040_DELAY);
通过定时器调度队列;

5、max17040测到电量后如何上传到系统(这个电池系统中有简要的分析);

4中的定时器记时完成,就可以调度队列,chip->work执行:max17040_work函数,把改读取的信息上传,我们看下max17040_work函数的实现:

(1)、保存老的电池信息,如电量、AC、USB是否插入

(2)、读取电池新的状态信息

(3)、如果电池信息有变化,就上报系统

power_supply_changed这个函数比较重要, 我们后面分析;

(4)、如果用PM2301充电IC,USB充电功能不用

这个是由于我们的系统耗电比较大,用USB充电时,电流过小,所以出现越充越少的现象,所以这个功能给去掉了。

(5)、如果有DC插入,则跟新充电状态

6、AC、USB充电状态怎么更新到应用

如上面所说,通过power_supply_changed上报;

7、电池曲线的测量与加入

电池曲线,就是电池的冲放电信息,就是用专业的设备,对电池连续充放电几天,测出一个比较平均的值。然后转换成针对电量IC(如我们用的max17040)的数字量,填入一个数组中,如下图所示:

下面数据时针对电池曲线的数字量,和相关参数。如上图所示,为160小时的电池信息,包括:不同颜色分别代表不同的曲线:如temperature ,reference SOC ,fuel gauge SOC,Vcell,Empty Voltage

数据表格如下:

 Device=MAX17040
Title = 1055_2_113012
EmptyAdjustment =
FullAdjustment=
RCOMP0=
TempCoUp =
TempCoDown = -
OCVTest =
SOCCheckA =
SOCCheckB =
bits=
0xC2 0xE8 0x0D 0x37 0x51 0x5B 0x5E 0x62
0x6A 0x88 0xA6 0xCB 0xF1 0x3C 0x99 0x1A
0x60 0x0D 0x80 0x0D 0xA0 0x01 0xC0 0x0C
0xF0 0x0F 0x30 0x0F 0x90 0x06 0x10 0x06 0xAC 0x20 0xAE 0x80 0xB0 0xD0 0xB3 0x70
0xB5 0x10 0xB5 0xB0 0xB5 0xE0 0xB6 0x20
0xB6 0xA0 0xB8 0x80 0xBA 0x60 0xBC 0xB0
0xBF 0x10 0xC3 0xC0 0xC9 0x90 0xD1 0xA0
0x02 0x90 0x0E 0x00 0x0C 0x10 0x0E 0x20
0x2C 0x60 0x4C 0xB0 0x39 0x80 0x39 0x80
0x0C 0xD0 0x0C 0xD0 0x0A 0x10 0x09 0xC0
0x08 0xF0 0x07 0xF0 0x05 0x60 0x05 0x60 0xC0 0x09 0xE0 0x00 0x00 0x01 0x30 0x02
0x52 0x06 0x54 0x0B 0x53 0x080x63 0x08
0x29 0xE0 0xC1 0xE2 0xC6 0xCB 0x98 0x98
0xCD 0xCD 0xA1 0x9C 0x8F 0x7F 0x56 0x56
 

加入驱动中的值:

/driver/power/max17040_common.c中

 unsigned char model_data[] = {
0x40, /* 1st field is start reg address, others are model parameters */
0xAC, 0x20,0xAE, 0x80, 0xB0, 0xD0, 0xB3, 0x70,
0xB5, 0x10, 0xB5, 0xB0, 0xB5, 0xE0,0xB6, 0x20,
0xB6, 0xA0, 0xB8, 0x80, 0xBA, 0x60, 0xBC, 0xB0,
0xBF, 0x10, 0xC3, 0xC0, 0xC9, 0x90, 0xD1, 0xA0,
0x02, 0x90, 0x0E, 0x00, 0x0C, 0x10,0x0E, 0x20,
0x2C, 0x60,0x4C, 0xB0, 0x39, 0x80, 0x39, 0x80,
0x0C, 0xD0,0x0C, 0xD0, 0x0A, 0x10,0x09, 0xC0,
0x08, 0xF0, 0x07, 0xF0, 0x05, 0x60, 0x05, 0x60,
}; unsigned char INI_OCVTest_High_Byte = 0xDB; //
unsigned char INI_OCVTest_Low_Byte = 0xA0;
unsigned char INI_SOCCheckA = 0x71;//
unsigned char INI_SOCCheckB = 0x73;//
unsigned char INI_RCOMP = 0xa1;//
unsigned char INI_bits = ;
unsigned char original_OCV_1;
unsigned char original_OCV_2;
#elseunsigned char INI_RCOMP = 0x64;
unsigned char INI_bits = ;
unsigned char original_OCV_1;
<strong>unsigned char original_OCV_2;</strong>

四、驱动分析

1、Probe函数分析

上面我们简单了解驱动中用到的主要知识点,后面我们把这些点串起来,驱动还是从probe说起;

 static int __devinit max17040_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct max17040_chip *chip;
int ret;
printk("MAX17040 probe !!\n"); if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
return -EIO; chip = kzalloc(sizeof(*chip), GFP_KERNEL); if (!chip)
return -ENOMEM; g_chip = chip;
g_i2c_client = client;//(1)、IIC 驱动部分client 申请; chip->client = client;
chip->pdata = client->dev.platform_data;
i2c_set_clientdata(client, chip);
chip->battery.name = "battery";//(2)、chip name;
chip->battery.type = POWER_SUPPLY_TYPE_BATTERY;
chip->battery.get_property = max17040_get_property;//(3)、获取电池信息;
chip->battery.properties = max17040_battery_props;//(4)、电池各种信息;
chip->battery.num_properties = ARRAY_SIZE(max17040_battery_props);
chip->battery.external_power_changed = NULL;
ret = power_supply_register(&client->dev, &chip->battery);//(5)、battery加入power_supply
if (ret)
goto err_battery_failed; chip->ac.name = "ac"
chip->ac.type = POWER_SUPPLY_TYPE_MAINS;
chip->ac.get_property = adapter_get_property;
chip->ac.properties = adapter_get_props;
chip->ac.num_properties = ARRAY_SIZE(adapter_get_props);
chip->ac.external_power_changed = NULL;
ret = power_supply_register(&client->dev, &chip->ac);//(6)、和battery相似,把ac加入power_supply
if (ret)
goto err_ac_failed; #if !defined(CONFIG_CHARGER_PM2301)
chip->usb.name = "usb";
chip->usb.type = POWER_SUPPLY_TYPE_USB;
chip->usb.get_property = usb_get_property;
chip->usb.properties = usb_get_props;
chip->usb.num_properties = ARRAY_SIZE(usb_get_props);
chip->usb.external_power_changed = NULL;
ret = power_supply_register(&client->dev, &chip->usb);//(7)、和battery相似,把usb加入power_supply
if (ret)
goto err_usb_failed; if (chip->pdata->hw_init && !(chip->pdata->hw_init())) {
dev_err(&client->dev, "hardware initial failed.\n");
goto err_hw_init_failed;
}
#endif #ifdef MAX17040_SUPPORT_CURVE
g_TimeCount = ;
handle_model();
#endif
max17040_get_version(client);
battery_initial = ; INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);//(8)、任务宏初始化,max17040加入chip->work队列;
schedule_delayed_work(&chip->work, MAX17040_DELAY);//(9)、通过定时器调度队列; printk("MAX17040 probe success!!\n");
return ; err_hw_init_failed:
power_supply_unregister(&chip->usb);
err_usb_failed:
power_supply_unregister(&chip->ac);
err_ac_failed:
power_supply_unregister(&chip->battery);
err_battery_failed:
dev_err(&client->dev, "failed: power supply register\n");
i2c_set_clientdata(client, NULL);
kfree(chip);
return ret;
}

(1)、IIC 驱动部分client 申请;

(2)、chip name;

(3)、获取电池信息;

通过传递下来的参数,来读取结构体中相应的状态,这个函数实现比较简单。

(4)电池各种信息

 static enum power_supply_property max17040_battery_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_STATUS,
/*POWER_SUPPLY_PROP_ONLINE,*/
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_TEMP,
};

(5)、battery加入power_supply;

(6)、和battery相似,把ac加入power_supply;

(7)、和battery相似,把usb加入power_supply;

(8)、max17040加入chip->work队列;

前面已经分析;

(9)、通过定时器调度队列;

前面已经分析;

2、power_supply_changed简要分析

如:把电池电量信息上报:我们在max17040_work队列调度函数中, 如果有电池信息、状态变化,则上用power_supply_changed上报。

Kernel/drivers/power/power_supply_core.c中:

void power_supply_changed(struct power_supply *psy)
{
unsigned long flags; dev_dbg(psy->dev, "%s\n", __func__); spin_lock_irqsave(&psy->changed_lock, flags);
psy->changed = true;
wake_lock(&psy->work_wake_lock);
spin_unlock_irqrestore(&psy->changed_lock, flags);
schedule_work(&psy->changed_work);//调度psy->changed_work
}
Psy->changed_work的执行函数:
static void power_supply_changed_work(struct work_struct *work)
{
unsigned long flags;
struct power_supply *psy = container_of(work, struct power_supply,
changed_work); dev_dbg(psy->dev, "%s\n", __func__); spin_lock_irqsave(&psy->changed_lock, flags);
if (psy->changed) {
psy->changed = false;
spin_unlock_irqrestore(&psy->changed_lock, flags); class_for_each_device(power_supply_class, NULL, psy,
__power_supply_changed_work); power_supply_update_leds(psy); kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);//uevent状态
spin_lock_irqsave(&psy->changed_lock, flags);
}
if (!psy->changed)
wake_unlock(&psy->work_wake_lock);
spin_unlock_irqrestore(&psy->changed_lock, flags);
}

android电池(四):电池 电量计(MAX17040)驱动分析篇【转】的更多相关文章

  1. 【转】android电池(四):电池 电量计(MAX17040)驱动分析篇

    关键词:android 电池  电量计  MAX17040 任务初始化宏 power_supply 平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台: ...

  2. 【转】android电池(五):电池 充电IC(PM2301)驱动分析篇

    关键词:android 电池  电量计  PL2301任务初始化宏 power_supply 中断线程化 平台信息:内核:linux2.6/linux3.0系统:android/android4.0  ...

  3. android电池(五):电池 充电IC(PM2301)驱动分析篇【转】

    本文转载自:http://blog.csdn.net/xubin341719/article/details/8970363 android充电这块,有的电源管理芯片内部包含充电管理,如s5pv210 ...

  4. 【转】android 电池(一):锂电池基本原理篇

    关键词:android  电池关机充电 androidboot.mode charger 平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台:S5PV3 ...

  5. 【转】android 电容屏(三):驱动调试之驱动程序分析篇

    关键词:android  电容屏 tp 工作队列 中断 坐点计算  电容屏主要参数平台信息:内核:linux2.6/linux3.0系统:android/android4.0  平台:S5PV310( ...

  6. [Android6.0][RK3399] 电池系统(三)电量计 CW2015 驱动流程分析【转】

    本文转载自:http://blog.csdn.net/dearsq/article/details/72770295 Platform: RK3399 OS: Android 6.0 Kernel: ...

  7. 【转】Android LCD(四):LCD驱动调试篇

    关键词:android LCD TFTSN75LVDS83B  TTL-LVDS LCD电压背光电压 平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台 ...

  8. 【转】android camera(四):camera 驱动 GT2005

    关键词:android  camera CMM 模组 camera参数  GT2005 摄像头常见问题 平台信息: 内核:linux系统:android 平台:S5PV310(samsung exyn ...

  9. Android : 跟我学Binder --- (4) 驱动情景分析

    目录: Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制? Android : 跟我学Binder --- (2) AIDL分析及手动实现 ...

随机推荐

  1. CentOS4.5下LVS方案

    环境描述:本文在配置LVS时使用三台linux,一台做Directorserver (192.168.0.25) ,两台做realserver(192.168.0.127 192.168.0.12,在 ...

  2. linux下OpenSSL的RSA密钥生成

    工具的安装: 一.源码安装 OpenSSL Version:openssl-1.0.0e.tar.gz ------------------------安装: 1.将下载的压缩包放在根目录, 2.在文 ...

  3. stage3D基础五-----Working with 3D cameras(转)

    原文地址:http://www.adobe.com/cn/devnet/flashplayer/articles/3d-cameras.html 原文是英文的,这里就不贴了,内容主要介绍直接使用相机坐 ...

  4. BMFont中文字体图集制作的方法~(for unity ngui)

    BMFont中文字体图集制作的方法~(for unity ngui) 好吧~似乎这个问题困扰了很多人,游戏开始中文化是个不错的事儿啊,这里我就做下说明,如何制作中文字体图集~ 这里的字库图集的制作更多 ...

  5. PHP代码中使用post参数上传大文件

    今天连续碰到了两个同事向我反应上传大文件(8M)失败的事情! 都是在PHP代码中通常使用post参数进行上传文件时,当文件的大小大于8M时,上传不能不成功. 首先,我想到了nginx的client_m ...

  6. 怎样查看Eclipse是32位还是64位?

    怎样查看Eclipse是32位还是64位? 1.去Eclipse的安装文件夹,找到eclipse.ini 2.打开这个文件.寻找:launcher.library,我的机器上,在第二行 3.查看&qu ...

  7. Linux的各个文件夹名称解释(FHS)

    对于接触和已经接触过一段时间Linux的使用者来说,系统的各个文件夹名字还是挺让人费解的,什么etc,usr,var等等,大部分也是耳濡目染才有一个大概的概念,例如usr是存放自己编译安装的软件,et ...

  8. TP5.0中的小知识总结

    2017年6月26日15:01:231.input    获取输入数据 支持默认值和过滤:接收用户在前台输入的数据,可以是get方式也可以是post方式.2.ThinkPHP5.0内置了分页实现,要给 ...

  9. java基础之【堆、栈、方法区】结构图

    |--数组实例化过程 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHViaWFvXzA2MTg=/font/5a6L5L2T/fontsize/400/ ...

  10. 【BZOJ1125】[POI2008]Poc hash+map+SBT

    [BZOJ1125][POI2008]Poc Description n列火车,每条有l节车厢.每节车厢有一种颜色(用小写字母表示).有m次车厢交换操作.求:对于每列火车,在交换车厢的某个时刻,与其颜 ...