MTK Battery系统

来源:互联网 发布:淘宝卖家发货怎么打印 编辑:程序博客网 时间:2024/05/18 03:48

        MTK方案的电池充电过程分为预充、恒流充电(CC模式)、恒压充电(CV模式)三种模式,整个充电过程如下充电状态图所示:


        从充电状态图看出来,刚开始充电的时候,代码先判断是插USB充电还是插ac充电,电池在进入充电阶段分为快速充电、CC(恒流充电)、CV(恒压充电)。而从CC模式切换到CV模式在代码中的alps/mediatek/kernel/drivers/power/linear_charging.c和alps/mediatek/kernel/drivers/power/ switch_charging.c。

     MTK Battery系统驱动的大致流程主要是通过系统platform总线注册device和driver,然后在probe函数里面创建了一个线程,然后创建一个hrtimer定时器,定时器没10s运行一次,同时在probe函数里面会创建一些设备节点,通过这些设备节点,系统将每10s更新的数据上传给上层供上层调用显示。

1、battery硬件原理图


  首先先介绍几个电压值:

VCHG:USB正极

VCDT:充电电压检测脚

ISENSE:充电电流检测电阻的正极

BATSNS:充电电流检测电阻的负极

BAT:电池正极引脚

BAT_ON:电池NTC(热敏电阻)引脚 

      通过三极管可以开启,关闭充电功能,开启充电的时候调节三极管的基级电流可以控制流过三极管CE端的电流从而实现充电电流大小的设置。

     Rsense采样电阻:对于软件来说可以测量充电电流的大小:电流计算方法:(ISENSE – VBAT)/Rsense。对于PMU来说通过Rsense可以实现电流控制。比如要实现1A的充电电流,Rsense为0.2欧。PMU实现该电流的方法就是设法一直保证Rsense两端的电压是0.2V。

 

BAT_ON在充电中的作用:

1,检测电池是否存在。电压必须小于1.062V,否则认为电池不存在不能充电,硬件行为,软件无法关闭此功能

2,高温检测,电池电压必须大于0.2V,否则认为电池温度过高不能充电,硬件行为,软件可以关闭此功能

3,软件检测电池温度。

上图中NTC电阻会串联一个24k的电阻,这个会在后面源代码分析时起作用。

2、Battery架构简析


    

      MTK电池显示的具体过程为:硬件ADC读取Battery的各路信息:包括温度,电压等。然后利用MTK开发的电量算法分析得到的数据。Kernel层将电量信息通过写文件节点的方式更新,并通过UEVENT通知上层。上层Service开启UEVENT LISTENER,监听到UEVENT后,读取battery相关文件节点,获取电量信息。Service更新数据后,通过Broadcast通知所有开启了相关listener的activities。

根据不同的电量读取和计算的策略,第一步的读取和第二步的算法部分会有比较大的差异,而后面的数据更新和事件通知部分一致性较高。

3、MTK电池驱动分析

       在分析源代码前,先来看看PMU_ChargerStruct这个结构体。

