点击打开链接

高通8064 8974 8926等pm芯片都集成了电量计,估计后续芯片都会一直存在,现在许多项目UI状态栏电池都有百分比显示,所以需要深入分析BMS有助于解决电量方面的BUG。

一: SOC(荷电状态)计算方法

名词:

FCC  Full-charge capacity

UC     Remaining capacity
CC     Coulumb counter    
UUC  Unusable capacity
RUC   Remaining usable capacity //    RUC=RC-CC-UUC
SoC   State of charge    
OCV    Open circuit voltage

SOC=(RC-CC-UUC)/(FCC-UUC)

以下是各个变量的计算方法:
1:FCC:
   在校准的电池profile中有定义,会随温度有变化;

  1. static struct single_row_lut fcc_temp = {
  2. .x  = {-20, 0, 25, 40, 60},
  3. .y  = {3193, 3190, 3190, 3180, 3183},
  4. .cols = 5
  5. };

2:RC: 开机通过开始获取的开路电压(ocv)来查表(电池校准的profile文件)计算百分比,来比对计算(电压与电荷量正比);(ocv=vbatt+rbatt*i_ma)
内核计算方法:

  1. static int calculate_remaining_charge_uah(struct pm8921_bms_chip *chip,
  2. struct pm8921_soc_params *raw,
  3. int fcc_uah, int batt_temp,
  4. int chargecycles)
  5. {
  6. int  ocv, pc, batt_temp_decidegc;
  7. ocv = raw->last_good_ocv_uv;
  8. batt_temp_decidegc = chip->last_ocv_temp_decidegc;
  9. pc = calculate_pc(chip, ocv, batt_temp_decidegc, chargecycles);
  10. pr_info("ocv = %d pc = %d\n", ocv, pc);
  11. return (fcc_uah * pc) / 100;
  12. }

但是通常情况下开机使用计算RC的ocv是上次关机存下的百分比,反向查表算出的ocv;
现在我们做法是通过判断开机时的ocv与关机的ocv如果偏差太大,我们将采用开机ocv来计算RC,所以开机的ocv对开机的百分比影响非常大;

3:CC:pmic库伦计 ADC采样到的:
内核获取方法:

  1. /**
  2. * calculate_cc_uah -
  3. * @chip:  the bms chip pointer
  4. * @cc:   the cc reading from bms h/w
  5. * @val:  return value
  6. * @coulumb_counter: adjusted coulumb counter for 100%
  7. *
  8. * RETURNS: in val pointer coulumb counter based charger in uAh
  9. *        (micro Amp hour)
  10. */
  11. static void calculate_cc_uah(struct pm8921_bms_chip *chip, int cc, int *val)
  12. {
  13. int64_t cc_voltage_uv, cc_pvh, cc_uah;
  14. cc_voltage_uv = cc;
  15. pr_debug("cc = %d\n", cc);
  16. cc_voltage_uv = cc_to_microvolt(chip, cc_voltage_uv);
  17. cc_voltage_uv = pm8xxx_cc_adjust_for_gain(cc_voltage_uv);
  18. pr_debug("cc_voltage_uv = %lld microvolts\n", cc_voltage_uv);
  19. cc_pvh = ccmicrovolt_to_pvh(cc_voltage_uv);
  20. pr_debug("cc_pvh = %lld pico_volt_hour\n", cc_pvh);
  21. cc_uah = div_s64(cc_pvh, chip->r_sense_uohm);
  22. *val = cc_uah;
  23. }

