电池 电量计(MAX17040)驱动分析篇

关键词:android 电池  电量计  MAX17040 任务初始化宏 power_supply

平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0 
平台:samsung exynos 4210、exynos 4412 exynos 5250

作者:xubin341719(欢迎转载,请注明作者)


完整驱动代码&规格书下载:MAX17040_PL2301

android 电池(一):锂电池基本原理篇

android 电池(二):android关机充电流程、充电画面显示

android 电池(三):android电池系统

android电池(四):电池 电量计(MAX17040)驱动分析篇

android电池(五):电池 充电IC(PM2301)驱动分析篇 

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

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

电池 电量计(MAX17040)驱动分析篇_第1张图片

一、MAX17040的工作原理

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

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

电池 电量计(MAX17040)驱动分析篇_第2张图片

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

电池 电量计(MAX17040)驱动分析篇_第3张图片

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

电池 电量计(MAX17040)驱动分析篇_第4张图片

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

二、MAX17040 总体流程

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

电池 电量计(MAX17040)驱动分析篇_第5张图片

三、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驱动的注册:

[csharp]  view plain copy
  1. static const struct i2c_device_id max17040_id[] = {  
  2.     { "max17040", 0 },  
  3.     { }  
  4. };  
  5. MODULE_DEVICE_TABLE(i2c, max17040_id);  
  6.   
  7. static struct i2c_driver max17040_i2c_driver = {  
  8.     .driver = {  
  9.         .name   = "max17040",  
  10.     },  
  11.     .probe      = max17040_probe,  
  12.     .remove     = __devexit_p(max17040_remove),  
  13.     .suspend    = max17040_suspend,  
  14.     .resume     = max17040_resume,  
  15.     .id_table   = max17040_id,  
  16. };  
  17.   
  18. static int __init max17040_init(void)  
  19. {  
  20.     printk("MAX17040 max17040_init !!\n");  
  21.     wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present");  
  22.     return i2c_add_driver(&max17040_i2c_driver);  
  23. }  
  24. module_init(max17040_init);  

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

[csharp]  view plain copy
  1. static struct i2c_board_info i2c_devs2[] __initdata = {  
  2. #if defined(CONFIG_BATTERY_MAX17040)  
  3.     {  
  4.         I2C_BOARD_INFO("max17040", 0x36),//IIC地址;  
  5.         .platform_data = &max17040_platform_data,  
  6.     },  
  7. #endif  
  8. ……………………  
  9. };  

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

/sys/bus/i2c/drivers/max17040 

电池 电量计(MAX17040)驱动分析篇_第6张图片

2、linux 中定时器的使用

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

[csharp]  view plain copy
  1. #include <linux/delay.h>  
  2. #define MAX17040_DELAY          msecs_to_jiffies(5000)  
电池 电量计(MAX17040)驱动分析篇_第7张图片

3任务初始化宏

[csharp]  view plain copy
  1. INIT_WORK(work,func);  
  2. INTI_DELAYED_WORK(work,func);  
  3. INIT_DELAYED_WORK_DEFERRABLE(work,func);  

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

[csharp]  view plain copy
  1. INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);  
  2. 调度函数 max17040_work加入chip->work队列;  

4、linux定时器调度队列

[csharp]  view plain copy
  1. INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);  
  2. schedule_delayed_work(&chip->work, MAX17040_DELAY);  
  3. 通过定时器调度队列;  

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

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