[cpp] view plain copy
print?
  1. 146 typedef struct  
  2. 147 {  
  3. 148         kal_bool                        bat_exist;  // 判断电池是否存在  
  4. 149         kal_bool                        bat_full;  //判断电池是否充满  
  5. 150         INT32                   bat_charging_state; //判断充电状态  
  6. 151         UINT32                  bat_vol; //电池平均电压  
  7. 152         kal_bool                        bat_in_recharging_state; //电池是否在回充  
  8. 153         kal_uint32              Vsense; // 电池瞬间电压  
  9. 154         kal_bool                        charger_exist; // Charger是否存在Charger电压  
  10. 155         UINT32                  charger_vol;  // Charger电压  
  11. 156         INT32                   charger_protect_status; //充电保护状态,过流或者过压保护状态  
  12. 157         INT32                   ICharging; // 充电电流  
  13. 158         INT32                   IBattery;  
  14. 159         INT32                   temperature; // 电池温度  
  15. 160         INT32                   temperatureR;  
  16. 161         INT32                   temperatureV;  
  17. 162         UINT32                  total_charging_time; //总的充电时间  
  18. 163         UINT32                  PRE_charging_time; // Pre cc充电时间  
  19. 164         UINT32                  CC_charging_time; //cc充电时间  
  20. 165         UINT32                  TOPOFF_charging_time; //TOPOFF充电时间  
  21. 166         UINT32                  POSTFULL_charging_time; //Postfull充电时间  
  22. 167         UINT32                  charger_type; //充电器类型  
  23. 168         INT32                   SOC; //底层的电量  
  24. 169         INT32                   UI_SOC; // 上层的电量  
  25. 170         UINT32                  nPercent_ZCV;   
  26. 171         UINT32                  nPrecent_UI_SOC_check_point; //N%同步点对应的开路电压以及UI电量  
  27. 172         UINT32                  ZCV; //电池当前开路电压  
  28. 173 } PMU_ChargerStruct;  
       结构体中个变量定义可参考注释,PMU_ChargerStruct记录了整个充电代码的所有充电的变化情况,包括上报给上层的电量值,可以说整个充电的过程就是围绕这这个结构体进行数值的变化,所以这个结构体在整个充电过程中起着非常重要的作用。

     下面来看看代码的具体实现。

      MTK的battery的注册是通过platform总线实现的,在mediatek/kernel/drivers/power/battery_common.c中,我们会看到对battery的device和driver进行注册的函数,device和driver通过name进行匹配,device和driver注册后,就是执行battery_driver结构体里面的函数,执行battery_driver里面的函数首先运行的是battery_probe函数,在battery_probe函数里面,先对battery进行字符设备的注册,注册的字符设备名为MT_pmic_adc_cali。

   在注册完字符设备后,probe会执行get_charging_control();这个函数,这个函数在整个充电过程中起着很重要的作用,该函数就一行代码:

[cpp] view plain copy
print?
  1. static void get_charging_control(void)  
  2. {  
  3.     battery_charging_control = chr_control_interface;  
  4. }  
  5. kal_int32 chr_control_interface(CHARGING_CTRL_CMD cmd, void *data)  
  6. {  
  7.         kal_int32 status;  
  8.         if(cmd < CHARGING_CMD_NUMBER)  
  9.                status = charging_func[cmd](data);  
  10.         else  
  11.                 return STATUS_UNSUPPORTED;  
  12.          return status;  
  13.   }  
        在get_charging_control函数里面,就是将chr_control_interface函数指向battery_charging_control,在后面会有很多对battery_charging_control函数的调用,而所有的调用都是传递一个参数进来,然后对比charging_func数组里面的函数指正,在对其他函数进行调用。下面看看battery_charging_control(CHARGING_CMD_GET_PLATFORM_BOOT_MODE,&g_platform_boot_mode);这个语句的调用,就可以知道battery_charging_control函数是怎样运行的。

[cpp] view plain copy
print?
  1. typedef enum  
  2. {  
  3.         CHARGING_CMD_INIT,  
  4.        CHARGING_CMD_DUMP_REGISTER,  
  5.         CHARGING_CMD_ENABLE,  
  6.         CHARGING_CMD_SET_CV_VOLTAGE,  
  7.         CHARGING_CMD_GET_CURRENT,  
  8.         CHARGING_CMD_SET_CURRENT,  
  9.         CHARGING_CMD_SET_INPUT_CURRENT,  
  10.         CHARGING_CMD_GET_CHARGING_STATUS,  
  11.         CHARGING_CMD_RESET_WATCH_DOG_TIMER,  
  12.         CHARGING_CMD_SET_HV_THRESHOLD,  
  13.         CHARGING_CMD_GET_HV_STATUS,  
  14.         CHARGING_CMD_GET_BATTERY_STATUS,  
  15.         CHARGING_CMD_GET_CHARGER_DET_STATUS,  
  16.         CHARGING_CMD_GET_CHARGER_TYPE,  
  17.         CHARGING_CMD_GET_IS_PCM_TIMER_TRIGGER,  
  18.         CHARGING_CMD_SET_PLATFORM_RESET,  
  19.         CHARGING_CMD_GET_PLATFORM_BOOT_MODE,  
  20.         CHARGING_CMD_SET_POWER_OFF,  
  21.         CHARGING_CMD_NUMBER  
  22. } CHARGING_CTRL_CMD;  
        CHARGING_CMD_GET_PLATFORM_BOOT_MODE定义在一个枚举型变量中,并且在枚举类型中的是第十七个元素,而枚举类型如果没有初始化第一参数时第一个参数就为0,所以CHARGING_CMD_GET_PLATFORM_BOOT_MODE的值就为16,而battery_charging_control函数是通过chr_control_interface函数指向相同的地址的,所以,调用battery_charging_control函数并传递参数实际就是对chr_control_interface函数进行操作,在chr_control_interface函数中,会直接将cmd参数直接给charging_func结构体,而charging_func结构体的定义为:

[cpp] view plain copy
print?
  1. static kal_uint32 (*charging_func[CHARGING_CMD_NUMBER])(void *data)=  
  2. {  
  3.         charging_hw_init  
  4.        ,charging_dump_register  
  5.        ,charging_enable  
  6.        ,charging_set_cv_voltage  
  7.        ,charging_get_current  
  8.        ,charging_set_current  
  9.        ,charging_set_input_current             // not support, empty function  
  10.        ,charging_get_charging_status   // not support, empty function  
  11.        ,charging_reset_watch_dog_timer  
  12.        ,charging_set_hv_threshold  
  13.        ,charging_get_hv_status  
  14.        ,charging_get_battery_status  
  15.        ,charging_get_charger_det_status  
  16.        ,charging_get_charger_type  
  17.        ,charging_get_is_pcm_timer_trigger  
  18.        ,charging_set_platform_reset  
  19.        ,charging_get_platfrom_boot_mode  
  20.        ,charging_set_power_off  
  21. };  
       CHARGING_CMD_GET_PLATFORM_BOOT_MODE在CHARGING_CTRL_CMD枚举类型中的值为17,所以执行status = charging_func[cmd](data)语句的时候就直接调用到charging_func中的第十七个参数,并且返回值直接跟data指向相同的地址,所以返回值会在data。所以,对于battery_charging_control(CHARGING_CMD_GET_PLATFORM_BOOT_MODE,&g_platform_boot_mode)这类函数的调用,首先就是确定CHARGING_CMD_GET_PLATFORM_BOOT_MODE在CHARGING_CTRL_CMD枚举中的位置,然后到charging_func结构体中相对应的位置查询相对应的函数执行,最后将执行函数后的返回值通过data这个指针变量传给调用函数。

      当probe函数注册完了字符设备后,函数进行的随后的进行的操作是在sys下面建立设备节点,总共建立了四个设备节点,分别为ac_main、usb_main、wireless_main和battery_main,这四个节点分别为使用适配器、USB、无线充电以及使用电池供电。电池电量发生变化的时候,会通过这些节点将数据上报给上层,也就是说上层是通过这些节点来读取底层电池电量变化的数据的。
     当初始化完成后,probe函数会创建一个hrtimer定时器,定时器启动bat_thread_kthread函数,bat_thread_kthread函数中的while(1)里面包含了BAT_thread()函数,BAT_thread()就是充电的核心函数。

[cpp] view plain copy
print?
  1. int bat_thread_kthread(void *x)  
  2. {  
  3.     ktime_t ktime = ktime_set(3, 0);  // 10s, 10* 1000 ms     
  4.       
  5.     /* Run on a process content */    
  6.     while (1) {                 
  7.         mutex_lock(&bat_mutex);  
  8.         if((chargin_hw_init_done == KAL_TRUE) && (battery_suspended == KAL_FALSE))  
  9.             BAT_thread();                        
  10.         mutex_unlock(&bat_mutex);  
  11.         battery_xlog_printk(BAT_LOG_FULL, "wait event \n" );  
  12.         wait_event(bat_thread_wq, (bat_thread_timeout == KAL_TRUE));  
  13.         bat_thread_timeout = KAL_FALSE;  
  14.         hrtimer_start(&battery_kthread_timer, ktime, HRTIMER_MODE_REL);     
  15.         ktime = ktime_set(BAT_TASK_PERIOD, 0);  // 10s, 10* 1000 ms  
  16.         if( chr_wake_up_bat == KAL_TRUE && g_smartbook_update != 1) // for charger plug in/ out  
  17.         {  
  18.               g_smartbook_update = 0;  
  19.             battery_meter_reset();  
  20.             chr_wake_up_bat = KAL_FALSE;   
  21.             battery_xlog_printk(BAT_LOG_CRTI, "[BATTERY] Charger plug in/out, Call battery_meter_reset. (%d)\n", BMT_status.UI_SOC);  
  22.         }   
  23. }  
  24. return 0;  
  25. }  
        bat_thread_kthread函数作为定时器的触发函数,在函数中通过ktime =ktime_set(BAT_TASK_PERIOD, 0);将定时触发时间设置为10s,所以每10s就将会对该函数进行触发,该函数的核心函数为BAT_thread(),基本上整个充电过程都在这个函数里面实现。