4:UUC:计算方法和UC一致,但是rbatt是动态变化的,会复杂点;

  1. static int calculate_termination_uuc(struct pm8921_bms_chip *chip,
  2. int batt_temp, int chargecycles,
  3. int fcc_uah, int i_ma,
  4. int *ret_pc_unusable)
  5. {
  6. int unusable_uv, pc_unusable, uuc;
  7. int i = 0;
  8. int ocv_mv;
  9. int batt_temp_degc = batt_temp / 10;
  10. int rbatt_mohm;
  11. int delta_uv;
  12. int prev_delta_uv = 0;
  13. int prev_rbatt_mohm = 0;
  14. int prev_ocv_mv = 0;
  15. int uuc_rbatt_uv;
  16. for (i = 0; i <= 100; i++) {
  17. ocv_mv = interpolate_ocv(chip->pc_temp_ocv_lut,
  18. batt_temp_degc, i);
  19. rbatt_mohm = get_rbatt(chip, i, batt_temp);
  20. unusable_uv = (rbatt_mohm * i_ma) + (chip->v_cutoff * 1000);
  21. delta_uv = ocv_mv * 1000 - unusable_uv;
  22. pr_debug("soc = %d ocv = %d rbat = %d u_uv = %d delta_v = %d\n",
  23. i, ocv_mv, rbatt_mohm, unusable_uv, delta_uv);
  24. if (delta_uv > 0)
  25. break;
  26. prev_delta_uv = delta_uv;
  27. prev_rbatt_mohm = rbatt_mohm;
  28. prev_ocv_mv = ocv_mv;
  29. }
  30. uuc_rbatt_uv = linear_interpolate(rbatt_mohm, delta_uv,
  31. prev_rbatt_mohm, prev_delta_uv,
  32. 0);
  33. unusable_uv = (uuc_rbatt_uv * i_ma) + (chip->v_cutoff * 1000);
  34. pc_unusable = calculate_pc(chip, unusable_uv, batt_temp, chargecycles);
  35. uuc = (fcc_uah * pc_unusable) / 100;
  36. pr_debug("For i_ma = %d, unusable_rbatt = %d unusable_uv = %d unusable_pc = %d uuc = %d\n",
  37. i_ma, uuc_rbatt_uv, unusable_uv,
  38. pc_unusable, uuc);
  39. *ret_pc_unusable = pc_unusable;
  40. return uuc;
  41. }

高通的这套BMS算法运行起来由于ocv的校准和温度等等原因,会有一定的偏差,高通还有一套通过校准OCV来估算SOC(简称soc_est)的机制,下面就是使用这套来校准SOC;

二:校准SOC
 
 高通算法通过对soc与soc_est比较计算出ocv的差值,来改变last_ocv_uv的值,主要是改变RC,重新计算soc,将会使得soc与soc_est越来越接近,越来越准;

ocv在以下2种情况会被改变:

1:系统睡眠唤醒期间,cov被更新,库仑计RST;

2:低电进入adjust_soc()方法调节;

在高通8064平台由于电量计对大电流计算不准确,一直亮屏的情况(没有经历睡眠唤醒的ocv更新与CC RST)会导致关机电压到达3.74V。要想解决这个问题必须使得校准SOC可以正常工作。但是当满电时开机就会记录ocv的值偏高,导致快要低电时不能很好的校准soc。所以有必要在马上进入低电(15%)时做一次模拟开机一次(电量计RERST CC=0从soc找出ocv )使得last_ocv_uv降下来,才可以完美发挥adjust_soc的作用,使得关机电压能一直能到3.4V左右。
 

  1. <6>[ 7796.038269] read_soc_params_raw: 333333333 last_good_ocv_uv= 3777000uV
  2. <6>[ 7796.038360] read_soc_params_raw: last_good_ocv_raw= 0x943f, last_good_ocv_uv= 3777000uV
  3. <6>[ 7796.038543] calculate_soc_params: FCC = 3190000uAh batt_temp = 300, cycles = 0
  4. <6>[ 7796.038635] calculate_remaining_charge_uah: ocv = 3777000 pc = 35
  5. <6>[ 7796.038665] calculate_soc_params: RC = 1116500uAh
  6. <6>[ 7796.038726] calculate_soc_params: cc_uah = 394979uAh raw->cc = 5764312
  7. <6>[ 7796.038818] calculate_state_of_charge: RUC(RC-CC-UUC) = 657721uAh RC = 1116500uAh CC= 394979uAh UUC= 63800uAh FCC= 3190000uAh SOC(RUC/FCC-UUC) =21

