android 电池

news/2024/12/22 20:00:15/
 

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

转载至:http://blog.csdn.net/xubin341719/article/details/8497830

关键词:android  电池关机充电 androidboot.mode charger

平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0 
平台:S5PV310(samsungexynos 4210) 

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

       电池在电子产品中所占的地位就不用说了。不过电池在物理接口上比较简单,就两条线:正极、负极,这个小学生科普知识都知道;不过真正用到电子产品中时,有关电池方面的东西还是有点多的。

       从三个方面介绍:

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

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

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

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

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


电池充电最重要的就是这三步:

第一步:判断电压<3V,要先进行预充电,0.05C电流;

第二步:判断 3V<电压<4.2V,恒流充电0.2C~1C电流;

第三步:判断电压>4.2V,恒压充电,电压为4.20V,电流随电压的增加而减少,直到充满。


一、锂电池

1、简述锂电池以及工作原理

锂离子电池自1990年问世以来,因其卓越的性能得到了迅猛的发展,并广泛地应用于社会。锂离子电池以其它电 池所不可比拟的优势迅速占领了许多领域,象大家熟知的移动电话、笔记本电脑、小型摄像机等等。

目前锂电池公认的基本原理是所谓的“摇椅理论”。锂电池的冲放电不是通过传统的方式实现电子的转移,而是通过锂离子在层壮物质的晶体中的出入,发生能量变化。在正常冲放电情况下,锂离子的出入一般只引起层间距的变化,而不会引起晶体结构的破坏,因此从冲放电反映来讲,锂离子电池是一种理想的可逆电池。在冲放电时锂离子在电池正负极往返出入,正像摇椅一样在正负极间摇来摇去,故有人将锂离子电池形象称为摇椅电池。

我们经常说的锂离子电池的优越性是针对于传统的镍镉电池(Ni/Cd)和镍氢电池(Ni/MH)来讲的。 具有工作电压高比能量大循环寿命长自放电率低无记忆效应等优点

2、锂电池日常使用过程中的常识
(1)、误区:“电池激活,前三次充电12小时以上”

对于锂电池的“激活”问题,众多的说法是:充电时间一定要超过12小时,反复做三次,以便激活电池。这种“前三次充电要充 12小时以上”的说法,明显是从镍电池(如镍镉和镍氢)延续下来的说法。所以这种说法,可以说一开始就是误传。经过抽样调查,可以看出有相当一部分人混淆了两种电池的充电方法。

  锂电池和镍电池的充放电特性有非常大的区别,所查阅过的所有严肃的正式技术资料都强调过充和过放电会对锂电池、特别是液体锂离子电池造成巨大的伤害。因而充电最好按照标准时间和标准方法充电,特别是不要进行超过12个小时的超长充电。通常,手机说明书上介绍的充电方法,就是适合该手机的标准充电方法。

(2)、 不益长时间充电、电池完全用完再充电

 锂电池的手机或充电器在电池充满后都会自动停充,并不存在镍电充电器所谓的持续10几小时的“涓流”充电。如果锂电池在充满后,放在充电器上也是也不再充电。

          超常时间充电和完全用空电量会造成过度充电和过度放电,将对锂离子电池的正负极造成永久的损坏,从分子层面看,过度放电将导致负极碳过度释出锂离子而使得其片层结构出现塌陷,过度充电将把太多的锂离子硬塞进负极碳结构里去,而使得其中一些锂离子再也无法释放出来。

(3)、电池寿命

          关于锂离子电池充放电循环的实验表,关于循环寿命的数据列出如下(DOD是放电深度的英文缩写): 
          循环寿命 (10%DOD):> 1000次 
    循环寿命 (100%DOD):> 200次 
          从上面数据可见,可充电次数和放电深度有关,10%DOD时的循环寿命要比100%DOD的要长很多。当然 如果折合到实际充电的相对总容量10%*1000=100,100%*200=200,后者的完全充放电还是要比较好一 些。但是锂电池的寿命主要体现在充放电周期上,这个周期是一个绝对概念,上次使用了30%电力,充满电,下次又使用了70%的电力,又充满电,这个刚好是 一个充电周期。所以还是遵循锂电池发明者的口号“即用即充,即充即用”的方法使用锂电池。

(4)、定期深度充放电进行 电池校准

          锂离子电池一般都带有管理芯片和充电控制芯片。其中管理芯片中有一系列的寄存器,存有容量、 温度、ID 、充电状态、放电次数等数值。这些数值在使用中会逐渐变化。使用说明中的“使用一个月左右应该全充放一次”的做法主要的作用应该就是修正这些寄存器里不当 的值。

 

 二、锂电池的充电方式是限压横流方式

主要分三步完成:

第一步:判断电压<3V,要先进行预充电,0.05C电流; 

第二步:判断 3V<电压<4.2V,恒流充电0.2C~1C电流;

第三步:判断电压>4.2V,恒压充电,电压为4.20V,电流随电压的增加而减少,直到充满。

       其实今天我就是这一点有些不懂,在网上查了一下,然后上面那些做为常识了解。
        充电开始时,应先检测待充电电池的电压,如果电压低于3V,要先进行预充电,充电电流为设定电流 的1/10,一般选0.05C左右。电压升到3V后,进入标准充电过程。标准充电过程为:以设定电流进行恒流充电,电池电压升到4.20V时,改为恒压充电,保持充电电压为4.20V。此时,充电电流逐渐下降,当电流下降至设定充电电流的1/10时,充电结束。
          一般锂电池充电电流设定在0.2C至1C之间,电流越大,充电越快,同时电池发热也越大。而且,过大的电流充电,容量不够满,因为电池内部的电化学反应需要时间。就跟倒啤酒一样,倒太快的话会产生泡沫,反而不满。
          术语解释:充放电电流一般用C作参照,C是对应电池容量的数值。电池容量一般用Ah、mAh表示,如M8的电池容量1200mAh,对应的C就是1200mA。0.2C就等于240mA。
下面是锂电池典型充电曲线图:


三、锂电池的放电,对电池来说,正常使用就是放电过程
          锂电池放电只需要注意很少的几点:

          1、放电电流不能过大, 过大的电流导致电池内部发热,有可能会造成永久性的损害;

          2、绝对不能过放电!锂电池最怕过放电,一旦放电 电压低于2.7V,将可能导致电池报废。
下面是一般锂电池的典型放电曲线图:


          从典型放电曲线图上可以看出,电池放电电流越大,放电容量越小,电压下降更快。
所以,一般情况下电池大负荷工作后,减少负荷会出现电压回升现象,就是所说的“回电”现象。

 

给个图看看,这个放电曲线图在放电过程中停了一下,出现了“回电”。


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

http://blog.csdn.net/xubin341719/article/details/8498580

关键词:android 电池关机充电 androidboot.mode charger关机充电 充电画面显示 
平台信息:
内核:linux2.6/linux3.0
系统:android/android4.0 
平台:S5PV310(samsungexynos 4210) 

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

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

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

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

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

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


上一篇我们讲了锂电池的充放电的流程和电池的一些特性,这一节我们重点说一下android关机充电是怎么、充电画面显示是怎么实现的,这个在工作中也比较有用,我们开始做这一块的时候也走了不少的弯路。我记得我们做adnroid2.3的时候,关机状态和充电logo显示是在uboot中做的。应该是有两种做法,回头我再看下uboot中做画面显示那一块是怎么做的,这一节我们重点说系统中的充电logo显示。

一、android正常开机流程、关机充电流程

在写这篇文章之前我们先看两个流程:正常开机流程,关机充电系统启动流程

1、正常开机流程,按开机键。

可大致分成三部分

(1)、OS_level:UBOOT、kenrel、init这三步完成系统启动;

(2)、Android_level:这部分完成android部的初始化;

(3)、Home Screen:这部分就是我们看到的launcher部分。


2、关机充电系统启动流程

       与前面相比,这个流程只走到init这一部分,就没有往后走了,这部分我们会在后面的代码中分析。


二、关机充电逻辑硬件逻辑

1、插入DC,charger IC从硬件上唤醒系统,相当于长按开机键开机。


下面这部分是charger IC连接系统的控制部分。


三、软件逻辑。

DC插入,其实相当于关机状态下“按开机键”开机。第一步要走UBOOT、kernel 、android init这一流程。

1、UBOOT

       UBOOT启动代码我们不在这里详细分析,这里我们只要注意二个问题:

a:如何判断是DC插入;

b:设定setenv("bootargs", "androidboot.mode=charger"),androidboot.mode这个参数相当重要,这个参数决定系统是正常启动、还是关机充电状态。

Uboot/board/samsung/smdk4212/smkd4212.c