[cpp] view plain copy
print?
  1. void BAT_thread(void)  
  2. {  
  3.     static kal_bool  battery_meter_initilized = KAL_FALSE;  
  4.     _g_bat_sleep_total_time = 0;  
  5.     if(battery_meter_initilized == KAL_FALSE)  
  6.     {  
  7.         battery_meter_initial();    //move from battery_probe() to decrease booting time  
  8.         BMT_status.nPercent_ZCV = battery_meter_get_battery_nPercent_zcv();  
  9.         battery_meter_initilized = KAL_TRUE;  
  10.     }  
  11.     mt_battery_charger_detect_check();  
  12.     mt_battery_GetBatteryData();  
  13.     mt_battery_thermal_check();  
  14.     mt_battery_notify_check();      
  15.     if( BMT_status.charger_exist == KAL_TRUE )  
  16.     {  
  17.         mt_battery_CheckBatteryStatus();          
  18.         mt_battery_charging_algorithm();  
  19.     }  
  20.     mt_battery_update_status();  
  21. }  
        当系统第一次启动的时候battery_meter_initilized为KAL_FALSE,所以BAT_thread会调用battery_meter_initial函数。

battery_meter_initial函数为系统启动时运行的,也电池充电做一些初始化操作。MTK有AUXADC、SW_FG、HW_FG三种不同的电池算法方案,这三种方案分别初始化,因为82平台采用的SW_FG,所以接下去先主要分析SW_FG的流程。而SW_FG中主要是利用线性插值算法来重构zcv表格,利用积分算法求电池的当前电量。

      先来看看系统是如何利用线性插值的方法来求电池的温度,重构zcv表格,在table_init函数中:

[cpp] view plain copy
print?
  1. void table_init(void)  
  2. {  
  3.     ..........  
  4.     int temperature = force_get_tbat(); //求电池的温度  
  5.   
  6.     // Re-constructure r-table profile according to current temperature  
  7.     profile_p_r_table = fgauge_get_profile_r_table(TEMPERATURE_T);  
  8.     if (profile_p_r_table == NULL)  
  9.     {  
  10.         bm_print(BM_LOG_CRTI, "[FGADC] fgauge_get_profile_r_table : create table fail !\r\n");  
  11.     }  
  12.     fgauge_construct_r_table_profile(temperature, profile_p_r_table);  
  13.   
  14.     // Re-constructure battery profile according to current temperature  
  15.     profile_p = fgauge_get_profile(TEMPERATURE_T);  
  16.     if (profile_p == NULL)  
  17.     {  
  18.         bm_print(BM_LOG_CRTI, "[FGADC] fgauge_get_profile : create table fail !\r\n");  
  19.     }  
  20.     fgauge_construct_battery_profile(temperature, profile_p);  
  21. }  
table_init首先获取系统的温度int temperature =force_get_tbat(),这里是通过读NTC电阻的电压,然后通过查表来求电阻的。    
[cpp] view plain copy
print?
  1. int force_get_tbat(void)  
  2. {  
  3. ……………  
  4. bat_temperature_volt = 2;   
  5. //求的NTC电阻的电压,也就是原理图中的BAT_ON  
  6.     ret = battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_TEMP, &bat_temperature_volt);  
  7.     if(bat_temperature_volt != 0)  
  8.     {     
  9. ……………  
  10.         bat_temperature_val = BattVoltToTemp(bat_temperature_volt);          
  11.     }  
  12.     return bat_temperature_val;      
  13. #endif      
  14. }  

      BattVoltToTemp函数就是任何将ADC读出的电压值转换为温度值,该函数其实就是做了两个运算,运算的原理如下图所示:

       NTC电阻就是通过与电阻的串联跟并联并且通过电压值来得到的。计算出系统当前NTC电阻的电阻值后,然后就调用BattThermistorConverTemp函数进行查表,对比出当前系统的温度。而BattThermistorConverTemp函数是通过alps/mediatek/custom/mt6582/kernel/battery/battery/cust_battery_meter_table.h中的Batt_Temperature_Table结构体,然后根据电阻值落在哪个区间,根据线性插值的方法求出当前电池的温度。     然后在回到table_init函数,MTK的zcv电池参数表格会预先测得的在-100 25 50 摄氏度开路电量跟放电深度之间的关系。结合真实的温度值,系统会自己构建一张当前温度值的ZCV电池曲线表格。