adjust_soc方法:

  1. </pre><p class="pa-1" style="line-height: 18px; font-size: 14px; padding-top: 0px; padding-bottom: 0px; margin-top: 0px; margin-bottom: 10px; color: rgb(68, 68, 68); font-family: 宋体;"><pre name="code" class="html"> static int last_soc_est = -EINVAL;
  2. static int adjust_soc(struct pm8921_bms_chip *chip, int soc,
  3. int batt_temp, int chargecycles,
  4. int rbatt, int fcc_uah, int uuc_uah, int cc_uah)
  5. {
  6. int ibat_ua = 0, vbat_uv = 0;
  7. int ocv_est_uv = 0, soc_est = 0, pc_est = 0, pc = 0;
  8. int delta_ocv_uv = 0;
  9. int n = 0;
  10. int rc_new_uah = 0;
  11. int pc_new = 0;
  12. int soc_new = 0;
  13. int m = 0;
  14. int rc = 0;
  15. int delta_ocv_uv_limit = 0;
  16. int correction_limit_uv = 0;
  17. rc = pm8921_bms_get_simultaneous_battery_voltage_and_current(
  18. &ibat_ua,
  19. &vbat_uv);
  20. if (rc < 0) {
  21. pr_err("simultaneous vbat ibat failed err = %d\n", rc);
  22. goto out;
  23. }
  24. very_low_voltage_check(chip, ibat_ua, vbat_uv);
  25. if (chip->low_voltage_detect &&
  26. wake_lock_active(&chip->low_voltage_wake_lock)) {
  27. if (is_voltage_below_cutoff_window(chip, ibat_ua, vbat_uv)) {
  28. soc = 0;
  29. pr_info("Voltage below cutoff, setting soc to 0\n");
  30. goto out;
  31. }
  32. }
  33. delta_ocv_uv_limit = DIV_ROUND_CLOSEST(ibat_ua, 1000);
  34. ocv_est_uv = vbat_uv + (ibat_ua * rbatt)/1000;
  35. calc_current_max(chip, ocv_est_uv, rbatt);
  36. pc_est = calculate_pc(chip, ocv_est_uv, batt_temp, last_chargecycles);
  37. soc_est = div_s64((s64)fcc_uah * pc_est - uuc_uah*100,
  38. (s64)fcc_uah - uuc_uah);
  39. soc_est = bound_soc(soc_est);
  40. /* never adjust during bms reset mode */
  41. if (bms_reset) {
  42. pr_debug("bms reset mode, SOC adjustment skipped\n");
  43. goto out;
  44. }
  45. if (ibat_ua < 0 && pm8921_is_batfet_closed()) {
  46. soc = charging_adjustments(chip, soc, vbat_uv, ibat_ua,
  47. batt_temp, chargecycles,
  48. fcc_uah, cc_uah, uuc_uah);
  49. goto out;
  50. }
  51. /*
  52. * do not adjust
  53. * if soc_est is same as what bms calculated
  54. * OR if soc_est > 15
  55. * OR if soc it is above 90 because we might pull it low
  56. * and  cause a bad user experience
  57. */
  58. if (soc_est == soc
  59. || soc_est > 15
  60. || soc >= 90)
  61. goto out;
  62. if (last_soc_est == -EINVAL)
  63. last_soc_est = soc;
  64. n = min(200, max(1 , soc + soc_est + last_soc_est));
  65. /* remember the last soc_est in last_soc_est */
  66. last_soc_est = soc_est;
  67. pc = calculate_pc(chip, chip->last_ocv_uv,
  68. chip->last_ocv_temp_decidegc, last_chargecycles);
  69. if (pc > 0) {
  70. pc_new = calculate_pc(chip, chip->last_ocv_uv - (++m * 1000),
  71. chip->last_ocv_temp_decidegc,
  72. last_chargecycles);
  73. while (pc_new == pc) {
  74. /* start taking 10mV steps */
  75. m = m + 10;
  76. pc_new = calculate_pc(chip,
  77. chip->last_ocv_uv - (m * 1000),
  78. chip->last_ocv_temp_decidegc,
  79. last_chargecycles);
  80. }
  81. } else {
  82. /*
  83. * pc is already at the lowest point,
  84. * assume 1 millivolt translates to 1% pc
  85. */
  86. pc = 1;
  87. pc_new = 0;
  88. m = 1;
  89. }
  90. delta_ocv_uv = div_s64((soc - soc_est) * (s64)m * 1000,
  91. n * (pc - pc_new));
  92. if (abs(delta_ocv_uv) > delta_ocv_uv_limit) {
  93. pr_debug("limiting delta ocv %d limit = %d\n", delta_ocv_uv,
  94. delta_ocv_uv_limit);
  95. if (delta_ocv_uv > 0)
  96. delta_ocv_uv = delta_ocv_uv_limit;
  97. else
  98. delta_ocv_uv = -1 * delta_ocv_uv_limit;
  99. pr_debug("new delta ocv = %d\n", delta_ocv_uv);
  100. }
  101. if (wake_lock_active(&chip->low_voltage_wake_lock)) {
  102. pr_debug("Low Voltage, apply only ibat limited corrections\n");
  103. goto skip_limiting_corrections;
  104. }
  105. if (chip->last_ocv_uv > 3800000)
  106. correction_limit_uv = the_chip->high_ocv_correction_limit_uv;
  107. else
  108. correction_limit_uv = the_chip->low_ocv_correction_limit_uv;
  109. if (abs(delta_ocv_uv) > correction_limit_uv) {
  110. pr_debug("limiting delta ocv %d limit = %d\n", delta_ocv_uv,
  111. correction_limit_uv);
  112. if (delta_ocv_uv > 0)
  113. delta_ocv_uv = correction_limit_uv;
  114. else
  115. delta_ocv_uv = -1 * correction_limit_uv;
  116. pr_debug("new delta ocv = %d\n", delta_ocv_uv);
  117. }
  118. skip_limiting_corrections:
  119. chip->last_ocv_uv -= delta_ocv_uv;
  120. if (chip->last_ocv_uv >= chip->max_voltage_uv)
  121. chip->last_ocv_uv = chip->max_voltage_uv;
  122. /* calculate the soc based on this new ocv */
  123. pc_new = calculate_pc(chip, chip->last_ocv_uv,
  124. chip->last_ocv_temp_decidegc, last_chargecycles);
  125. rc_new_uah = (fcc_uah * pc_new) / 100;
  126. soc_new = (rc_new_uah - cc_uah - uuc_uah)*100 / (fcc_uah - uuc_uah);
  127. soc_new = bound_soc(soc_new);
  128. /*
  129. * if soc_new is ZERO force it higher so that phone doesnt report soc=0
  130. * soc = 0 should happen only when soc_est == 0
  131. */
  132. if (soc_new == 0 && soc_est >= the_chip->hold_soc_est)
  133. soc_new = 1;
  134. soc = soc_new;
  135. out:
  136. pr_debug("ibat_ua = %d, vbat_uv = %d, ocv_est_uv = %d, pc_est = %d, "
  137. "soc_est = %d, n = %d, delta_ocv_uv = %d, last_ocv_uv = %d, "
  138. "pc_new = %d, soc_new = %d, rbatt = %d, m = %d\n",
  139. ibat_ua, vbat_uv, ocv_est_uv, pc_est,
  140. soc_est, n, delta_ocv_uv, chip->last_ocv_uv,
  141. pc_new, soc_new, rbatt, m);
  142. return soc;
  143. }