[cpp]  view plain copy
  1. int board_late_init (void)  
  2. {  
  3.     int keystate = 0;  
  4.     printf("check start mode\n");  
  5.   if ((*(int *)0x10020800==0x19721212) || (*(int *)0x10020804==0x19721212)  
  6. || (*(int *)0x10020808==0x19721212)) //(1)、检查是否有DC插入;  
  7. {  
  8.     setenv ("bootargs""");//(2)、没有DC插入;  
  9.   } else  {//DC插入  
  10.         int tmp=*(int *)0x11000c08;  
  11.     *(int *)0x10020800=*(int *)0x10020804=0x19721212;  
  12.     *(int *)0x11000c08=(tmp&(~0xc000))|0xc000;  
  13.     udelay(10000);  
  14.     if ((*(int *)0x11000c04 & 0x80)!=0x80 && INF_REG4_REG != 0xf) {  
  15.         setenv ("bootargs""androidboot.mode=charger");//(3)、设定bootargs为charger状态  
  16.         printf("charger mode\n");  
  17.     } else {  
  18.         setenv ("bootargs""");  
  19.     }  
  20.     *(int *)0x11000c08=tmp;  
  21.   }  
  22. #ifdef CONFIG_CPU_EXYNOS4X12  
  23.     int charge_status=CheckBatteryLow();//(4)、检查电池电量;  
  24.     keystate=board_key_check();//(5)、检查按键状态;  
  25.     // fuse bootloader  
  26.     if(second_boot_info != 0) {  
  27.         boot_symbol=1;  
  28.         INF_REG2_REG =0x8;  
  29.         run_command(CONFIG_BOOTCMD_FUSE_BOOTLOADER, NULL);  
  30.     }  
  31.     if((INF_REG4_REG == 0xd)) {  
  32.         // reboot default  
  33.         char buf[10];  
  34.         sprintf(buf, "%d", CONFIG_BOOTDELAY);  
  35.         setenv ("bootdelay", buf);  
  36.         setenv ("reserved", NULL);  
  37.         saveenv();  
  38.     } else if((INF_REG4_REG == 0xe) || keystate == (0x1 | 0x2)) {//(6)、按键进入fastboot模式;  
  39.         // reboot bootloader  
  40.         boot_symbol=1;  
  41.         INF_REG2_REG =0x8;  
  42.         printf("BOOTLOADER - FASTBOOT\n");  
  43.         setenv ("reserved""fastboot");  
  44.         setenv ("bootdelay""0");  
  45.     } else if((INF_REG4_REG == 0xf) || keystate == (0x1 | 0x2 | 0x4)) {//(7)、按键进入recovery模式;  
  46.         // reboot recovery  
  47.         printf("BOOTLOADER - RECOVERY\n");  
  48.         boot_symbol=1;  
  49.         INF_REG2_REG =0x8;  
  50.         setenv ("reserved", CONFIG_BOOTCMD_RECOVERY);  
  51.         setenv ("bootdelay""0");  
  52.     } else  
  53.     if(keystate == (0x1 | 0x4) || second_boot_info != 0 || partition_check()) {//(8)、按键进入卡升级模式;  
  54.         // 2nd boot  
  55.         printf("BOOTLOADER - 2ND BOOT DEVICE\n");  
  56.         boot_symbol=1;  
  57.         INF_REG2_REG =0x8;  
  58.         setenv ("bootcmd", CONFIG_BOOTCOMMAND);  
  59.         setenv ("reserved", CONFIG_BOOTCMD_FUSE_RELEASE);  
  60.         setenv ("bootdelay""0");  
  61.     } else {//(9)、正常启动;  
  62.         // normal case  
  63.         char buf[10];  
  64.         sprintf(buf, "%d", CONFIG_BOOTDELAY);  
  65.         setenv ("bootdelay", buf);  
  66.     }  
  67.     INF_REG4_REG = 0;  
  68.     return 0;  
  69. }  

(1)、检查是否有DC插入;

[cpp]  view plain copy
  1. if ((*(int *)0x10020800==0x19721212) || (*(int *)0x10020804==0x19721212)  
  2.  (*(int *)0x10020808==0x19721212))   

这部分检查寄存器的值。

(2)、没有DC插入;

(3)、设定bootargs为charger状态