[cpp] view plain copy
print?
  1.   // Re-constructure r-table profile according to current temperature  
  2.     profile_p_r_table = fgauge_get_profile_r_table(TEMPERATURE_T); //返回NTC电阻跟电压表格  
  3.     if (profile_p_r_table == NULL)  
  4.     {  
  5.         bm_print(BM_LOG_CRTI, "[FGADC] fgauge_get_profile_r_table : create table fail !\r\n");  
  6. }  
  7. //动态构建一个NTC电阻跟电压关系的表格  
  8. fgauge_construct_r_table_profile(temperature, profile_p_r_table);  
  9.   
  10. R_PROFILE_STRUC_P fgauge_get_profile_r_table(kal_uint32 temperature)  
  11. {  
  12.     switch (temperature)  
  13.     {  
  14.         case TEMPERATURE_T0:  
  15.             return &r_profile_t0[g_fg_battery_id][0];  
  16.             break;  
  17.         case TEMPERATURE_T1:  
  18.             return &r_profile_t1[g_fg_battery_id][0];  
  19.             break;  
  20.         case TEMPERATURE_T2:  
  21.             return &r_profile_t2[g_fg_battery_id][0];  
  22.             break;  
  23.         case TEMPERATURE_T3:  
  24.             return &r_profile_t3[g_fg_battery_id][0];  
  25.             break;  
  26.         case TEMPERATURE_T:  
  27.             return &r_profile_temperature[0];  
  28.             break;  
  29.         default:  
  30.             return NULL;  
  31.             break;  
  32.     }  
  33. }  
    调用fgauge_get_profile_r_table函数会根据上面读取到的温度来返回相对应的r_profile_t数组,r_profile_t数组在alps/mediatek/custom/mt6582/kernel/battery/battery/cust_battery_meter_table.h中,而随后调用的fgauge_construct_r_table_profile函数也是先根据当前电池的温度确定是落入哪个温度范围,假如当前电池温度是落入打0到25度之间,然后根据开路电压值和NTC电阻值的不同采用线性平均法分别构建出两个数值,并以这两个值在构造出一个新的开路电压跟NTC电阻的动态的结构体。