[csharp]  view plain copy
  1. static void max17040_work(struct work_struct *work)  
  2. {  
  3.     struct max17040_chip *chip;  
  4.     int old_usb_online, old_online, old_vcell, old_soc;  
  5.     chip = container_of(work, struct max17040_chip, work.work);  
  6.  
  7. #ifdef MAX17040_SUPPORT_CURVE  
  8.     /* The module need to be update per hour (60*60)/3 = 1200 */  
  9.     if (g_TimeCount >= 1200) {  
  10.         handle_model(0);  
  11.         g_TimeCount = 0;  
  12.     }  
  13.     g_TimeCount++;  
  14. #endif  
  15.   
  16.     old_online = chip->online;//(1)、保存老的电池信息,如电量、AC、USB是否插入;  
  17.     old_usb_online = chip->usb_online;  
  18.     old_vcell = chip->vcell;  
  19.     old_soc = chip->soc;  
  20.     max17040_get_online(chip->client);//(2)、读取电池新的状态信息  
  21.     max17040_get_vcell(chip->client);  
  22.     max17040_get_soc(chip->client);  
  23.     max17040_get_status(chip->client);  
  24.   
  25.     if ((old_vcell != chip->vcell) || (old_soc != chip->soc)) {//(3)、如果电池信息有变化,就上报系统;  
  26.         /* printk(KERN_DEBUG "power_supply_changed for battery\n"); */  
  27.         power_supply_changed(&chip->battery);  
  28.     }  
  29.  
  30. #if !defined(CONFIG_CHARGER_PM2301)//(4)、如果用PM2301充电IC,USB充电功能不用;  
  31.     if (old_usb_online != chip->usb_online) {  
  32.         /* printk(KERN_DEBUG "power_supply_changed for usb\n"); */  
  33.   
  34.         power_supply_changed(&chip->usb);  
  35.     }  
  36. #endif  
  37.   
  38.     if (old_online != chip->online) {//(5)、如果有DC插入,则更新充电状态;  
  39.         /* printk(KERN_DEBUG "power_supply_changed for AC\n"); */  
  40.         power_supply_changed(&chip->ac);  
  41.     }  
  42.   
  43.     schedule_delayed_work(&chip->work, MAX17040_DELAY);  
  44. }  

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

[csharp]  view plain copy
  1. old_online = chip->online;  
  2. old_usb_online = chip->usb_online;  
  3. old_vcell = chip->vcell;  
  4. old_soc = chip->soc;  

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

[csharp]  view plain copy
  1. max17040_get_online(chip->client);//读取是否有AC插入;  
  2. max17040_get_vcell(chip->client);//读取电池电量;  
  3. max17040_get_soc(chip->client);  
  4. max17040_get_status(chip->client);//读取状态;  

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

[csharp]  view plain copy
  1. if ((old_vcell != chip->vcell) || (old_soc != chip->soc)) {  
  2.     /* printk(KERN_DEBUG "power_supply_changed for battery\n"); */  
  3.     power_supply_changed(&chip->battery);  
  4. }  

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

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

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

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

[csharp]  view plain copy
  1. power_supply_changed(&chip->ac);  

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

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

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

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

电池 电量计(MAX17040)驱动分析篇_第8张图片

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

数据表格如下:

[csharp]  view plain copy
  1. Device=MAX17040  
  2. Title = 1055_2_113012  
  3. EmptyAdjustment = 0  
  4. FullAdjustment= 100  
  5. RCOMP0=161  
  6. TempCoUp =0  
  7. TempCoDown = -2  
  8. OCVTest = 56224  
  9. SOCCheckA = 113  
  10. SOCCheckB = 115  
  11. bits= 18  
  12. 0xC2 0xE8 0x0D 0x37 0x51 0x5B 0x5E 0x62   
  13. 0x6A 0x88 0xA6 0xCB 0xF1 0x3C 0x99 0x1A   
  14. 0x60 0x0D 0x80 0x0D 0xA0 0x01 0xC0 0x0C   
  15. 0xF0 0x0F 0x30 0x0F 0x90 0x06 0x10 0x06   
  16.   
  17. 0xAC 0x20 0xAE 0x80 0xB0 0xD0 0xB3 0x70  
  18. 0xB5 0x10 0xB5 0xB0 0xB5 0xE0 0xB6 0x20   
  19. 0xB6 0xA0 0xB8 0x80 0xBA 0x60 0xBC 0xB0   
  20. 0xBF 0x10 0xC3 0xC0 0xC9 0x90 0xD1 0xA0   
  21. 0x02 0x90 0x0E 0x00 0x0C 0x10 0x0E 0x20   
  22. 0x2C 0x60 0x4C 0xB0 0x39 0x80 0x39 0x80   
  23. 0x0C 0xD0 0x0C 0xD0 0x0A 0x10 0x09 0xC0   
  24. 0x08 0xF0 0x07 0xF0 0x05 0x60 0x05 0x60   
  25.   
  26. 0xC0 0x09 0xE0 0x00 0x00 0x01 0x30 0x02   
  27. 0x52 0x06 0x54 0x0B 0x53 0x080x63  0x08   
  28. 0x29 0xE0 0xC1 0xE2 0xC6 0xCB 0x98 0x98   
  29. 0xCD 0xCD 0xA1 0x9C 0x8F 0x7F 0x56 0x56  