[cpp]  view plain copy
  1. if ((*(int *)0x11000c04 & 0x80)!=0x80 && INF_REG4_REG != 0xf) {  
  2.         setenv ("bootargs""androidboot.mode=charger");  

这是这部分的重点,如果能过寄存器判断是DC插入,把androidboot.mode设定为charger状态。

以下这部分根据需要加入,通过判断不同的情况进入不同的功能,如fastboot\revovery…………,这部分不做详细解释。

(4)、检查电池电量;

    这个在正常开机状态下,如果检测电量太低,则不开机,这部分代码就不做分析。

(5)、检查按键状态;

      我们这个平台有几种模式:fastboot\recovery\卡升级等……

(6)、按键进入fastboot模式;

(7)、按键进入recovery模式;

(8)、按键进入卡升级模式

(9)、正常启动;

2、kernel

这部分和正常启动是一样的。

3、init

前面所有的描述其实只有一点和正常启动不太一样,那就是在UBOOT中把androidboot.mode设定为charger状态,内核正常流程启动,然后到init时要对charger这种状态处理。

system\core\init\init.c

[cpp]  view plain copy
  1. int main(int argc, char **argv)  
  2. {  
  3.     ………………  
  4.     action_for_each_trigger("early-init", action_add_queue_tail);  
  5.   
  6.     queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");  
  7.     queue_builtin_action(property_init_action, "property_init");  
  8.     queue_builtin_action(keychord_init_action, "keychord_init");  
  9.     queue_builtin_action(console_init_action, "console_init");  //(1)、显示initlogo.rle,也就是android第二张图片;  
  10.     queue_builtin_action(set_init_properties_action, "set_init_properties");  
  11.   
  12.     /* execute all the boot actions to get us started */  
  13.     action_for_each_trigger("init", action_add_queue_tail);  
  14.   
  15.     /* skip mounting filesystems in charger mode */  
  16.     if (strcmp(bootmode, "charger") != 0) {//(2)、这里就是UBOOT中设定的bootmode,如果是charger模式,跳过下面初始化;  
  17.         action_for_each_trigger("early-fs", action_add_queue_tail);  
  18.         action_for_each_trigger("fs", action_add_queue_tail);  
  19.         action_for_each_trigger("post-fs", action_add_queue_tail);  
  20.         action_for_each_trigger("post-fs-data", action_add_queue_tail);  
  21.     }  
  22.   
  23.     queue_builtin_action(property_service_init_action, "property_service_init");  
  24.     queue_builtin_action(signal_init_action, "signal_init");  
  25.     queue_builtin_action(check_startup_action, "check_startup");  
  26.   
  27.     if (!strcmp(bootmode, "charger")) {//(3)、如果为charger,则调用charger.c。  
  28.         action_for_each_trigger("charger", action_add_queue_tail);  
  29.     } else {  
  30.         action_for_each_trigger("early-boot", action_add_queue_tail);  
  31.         action_for_each_trigger("boot", action_add_queue_tail);  
  32.     }  
  33. ……………………  
  34. }  

(1)、显示initlogo.rle,也就是android第二张图片;

queue_builtin_action(console_init_action,"console_init");调用console_init_action

[cpp]  view plain copy
  1. static int console_init_action(int nargs, char **args)  
  2. {  
  3.     int fd;  
  4.     char tmp[PROP_VALUE_MAX];  
  5.     if (console[0]) {  
  6.         snprintf(tmp, sizeof(tmp), "/dev/%s", console);  
  7.         console_name = strdup(tmp);  
  8.     }  
  9.     fd = open(console_name, O_RDWR);  
  10.     if (fd >= 0)  
  11.         have_console = 1;  
  12.     close(fd);  
  13.     if( load_565rle_image(INIT_IMAGE_FILE) ) {//这里定义rle文件的名称#define INIT_IMAGE_FILE "/initlogo.rle"  
  14.         fd = open("/dev/tty0", O_WRONLY);  
  15.         if (fd >= 0) {//如果没有这张图片,就显示android字样,在屏幕左上角;  
  16.             const char *msg;  
  17.                 msg = "\n"  
  18.             "\n"  
  19.             "\n"  // console is 40 cols x 30 lines  
  20.             "\n"  
  21.             "\n"  
  22.             "\n"  
  23.             "\n"  
  24.             "\n"  
  25.             "\n"  
  26.             "\n"  
  27.             "             A N D R O I D ";  
  28.             write(fd, msg, strlen(msg));  
  29.             close(fd);  
  30.         }  
  31.     }  
  32.     return 0;  
  33. }  

(2)、这里就是UBOOT中设定的bootmode,如果是charger模式,跳过下面初始化;

[cpp]  view plain copy
  1. /* skip mounting filesystems in charger mode */  
  2. if (strcmp(bootmode, "charger") != 0) {  
  3.     action_for_each_trigger("early-fs", action_add_queue_tail);  
  4.     action_for_each_trigger("fs", action_add_queue_tail);  
  5.     action_for_each_trigger("post-fs", action_add_queue_tail);  
  6.     action_for_each_trigger("post-fs-data", action_add_queue_tail);  
  7. }  

(3)、如果为charger,则调用charger.c

[cpp]  view plain copy
  1. action_for_each_trigger("charger", action_add_queue_tail);  

我们在后面细分charger这部分。

4、charger.c

这部分就是我们充电部分,充电画面显示的实现。

system\core\charger\charger.c

[cpp]  view plain copy
  1. int main(int argc, char **argv)  
  2. {  
  3. ………………  
  4.     klog_set_level(CHARGER_KLOG_LEVEL);  
  5.     dump_last_kmsg();  
  6.     LOGI("--------------- STARTING CHARGER MODE ---------------\n");  
  7.   
  8.     gr_init();  
  9.     gr_font_size(&char_width, &char_height); //(1)、初始化graphics,包括buf大小;  
  10.   
  11.     ev_init(input_callback, charger);//(2)初始化按键;  
  12.      
  13. fd = uevent_open_socket(64*1024, true);  
  14.     if (fd >= 0) {  
  15.         fcntl(fd, F_SETFL, O_NONBLOCK);  
  16.         ev_add_fd(fd, uevent_callback, charger);  
  17.     }  
  18.   
  19.     charger->uevent_fd = fd;  
  20.     coldboot(charger, "/sys/class/power_supply""add");//(3)、创建/sys/class/power_supply结点,把socket信息通知应用层;  
  21.       
  22. ret = res_create_surface("charger/battery_fail", &charger->surf_unknown);  
  23.     if (ret < 0) {  
  24.         LOGE("Cannot load image\n");  
  25.         charger->surf_unknown = NULL;  
  26.     }  
  27.     for (i = 0; i < charger->batt_anim->num_frames; i++) {//(4)、这里是显示charger logo,res_create_surface显示图片函数;  
  28.         struct frame *frame = &charger->batt_anim->frames[i];  
  29.         ret = res_create_surface(frame->name, &frame->surface);  
  30.         if (ret < 0) {  
  31.             LOGE("Cannot load image %s\n", frame->name);  
  32.             /* TODO: free the already allocated surfaces... */  
  33.             charger->batt_anim->num_frames = 0;  
  34.             charger->batt_anim->num_cycles = 1;  
  35.             break;  
  36.         }  
  37.     }  
  38. ev_sync_key_state(set_key_callback, charger);  
  39.     gr_fb_blank(true);  
  40.   
  41.     charger->next_screen_transition = now - 1;  
  42.     charger->next_key_check = -1;  
  43.     charger->next_pwr_check = -1;  
  44.     reset_animation(charger->batt_anim);  
  45.     kick_animation(charger->batt_anim);  
  46.     event_loop(charger);//(5)、event_loop循环,电池状态,检测按键是否按下;  
  47.     return 0;  
  48.   
  49. }  

(1)、初始化graphics,包括buf大小

android/bootable/recovery/minui/graphics.c

gr_init():minui/graphics.c[settty0 to graphic mode, open fb0],设制tty0为图形模式,打开fb0;

[cpp]  view plain copy
  1. int gr_init(void)  
  2. {  
  3.     gglInit(&gr_context);  
  4.     GGLContext *gl = gr_context;  
  5.     gr_init_font();  
  6.     gr_vt_fd = open("/dev/tty0", O_RDWR | O_SYNC);  
  7.     if (gr_vt_fd < 0) {  
  8.         // This is non-fatal; post-Cupcake kernels don't have tty0.  
  9.         perror("can't open /dev/tty0");  
  10.   
  11.     } else if (ioctl(gr_vt_fd, KDSETMODE, (void*) KD_GRAPHICS)) {  
  12.         // However, if we do open tty0, we expect the ioctl to work.  
  13.         perror("failed KDSETMODE to KD_GRAPHICS on tty0");  
  14.         gr_exit();  
  15.         return -1;  
  16.     }  
  17.     gr_fb_fd = get_framebuffer(gr_framebuffer);  
  18.     if (gr_fb_fd < 0) {  
  19.         gr_exit();  
  20.         return -1;  
  21.     }  
  22.     get_memory_surface(&gr_mem_surface);  
  23.     fprintf(stderr, "framebuffer: fd %d (%d x %d)\n",  
  24.             gr_fb_fd, gr_framebuffer[0].width, gr_framebuffer[0].height);  
  25.         /* start with 0 as front (displayed) and 1 as back (drawing) */  
  26.     gr_active_fb = 0;  
  27.     set_active_framebuffer(0);  
  28.     gl->colorBuffer(gl, &gr_mem_surface);  
  29.     gl->activeTexture(gl, 0);  
  30.     gl->enable(gl, GGL_BLEND);  
  31.     gl->blendFunc(gl, GGL_SRC_ALPHA, GGL_ONE_MINUS_SRC_ALPHA);  
  32.     gr_fb_blank(true);  
  33.     gr_fb_blank(false);  
  34.     return 0;  
  35.   
  36. }  

(2)android/bootable/recovery/minui/events.c

ev_init():minui/events.c[open /dev/input/event*]打开 /dev/input/event*

这部分是在,充电状态下,按键操作的初始化,比如:短按显示充电logo,长按开机,初始化代码如下。

[cpp]  view plain copy
  1. int ev_init(ev_callback input_cb, void *data)  
  2. {  
  3.     DIR *dir;  
  4.     struct dirent *de;  
  5.     int fd;  
  6.     dir = opendir("/dev/input");//打开驱动结点;  
  7.     if(dir != 0) {  
  8.         while((de = readdir(dir))) {  
  9.             unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  
  10. //            fprintf(stderr,"/dev/input/%s\n", de->d_name);  
  11.             if(strncmp(de->d_name,"event",5)) continue;  
  12.             fd = openat(dirfd(dir), de->d_name, O_RDONLY);  
  13.             if(fd < 0) continue;  
  14.             /* read the evbits of the input device */  
  15.             if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) < 0) {  
  16.                 close(fd);  
  17.                 continue;  
  18.             }  
  19.             /* TODO: add ability to specify event masks. For now, just assume 
  20.              * that only EV_KEY and EV_REL event types are ever needed. */  
  21.             if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits)) {  
  22.                 close(fd);  
  23.                 continue;  
  24.             }  
  25.             ev_fds[ev_count].fd = fd;  
  26.             ev_fds[ev_count].events = POLLIN;  
  27.             ev_fdinfo[ev_count].cb = input_cb;  
  28.             ev_fdinfo[ev_count].data = data;  
  29.             ev_count++;  
  30.             ev_dev_count++;  
  31.             if(ev_dev_count == MAX_DEVICES) break;  
  32.         }  
  33.     }  
  34.     return 0;  
  35. }  

(3)、创建/sys/class/power_supply结点,把socket信息通知应用层

uevent_open_socket这个函数是通过kobject_uevent的方式通知的应用层,就是往一个socket广播一个消息,只需要在应用层打开socket监听NETLINK_KOBJECT_UEVENT组的消息,就可以收到了,主要是创建了socket接口获得uevent的文件描述符,然后触发/sys/class/power_supply目录及其子目录下的uevent,然后接受并创建设备节点,至此设备节点才算创建。

(4)、这里显示charger logo,res_create_surface显示图片函数;

res_create_surface:minui/resource.c[create surfaces for all bitmaps used later, include icons, bmps]

创建surface为所以的位图,包括图标、位图。  这些图片的位置为:system\core\charger\images

(5)、event_loop循环,电池状态,检测按键是否按下;

5、event_loop

       这个函数判断按键状态,DC是否插拔。如果长按开机:执行android_reboot(ANDROID_RB_RESTART,0, 0);如果拔出DC:执行android_reboot(ANDROID_RB_POWEROFF,0, 0);

[cpp]  view plain copy
  1. static void event_loop(struct charger *charger)  
  2. {  
  3.     int ret;  
  4.     while (true) {  
  5.         int64_t now = curr_time_ms();//(1)、获得当前时间;  
  6.         LOGV("[%lld] event_loop()\n", now);  
  7.         handle_input_state(charger, now);//(2)、检查按键状态;  
  8.         handle_power_supply_state(charger, now);// (3)、检查DC是否拔出;   
  9.         /* do screen update last in case any of the above want to start 
  10.          * screen transitions (animations, etc) 
  11.          */  
  12.         update_screen_state(charger, now);//(4)、对按键时间状态标志位的判断,显示不同电量的充电logo;   
  13.         wait_next_event(charger, now);  
  14.     }  
  15. }  

(1)、获得当前时间;

   int64_t now = curr_time_ms();

       这个时间来判断,有没有屏幕超时,如果超时关闭屏幕充电logo显示。

(2)、检查按键状态;