[cpp] view plain copy
print?
  1. for (i = 0; i < saddles; i++) // 构建表格中的电压值  
  2.     {  
  3.         if( ((high_profile_p + i)->voltage) > ((low_profile_p + i)->voltage) )  
  4.         {  
  5.             temp_v_1 = (high_profile_p + i)->voltage;  
  6.             temp_v_2 = (low_profile_p + i)->voltage;      
  7.             (temp_profile_p + i)->voltage = temp_v_2 + //采用线性平均法求电压  
  8.             (  
  9.                 (  
  10.                     (temperature - low_temperature) *   
  11.                     (temp_v_1 - temp_v_2)  
  12.                 ) /   
  13.                 (high_temperature - low_temperature)                  
  14.             );  
  15.         }  
  16.         else  
  17.         {  
  18.             temp_v_1 = (low_profile_p + i)->voltage;  
  19.             temp_v_2 = (high_profile_p + i)->voltage;  
  20.             (temp_profile_p + i)->voltage = temp_v_2 +  
  21.             (  
  22.                 (  
  23.                     (high_temperature - temperature) *   
  24.                     (temp_v_1 - temp_v_2)  
  25.                 ) /   
  26.                 (high_temperature - low_temperature)                  
  27.             );  
  28.         }  
  29.     }  
  30.     /* Interpolation for R_BAT */  
  31.     for (i = 0; i < saddles; i++) // 构建表格中的NTC电阻值  
  32.     {  
  33.         if( ((high_profile_p + i)->resistance) > ((low_profile_p + i)->resistance) )  
  34.         {  
  35.             temp_r_1 = (high_profile_p + i)->resistance;  
  36.             temp_r_2 = (low_profile_p + i)->resistance;      
  37.             (temp_profile_p + i)->resistance = temp_r_2 + //采用线性平均法求电阻  
  38.             (  
  39.                 (  
  40.                     (temperature - low_temperature) *   
  41.                     (temp_r_1 - temp_r_2)  
  42.                 ) /   
  43.                 (high_temperature - low_temperature)                  
  44.             );  
  45.         }  
  46.         else  
  47.         {  
  48.             temp_r_1 = (low_profile_p + i)->resistance;  
  49.             temp_r_2 = (high_profile_p + i)->resistance;  
  50.             (temp_profile_p + i)->resistance = temp_r_2 +  
  51.             (  
  52.                 (  
  53.                     (high_temperature - temperature) *   
  54.                     (temp_r_1 - temp_r_2)  
  55.                 ) /   
  56.                 (high_temperature - low_temperature)                  
  57.             );  
  58.         }  
  59.     }  
     重构ZCV表格的方法是通过线性插值的方法重构的,具体原理如下图:


     table_init后面利用同样的原理,动态生成了一个开路电压与放电深度关系的结构体,这里就不介绍了。

   当table_init函数后系统会对一些变量进行初始化操作,包括在dod_init函数中对oam_v_ocv_1和oam_v_ocv_2进行初始化赋值,读取RTC实时时钟芯片的电量值等等,经过这一系列操作后,就会进入battery系统一个最重要的部分,利用积分的方式来求电池的当前电量。

         MTK系统是通过battery_meter_get_battery_percentage函数来读取当前电池的电量的,然后在通过设备结点,通过给上层调用。battery_meter_get_battery_percentage函数主要就是调用oam_run函数来实现电流的库伦算法。积分法是利用电流计算公式 I = Q/t来求的,它的优点时适用于各种电池,但缺点是初始电量无法获取。

      而整个积分过程可参考下图:


[cpp] view plain copy
print?
  1. void oam_run(void)  
  2. {  
  3.     ……  
  4.     //now_time = rtc_read_hw_time();  
  5.     getrawmonotonic(&now_time); //获取系统当前时间  
  6.     delta_time = now_time.tv_sec - last_oam_run_time.tv_sec;  
  7.       
  8.     last_oam_run_time = now_time;  
  9.   
  10.     // Reconstruct table if temp changed;  
  11.     fgauge_construct_table_by_temp(); // 当电压表发生改变了的时候,重构电压表  
  12.           
  13.     vol_bat = 15; //set avg times  
  14.     ret = battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_SENSE, &vol_bat); //得到闭路电压  
  15.   
  16.     oam_i_1 = (((oam_v_ocv_1-vol_bat)*1000)*10) / oam_r_1;    //0.1mA   
  17.     oam_i_2 = (((oam_v_ocv_2-vol_bat)*1000)*10) / oam_r_2;    //0.1mA   
  18.   
  19.     oam_car_1 = (oam_i_1*delta_time/3600) + oam_car_1; //0.1mAh   
  20.     oam_car_2 = (oam_i_2*delta_time/3600) + oam_car_2; //0.1mAh  
  21.   
  22.     oam_d_1 = oam_d0 + (oam_car_1*100/10)/gFG_BATT_CAPACITY_aging; //gFG_BATT_CAPACITY_aging is Q_MAX  
  23.     if(oam_d_1 < 0)   oam_d_1 = 0;  
  24.     if(oam_d_1 > 100) oam_d_1 = 100;  
  25.       
  26.     oam_d_2 = oam_d0 + (oam_car_2*100/10)/gFG_BATT_CAPACITY_aging;  
  27.     if(oam_d_2 < 0)   oam_d_2 = 0;  
  28.     if(oam_d_2 > 100) oam_d_2 = 100;  
  29.       
  30.     oam_v_ocv_1 = vol_bat + mtk_imp_tracking(vol_bat, oam_i_2, 5);  
  31.       
  32.     oam_d_3 = fgauge_read_d_by_v(oam_v_ocv_1);          
  33.     if(oam_d_3 < 0)   oam_d_3 = 0;  
  34.     if(oam_d_3 > 100) oam_d_3 = 100;  
  35.   
  36.     oam_r_1 = fgauge_read_r_bat_by_v(oam_v_ocv_1);  
  37.   
  38.     oam_v_ocv_2 = fgauge_read_v_by_d(oam_d_2);  
  39.     oam_r_2 = fgauge_read_r_bat_by_v(oam_v_ocv_2);   

        oam_run函数中算法的大致思路为:系统当前的电量通过最终的开路电压oam_v_ocv_1查ZCV表得到当前的电量值,而最终开路电压需要通过闭路电压v_bat和闭路电流oam_i_2 去回溯电池内阻,逐次逼近,而oam_i_2 通过另一种方式即电量积分更新的电压oam_v_ocv_2来得到。

      下面看看具体代码的实现。

