MTK Battery系统

        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这个结构体。

146 typedef struct
147 {
148         kal_bool                        bat_exist;  // 判断电池是否存在
149         kal_bool                        bat_full;  //判断电池是否充满
150         INT32                   bat_charging_state; //判断充电状态
151         UINT32                  bat_vol; //电池平均电压
152         kal_bool                        bat_in_recharging_state; //电池是否在回充
153         kal_uint32              Vsense; // 电池瞬间电压
154         kal_bool                        charger_exist; // Charger是否存在Charger电压
155         UINT32                  charger_vol;  // Charger电压
156         INT32                   charger_protect_status; //充电保护状态,过流或者过压保护状态
157         INT32                   ICharging; // 充电电流
158         INT32                   IBattery;
159         INT32                   temperature; // 电池温度
160         INT32                   temperatureR;
161         INT32                   temperatureV;
162         UINT32                  total_charging_time; //总的充电时间
163         UINT32                  PRE_charging_time; // Pre cc充电时间
164         UINT32                  CC_charging_time; //cc充电时间
165         UINT32                  TOPOFF_charging_time; //TOPOFF充电时间
166         UINT32                  POSTFULL_charging_time; //Postfull充电时间
167         UINT32                  charger_type; //充电器类型
168         INT32                   SOC; //底层的电量
169         INT32                   UI_SOC; // 上层的电量
170         UINT32                  nPercent_ZCV; 
171         UINT32                  nPrecent_UI_SOC_check_point; //N%同步点对应的开路电压以及UI电量
172         UINT32                  ZCV; //电池当前开路电压
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();这个函数,这个函数在整个充电过程中起着很重要的作用,该函数就一行代码:

static void get_charging_control(void)
{
	battery_charging_control = chr_control_interface;
}
kal_int32 chr_control_interface(CHARGING_CTRL_CMD cmd, void *data)
{
        kal_int32 status;
        if(cmd < CHARGING_CMD_NUMBER)
               status = charging_func[cmd](data);
        else
                return STATUS_UNSUPPORTED;
         return status;
  }
        在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函数是怎样运行的。

typedef enum
{
        CHARGING_CMD_INIT,
       CHARGING_CMD_DUMP_REGISTER,
        CHARGING_CMD_ENABLE,
        CHARGING_CMD_SET_CV_VOLTAGE,
        CHARGING_CMD_GET_CURRENT,
        CHARGING_CMD_SET_CURRENT,
        CHARGING_CMD_SET_INPUT_CURRENT,
        CHARGING_CMD_GET_CHARGING_STATUS,
        CHARGING_CMD_RESET_WATCH_DOG_TIMER,
        CHARGING_CMD_SET_HV_THRESHOLD,
        CHARGING_CMD_GET_HV_STATUS,
        CHARGING_CMD_GET_BATTERY_STATUS,
        CHARGING_CMD_GET_CHARGER_DET_STATUS,
        CHARGING_CMD_GET_CHARGER_TYPE,
        CHARGING_CMD_GET_IS_PCM_TIMER_TRIGGER,
        CHARGING_CMD_SET_PLATFORM_RESET,
        CHARGING_CMD_GET_PLATFORM_BOOT_MODE,
        CHARGING_CMD_SET_POWER_OFF,
        CHARGING_CMD_NUMBER
} 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结构体的定义为:

static kal_uint32 (*charging_func[CHARGING_CMD_NUMBER])(void *data)=
{
        charging_hw_init
       ,charging_dump_register
       ,charging_enable
       ,charging_set_cv_voltage
       ,charging_get_current
       ,charging_set_current
       ,charging_set_input_current             // not support, empty function
       ,charging_get_charging_status   // not support, empty function
       ,charging_reset_watch_dog_timer
       ,charging_set_hv_threshold
       ,charging_get_hv_status
       ,charging_get_battery_status
       ,charging_get_charger_det_status
       ,charging_get_charger_type
       ,charging_get_is_pcm_timer_trigger
       ,charging_set_platform_reset
       ,charging_get_platfrom_boot_mode
       ,charging_set_power_off
};
       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()就是充电的核心函数。

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

void BAT_thread(void)
{
    static kal_bool  battery_meter_initilized = KAL_FALSE;
    _g_bat_sleep_total_time = 0;
    if(battery_meter_initilized == KAL_FALSE)
    {
        battery_meter_initial();	//move from battery_probe() to decrease booting time
       	BMT_status.nPercent_ZCV = battery_meter_get_battery_nPercent_zcv();
        battery_meter_initilized = KAL_TRUE;
    }
    mt_battery_charger_detect_check();
    mt_battery_GetBatteryData();
    mt_battery_thermal_check();
    mt_battery_notify_check();    
    if( BMT_status.charger_exist == KAL_TRUE )
    {
        mt_battery_CheckBatteryStatus();	    
        mt_battery_charging_algorithm();
    }
    mt_battery_update_status();
}
        当系统第一次启动的时候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函数中:

void table_init(void)
{
    ..........
    int temperature = force_get_tbat(); //求电池的温度

    // Re-constructure r-table profile according to current temperature
    profile_p_r_table = fgauge_get_profile_r_table(TEMPERATURE_T);
    if (profile_p_r_table == NULL)
    {
        bm_print(BM_LOG_CRTI, "[FGADC] fgauge_get_profile_r_table : create table fail !\r\n");
    }
    fgauge_construct_r_table_profile(temperature, profile_p_r_table);

    // Re-constructure battery profile according to current temperature
    profile_p = fgauge_get_profile(TEMPERATURE_T);
    if (profile_p == NULL)
    {
        bm_print(BM_LOG_CRTI, "[FGADC] fgauge_get_profile : create table fail !\r\n");
    }
    fgauge_construct_battery_profile(temperature, profile_p);
}
table_init首先获取系统的温度int temperature =force_get_tbat(),这里是通过读NTC电阻的电压,然后通过查表来求电阻的。    
int force_get_tbat(void)
{
……………
bat_temperature_volt = 2; 
//求的NTC电阻的电压,也就是原理图中的BAT_ON
    ret = battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_TEMP, &bat_temperature_volt);
    if(bat_temperature_volt != 0)
    {   
……………
        bat_temperature_val = BattVoltToTemp(bat_temperature_volt);        
    }
    return bat_temperature_val;    
#endif    
}

       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电池曲线表格。

  // Re-constructure r-table profile according to current temperature
    profile_p_r_table = fgauge_get_profile_r_table(TEMPERATURE_T); //返回NTC电阻跟电压表格
    if (profile_p_r_table == NULL)
    {
        bm_print(BM_LOG_CRTI, "[FGADC] fgauge_get_profile_r_table : create table fail !\r\n");
}
//动态构建一个NTC电阻跟电压关系的表格
fgauge_construct_r_table_profile(temperature, profile_p_r_table);

