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. 扩增子统计绘图1箱线图:Alpha多样性
  2. linux怎么创建vi脚本,Vim 创建Python脚本时候自动补全解释器和编码方法
  3. 7-57 又来一个上三角数字三角形 (10 分)
  4. java ftp上传文件_jaVA使用FTP上传下载文件的问题
  5. HTML学习笔记--HTML的语法【1】
  6. python内存的回收机制_python的内存管理和垃圾回收机制详解
  7. 经典Mathematica函数大全
  8. error:“Unexpected namespace prefix xmlns found for tag LinearLayout”
  9. Android 7.0后SettingProvider ContactsProvider TelephonyProvider MediaProvider数据库位置
  10. 汽车启动/停止系统电源方案
  11. Ubuntu16.0微信安装
  12. 央行数字货币(数字人民币)DCEP
  13. SPI驱动SX1278模块1-网关.SPI头文件,c文件,sx1278头文件和c文件,主函数
  14. sqlServer取余数
  15. 1101 -- 正弦和余弦
  16. MATLAB表白玫瑰花绘制——旋转玫瑰、蓝色玫瑰
  17. 第六章 selenium自动化测试工具:天下工具为我所用
  18. Photoshop中如何把多张图片快速变为PDF文档
  19. Leetcode 765 情侣牵手
  20. 卡迪夫大数据专业排名_数据科学与大数据技术专业哪家强?考取难度排名前100院校出炉!...

热门文章

  1. python绘制彩色地震剖面断层解释_地震剖面上的断层分析及相关意义
  2. java生成短连接(调用新浪微博api生成)
  3. 优酷Android包瘦身治理思路
  4. 过计算机管理共享文件夹,局域网中怎么查看自己共享过的文件
  5. 混合非线性整数规划matlab,非线性整数规划matlab
  6. 当跳槽遇到互联网公司裁员寒潮,该如何应对?
  7. Oracle 创建用户授权视图,别名通过同义词解决
  8. 大数据hadoop讲解
  9. Linux系统编程 —线程属性
  10. laravel mews/captcha包安装图形验证后 提示验证码错误