[cpp] view plain copy
print?
  1. vol_bat = 15; //set avg times  
  2. ret = battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_SENSE, &vol_bat);  
       首先是闭路电压的更新,这里求的是V_BAT的电压,不需要算法支持直接通过读寄存器实现,注意vol_bat这个参数被复用,返回的平均次数时为最终的v_bat电压值。
[cpp] view plain copy
print?
  1. oam_i_1 = (((oam_v_ocv_1-vol_bat)*1000)*10) / oam_r_1;    //0.1mA  
  2. oam_i_2 = (((oam_v_ocv_2-vol_bat)*1000)*10) / oam_r_2;    //0.1mA  
  3.   
  4. oam_car_1 = (oam_i_1*delta_time/3600) + oam_car_1; //0.1mAh  
  5. oam_car_2 = (oam_i_2*delta_time/3600) + oam_car_2; //0.1mAh  
       oam_v_ocv_1的值会在oam_init函数里面赋值,oam_init里给的值是hw_ocv的电压值,即系统刚起来时电流很小的时候的电压值。ocv_1和ocv_2是通过两种不同的方式来更新电压。这里是通过内阻压降即IR drop来求电流。如下图所示,图中R为电池内阻。

 这里的关键是oam_i_2,这边的I2 有几个作用:

1、因为电流是通过上图的内阻IR drop得到的,而方式一内阻回溯逼近开路电压本质也是IR drop,如果使用oam_i_1 则没有意义,只能使用不同体系的I2.

2、方式二 电流积分求电量查表  同样依赖 oam_i_2 这个体系是累积积分不需要引用其他体系的参数

3、I2的方向作为充电还是放电的依据

       而oam_i_1只有作用3。这里还要注意的是oam_i_1和oam_i_2的单位是0.1mA。而oam_car_1和oam_car_2 是累积电量,同样对应的单位是0.1mAh,显然oam_car_2是算法的有效参数。

[cpp] view plain copy
print?
  1. oam_v_ocv_1 = vol_bat + mtk_imp_tracking(vol_bat, oam_i_2, 5);  
     求oam_v_ocv_1电压的思路是闭路电压加上电池内阻消耗的电压,而mtk_imp_tracking函数是利用内阻回溯电池内阻压降,即回溯IR drop,然后逐渐比较开路电压。