[cpp]  view plain copy
  1. static void handle_input_state(struct charger *charger, int64_t now)  
  2. {  
  3.     process_key(charger, KEY_POWER, now);  
  4.     if (charger->next_key_check != -1 && now > charger->next_key_check)  
  5.         charger->next_key_check = -1;  
  6. }  
  7. 我们再看下:process_key(charger, KEY_POWER, now);  
  8. static void process_key(struct charger *charger, int code, int64_t now)  
  9. {  
  10. ………………  
  11.     if (code == KEY_POWER) {  
  12.         if (key->down) {  
  13.             int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME;  
  14.             if (now >= reboot_timeout) {//如果长按power键,就重新启动,也就是重启开机;  
  15.                 LOGI("[%lld] rebooting\n", now);  
  16.                 android_reboot(ANDROID_RB_RESTART, 0, 0);//重启命令;  
  17.             }  
  18.     ………………  
  19.     }  
  20.   
  21.     key->pending = false;  
  22. }  

(3)、检查DC是否拔出;

handle_power_supply_state(charger, now); 

[cpp]  view plain copy
  1. static void handle_power_supply_state(struct charger *charger, int64_t now)  
  2. {  
  3.     if (charger->num_supplies_online == 0) {  
  4.         if (charger->next_pwr_check == -1) {  
  5.             charger->next_pwr_check = now + UNPLUGGED_SHUTDOWN_TIME;  
  6.             LOGI("[%lld] device unplugged: shutting down in %lld (@ %lld)\n",  
  7.                  now, UNPLUGGED_SHUTDOWN_TIME, charger->next_pwr_check);  
  8.         } else if (now >= charger->next_pwr_check) {  
  9.             LOGI("[%lld] shutting down\n", now);  
  10.             android_reboot(ANDROID_RB_POWEROFF, 0, 0);//如果DC拔出,则关机;  
  11.         }   
  12. ………………  
  13. }  

(4)、对按键时间状态标志位的判断,显示不同电量的充电logo;

  update_screen_state(charger, now);

这个函数比较长了,其实做用就是:我们在状态的过程中,充电logo的电量是要增加的,比如电量是20%时,要从第一格开始闪烁;如果是80%时,则要从第三格开始闪烁,电量显示就是通过这个函数来计算实现的。

[cpp]  view plain copy
  1. static void update_screen_state(struct charger *charger, int64_t now)  
  2. {  
  3.     struct animation *batt_anim = charger->batt_anim;  
  4.     int cur_frame;  
  5.     int disp_time;  
  6.   
  7.     if (!batt_anim->run || now < charger->next_screen_transition)  
  8.         return;  
  9.   
  10.     /* animation is over, blank screen and leave */  
  11.     if (batt_anim->cur_cycle == batt_anim->num_cycles) {  
  12.         reset_animation(batt_anim);  
  13.         charger->next_screen_transition = -1;  
  14.         gr_fb_blank(true);  
  15.         LOGV("[%lld] animation done\n", now);  
  16.         return;  
  17.     }  
  18.   
  19.     disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time;  
  20.   
  21.     /* animation starting, set up the animation */  
  22.     if (batt_anim->cur_frame == 0) {  
  23.         int batt_cap;  
  24.         int ret;  
  25.   
  26.         LOGV("[%lld] animation starting\n", now);  
  27.         batt_cap = get_battery_capacity(charger);  
  28.         if (batt_cap >= 0 && batt_anim->num_frames != 0) {  
  29.             int i;  
  30.   
  31.             /* find first frame given current capacity */  
  32.             for (i = 1; i < batt_anim->num_frames; i++) {  
  33.                 if (batt_cap < batt_anim->frames[i].min_capacity)  
  34.                     break;  
  35.             }  
  36.             batt_anim->cur_frame = i - 1;  
  37.   
  38.             /* show the first frame for twice as long */  
  39.             disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time * 2;  
  40.         }  
  41.   
  42.         batt_anim->capacity = batt_cap;  
  43.     }  
  44.   
  45.     /* unblank the screen  on first cycle */  
  46.     if (batt_anim->cur_cycle == 0)  
  47.         gr_fb_blank(false);  
  48.   
  49.     /* draw the new frame (@ cur_frame) */  
  50.     redraw_screen(charger);  
  51.   
  52.     /* if we don't have anim frames, we only have one image, so just bump 
  53.      * the cycle counter and exit 
  54.      */  
  55.     if (batt_anim->num_frames == 0 || batt_anim->capacity < 0) {  
  56.         LOGV("[%lld] animation missing or unknown battery status\n", now);  
  57.         charger->next_screen_transition = now + BATTERY_UNKNOWN_TIME;  
  58.         batt_anim->cur_cycle++;  
  59.         return;  
  60.     }  
  61.   
  62.     /* schedule next screen transition */  
  63.     charger->next_screen_transition = now + disp_time;  
  64.   
  65.     /* advance frame cntr to the next valid frame 
  66.      * if necessary, advance cycle cntr, and reset frame cntr 
  67.      */  
  68.     batt_anim->cur_frame++;  
  69.   
  70.     /* if the frame is used for level-only, that is only show it when it's 
  71.      * the current level, skip it during the animation. 
  72.      */  
  73.     while (batt_anim->cur_frame < batt_anim->num_frames &&  
  74.            batt_anim->frames[batt_anim->cur_frame].level_only)  
  75.         batt_anim->cur_frame++;  
  76.     if (batt_anim->cur_frame >= batt_anim->num_frames) {  
  77.         batt_anim->cur_cycle++;  
  78.         batt_anim->cur_frame = 0;  
  79.   
  80.         /* don't reset the cycle counter, since we use that as a signal 
  81.          * in a test above to check if animation is over 
  82.          */  
  83.     }  
  84. }  

下面是不能容量时显示logo的函数:

[cpp]  view plain copy
  1. static struct frame batt_anim_frames[] = {  
  2.     {  
  3.         .name = "charger/battery_0",  
  4.         .disp_time = 750,  
  5.         .min_capacity = 0,  
  6.     },  
  7.     {  
  8.         .name = "charger/battery_1",  
  9.         .disp_time = 750,  
  10.         .min_capacity = 20,  
  11.     },  
  12.     {  
  13.         .name = "charger/battery_2",  
  14.         .disp_time = 750,  
  15.         .min_capacity = 40,  
  16.     },  
  17.     {  
  18.         .name = "charger/battery_3",  
  19.         .disp_time = 750,  
  20.         .min_capacity = 60,  
  21.     },  
  22.     {  
  23.         .name = "charger/battery_4",  
  24.         .disp_time = 750,  
  25.         .min_capacity = 80,  
  26.         .level_only = true,  
  27.     },  
  28.     {  
  29.         .name = "charger/battery_5",  
  30.         .disp_time = 750,  
  31.         .min_capacity = BATTERY_FULL_THRESH,  
  32.     },  
  33. };  

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

关键词:android电池系统电池系统架构 uevent power_supply驱动

平台信息:

内核:linux2.6/linux3.0
系统:android/android4.0 
平台:S5PV310(samsungexynos4210) samsung exynos4412

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

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

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

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

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

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


一、电池系统结构

       Android中的电池使用方式主要有三种:AC、USB、Battery 等不同的模式。在应用程序层次,通常包括了电池状态显示的功能。因此从 Android 系统的软件方面(包括驱动程序和用户空间内容)需要在一定程度上获得电池的状态,电池系统主要负责电池信息统计、显示。电池系统的架构如下所示:


自下而上, Android 的电池系统分成以下几个部分:

1、驱动程序

特定硬件平台电池的驱动程序,用 Linux的Power Supply 驱动程序,实现向用户空间提供信息。Battery 驱动程序需要通过sys文件系 统向用户空间提供接口, sys文件系统的路径是由上层的程序指定的。Linux标准的 Power Supply驱动程序 所使用的文件系统路径为:/sys/class/power_supply ,其中的每个子目录表示一种能源供应设备的名称。


Power Supply 驱动程序的头文件在 include/linux/power_supply.h中定义,注册和注销驱动程序的函数如下所示: 

[cpp]  view plain copy
  1. int power_supply_register(struct device *parent,struct power_supply *psy);   
  2. void power_supply_unregister(struct power_supply *psy);   
  3. struct power_supply {   
  4. const char *name;   
  5. /* 设备名称 */   
  6. enum power_supply_type type;   
  7. /* 类型 */   
  8. enum power_supply_property *properties;   
  9. /* 属性指针 */   
  10. size_t num_properties;   
  11. /* 属性的数目 */   
  12. char **supplied_to;   
  13. size_t num_supplicants;   
  14. int (*get_property)(struct power_supply *psy, /* 获得属性 */   
  15. enum power_supply_property psp,   
  16. union power_supply_propval *val);   
  17. void (*external_power_changed)(struct power_supply *psy);   
  18. /* ...... 省略部分内容 */   
  19. };   

Linux中驱动程序:power_supply


2、本地代码 - JNI

代码路径: frameworks/base/services/jni/com_android_server_BatteryService.cpp 这个类调用sys文件系统访问驱动程序,也同时提供了JNI的接口。

这个文件提供的方法列表如下所示: 

[java]  view plain copy
  1. static JNINativeMethod sMethods[] = {   
  2. {"native_update""()V", (void*)android_server_BatteryService_update},   
  3. };    

处理的流程为根据设备类型判定设备后, 得到各个设备的相关属性,则需要得到更多得 信息。例如:果是交流或者 USB 设备,只需 要得到它们是否在线( onLine );如果是电 池设备,则需要得到更多的信息,例如状态 ( status ),健康程度( health ),容 量( capacity ),电压 ( voltage_now )等。

Linux 驱动 driver 维护着保存电池信息的一组文件 sysfs,供应用程序获取电源相关状态: 

[cpp]  view plain copy
  1. #define AC_ONLINE_PATH "/sys/class/power_supply/ac/online" AC 电源连接状态   
  2. #define USB_ONLINE_PATH "/sys/class/power_supply/usb/online" USB电源连接状态   
  3. #define BATTERY_STATUS_PATH "/sys/class/power_supply/battery/status"充电状态   
  4. #define BATTERY_HEALTH_PATH "/sys/class/power_supply/battery/health"电池状态   
  5. #define BATTERY_PRESENT_PATH "/sys/class/power_supply/battery/present"使用状态   
  6. #define BATTERY_CAPACITY_PATH "/sys/class/power_supply/battery/capacity"电池 level   
  7. #define BATTERY_VOLTAGE_PATH "/sys/class/power_supply/battery/batt_vol"电池电压   
  8. #define BATTERY_TEMPERATURE_PATH "/sys/class/power_supply/battery/batt_temp"电池温度   
  9. #define BATTERY_TECHNOLOGY_PATH "/sys/class/power_supply/battery/technology"电池技术 当电池状态发生变化时,driver 会更新这些文件。传送信息到java   

3 、JAVA 代码

代码路径:

frameworks/base/services/java/com/android/server/BatteryService.java

frameworks/base/core/java/android/os/ : android.os :包中和Battery 相关的部分

frameworks/base/core/java/com/android/internal/os/:和Battery 相关的内部部分 BatteryService.java 通过调用, BatteryService JNI来实现com.android.server包中的 BatteryService类。BatteryManager.java中定义了一些 JAVA 应用程序层可以使用的常量。

       电池系统在驱动程序层以上的部分都是Android 系统中默认的内容。在移植的过程中基本不需要改动。电池系统需要移植的部分仅有Battery驱动程序。Battery 驱动程序用Linux 标准的Power Supply驱动程序与上层的接口是sys文件系统,主要用于读取sys文件系统中的文件来获取电池相关的信息。整个系统中各部件的联系:

BatteryService 作为电池及充电相关的服务: 监听 Uevent、读取sysfs 里中的状态 、广播Intent.ACTION_BATTERY_CHANGED。

(1)、mUEventObserver

BatteryService实现了一个UevenObserver mUEventObserver。uevent是Linux 内核用来向用户空间主动上报事件的机制,对于JAVA程序来说,只实现 UEventObserver的虚函数 onUEvent,然后注册即可。

BatteryService只关注 power_supply 的事件,所以在构造函数注册:

(2)、update()

update读取sysfs文件做到同步取得电池信息, 然后根据读到的状态更新 BatteryService 的成员变量,并广播一个Intent来通知其它关注电源状态的 组件。

当kernel有power_supply事件上报时, mUEventObserver调用update()函数,然后update 调用native_update从sysfs中读取相关状态(com_android_server_BatteryService.cpp):

(3)、sysfs

Linux 驱动 driver 维护着保存电池信息的一组文件 sysfs,供应用程序获

取电源相关状态:

二、Uevent部分

Uevent是内核通知android有状态变化的一种方法,比如USB线插入、拔出,电池电量变化等等。其本质是内核发送(可以通过socket)一个字符串,应用层(android)接收并解释该字符串,获取相应信息。如下图所示,如果其中有信息变化,uevent触发,做出相应的数更新。


Android中的BatteryService及相关组件


1、Androiduevent架构

     Android很多事件都是通过uevent跟kernel来异步通信的。其中类UEventObserver是核心。UEventObserver接收kernel的uevent信息的抽象类。

(1)、server层代码
       battery server:
       frameworks/frameworks/base/services/java/com/android/server/SystemServer.java
       frameworks/frameworks/base/services/java/com/android/server/BatteryService.java

(2)、java层代码
      frameworks/base/core/java/android/os/UEventObserver.java
(3)、JNI层代码
        frameworks/base/core/jni/android_os_UEventObserver.cpp
(4)、底层代码
         hardware/libhardware_legacy/uevent/uevent.c

读写kernel的接口socket(PF_NETLINK,SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
2、UEventObserver的使用

类UEventObserver提供了三个接口给子类来调用:
(1)、onUEvent(UEvent event) : 子类必须重写这个onUEvent来处理uevent。
(2)、startObserving(Stringmatch) : 启动进程,要提供一个字符串参数。
(3)、stopObserving() : 停止进程。
   例子://在BatteryService.java中

[java]  view plain copy
  1. mUEventObserver.startObserving("SUBSYSTEM=power_supply");  
  2.        private UEventObserver mUEventObserver = new UEventObserver() {  
  3.        @Override  
  4.              public void onUEvent(UEventObserver.UEvent event) {  
  5.               update();  
  6.              }  
  7.        };  

 在UEvent thread中会不停调用 update()方法,来更新电池的信息数据。

3、vold server分析
(1)、在system/vold/NetlinkManager.cpp中:

[cpp]  view plain copy
  1. if ((mSock = socket(PF_NETLINK,SOCK_DGRAM,NETLINK_KOBJECT_UEVENT)) < 0) {  
  2.               SLOGE("Unable to create uevent socket: %s", strerror(errno));  
  3.               return -1;  
  4.       }  
  5.       if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {  
  6.               SLOGE("Unable to set uevent socket options: %s", strerror(errno));  
  7.               return -1;  
  8.       }  
  9.       if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {  
  10.               SLOGE("Unable to bind uevent socket: %s", strerror(errno));  
  11.               return -1;  
  12.       }  
 (2)、然后在system/vold/NetlinkHandler.cpp的NetlinkHandler::onEvent中处理  
[cpp]  view plain copy
  1. void NetlinkHandler::onEvent(NetlinkEvent *evt) {  
  2.          VolumeManager *vm = VolumeManager::Instance();  
  3.         const char *subsys = evt->getSubsystem();  
  4.         if (!subsys) {  
  5.               SLOGW("No subsystem found in netlink event");  
  6.               return;  
  7.         }  
  8.         if (!strcmp(subsys, "block")) {  
  9.               vm->handleBlockEvent(evt);  
  10.         } else if (!strcmp(subsys, "switch")) {  
  11.               vm->handleSwitchEvent(evt);  
  12.         } else if (!strcmp(subsys, "battery")) {  
  13.         } else if (!strcmp(subsys, "power_supply")) {  
  14.         }  
  15. }  

(3)、在system/core/libsysutils/src/NetlinkListener.cpp中监听。
4、batteryserver分析

   java代码:frameworks/frameworks/base/services/java/com/android/server/BatteryService.java
   JNI代码: frameworks/base/services/jni/com_android_server_BatteryService.cpp

  (1)、BatteryService是跑在system_process当中,在系统初始化的时候启动

如下在BatteryService.java中:

[java]  view plain copy
  1. Log.i(TAG, “Starting Battery Service.”);  
  2. BatteryService battery = new BatteryService(context);  
  3. ServiceManager.addService(“battery”, battery);  
    (2)、数据来源
       BatteryService通过JNI(com_android_server_BatteryService.cpp)读取数据。
        BatteryService通过JNI注册的不仅有函数,还有变量。 如下: BatteryService是跑在system_process当中,在系统初始化的时候启动 ,如下在BatteryService.java中:
[java]  view plain copy
  1.   //##############在BatteryService.java中声明的变量################  
  2.   private boolean mAcOnline;  
  3.   private boolean mUsbOnline;  
  4.   private int mBatteryStatus;  
  5.   private int mBatteryHealth;  
  6.   private boolean mBatteryPresent;  
  7.   private int mBatteryLevel;  
  8.   private int mBatteryVoltage;  
  9.   private int mBatteryTemperature;  
  10.   private String mBatteryTechnology;  
  11. //在BatteryService.java中声明的变量,在com_android_server_BatteryService.cpp中共用,即在com_android_server_BatteryService.cpp中其实操作的也是BatteryService.java中声明的变量。  
  12. gFieldIds.mAcOnline = env->GetFieldID(clazz, “mAcOnline”, “Z”);  
  13. gFieldIds.mUsbOnline = env->GetFieldID(clazz, “mUsbOnline”, “Z”);  
  14. gFieldIds.mBatteryStatus = env->GetFieldID(clazz, “mBatteryStatus”, “I”);  
  15. gFieldIds.mBatteryHealth = env->GetFieldID(clazz, “mBatteryHealth”, “I”);  
  16. gFieldIds.mBatteryPresent = env->GetFieldID(clazz, “mBatteryPresent”, “Z”);  
  17. gFieldIds.mBatteryLevel = env->GetFieldID(clazz, “mBatteryLevel”, “I”);  
  18. gFieldIds.mBatteryTechnology = env->GetFieldID(clazz, “mBatteryTechnology”, Ljava/lang/String;”);  
  19. gFieldIds.mBatteryVoltage = env->GetFieldID(clazz, “mBatteryVoltage”, “I”);  
  20. gFieldIds.mBatteryTemperature = env->GetFieldID(clazz, “mBatteryTemperature”, “I”);  
  21. //上面这些变量的值,对应是从下面的文件中读取的,一只文件存储一个数值。  
  22. #define AC_ONLINE_PATH “/sys/class/power_supply/ac/online”  
  23. #define USB_ONLINE_PATH “/sys/class/power_supply/usb/online”  
  24. #define BATTERY_STATUS_PATH “/sys/class/power_supply/battery/status”  
  25. #define BATTERY_HEALTH_PATH “/sys/class/power_supply/battery/health”  
  26. #define BATTERY_PRESENT_PATH “/sys/class/power_supply/battery/present”  
  27. #define BATTERY_CAPACITY_PATH “/sys/class/power_supply/battery/capacity”  
  28. #define BATTERY_VOLTAGE_PATH “/sys/class/power_supply/battery/batt_vol”  
  29. #define BATTERY_TEMPERATURE_PATH “/sys/class/power_supply/battery/batt_temp”  
  30. #define BATTERY_TECHNOLOGY_PATH “/sys/class/power_supply/battery/technology”  
  (3)、数据传送
     BatteryService主动把数据传送给所关心的应用程序,所有的电池的信息数据是通过Intent传送出去的。在BatteryService.java中,Code如下:

[java]  view plain copy
  1. Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);  
  2. intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);  
  3. intent.putExtra(“status”, mBatteryStatus);  
  4. intent.putExtra(“health”, mBatteryHealth);  
  5. intent.putExtra(“present”, mBatteryPresent);  
  6. intent.putExtra(“level”, mBatteryLevel);  
  7. intent.putExtra(“scale”, BATTERY_SCALE);  
  8. intent.putExtra(“icon-small”, icon);  
  9. intent.putExtra(“plugged”, mPlugType);  
  10. intent.putExtra(“voltage”, mBatteryVoltage);  
  11. intent.putExtra(“temperature”, mBatteryTemperature);  
  12. intent.putExtra(“technology”, mBatteryTechnology);  
  13. ActivityManagerNative.broadcastStickyIntent(intent, null);  

(4)、数据接收

 应用如果想要接收到BatteryService发送出来的电池信息,则需要注册一个Intent为Intent.ACTION_BATTERY_CHANGED的BroadcastReceiver。

       注册方法如下:
[java]  view plain copy
  1. IntentFilter mIntentFilter = new IntentFilter();  
  2. mIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);  
  3. registerReceiver(mIntentReceiver, mIntentFilter);  
  4. private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {  
  5.     @Override  
  6.     public void onReceive(Context context, Intent intent) {  
  7.         // TODO Auto-generated method stub  
  8.         String action = intent.getAction();  
  9.         if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {  
  10.             int nVoltage = intent.getIntExtra(“voltage”, 0);  
  11.             if(nVoltage!=0){  
  12.                 mVoltage.setText(“V: ” + nVoltage + “mV – Success…”);  
  13.             }  
  14.             else{  
  15.                 mVoltage.setText(“V: ” + nVoltage + “mV – fail…”);  
  16.             }  
  17.         }  
  18.     }  
  19. };  

