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);elsereturn 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 msif( 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 timeBMT_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 temperatureprofile_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 temperatureprofile_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_ONret = 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 temperatureprofile_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 timesret = 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.1mAhoam_d_1 = oam_d0 + (oam_car_1*100/10)/gFG_BATT_CAPACITY_aging; //gFG_BATT_CAPACITY_aging is Q_MAXif(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 timesret = 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.1mAoam_i_2 = (((oam_v_ocv_2-vol_bat)*1000)*10) / oam_r_2;    //0.1mAoam_car_1 = (oam_i_1*delta_time/3600) + oam_car_1; //0.1mAhoam_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%,这样用户体验会比较好。防止因为低电压时陡峭的电量曲线,以及比较耗电的应用,或突然的大电流引起的电量跳变。当然特殊应用下应该取消这个功能,否则会带来电量变化延时等问题。

MTK Battery系统相关推荐

  1. MTK android系统源码修改快速上手

    1.拷贝代码仓库 从git@192.168.1.3:a89.git 到work目录下: cbk@YCS:~/work$ ll cbk@YCS:~/work$ rm -rf a89/ cbk@YCS:~ ...

  2. mtk log系统详解

    Log总览 Android Log Android java层和native层 log main log.system log.radio log.event log Kernel Log Linux ...

  3. android 系统(99)---MTK 平台系统重启分类

    如何快速对系统重启问题进行归类 1. 问题分类 当手机发生系统重启,即导致kernel重启的异常时,会在手机中的/data/aee_exp目录下保存异常重启的db.可以通过GAT的bug report ...

  4. Android系统之路(初识MTK) ------ 设置系统默认语言/客制化可选语言/设置默认时区

    在这一版本的平板系统定制中,客户需要定制系统默认语言,默认英语,可选语种分别是 语言代码      国家/地区 bn_BD      孟加拉语(孟加拉) en_US      英文  ar       ...

  5. MTK adnroid系统音频参数说明

    mtk音频参数的功能说明 修改外放增益VER1_AUD_SPEAKER_VOLUME_DEFAULT ,修改第一个参数即可起作用 对应工程模式audio下面的四种模式 MT8163修改mic #def ...

  6. Android 9 MTK 更改系统的版本号

    系统的内部版本号是固定的,想在版本号加入时间 1 Setting 应用层调用 2 framework层定义 获得ro.build.version.incremental属性为系统内部版本号,接下来找在 ...

  7. MTK去掉系统提取odex、selinux改为permissive、adb root

    1.去掉系统提取odex device/mediatek/common/BoardConfig.mk +WITH_DEXPREOPT := false 2.selinux改为permissive sy ...

  8. mtk android 系统代码问题,MTK Android Driver 之 LCM 知识

    本帖最后由 TimKing 于 2018-4-8 20:18 编辑 1.lcm 相关概念 1.1) MIPI接口:一共有三种接口:DBI(也做CPU或MCU接口).DPI(也叫RGB接口).DSI.在 ...

  9. MTK eCos系统的有线驱动收包流程

    驱动设备注册 如下,注册了ra305x_eth_netdev1,关联ra305x_eth_sc1方法,ra305x_eth_sc1定义时关联了eth_drv_funs.if_ra305x_init将在 ...

  10. 为MTK andorid系统添加adb reboot factory命令

    此添加方法,通过重启命令的参数修改RTC模块的的一个端口的一位寄存器,待重新启动后通过读取改为的值来选择进入normal模式还是factory模式. (1)mediatek/platform/mt65 ...

最新文章

  1. eclipse下tomcat添加部署Module,Web名称与项目名称不一致的解决方法
  2. 浅析营销型网站SEO优化的四大原则!
  3. Python学习笔记010——作用域
  4. 电脑知识--Windows一片
  5. Ajax.net实现的动态输入项
  6. (十一)nodejs循序渐进-高性能游戏服务器框架pomelo之启动流程和组件
  7. mysql_根据身份证号识别性别、年龄、所在省份
  8. Java基础知识之方法的通用格式、注意事项与带参数的方法
  9. Weblogic Server 的下载,安装配置与部署
  10. 威胁情报 设备之外的安全能力
  11. 最常见到的runtime exception 异常
  12. 面向对象的四个基本特征
  13. Microsoft Office Visio 2007 下载安装密钥
  14. 如何成为优秀的管理者?(摘自《代码之道》第9章)
  15. 互联网金融网络借贷系统架构
  16. android 混淆字符串,android 代码混淆
  17. 直播教学系统16项功能
  18. 见猎心喜 浅尝辄止 偶有所得 不足为法
  19. 大数据剖析| 二线城市抢人大战,拼的到底是什么?
  20. java入门-springboot+mybatis+vue实现简单的后台管理系统

热门文章

  1. 使用pytorch操作矩阵
  2. Postgresql模糊查询插件pg_bigm安装
  3. 《掌控习惯》学习总结
  4. archlinux fcitx5-rime五笔输入法
  5. 每个设计师都在用的UI标注工具UI切图软件——PxCook像素大厨
  6. 博客营销成功案例分析
  7. C++核心编程笔记整理
  8. 苹果系统备份文件服务器地址,iphone备份文件在哪 iphone备份文件位置介绍
  9. android 上拉抽屉,Flutter上拉抽屉实现
  10. java实习面试题整理