[cpp] view plain copy
print?
  1. kal_int32 mtk_imp_tracking(kal_int32 ori_voltage, kal_int32 ori_current, kal_int32 recursion_time)  
  2. {  
  3.     kal_int32 ret_compensate_value = 0;  
  4.     kal_int32 temp_voltage_1 = ori_voltage;  
  5.     kal_int32 temp_voltage_2 = temp_voltage_1;  
  6.     int i = 0;  
  7.   
  8.     for(i=0 ; i < recursion_time ; i++)   
  9.     {  
  10.         gFG_resistance_bat = fgauge_read_r_bat_by_v(temp_voltage_2);   
  11.         ret_compensate_value = ( (ori_current) * (gFG_resistance_bat + R_FG_VALUE)) / 1000;  
  12.         ret_compensate_value = (ret_compensate_value+(10/2)) / 10;   
  13.         temp_voltage_2 = temp_voltage_1 + ret_compensate_value;  
  14.   
  15.         bm_print(BM_LOG_FULL, "[mtk_imp_tracking] temp_voltage_2=%d,temp_voltage_1=%d,ret_compensate_value=%d,gFG_resistance_bat=%d\n",   
  16.             temp_voltage_2,temp_voltage_1,ret_compensate_value,gFG_resistance_bat);  
  17.     }  
  18.       
  19.     gFG_resistance_bat = fgauge_read_r_bat_by_v(temp_voltage_2);   
  20.     ret_compensate_value = ( (ori_current) * (gFG_resistance_bat + R_FG_VALUE + FG_METER_RESISTANCE)) / 1000;      
  21.     ret_compensate_value = (ret_compensate_value+(10/2)) / 10;   
  22.   
  23.     gFG_compensate_value = ret_compensate_value;  
  24.   
  25.     bm_print(BM_LOG_FULL, "[mtk_imp_tracking] temp_voltage_2=%d,temp_voltage_1=%d,ret_compensate_value=%d,gFG_resistance_bat=%d\n",   
  26.         temp_voltage_2,temp_voltage_1,ret_compensate_value,gFG_resistance_bat);      
  27.   
  28.     return ret_compensate_value;  
  29. }  
        mtk_imp_tracking函数主要是在for循环实现逼近。首先通过闭路电压vol_bat去查表得到电池的内阻gFG_resistance_bat,然后通过U=I*R来算出内阻分去的电压ret_compensate_value,这里要注意的是调用mtk_imp_tracking函数时第二个参数传递的参数为oam_i_2,为oam_i_2的单位为0.1mA,所以这里算出来的内阻分去的电压值ret_compensate_value的单位为0.1mV。然后利用闭路电压加上内阻分去的电压值来获取开路电压。这里求开路电压不是一次进行一次直接求出,而是通过for循环进行5次逼近,这样反复就能更真实的逼近电池的开路电压。这里还需注意两点,首先是这条语句ret_compensate_value = (ret_compensate_value+(10/2)) / 10;因为ret_compensate_value为int型变量,而int型变量在进行整除的时候往往小数点后面的会被去掉,会引入较大误差,这里做的目的就是起到四舍五入的目的。另外一点需要注意的是R_FG_VALUE,R_FG_VALUE是指硬件FG使用的FG电阻(一般是20毫欧)这边是SW_FG所以为0。
[cpp] view plain copy
print?
  1. oam_r_1 = fgauge_read_r_bat_by_v(oam_v_ocv_1);  
  2.  oam_v_ocv_2 = fgauge_read_v_by_d(oam_d_2);  
  3.  oam_r_2 = fgauge_read_r_bat_by_v(oam_v_ocv_2);  
     然后函数会根据之前读出来的oam_v_ocv_1和oam_d_2在去求出oam_r_1和oam_r_2这也是为下次积分做准备,因为系统每10s会调用这个函数。MTK为了增加用户的体验感,在D3的问题上针对电量跳变的情况 又做了步优化得到D5;
[cpp] view plain copy
print?
  1. if(d5_count >= d5_count_time)  
  2.  {  
  3.      if(gFG_Is_Charging == KAL_FALSE)  
  4.      {  
  5.          if( oam_d_3 > oam_d_5 )  
  6.          {  
  7.              oam_d_5 = oam_d_5 + 1;  
  8.          }  
  9.          else  
  10.          {                  
  11.              if(oam_d_4 > oam_d_5)  
  12.              {  
  13.                  oam_d_5 = oam_d_5 + 1;  
  14.              }  
  15.          }  
  16.      }  
  17.      else  
  18.      {              
  19.          if( oam_d_5 > oam_d_3 )  
  20.          {  
  21.              oam_d_5 = oam_d_5 - 1;  
  22.          }  
  23.          else  
  24.          {                  
  25.              if(oam_d_4 < oam_d_5)  
  26.              {  
  27.                  oam_d_5 = oam_d_5 - 1;  
  28.              }  
  29.          }  
  30.      }  
  31.      d5_count = 0;  
  32.      oam_d_3_pre = oam_d_3;  
  33.      oam_d_4_pre = oam_d_4;  
  34.  }  
  35.  else  
  36.  {  
  37.      d5_count = d5_count + 10;  
  38.  }  
     这部分代码比较简单,1分钟内电量值不会改变,且每分钟电量的变化不会大于1%,这样用户体验会比较好。防止因为低电压时陡峭的电量曲线,以及比较耗电的应用,或突然的大电流引起的电量跳变。当然特殊应用下应该取消这个功能,否则会带来电量变化延时等问题。






原创粉丝点击