(5)、数据更新
        电池的信息会随着时间不停变化,自然地,就需要考虑如何实时的更新电池的数据信息。在BatteryService启动的时候,会同时通过UEventObserver启动一个onUEvent Thread。每一个Process最多只能有一个onUEvent Thread,即使这个Process中有多个UEventObserver的实例。当在一个Process中,第一次Call startObserving()方法后,这个UEvent thread就启动了。而一旦这个UEvent thread启动之后,就不会停止。

       //在BatteryService.java中

[java]  view plain copy
  1. mUEventObserver.startObserving(“SUBSYSTEM=power_supply”);  
  2. private UEventObserver mUEventObserver = new UEventObserver() {  
  3.     @Override  
  4.     public void onUEvent(UEventObserver.UEvent event) {  
  5.         update();  
  6.     }  
  7. };  

    在UEvent thread中会不停调用 update()方法,来更新电池的信息数据。
android电池(四):电池 电量计(MAX17040)驱动分析篇
http://blog.csdn.net/xubin341719/article/details/8969369

关键词: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的工作原理

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

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


下面是电路图,电路接口比较简单,VBAT+,接到max17040CELLIIC接到主控的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驱动的注册:

[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 

2、linux 中定时器的使用

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

[csharp]  view plain copy
  1. #include <linux/delay.h>  
  2. #define MAX17040_DELAY          msecs_to_jiffies(5000)  

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)的数字量,填入一个数组中,如下图所示:


下面数据时针对电池曲线的数字量,和相关参数。如上图所示,为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)电池各种信息


[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); 
android电池(五):电池 充电IC(PM2301)驱动分析篇
http://blog.csdn.net/xubin341719/article/details/8970363

关键词:android 电池  电量计  PL2301任务初始化宏 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)驱动分析篇

android充电这块,有的电源管理芯片内部包含充电管理,如s5pv210上常用的AT8937。我们这次用的max77686没有充电控制这块,所以我们加入一个充电IC来控制,选用PM2301.

一、PM2301和主控、电池的逻辑

如下图所示:


1、蓝色部分:IIC控制接口这个说得太多了,好多外围器件都是通过IIC控制的,这个一定要熟悉、熟悉、熟烂了,然后可以完成比较多的工作。

2、黄色部分:中断、使能控制脚,CHG_STATUSIRQ)、 DC_IN_INTWAKE_UP) 、 PM2301_LPLPN)、CHARGER_EN(ENN)控制引脚;

IRQ:充电IC的状态,如果有动作通知主控;

WAKE_UP:如果有DC插入,产生中断通知主控;

LPN

ENN:充电IC使能;

3PM2301 、电池、系统电压的大致逻辑

标号1:系统电压有PM2301提供;

标号2PM2301给电池充电;

标号3:系统电压有电池提供;

标号:1和标号:3不同时提供电压给系统,中间有一个MOS管切换;分两种情况:

(1)、不插充电器时,有电池提供电压给系统,走通道标号:3给系统供电;

(2)、插入DC后,系统侦测到DC插入,把3的通道关闭,打开1给系统供电,同时有2给电池充电;

二、PM2301硬件电路

如下所示:


Q5这个MOS管,就是控制系统供电的,没有充电时,VBATTVBAT+提供,充电时,VBATTSENSE_COMM提供。

控制脚对应主控的引脚:

IIC 

IIC ID 2

CHG_STATUSIRQ

 EXYNOS4_GPX1(3)

DC_IN_INTWAKE_UP

EXYNOS4_GPX0(7)

PM2301_LPLPN

EXYNOS4_GPX1(7)

CHARGER_EN(ENN)

EXYNOS4_GPL2(0)

下图为PM2301的参考电路解法,同样看到P1控制VSYSTEM电源部分的切换控制。


下图为整个电池充电的过程控制:

Trickle modeConstant current mode (CC mode or fast charge mode)Constant voltage mode (CV mode) End of charge feature


三、PL2301驱动部分

PL2301的硬件、工作原理做简单的解释,接下来我们分析驱动程序:

驱动用到知识点:

IIC的注册;

      任务初始化宏(在上一篇我们简单提过);

中断线程化;

1、IIC的注册

这个和上一篇所说的电量计相似;

1)、pm2301驱动部分

[cpp]  view plain copy
  1. static const struct i2c_device_id pm2301_id[] = {  
  2.     { "pm2301", 0 },  
  3.     { }  
  4. };  
  5. MODULE_DEVICE_TABLE(i2c, pm2301_id);  
  6.   
  7. static struct i2c_driver pm2301_i2c_driver = {  
  8.     .driver = {  
  9.         .name   = "pm2301",  
  10.     },  
  11.     .probe      = pm2301_probe,  
  12.     .remove     = __devexit_p(pm2301_remove),  
  13.     .suspend    = pm2301_suspend,  
  14.     .resume     = pm2301_resume,  
  15.     .id_table   = pm2301_id,  
  16. };  
  17.   
  18. static int __init pm2301_init(void)  
  19. {  
  20.     printk(KERN_INFO "pm2301_init !!\n");  
  21.     return i2c_add_driver(&pm2301_i2c_driver);  
  22. }  
  23. module_init(pm2301_init);  

(2)、平台驱动部分

arch/arm/mach-exynos/mach-smdk4x12.c