针对高通BMS的研究 高通电量计的更多相关文章

  1. 高通spi 屏幕 -lk代码分析

    lk SPI驱动 1. 初始化时钟 在lk中,我们是从kmain开始执行下来的,而执行顺序则是先初始化时钟,也就是在platform_early_init函数中开始执行的: 在这里我们需要修改这个函数 ...

  2. 高通8X16电池BMS算法(一)【转】

    本文转载自:http://www.voidcn.com/blog/yanleizhouqing/article/p-6037399.html 最近一直在搞电源管理相关内容,之前是8610的bms,现在 ...

  3. (转)Unity导出Android在高通骁龙800以上CPU概率性崩溃解决方法研究

    Android上的奇葩问题真的是太多了,开始测试反馈说游戏在某些Android手机上随机crash,后来经过详细的测试发现随机闪退的手机都是搭载了高通骁龙800以上的CPU.然后连上真机当crash的 ...

  4. 【转】高通平台android 环境配置编译及开发经验总结

    原文网址:http://blog.csdn.net/dongwuming/article/details/12784535 1.高通平台android开发总结 1.1 搭建高通平台环境开发环境 在高通 ...

  5. 高通开发笔记---Yangtze worknote

    点击打开链接 1. repo init -u git://review.sonyericsson.net/platform/manifest -b volatile-jb-mr1-yangtze 2. ...

  6. 高通 MSM8K bootloader : SBL1 .

    一. MSM8K Boot Flow 图1: 高通MSM8K平台bootloader启动流程基本类似,但具体各平台,比如MSM8974.MSM8916.MSM8994等,会有微小区别. 从上图,可以看 ...

  7. 高通android开发摘要

    一部分是开源的,可以从codeaurora.org上下载,还有一部分是高通产权的,需要从高通的网站上下载. 将高通产权的代码放到:vendor/qcom/proprietary 1. 设置bms一些参 ...

  8. 高通Android display架构分析

    目录(?)[-] Kernel Space Display架构介绍 函数和数据结构介绍 函数和数据结构介绍 函数和数据结构介绍 数据流分析 初始化过程分析 User Space display接口 K ...

  9. 高通 MSM8K bootloader之一: SBL1

    MSM8K Boot Flow  转自:http://www.cnblogs.com/liang123/p/6325257.html            http://blog.csdn.net/F ...