R_PROFILE_STRUC_P fgauge_get_profile_r_table(kal_uint32 temperature)
{
    switch (temperature)
    {
        case TEMPERATURE_T0:
            return &r_profile_t0[g_fg_battery_id][0];
            break;
        case TEMPERATURE_T1:
            return &r_profile_t1[g_fg_battery_id][0];
            break;
        case TEMPERATURE_T2:
            return &r_profile_t2[g_fg_battery_id][0];
            break;
        case TEMPERATURE_T3:
            return &r_profile_t3[g_fg_battery_id][0];
            break;
        case TEMPERATURE_T:
            return &r_profile_temperature[0];
            break;
        default:
            return NULL;
            break;
    }
}
    调用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电阻的动态的结构体。

for (i = 0; i < saddles; i++) // 构建表格中的电压值
    {
        if( ((high_profile_p + i)->voltage) > ((low_profile_p + i)->voltage) )
        {
            temp_v_1 = (high_profile_p + i)->voltage;
            temp_v_2 = (low_profile_p + i)->voltage;    
            (temp_profile_p + i)->voltage = temp_v_2 + //采用线性平均法求电压
            (
                (
                    (temperature - low_temperature) * 
                    (temp_v_1 - temp_v_2)
                ) / 
                (high_temperature - low_temperature)                
            );
        }
        else
        {
            temp_v_1 = (low_profile_p + i)->voltage;
            temp_v_2 = (high_profile_p + i)->voltage;
            (temp_profile_p + i)->voltage = temp_v_2 +
            (
                (
                    (high_temperature - temperature) * 
                    (temp_v_1 - temp_v_2)
                ) / 
                (high_temperature - low_temperature)                
            );
        }
    }
    /* Interpolation for R_BAT */
    for (i = 0; i < saddles; i++) // 构建表格中的NTC电阻值
    {
        if( ((high_profile_p + i)->resistance) > ((low_profile_p + i)->resistance) )
        {
            temp_r_1 = (high_profile_p + i)->resistance;
            temp_r_2 = (low_profile_p + i)->resistance;    
            (temp_profile_p + i)->resistance = temp_r_2 + //采用线性平均法求电阻
            (
                (
                    (temperature - low_temperature) * 
                    (temp_r_1 - temp_r_2)
                ) / 
                (high_temperature - low_temperature)                
            );
        }
        else
        {
            temp_r_1 = (low_profile_p + i)->resistance;
            temp_r_2 = (high_profile_p + i)->resistance;
            (temp_profile_p + i)->resistance = temp_r_2 +
            (
                (
                    (high_temperature - temperature) * 
                    (temp_r_1 - temp_r_2)
                ) / 
                (high_temperature - low_temperature)                
            );
        }
    }
     重构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来求的,它的优点时适用于各种电池,但缺点是初始电量无法获取。

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