[cpp]  view plain copy
  1. static struct i2c_board_info i2c_devs1[] __initdata = {  
  2.   
  3. …………  
  4. #ifdef CONFIG_CHARGER_PM2301  
  5.     {  
  6.         I2C_BOARD_INFO("pm2301", 0x2c),  
  7.         .platform_data  = &pm2301_platform_data,  
  8.     },  
  9. #endif  
  10. …………  
  11. };  

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

/sys/bus/i2c/drivers/pm2301

2、关于:pm2301_platform_data这个结构体

[cpp]  view plain copy
  1. static struct pm2301_platform_data pm2301_platform_data = {  
  2.     .hw_init = pm2301_hw_init,//(1)、硬件接口初始化化;  
  3.     .gpio_lpn = GPIO_PM2301_LP,//(2)、结构体初始化;  
  4.     .gpio_irq = GPIO_CHARGER_STATUS,  
  5.     .gpio_enn = GPIO_CHARGER_ENABLE,  
  6.     .gpio_wakeup = GPIO_CHARGER_ONLINE,  
  7. };  

arch/arm/mach-exynos/mach-smdk4x12.c

1)、硬件接口初始化

[cpp]  view plain copy
  1. static int pm2301_hw_init(void)  
  2. {  
  3.     printk("pm2301_hw_init !!\n");  
  4.   
  5.     if (gpio_request(GPIO_CHARGER_ONLINE, "GPIO_CHARGER_ONLINE"))   {  
  6.         printk(KERN_ERR "%s :GPIO_CHARGER_ONLINE request port error!\n", __func__);  
  7.         goto err_gpio_failed;  
  8.     } else {  
  9.         s3c_gpio_setpull(GPIO_CHARGER_ONLINE, S3C_GPIO_PULL_NONE);  
  10.         s3c_gpio_cfgpin(GPIO_CHARGER_ONLINE, S3C_GPIO_SFN(0));  
  11.         gpio_direction_input(GPIO_CHARGER_ONLINE);  
  12.         gpio_free(GPIO_CHARGER_ONLINE);  
  13.     }  
  14.   
  15.     if (gpio_request(GPIO_CHARGER_STATUS, "GPIO_CHARGER_STATUS"))   {  
  16.         printk(KERN_ERR "%s :GPIO_CHARGER_STATUS request port error!\n", __func__);  
  17.         goto err_gpio_failed;  
  18.     } else {  
  19.         s3c_gpio_setpull(GPIO_CHARGER_STATUS, S3C_GPIO_PULL_NONE);  
  20.         s3c_gpio_cfgpin(GPIO_CHARGER_STATUS, S3C_GPIO_SFN(0));  
  21.         gpio_direction_input(GPIO_CHARGER_STATUS);  
  22.         gpio_free(GPIO_CHARGER_STATUS);  
  23.     }  
  24.   
  25.   
  26.     if (gpio_request(GPIO_CHARGER_ENABLE, "GPIO_CHARGER_ENABLE"))   {  
  27.         printk(KERN_ERR "%s :GPIO_CHARGER_ENABLE request port error!\n", __func__);  
  28.         goto err_gpio_failed;  
  29.     } else {  
  30.         s3c_gpio_setpull(GPIO_CHARGER_ENABLE, S3C_GPIO_PULL_NONE);  
  31.         s3c_gpio_cfgpin(GPIO_CHARGER_ENABLE, S3C_GPIO_SFN(1));  
  32.         gpio_direction_output(GPIO_CHARGER_ENABLE, 0);  
  33.         gpio_free(GPIO_CHARGER_ENABLE);  
  34.     }  
  35.   
  36.     if (gpio_request(GPIO_PM2301_LP, "GPIO_PM2301_LP")) {  
  37.         printk(KERN_ERR "%s :GPIO_PM2301_LP request port error!\n", __func__);  
  38.         goto err_gpio_failed;  
  39.     } else {  
  40.         s3c_gpio_setpull(GPIO_PM2301_LP, S3C_GPIO_PULL_NONE);  
  41.         s3c_gpio_cfgpin(GPIO_PM2301_LP, S3C_GPIO_SFN(1));  
  42.         gpio_direction_output(GPIO_PM2301_LP, 1);  
  43.         gpio_free(GPIO_PM2301_LP);  
  44.     }  
  45.   
  46.     return 1;  
  47.   
  48. err_gpio_failed:  
  49.     return 0;  
  50. }  

2)、结构体初始化

Include/linux/pm2301_charger.h

[cpp]  view plain copy
  1. #define GPIO_CHARGER_ONLINE     EXYNOS4_GPX0(7)//对应控制脚的主控接口  
  2. #define GPIO_CHARGER_STATUS     EXYNOS4_GPX1(3)  
  3. #define GPIO_CHARGER_ENABLE     EXYNOS4_GPL2(0)  
  4. #define GPIO_PM2301_LP          EXYNOS4_GPX1(7)  
  5. struct pm2301_platform_data {  
  6.     int (*hw_init)(void);  
  7.     int gpio_enn;  
  8.     int gpio_wakeup;  
  9.     int gpio_irq;  
  10.     int gpio_lpn;  
  11. };  
  12. extern int pm2301_get_online(void);  
  13. extern int pm2301_get_status(void);  

3、probe函数分析

如果你是初学者,建议多看程序,你会发现,其实驱动程序的格式大多都是相同的,如这个IIC 器件的, 队列、定时器之类的东西。

[cpp]  view plain copy
  1. static int __devinit pm2301_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 pm2301_chip *chip;  
  6.     int ret;  
  7.     printk(KERN_INFO "PM2301 probe !!\n");  
  8. //(1)、前面这部分是对IIC的初始化;  
  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.     chip->client = client;  
  19.     chip->pdata = client->dev.platform_data;  
  20.     i2c_set_clientdata(client, chip);  
  21.   
  22.     /* Hardware Init for PM2301 */  
  23.     if (chip->pdata->hw_init && !(chip->pdata->hw_init())) {  
  24.         dev_err(&client->dev, "hardware initial failed.\n");  
  25.         goto err_hw_failed;  
  26.     }  
  27.   
  28.     mutex_init(&i2c_lock);  
  29. //(2)、初始化两个队列  
  30.     INIT_DELAYED_WORK_DEFERRABLE(&chip->work_online, pm2301_online_work);  
  31.     INIT_DELAYED_WORK_DEFERRABLE(&chip->work_status, pm2301_ststus_work);  
  32. //(3)、中断线程化  
  33.     chip->irq_online = gpio_to_irq(chip->pdata->gpio_wakeup);  
  34.     chip->irq_status = gpio_to_irq(chip->pdata->gpio_irq);  
  35.     /* Request IRQ for PM2301 */  
  36.     ret = request_threaded_irq(chip->irq_online,  
  37.                                NULL, pm2301_dcin,  
  38.                                IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,  
  39.                                "PM2301 DC IN", chip);  
  40.   
  41.     if (ret) {  
  42.         printk(KERN_ERR "Cannot request irq %d for DC (%d)\n",  
  43.                chip->irq_online, ret);  
  44.         goto err_hw_failed;  
  45.     }  
  46.   
  47. #ifdef PM2301_REPORT_STATUS_BY_IRQ  
  48.   
  49.     ret = request_threaded_irq(chip->irq_status,  
  50.                                NULL, pm2301_status,  
  51.                                IRQF_TRIGGER_FALLING,  
  52.                                "PM2301 STATUS", chip);  
  53.   
  54.     if (ret) {  
  55.         printk(KERN_ERR "Cannot request irq %d for CHARGE STATUS (%d)\n",  
  56.                chip->irq_status, ret);  
  57.         goto err_hw_failed;  
  58.     }  
  59. #endif  
  60.   
  61.   
  62.     charger_initial = 1;  
  63.     g_has_charged = 0;  
  64.     g_has_charging_full_or_stop = 0;  
  65.   
  66. #ifdef PM2301_REPORT_STATUS_BY_IRQ  
  67.     /* Set wakeup source for online pin*/  
  68.     irq_set_irq_wake(chip->irq_status, 1);  
  69. #endif  
  70.     /* Set wakeup source for online pin*/  
  71.     irq_set_irq_wake(chip->irq_online, 1);  
  72.   
  73.     /* Init default interrupt route for PM2301 */  
  74.     pm2301_reg_init(chip->client);  
  75.     /* Init online & status value */  
  76.     chip->online = pm2301_charger_online(chip);  
  77.     g_pm2301_online = chip->online;  /* Sync to global */  
  78.     pm2301_charger_enable(chip->client, chip->online);  
  79.     pm2301_charger_status(chip);  
  80.   
  81.     printk(KERN_INFO "PM2301 probe success!!\n");  
  82.     return 0;  
  83. err_hw_failed:  
  84.     dev_err(&client->dev, "failed: power supply register\n");  
  85.     i2c_set_clientdata(client, NULL);  
  86.     kfree(chip);  
  87.     return ret;  
  88. }  

(1)、前面这部分是对IIC的初始化

这部分就不再多说了,搞来搞去都是这个老样子;