加入驱动中的值:

/driver/power/max17040_common.c

[csharp]  view plain copy
  1. unsigned char model_data[65] = {  
  2.     0x40,   /* 1st field is start reg address, others are model parameters */  
  3.     0xAC, 0x20,0xAE, 0x80, 0xB0, 0xD0, 0xB3, 0x70,  
  4.     0xB5, 0x10, 0xB5, 0xB0, 0xB5, 0xE0,0xB6, 0x20,  
  5.     0xB6, 0xA0, 0xB8, 0x80, 0xBA, 0x60, 0xBC, 0xB0,  
  6.     0xBF, 0x10, 0xC3, 0xC0, 0xC9, 0x90, 0xD1, 0xA0,  
  7.     0x02, 0x90, 0x0E, 0x00, 0x0C, 0x10,0x0E, 0x20,  
  8.     0x2C, 0x60,0x4C, 0xB0, 0x39, 0x80, 0x39, 0x80,  
  9.     0x0C, 0xD0,0x0C, 0xD0,  0x0A, 0x10,0x09, 0xC0,  
  10.     0x08, 0xF0, 0x07, 0xF0, 0x05, 0x60, 0x05, 0x60,  
  11. };  
  12.   
  13. unsigned char INI_OCVTest_High_Byte = 0xDB; //56224  
  14. unsigned char INI_OCVTest_Low_Byte = 0xA0;  
  15. unsigned char INI_SOCCheckA = 0x71;// 113  
  16. unsigned char INI_SOCCheckB = 0x73;//115  
  17. unsigned char INI_RCOMP = 0xa1;//161  
  18. unsigned char INI_bits = 18;  
  19. unsigned char original_OCV_1;  
  20. unsigned char original_OCV_2;  
  21. #elseunsigned char INI_RCOMP = 0x64;  
  22. unsigned char INI_bits = 19;  
  23. unsigned char original_OCV_1;  
  24. <strong>unsigned char original_OCV_2;</strong>  

四、驱动分析

1Probe函数分析

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