void oam_run(void)
{
    ……
	//now_time = rtc_read_hw_time();
	getrawmonotonic(&now_time); //获取系统当前时间
	delta_time = now_time.tv_sec - last_oam_run_time.tv_sec;
	
	last_oam_run_time = now_time;

    // Reconstruct table if temp changed;
    fgauge_construct_table_by_temp(); // 当电压表发生改变了的时候,重构电压表
		
    vol_bat = 15; //set avg times
    ret = battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_SENSE, &vol_bat); //得到闭路电压

    oam_i_1 = (((oam_v_ocv_1-vol_bat)*1000)*10) / oam_r_1;    //0.1mA 
    oam_i_2 = (((oam_v_ocv_2-vol_bat)*1000)*10) / oam_r_2;    //0.1mA 

    oam_car_1 = (oam_i_1*delta_time/3600) + oam_car_1; //0.1mAh 
    oam_car_2 = (oam_i_2*delta_time/3600) + oam_car_2; //0.1mAh

    oam_d_1 = oam_d0 + (oam_car_1*100/10)/gFG_BATT_CAPACITY_aging; //gFG_BATT_CAPACITY_aging is Q_MAX
    if(oam_d_1 < 0)   oam_d_1 = 0;
    if(oam_d_1 > 100) oam_d_1 = 100;
    
    oam_d_2 = oam_d0 + (oam_car_2*100/10)/gFG_BATT_CAPACITY_aging;
    if(oam_d_2 < 0)   oam_d_2 = 0;
    if(oam_d_2 > 100) oam_d_2 = 100;
    
    oam_v_ocv_1 = vol_bat + mtk_imp_tracking(vol_bat, oam_i_2, 5);
    
    oam_d_3 = fgauge_read_d_by_v(oam_v_ocv_1);        
    if(oam_d_3 < 0)   oam_d_3 = 0;
    if(oam_d_3 > 100) oam_d_3 = 100;

    oam_r_1 = fgauge_read_r_bat_by_v(oam_v_ocv_1);

    oam_v_ocv_2 = fgauge_read_v_by_d(oam_d_2);
    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来得到。

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

    vol_bat = 15; //set avg times
    ret = battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_SENSE, &vol_bat);
       首先是闭路电压的更新,这里求的是V_BAT的电压,不需要算法支持直接通过读寄存器实现,注意vol_bat这个参数被复用,返回的平均次数时为最终的v_bat电压值。
    oam_i_1 = (((oam_v_ocv_1-vol_bat)*1000)*10) / oam_r_1;    //0.1mA
    oam_i_2 = (((oam_v_ocv_2-vol_bat)*1000)*10) / oam_r_2;    //0.1mA

    oam_car_1 = (oam_i_1*delta_time/3600) + oam_car_1; //0.1mAh
    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是算法的有效参数。