(2)、任务初始化宏

[cpp]  view plain copy
  1. INIT_DELAYED_WORK_DEFERRABLE(&chip->work_online, pm2301_online_work);  
  2. INIT_DELAYED_WORK_DEFERRABLE(&chip->work_status, pm2301_ststus_work);  

把pm2301_online_work加入队列chip->work_online, pm2301_ststus_work加入chip->work_status队列。

(3)、中断线程化  request_threaded_irq

为什么要提出中断线程化?
在 Linux 中,中断具有最高的优先级。不论在任何时刻,只要产生中断事件,内核将立即执行相应的中断处理程序,等到所有挂起的中断和软中断处理完毕后才能执行正常的任务,因此有可能造成实时任务得不到及时的处理。中断线程化之后,中断将作为内核线程运行而且被赋予不同的实时优先级,实时任务可以有比中断线程更高的优先级。这样,具有最高优先级的实时任务就能得到优先处理,即使在严重负载下仍有实时性保证。但是,并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当被线程化。 

看下我们程序中如何把中断线程化的:

[cpp]  view plain copy
  1. chip->irq_online = gpio_to_irq(chip->pdata->gpio_wakeup);  
  2. chip->irq_status = gpio_to_irq(chip->pdata->gpio_irq);  

看到这里是否想起:

[cpp]  view plain copy
  1. static struct pm2301_platform_data pm2301_platform_data = {  
  2.     ………………  
  3.     .gpio_lpn = GPIO_PM2301_LP,  
  4.     .gpio_irq = GPIO_CHARGER_STATUS,  
  5.     .gpio_enn = GPIO_CHARGER_ENABLE,  
  6.     .gpio_wakeup = GPIO_CHARGER_ONLINE,  
  7. };  
  8.   
  9. #define GPIO_CHARGER_ONLINE     EXYNOS4_GPX0(7)  
  10. #define GPIO_CHARGER_STATUS     EXYNOS4_GPX1(3)  
  11. #define GPIO_CHARGER_ENABLE         EXYNOS4_GPL2(0)  
  12. #define GPIO_PM2301_LP          EXYNOS4_GPX1(7)  

感觉申请个中断脚,这样有点费劲呀;

中断线程化:

[cpp]  view plain copy
  1. /* Request IRQ for PM2301 */  
  2. ret = request_threaded_irq(chip->irq_online,  
  3.                            NULL, pm2301_dcin,  
  4.                            IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,  
  5.                            "PM2301 DC IN", chip);  

当有插入DC中断出发时调用:

[cpp]  view plain copy
  1. static irqreturn_t pm2301_dcin(int irq, void *_data)  
  2. {  
  3.     struct pm2301_chip *chip = _data;  
  4.     schedule_delayed_work(&chip->work_online, PM2301_DELAY);  
  5.     return IRQ_HANDLED;  
  6. }  

Pm2301_dcin调度队列:chip->work_online执行:pm2301_online_work函数

[cpp]  view plain copy
  1. static void pm2301_online_work(struct work_struct *work)  
  2. {  
  3.     struct pm2301_chip *chip;  
  4.     chip = container_of(work, struct pm2301_chip, work_online.work);  
  5.     int new_online = pm2301_charger_online(chip);  
  6.   
  7.     if (chip->online != new_online) {  
  8.         chip->online = new_online;  
  9.         g_pm2301_online = chip->online;  /* Sync to global */  
  10.         pm2301_charger_enable(chip->client, chip->online);//①、初始化充电IC;  
  11. #ifdef PM2301_REPORT_STATUS_BY_IRQ  
  12.   
  13.         /*To avoid status pin keep low*/  
  14.         schedule_delayed_work(&chip->work_status, 1000);  
  15. #endif  
  16. #if defined(CONFIG_BATTERY_MAX17040)  
  17.         TriggerGasgaugeUpdate();//②、把DC状态更新到max17040;  
  18. #endif  
  19.     }  
  20. }  

①、初始化电IC

这里面主要是写一些寄存器

[cpp]  view plain copy
  1. static void pm2301_charger_enable(struct i2c_client *client, int online)  
  2. {  
  3.     if (online) {   /* Enabled Charging*/  
  4.         int batt_capacity = 0;  
  5.         batt_capacity = GetGasgaugeCapacity();  
  6.         /* Don't start charging if battery capacity above 95% when DC plug in*/  
  7.         if(0) {  
  8.         //if( batt_capacity >= 95 ) {  
  9.             pm2301_write_reg(client, 0x01, 0x02);  
  10.             pm2301_write_reg(client, 0x26, 0x00);   /* always keep the register to 0 */  
  11.         } else {  
  12.             pm2301_write_reg(client, 0x00, 0x01);   /* force resume of charging */  
  13.             pm2301_write_reg(client, 0x01, 0x06);   /* ChEn=1, AutoResume=1 */  
  14.             pm2301_write_reg(client, 0x05, 0x7A);   /* ChEoccurrentLevel:150mA, ChPrechcurrentLevel:100mA, ChCCcurrentLevel:1000mA/2000mA */  
  15.             pm2301_write_reg(client, 0x06, 0x0A);   /* ChVersumeVot:3.6V ChPrechVoltLevel:2.9V */  
  16.             pm2301_write_reg(client, 0x07, 0x1E);   /* ChVoltLevel:4.25V */  
  17.             pm2301_write_reg(client, 0x26, 0x00);   /* always keep the register to 0 */  
  18.         }  
  19.         g_has_charged = 1;  
  20.     } else {    /* Disable Charging*/  
  21.         pm2301_write_reg(client, 0x01, 0x02);  
  22.         pm2301_write_reg(client, 0x26, 0x00);   /* always keep the register to 0 */  
  23.         g_has_charged = 0;  
  24.     }  
  25. }  

②、把DC状态更新到max17040

[cpp]  view plain copy
  1. TriggerGasgaugeUpdate()  
插入DC这部流程如下:






http://www.ppmy.cn/news/300978.html

相关文章

adb命令查看手机电池信息

adb查看手机电池信息命令 adb命令 adb shell dumpsys battery 命令输出结果如下&#xff1a; 各字段说明&#xff1a; Current Battery Service state: ##当前电池服务状态 AC powered: false ##交流供电 USB powered: true ##USB供电 Wireless powered: false ##无线供电 …

【手机维修系列】手机电池会导致主板损坏么?

作为一个从事电池管理近20年的“老”工程师&#xff0c;希望能给大家拨乱反正&#xff0c;让大家在相关问题上不在困惑。 大家可能有一个担心&#xff1a;用了不好的电池&#xff0c;或者电池出问题&#xff0c;会导致主板损坏么&#xff1f;这是一个严肃的技术问题&#xff0…

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

关键词&#xff1a;android电池系统电池系统架构 uevent power_supply驱动 平台信息: 内核&#xff1a;linux2.6/linux3.0 系统&#xff1a;android/android4.0 平台&#xff1a;S5PV310(samsungexynos4210) 、samsung exynos4412 作者&#xff1a;xubin341719(欢迎转载&…

如何根据小米12手机电池信息计算充电时间

以我的小米12为例&#xff0c;电池信息如下&#xff0c;电池容量为4500mAh&#xff0c;注意mAh不是能量单位&#xff0c;而是电量单位&#xff0c;可以理解为最多存储的电子的数量。 为了通过理论计算出充电时间&#xff0c;光知道这些信息还不够&#xff0c;我们还需要知道充电…

手机电池充电时间

如今&#xff0c;手机用户逐年飞速增加&#xff0c;但是有个大家普遍非常关心的问题&#xff0c;就是手机电池充电应充多久才算充满?很多朋友都担心时间不够&#xff0c;电池电力不足&#xff0c;还可能缩短电池使用寿命&#xff1b;时间太长&#xff0c;又可能损害手机和电池…

手机电池不耐用解决

刚买一两年的手机电池不耐用&#xff0c;并不一定是电池坏了&#xff0c;而是手机的电源管理芯片在每次充放电时都会产生一个小的误差。时间久了&#xff0c;误差越来越大导致电池的虚电越来越多。 先将手机的电量用到20%以下&#xff0c;打开拨号键&#xff0c;输入以下内容&…

手机电池专业术语

20210721&#xff0c;今天对手机电池的一些专业术语进行总结 电池的充电周期 手机电池的一个重要指标就是充电周期&#xff0c;电池寿命与充电周期有着相当紧密的联系。一次充电周期指的是锂电池一次完整的充放电过程&#xff0c;也就是说当电池使用电量达到电池容量的100%&am…

锂电池基础

锂电池 1、锂电池的组成 我们平时见的手机电池都是锂电池&#xff0c;行业较电池PACK&#xff0c;锂电池PACK主要由两大块构成&#xff1a;电芯和保护板。电芯主要由五块构 成&#xff1a;正极材料、负极材料、电解液、隔膜和外壳。保护板主要由保护芯片、MOS管、电阻电容和PC…