[csharp]  view plain copy
  1. static int __devinit max17040_probe(struct i2c_client *client,  
  2.                                     const struct i2c_device_id *id)  
  3. {  
  4.     struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);  
  5.     struct max17040_chip *chip;  
  6.     int ret;  
  7.     printk("MAX17040 probe !!\n");  
  8.   
  9.     if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))  
  10.         return -EIO;  
  11.   
  12.     chip = kzalloc(sizeof(*chip), GFP_KERNEL);  
  13.   
  14.     if (!chip)  
  15.         return -ENOMEM;  
  16.   
  17.     g_chip = chip;  
  18.     g_i2c_client = client;//(1)、IIC 驱动部分client 申请;  
  19.   
  20.     chip->client = client;  
  21.     chip->pdata = client->dev.platform_data;  
  22.     i2c_set_clientdata(client, chip);  
  23.     chip->battery.name       = "battery";//(2)、chip name;  
  24.     chip->battery.type       = POWER_SUPPLY_TYPE_BATTERY;  
  25.     chip->battery.get_property   = max17040_get_property;//(3)、获取电池信息;  
  26.     chip->battery.properties = max17040_battery_props;//(4)、电池各种信息;  
  27.     chip->battery.num_properties = ARRAY_SIZE(max17040_battery_props);  
  28.     chip->battery.external_power_changed = NULL;  
  29.     ret = power_supply_register(&client->dev, &chip->battery);//(5)、battery加入power_supply  
  30.     if (ret)  
  31.         goto err_battery_failed;  
  32.   
  33.   
  34.     chip->ac.name        = "ac"  
  35.     chip->ac.type        = POWER_SUPPLY_TYPE_MAINS;  
  36.     chip->ac.get_property    = adapter_get_property;  
  37.     chip->ac.properties  = adapter_get_props;  
  38.     chip->ac.num_properties  = ARRAY_SIZE(adapter_get_props);  
  39.     chip->ac.external_power_changed = NULL;  
  40.     ret = power_supply_register(&client->dev, &chip->ac);//(6)、和battery相似,把ac加入power_supply  
  41.     if (ret)  
  42.         goto err_ac_failed;  
  43.  
  44.  
  45. #if !defined(CONFIG_CHARGER_PM2301)  
  46.     chip->usb.name       = "usb";  
  47.     chip->usb.type       = POWER_SUPPLY_TYPE_USB;  
  48.     chip->usb.get_property   = usb_get_property;  
  49.     chip->usb.properties = usb_get_props;  
  50.     chip->usb.num_properties = ARRAY_SIZE(usb_get_props);  
  51.     chip->usb.external_power_changed = NULL;  
  52.     ret = power_supply_register(&client->dev, &chip->usb);//(7)、和battery相似,把usb加入power_supply  
  53.     if (ret)  
  54.         goto err_usb_failed;  
  55.   
  56.     if (chip->pdata->hw_init && !(chip->pdata->hw_init())) {  
  57.         dev_err(&client->dev, "hardware initial failed.\n");  
  58.         goto err_hw_init_failed;  
  59.     }  
  60. #endif  
  61.  
  62. #ifdef MAX17040_SUPPORT_CURVE  
  63.      g_TimeCount = 0;  
  64.      handle_model(0);  
  65. #endif  
  66.     max17040_get_version(client);  
  67.     battery_initial = 1;  
  68.   
  69.     INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);//(8)、任务宏初始化,max17040加入chip->work队列;  
  70.     schedule_delayed_work(&chip->work, MAX17040_DELAY);//(9)、通过定时器调度队列;  
  71.   
  72.   
  73.     printk("MAX17040 probe success!!\n");  
  74.     return 0;  
  75.   
  76. err_hw_init_failed:  
  77.     power_supply_unregister(&chip->usb);  
  78. err_usb_failed:  
  79.     power_supply_unregister(&chip->ac);  
  80. err_ac_failed:  
  81.     power_supply_unregister(&chip->battery);  
  82. err_battery_failed:  
  83.     dev_err(&client->dev, "failed: power supply register\n");  
  84.     i2c_set_clientdata(client, NULL);  
  85.     kfree(chip);  
  86.     return ret;  
  87. }  

1IIC 驱动部分client 申请;

2chip name

3、获取电池信息;

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