oam_v_ocv_1 = vol_bat + mtk_imp_tracking(vol_bat, oam_i_2, 5);
     求oam_v_ocv_1电压的思路是闭路电压加上电池内阻消耗的电压,而mtk_imp_tracking函数是利用内阻回溯电池内阻压降,即回溯IR drop,然后逐渐比较开路电压。
kal_int32 mtk_imp_tracking(kal_int32 ori_voltage, kal_int32 ori_current, kal_int32 recursion_time)
{
    kal_int32 ret_compensate_value = 0;
    kal_int32 temp_voltage_1 = ori_voltage;
    kal_int32 temp_voltage_2 = temp_voltage_1;
    int i = 0;

    for(i=0 ; i < recursion_time ; i++) 
    {
        gFG_resistance_bat = fgauge_read_r_bat_by_v(temp_voltage_2); 
        ret_compensate_value = ( (ori_current) * (gFG_resistance_bat + R_FG_VALUE)) / 1000;
        ret_compensate_value = (ret_compensate_value+(10/2)) / 10; 
        temp_voltage_2 = temp_voltage_1 + ret_compensate_value;

        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", 
            temp_voltage_2,temp_voltage_1,ret_compensate_value,gFG_resistance_bat);
    }
    
    gFG_resistance_bat = fgauge_read_r_bat_by_v(temp_voltage_2); 
    ret_compensate_value = ( (ori_current) * (gFG_resistance_bat + R_FG_VALUE + FG_METER_RESISTANCE)) / 1000;    
    ret_compensate_value = (ret_compensate_value+(10/2)) / 10; 

    gFG_compensate_value = ret_compensate_value;

    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", 
        temp_voltage_2,temp_voltage_1,ret_compensate_value,gFG_resistance_bat);    

    return ret_compensate_value;
}
        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。
   oam_r_1 = fgauge_read_r_bat_by_v(oam_v_ocv_1);
    oam_v_ocv_2 = fgauge_read_v_by_d(oam_d_2);
    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;
   if(d5_count >= d5_count_time)
    {
        if(gFG_Is_Charging == KAL_FALSE)
        {
            if( oam_d_3 > oam_d_5 )
            {
                oam_d_5 = oam_d_5 + 1;
            }
            else
            {                
                if(oam_d_4 > oam_d_5)
                {
                    oam_d_5 = oam_d_5 + 1;
                }
            }
        }
        else
        {            
            if( oam_d_5 > oam_d_3 )
            {
                oam_d_5 = oam_d_5 - 1;
            }
            else
            {                
                if(oam_d_4 < oam_d_5)
                {
                    oam_d_5 = oam_d_5 - 1;
                }
            }
        }
        d5_count = 0;
        oam_d_3_pre = oam_d_3;
        oam_d_4_pre = oam_d_4;
    }
    else
    {
        d5_count = d5_count + 10;
    }
     这部分代码比较简单,1分钟内电量值不会改变,且每分钟电量的变化不会大于1%,这样用户体验会比较好。防止因为低电压时陡峭的电量曲线,以及比较耗电的应用,或突然的大电流引起的电量跳变。当然特殊应用下应该取消这个功能,否则会带来电量变化延时等问题。






  • 9
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值