随机推荐

  1. Centos7 创建个文件 thread 怪现象

    我在~下创建个目录thread, 然后写了个程序, 再执行, 提示需要权限, 之后我重命名下文件的名字, 重新编译下, 就可以正常执行了.

  2. Windows内核 内存管理基本概念

    内存管理概念: 1)物理内存 PC上有三条总线:数据总线.地址总线和控制总线.32位CPU的寻址能力是4GB个字节,用户最多可以使用4GB的真实物理内存.PC中很多设备都提供了自己的设备内存,例如显卡 ...

  3. git命令详解(转)

    Git使用 git branch 查看本地所有分支 git status 查看当前状态 git commit 提交 git branch -a 查看所有的分支 git branch -r 查看远程所有 ...

  4. CSS权威指南 - 内边距 边框 和 外边距

    九十年代的完全用表格布局,简单的段落边框都需要用表格.CSS让布局更方便. 基本元素框 basic element boxes 如同第七章基本视觉格式化那一章讲到,每个文档元素会生成一个元素框,这个框 ...

  5. 修改ArcGIS Server Account / 站点管理员的 账号、密码

    安装ArcGIS Server(10.1之后版本)时会先后创建两个账户:ArcGIS Server Account和站点管理账户 ArcGIS Server Account 是操作系统账户,Serve ...

  6. yii 基础版用rbac-plus

    1.将高级版的common/models/user.php覆盖掉基础版的models/user.php 2.将命名空间 namespace common\models;改为 namespace app ...

  7. How to prevent SQL injection attacks?

    In our earlier tutorial on SQL Injection, one way to have prevented the SQL injection attack was by ...

  8. angularJs自定义指令.directive==类似自定义标签

    创建自定义的指令 除了 AngularJS 内置的指令外,我们还可以创建自定义指令. 你可以使用 .directive 函数来添加自定义的指令. 要调用自定义指令,HTML 元素上需要添加自定义指令名 ...

  9. 【转】给npm设置代理

    可以运行如下两句命令设置代理,注意代理的地址改为自己实际可用的代理. npm config set proxy=http://127.0.0.1:8087 npm config set registr ...

  10. App软件开发的完整在线流程(一看就懂)

    App软件开发其实并不复杂,完整的在线App开发不过9个步骤,一看就明白到底是怎么回事. 第一步:需求评估 在App项目正式启动前,客户应该对自己的需求进行评估,确认项目的开发周期和成本.登录APIC ...