[csharp]  view plain copy
  1. static int max17040_get_property(struct power_supply *psy,  
  2.                                  enum power_supply_property psp,  
  3.                                  union power_supply_propval *val)  
  4. {  
  5.     struct max17040_chip *chip = container_of(psy,  
  6.                                  struct max17040_chip, battery);  
  7.   
  8.     switch (psp) {  
  9.     case POWER_SUPPLY_PROP_STATUS:  
  10.         val->intval = chip->status;  
  11.         break;  
  12.   
  13.     case POWER_SUPPLY_PROP_ONLINE:  
  14.         val->intval = chip->online;  
  15.         break;  
  16.   
  17.     case POWER_SUPPLY_PROP_VOLTAGE_NOW:  
  18.     case POWER_SUPPLY_PROP_PRESENT:  
  19.         val->intval = chip->vcell;  
  20.   
  21.         if (psp  == POWER_SUPPLY_PROP_PRESENT)  
  22.             val->intval = 1; /* You must never run Odrioid1 without Battery. */  
  23.   
  24.         break;  
  25.   
  26.     case POWER_SUPPLY_PROP_CAPACITY:  
  27.         val->intval = chip->soc;  
  28.         break;  
  29.   
  30.     case POWER_SUPPLY_PROP_TECHNOLOGY:  
  31.         val->intval = POWER_SUPPLY_TECHNOLOGY_LION;  
  32.         break;  
  33.   
  34.     case POWER_SUPPLY_PROP_HEALTH:  
  35.         if (chip->vcell  < 2850)  
  36.             val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;  
  37.         else  
  38.             val->intval = POWER_SUPPLY_HEALTH_GOOD;  
  39.   
  40.         break;  
  41.   
  42.     case POWER_SUPPLY_PROP_TEMP:  
  43.         val->intval = 365;  
  44.         break;  
  45.   
  46.     default:  
  47.         return -EINVAL;  
  48.     }  
  49.   
  50.     return 0;  
  51. }  

(4)电池各种信息

电池 电量计(MAX17040)驱动分析篇_第9张图片

[csharp]  view plain copy
  1. static enum power_supply_property max17040_battery_props[] = {  
  2.             POWER_SUPPLY_PROP_PRESENT,  
  3.             POWER_SUPPLY_PROP_STATUS,  
  4.             /*POWER_SUPPLY_PROP_ONLINE,*/  
  5.             POWER_SUPPLY_PROP_VOLTAGE_NOW,  
  6.             POWER_SUPPLY_PROP_CAPACITY,  
  7.             POWER_SUPPLY_PROP_TECHNOLOGY,  
  8.             POWER_SUPPLY_PROP_HEALTH,  
  9.             POWER_SUPPLY_PROP_TEMP,  
  10.         };  

5battery加入power_supply


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

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

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

前面已经分析;

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

前面已经分析;

2power_supply_changed简要分析

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

[csharp]  view plain copy
  1. power_supply_changed(&chip->battery);  

Kernel/drivers/power/power_supply_core.c中:

[csharp]  view plain copy
  1. void power_supply_changed(struct power_supply *psy)  
  2. {  
  3.     unsigned long flags;  
  4.   
  5.     dev_dbg(psy->dev, "%s\n", __func__);  
  6.   
  7.     spin_lock_irqsave(&psy->changed_lock, flags);  
  8.     psy->changed = true;  
  9.     wake_lock(&psy->work_wake_lock);  
  10.     spin_unlock_irqrestore(&psy->changed_lock, flags);  
  11.     schedule_work(&psy->changed_work);//调度psy->changed_work  
  12. }  
  13. Psy->changed_work的执行函数:  
  14. static void power_supply_changed_work(struct work_struct *work)  
  15. {  
  16.     unsigned long flags;  
  17.     struct power_supply *psy = container_of(work, struct power_supply,  
  18.                         changed_work);  
  19.   
  20.     dev_dbg(psy->dev, "%s\n", __func__);  
  21.   
  22.     spin_lock_irqsave(&psy->changed_lock, flags);  
  23.     if (psy->changed) {  
  24.         psy->changed = false;  
  25.         spin_unlock_irqrestore(&psy->changed_lock, flags);  
  26.   
  27.         class_for_each_device(power_supply_class, NULL, psy,  
  28.                       __power_supply_changed_work);  
  29.   
  30.         power_supply_update_leds(psy);  
  31.   
  32.         kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);//uevent状态  
  33.         spin_lock_irqsave(&psy->changed_lock, flags);  
  34.     }  
  35.     if (!psy->changed)  
  36.         wake_unlock(&psy->work_wake_lock);  
  37.     spin_unlock_irqrestore(&psy->changed_lock, flags);  
  38. }  

你可能感兴趣的:(电池 电量计(MAX17040)驱动分析篇)